Serial Routines

keep it safe

We start always at the low level and we go up. Let's think what should happen when a new byte is received. In the ISR, the new position of the Write pointer is calculated and the new byte is stored, simple enough. So, is it a simple advance for RxRW fine? No, it has to wrap around and not to fall off the buffer edge. This is what masking does, grab a piece of paper and do the maths. By the way, why the buffer length should be a power of 2 bytes long?


SIGNAL(SIG_UART_RECV)
{
   // advance RxWR
   RxWR++;
   // mask it
   RxWR &= RxbufMask;
   // store received byte in buffer
   RxBuf[RxWR] = UDR;
   // data added
   numRx++;
}
 

The same concept applies for our ReceiveByte routine that is used within our main program. Here, if there are bytes in the incoming buffer we point to the oldest of them and we return it.


u8 ReceiveByte(void)
{
   // advance RxRD
   RxRD++;
   // mask it
   RxRD &= RxbufMask;
   // data consumed
   numRx--; 
   // return data
   return Rxbuf[RxRD];
}
 

Regarding transmission, the Data Register Empty interrupt is triggered every time a byte is transmitted and the data register gets empty. Therefore, as long as we have bytes to send, we load them and we enable the interrupt. If there are no more bytes to send, the interrupt should be disabled. Have you noticed that for transmission the roles are different? Here the consumer is not the actual TransmitByte routine but the ISR, the TransmitByte routine generates data. Thus, within the ISR we have to read data from the buffer. Another interesting thing here is the fact that upon loading the Data Register, transmission starts.


SIGNAL(SIG_UART_DATA)
{
   // check if more bytes are available
   if (numTx>0) 
   {
      // advance TxRD
      TxRD++;
      // mask it
      TxRD &= TxbufMask;
      // start transmission by loading UDR
      UDR = TxBuf[TxRD];
      // data sent
      numTx--; 
   }
   else
   // Disable UDRE interrupt (no more bytes)
   UCSRB &= ~(1<<UDRIE);
}
 

Now the TransmitByte routine should look very reasonable, TxWR is advanced, a byte is stored in the outgoing buffer and the interrupt is enabled for the ISR to be executed.


void TransmitByte(u8 data)
{
   // advance TxWR
   TxWR++;
   // mask it
   TxWR &= TxbufMask;
   // store data in buffer
   TxBuf[TxWR] = data;
   // data added
   numTx++;
   // enable UDRE interrupt
   UCSRB |= 1<<UDRIE;
}
 

That's all folks! Time to test it, here is the loopback code you may use to test your routines.


while (1)
{
   if (numRx>0)
   {
      TransmitByte(ReceiveByte()+1);
   }
}
 
 
 
victor@avrtutor.com