THEORY OF OPERATION:
The USART outputs serial data over the TX (transmit) pin and listens for data on the RX (Receive) pin using TTL voltage levels (0-5V). The protocol is fairly simple, if the settings on both devices match they should be able to talk to each other.
The AVR datasheet gives a good writeup of the protocol so I’m not going to go into any detail about it. I however say that the most common setting is:
Baud rate: 9600
Data bits: 8
Parity: None
Stop Bit: 1
Flow Control: None
The difference between the registers on the ATmega8 and the ATmega168/328 is mostly a “0″ at the end of most register names. I’m guessing that this is preparations for AVRs with multiple USART ports. In this section I am going to change up for format a bit and show both registers under the same table.
|
7 bit |
6 bit |
5 bit |
4 bit |
3 bit |
2 bit |
1 bit |
0 bit |
AVR Type |
| UCSRA |
RXC |
TXC |
UDRE |
FE |
DOR |
PE |
U2X |
MPCM |
ATmega8 |
| UCSR0A |
RXCn |
TXCn |
UDREn |
FEn |
DORn |
PEn |
U2Xn |
MPCMn |
ATmega168/328 |
USART Control and Status Register A
|
7 bit |
6 bit |
5 bit |
4 bit |
3 bit |
2 bit |
1 bit |
0 bit |
AVR Type |
| UCSRB |
RXCIE |
TXCIE |
UDRIE |
RXEN |
TXEN |
UCSZ2 |
RXB8 |
TXB8 |
ATmega8 |
| UCSR0B |
RXCIE0 |
TXCIE0 |
UDRIE0 |
RXEN0 |
TXEN0 |
UCSZ02 |
RXB80 |
TXB80 |
ATmega168/328 |
USART Control and Status Register B
|
7 bit |
6 bit |
5 bit |
4 bit |
3 bit |
2 bit |
1 bit |
0 bit |
AVR Type |
| UCSRC |
URSEL* |
UMSEL |
UPM1 |
UPM0 |
USBS |
UCSZ1 |
UCSZ0 |
UCPOL |
ATmega8 |
| UCSR0C |
UMSELn1 |
UMSELn0 |
UPMn1 |
UPMn0 |
USBSn |
UCSZn1 |
UCSZn0 |
UCPOLn |
ATmega168/328 |
USART Control and Status Register C (* ATmega8 only)
| UMSEL |
MODE |
| 0 |
Asynchronous Operation |
| 1 |
Synchronous Operation |
UMSEL Bit Settings (ATmega8 ONLY)
| UMSELn |
UMSELn0 |
MODE |
| 0 |
0 |
Asynchronous Operation |
| 0 |
1 |
Synchronous Operation |
| 1 |
0 |
(Reserved) |
| 1 |
1 |
Master SPI Mode |
UMSEL Bits Settings (ATmega168/328 Only)
| UPM1 (UPMn1) |
UPM0 (UPM00) |
PARITY MODE
|
| 0 |
0 |
Disabled |
| 0 |
1 |
Reserved |
| 1 |
0 |
Enabled, Even Parity |
| 1 |
1 |
Enabled, Odd Parity |
UPM (UPMn) Bit Settings
| USBS (USBS0) |
Stop Bit(s) |
| 0 |
1 bit |
| 1 |
2 bit |
USBS (USBS0) Bit Settings
| UCSZ2 (UCSZ02) |
UCSZ1 (UCSZ01) |
UCSZ0 (UCSZ00) |
CHAR SIZE |
| 0 |
0 |
0 |
5-bit |
| 0 |
0 |
1 |
6-bit |
| 0 |
1 |
0 |
7-bit |
| 0 |
1 |
1 |
8-bit |
| 1 |
0 |
0 |
Reserved |
| 1 |
0 |
1 |
Reserved |
| 1 |
1 |
0 |
Reserved |
| 1 |
1 |
1 |
9-bit |
UCSZ (UCSZn) Bit Settings
| UCPOL (UCPOLn) |
TX Data Change |
RX Data Sampled |
| 0 |
Rising XCK Edge |
Falling XCK Edge |
| 1 |
Falling XCK Edge |
Rising XCK Edge |
UCPOL (UCPOL0) Bit Settings
| NAME |
FUNCTION |
SIZE |
AVR TYPE |
| UDR |
Transmit / Receive Buffer |
8 bit |
ATmega8 |
| UDRn |
Transmit / Receive Buffer |
8 bit |
ATmega328 |
| UBRRH |
Baud Rate Register upper 4 bits |
8 bit (3:0 used) |
ATmega8 |
| UBRRnH |
Baud Rate Register upper 4 bits |
8 bit (3:0 used) |
ATmega328 |
| UBRRL |
Baud Rate Register Lower 8 bits |
8 bit |
ATmega8 |
| UBRRRnL |
Baud Rate Register Lower 8 bits |
8 bit |
ATmega328 |
Other Registers
So we have 3 Control and Status Registers (A, B & C). Register A mainly contains status data. Register B contains all interrupt settings and some none optional settings. Register C contains all the configuration settings.
Transmitting Speed:
For whatever reason the USART circuitry within the AVR really likes clock frequencies divisible by 1.8432MHz. If your clock source is not divisible by 1.8432 you will have a percentage of error which will result in unstable communications. The datasheet provides a large table called ”Examples of Baud Rate Setting” under the “USART” section which shows you the Error rate for all baud rates at given System Clock Settings, As long as the value is less then 0.5% your communication should be solid. The chart also shows you the value that you want to write into the UBRR register.
Most programmers choose to use a a formula in code to figure this out, this way they don’t have to reference the datasheet every time they need to change the speed. The formula for this is:
ubrr_value = (Clock_Speed[a.k.a. FOSC] / 16 / Baud_Rate) – 1
Just remember this value can be up to 12 bits in size, and has to be written across 2 different 8 bit registers, so make sure you make ubrr_value an integer. Then we need to split the register the value into the upper and lowest 8 bits into the UBRRL(UBRRnL) register and the highest 4 bits into the UBRRH (UBRRnH) register.
Special Note for ATmega8:
The C register (UCRSC) on the ATmega8 contains URSEL (USART Select) bit. In order to write data to the UBRRH register the URSEL bit needs to be LOW(0), and in order to write to write to register C (UCRSC) you need to set this bit (URSEL) to HIGH(1). This does not exist on the ATmega168/328.
Frame Formats:
Now that we know how fast to send the data, we have to figure out how to format our data so that the other device could understand the data that we are sending it (and so that we could understand the data that is being sent to us). All of our configuration options are located in Register C (UCRSC or UCSRnC).
The first thing we want to do is setup the character size which is controlled by the UCSZ1(UCSZn1) bit and UCSZ0(UCSZn0) bit which are located in Register C, and the oddball UCSZ2(UCSZn2) in register B (which is used for 9 bit data, this rare).
The next thing we want to setup is the parity which is set by the UPM1 (UPMn1) bit and the UPM0 (UPMn0) bit, the most common parity setting is NONE so we can simply leave the bits LOW (0).
Lastly the stop bit which is controlled by the USBS(USBS0) bit, again, 2 bit stop is rarely used so we usually leave this bit LOW(0) for 1bit.
Enabling Communication:
Now this is something really cool, you can enable the transmission and receive functionality independently. Both the RXEN(RXENn) bit (Receive Enable) and the TXEN(TXENn) bit (Transmitter Enable) are both located in the B Register (UCSRB or UCSRnB)
Transmit and Receive Buffer:
This is something that blows my mind, the Transmit and Receive share the same register the UDR(UDRn). Whats even more wild is that the Receive buffer can store 2 characters. While this sound a bit crazy it does work. We have to keep 2 things in mind in order to get flawless operation. The buffer has to be clear in order to transmit. So all we really have to do is check to see if the buffer is clear UDR(UDRn) and if it is we can transmit. If it is not clear, reading the data will will cause it clear (much like reading the ADC clears it).
Status and Fault Bits:
Now that we have our communication up and running we need to be able to monitor its status and any faults. This is done by Register A (UCSRA or UCSRnA)
The RXC(RXCn) bit (Receive Complete) will be set to HIGH(1) when data has been received and is available in the UDR (UDR0) buffer.
The TXC(TXCn) bit (Transmission Complete) is set to HIGH(1) when a transmission is completed and there is no other data to send.
The UDRE(UDREn) is set to HIGH(1) when the buffer (UDR/UDRn) is empty, and therefore ready to be written.
The FE(FE0) bit (Frame Error) is set HIGH(1) if the next bit in the receive buffer has a Frame Error. If the recieve buffer is read the FE(FE0) bit is cleared.
The DOR(DORn) bit (Data OverRun) is set HIGH(1) if the receive buffer is full (2 characters). The bit is cleared LOW(0) if the UDR(UDRn) is read.
The UPE(UPEn) bit (Parity Error) is set HIGH(1) if the next character in the receive buffer has a parity error. The bit is cleared LOW(0) if the UDR(UDRn) is read.
Interrupts:Finally, the interrupts. There are 3 of them. Receive Complete, Data Register Empty and, Transmit Complete. All 3 of the bits required to set these interrupts are located in the B register (UCSRB or UCSRnB). Setting the RXCIE(RXCIEn) bit (Receive Complete Interrupt Enable) to HIGH(1) enables the Receive interrupt. Setting the TXCIE(TXCIEn) bit (Transmit Complete Interrupt Enable) to HIGH(1) enables the Transmit interrupt. Setting the UDRIE(UDRIEn) bit (USART Data Register Empty Interrupt Enable) enables the USART Data Register Empty Interrupt.
IMPORTANT: If you enable the receiver interrupt you MUST READ the UDR register in order to clear the interrupt flag. If you fail to read the UDR register in the interrupt routine the interrupt flag will not be cleared. This will cause the program to will jump back to the main routine, execute 1 line of code (Remember this back from Interrupt Tutorial) then jump back into the interrupt routine.
2. Experiment send char ‘a’ and Enter character