ATMega328P Serial Communications in C
I’m not feeling particularly wordy today, so let’s just get to the nitty-gritty.
First up, we have the baud rate we’re going to communicate at. To calculate our UBRR register, we follow a formula that is dependent on a few things.
In my code, F_CPU is the clock rate of the processor. (In my case, a 16mHz crystal. This value is pushed to the processor by the compiler. You can define it if necessary.) BAUD is the baud rate I’ve set, 9600. Since I am planning on using Asynch. Normal Mode, I multiple BAUD by 16. Then subtract 1 from all this.
It is absolutely essential that the settings of both machines match or you -will- get data corruption.
Let’s look at the initialization of the USART (by the way, that’s the Universal Synchronous and Asynchronous serial Receiver and Transmitter).
UBRRo represents the USART Baud Rate Register. It is a 12-bit register, with the four most significant bits stored in UBRR0H, and the eight least significant bits in UBBR0L.
Since our UBRR was calculated earlier with our formula, simply shift the 8 low bits off of it and stick the remaining 4 in UBRR0H, and then the 8 low bits into UBBR0L. Simple, hey?
After this are some settings registers for the USART. These registers are known as the USART Control and Status Registers and are represented by the UCSR0A, UCSR0B, and UCSR0C registers.
The change in UCSR0A turns off the bit that dictates whether to use asynch. double speed or not (U2X0). The Arduino Uno has a quirky functionality where this bit is turned on automatically in some specific cases I can’t quite identify. This caused me to have all sorts of weird data errors unless I connected at twice the speed I specified (19200). Look at the formulas for UBRR calculations again, and you’ll see why I had to double up the speed.
The other changes regarding registers are all fairly obvious as indicated by commenting. Refer to the Atmel ATMega328P documentation to check out the other settings in the registers, as they’re discussed at length.
Finally, sei() is a function that enables the global interrupt flag. This is necessary as we are going to be using USART interrupts to drive it’s communication. More on that in a bit.
Let’s look at the receive and send functions first. The UDRE0 bit is known as the USART Data Register Empty bit. It will be set to 1 when the transmit buffer (TXB in UDR0) is empty and ready to be written. This is done so we don’t accidentally overwrite any existing data in the UDR0 register. Try commenting the loop out and see what it does to the code. (NOTE: These can be made more operation specific by replacing the UDRE0 bit with either the RX Complete bit, RXC0, or the TX Complete bit, TXC0. Experiment!)
The USART_putstring() function simply outputs a c-string byte by byte, as the serial communication only outputs a single byte at a time.
Finally, what makes the program work, interrupts. These are included in the <avr/interrupts.h> library, and are very simple to use. ISR(USART_RX_vect) is simply an instruction that says every time an RX Complete interrupt is generated, execute the following code. A RX Complete interrupt is generated when the RX Complete bit (RXC0) is set to 1. One must have also set the RXCIE0 bit to 1, as well as enabled the global interrupt flag.
After that, the rest of the code is simple. This is just a small example of how to communicate with your computer over a serial port. There are other hang-ups involved with communicating with other devices that aren’t accessed through the USB port of the Arduino, but that’s a lesson for another day.