|
Now that we have the idea let's write the code for it.
Although the code is straightforward enough, it is a bit hard to start
from nothing and write the final thing. When you have an idea and you try
to write something that works start by defining the elements of your idea,
the declarations. By the way, this is the profound difference between
engineers and marketing people. They are happy just with the idea.
So, one buffer is not enough, as long as communication is
full-duplex it is better to utilize two buffers, the incoming buffer (Rx)
and the outgoing buffer (Tx). The buffers do not need to have the same
length. What else? We need two pointers for each buffer, the Read and
Write pointer. In terms of functions, we need an initialization routine,
a Transmit and a Receive routine that will be used within our main loop
and of course we will write the ISRs for the USART interrupts,
the Rx Complete and the Data Register Empty interrupt. The former triggers
when a byte has been received whereas the latter, when a byte has been
sent.
/// Includes //////////
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
/// Typedefs //////////
typedef unsigned char u8;
typedef unsigned int u16;
/// Defines ///////////
#define TxbufLength 32
#define TxbufMask TxbufLength - 1
#define RxbufLength 128
#define RxbufMask RxbufLength - 1
/// Prototypes ////////
void InitUART(u16 baud);
void TransmitByte(u8 data);
u8 ReceiveByte(void);
/// Globals ///////////
static u8 TxBuf[TxbufLength];
volatile static u8 TxWR, TxRD, numTx;
static u8 RxBuf[RxbufLength];
volatile static u8 RxWR, RxRD, numRx;
This is what we are going to use. A 32 bytes long outgoing
buffer and an 128 bytes long incoming buffer. We need the incoming buffer
to be fairly long, the longer it is the less likely to experience an overflow.
On the other hand, we expect to transmit when we have a result, another
task is not expected to run simultaneously so, we keep the buffer short.
Do we need to keep the number of bytes in each buffer? No, numTx and numRx
are redundant since they are equal with the respective pointer differences
but, I like them and I am going to use them. What about the Mask definitions?
Well, they just appeared to be needful, you 'll see why.
Note that since our variables are altered within interrupt
service routines, they should be declared volatile. This tells the processor
that their values should not be taken for granted. They may change out
of the normal program flow and under any optimization they should not
be left out. Onto the initialization routine, have a look at the datasheet
to verify our settings.
void InitUSART(u16 baud)
{
// set baud rate
UBRRH = (u8)(baud>>8);
UBRRL = (u8)baud;
// enable Rx, Tx interrupts, 0 parity, 1 stop bit
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
// initialize Tx, Rx buffer pointers
TxWR = 0; TxRD = 0; numTx = 0;
RxWR = 0; RxRD = 0; numRx = 0;
}
|