Using the UART and USART to Communicate Using the AVR Microcontroller
Share
Using the UART and USART to Communicate Using the AVR Microcontroller
Here is the plain english information to understand the USART (Universal Synchronous Asynchronous Transmitter/Receiver). That is to say, the information contained here is an interpretation of the datasheet so it is easily understandable. The USART feature of the AVR microcontroller can communicate with another microcontroller (not necessarily another AVR microcontroller), multiple microcontrollers, or a computer using a voltage level shifter or converter. The USART can transmit data using a buffer and a shift register (more on that later), receive data using a shift register and buffer, create a frame of data that is recognized on both the receiving end and the transmitting end. All of this works according to an agreed upon speed from both sides, or with synchronous mode where the clock line (a wire) is directly connected.
Sending information and receiving information in UART and USART communication is like riding on a train. The train sends you to a destination like it sends data to another computer or microcontroller. The train station is like the internal transmitter and receiver inside the microcontroller. There must be a place that the train riders queue to get on the train. First, a passenger will get the ticket and sit down to wait for the next train to arrive. The passenger then must queue up to get on the train. Then the passenger gets on the train. This is exactly how a USART tranmitter functions. The data is sent to a buffer, which is like the waiting room for the train. The shift registers is like the queue to get on the train. The data moves from the buffer and onto the shif registers. This is after the previous data has left the shift registers. From the shift registers, the data moves along the transmit wire, just like a train moves along a track.
Receiving information is the same but in reverse. In the human analogy, the train arrives to the train station. The person gets off of the train and must wait in a queue so that the people in front of them makes space. In the microcontroller reception of data, the data gets off the wire and goes straight into the shift registers. With the Atmega32, there are two buffers that the data can use to wait to be used by the program in the microcontroller. With the shift register, this gives the receive area three total buffers for the data to sit, waiting to be used. This is established with the chip so Data Over Runs (DOR) are less likely. The DOR is a flag that you can look at.
The train is actually similar to the baud rate. The baud is the clock that pushes the data along the line. There are a couple types of clocks you can apply using UART and USART. The UART only allows you to apply an agreed upon baud rate from both parties (each microcontroller must be set with this specific baud rate). In USART, a clock wire must be connected between each microcontroller. This wire will pulse like a heartbeat. In the case of asynchronous, each microcontroller has its own clock and since the data is being sent with the transmitting microcontroller using this clock (baud rate), the receiving microcontroller must be receiving this data at the same pace, so its clock (baud rate) must be the same.
Clock Modes:
Pick between Synchronous or Asynchronous
UCSRC &= ~(1 << UMSEL); //setting the UMSEL bit to 0 for asynchronous mode
Asynchronous:
Asynchronous is where the microcontroller's clock is not connected together by a wire with the other microcontroller, but they need the same clock beat to process the data on the data line.
Example: UBBR = ( 1,000,000 / 16 * 2400 ) - 1 = ( 1,000,000 / 38,400 ) - 1 = 26.0416667 - 1 = 25.0416667 = 25
It's 25 because the UBBR will not accept a decimal value, but that introduces a 0.2% error, which is acceptable. You can find a table of commonly used UBBR numbers in the "Examples of baud rate settings" under the USART section of the datasheet.
Finding the baud from the UBBR is optional:
Example: baud = 1,000,000 / (16 * (25 + 1)) = 1,000,000 / (16 * 26) = 1,000,000 / 416 = 2403.84615 = 2400
Pretty close to the standard 2400 baud with 0.2% error which is acceptable. Check your datasheet for acceptable error percentages.
Setting the baud rate:
UBBRH = (unsigned char) (# from table >> 8);
UBBRL = (unsigned char) # from table;
The URSEL is set to 0 because UBBRH shares the same I/O as UCSRC, the URSEL must be set to 0 to write to UBBRH. The UBBRH sets the high portion of the baud rate - from bit 8 to bit 11. The UBBRL puts the remaining bits 0 to 7 into the low UBBR register.
Setting the Asynchronous mode:
Double Speed Asynchronous- U2X bit in UCSRA controls double speed asynchronous
UCSRA &= ~(1 << U2X); //setting the U2X bit to 0 for normal asynchronous
Synchronous
Where the clock is connected with a wire between the two microcontrollers - DDR_XCK Data Direction Register controls which microcontroller is the master and which is the slave. If the DDR_XCK is set to output, then the XCK pin on that microcontroller is the master since it will be creating the clock output on that XCK pin.
Slave Synchronous
Data Frame:
This is the actual data that is transmitted. It's the train with the data riding on-board. This is with 9 bits for the data bits, one start bit, two stop bits, one parity bit which is the maximum frame size.
Bit 01: The train engine - Bit 1: Start bit - always low (0)
Bit 02: Person #1 on the train - Data bit #0 - high or low depending on the data
Bit 03: Person #2 on the train - Data bit #1 - high or low depending on the data
Bit 04: Person #3 on the train - Data bit #2 - high or low depending on the data
Bit 05: Person #4 on the train - Data bit #3 - high or low depending on the data
Bit 06: Person #5 on the train - Data bit #4 - high or low depending on the data
Bit 07: Person #6 on the train - Data bit #5 - high or low depending on the data
Bit 08: Person #7 on the train - Data bit #6 - high or low depending on the data
Bit 09: Person #8 on the train - Data bit #7 - high or low depending on the data
Bit 10: Person #9 on the train - Data bit #8 - high or low depending on the data
Bit 11: Train car just before the caboose watching over everything - Parity bit - high or low depending on the 1's of the data
Bit 12: The caboose - Stop Bit - always high
Bit 13: The extra caboose - stop bit - always high and always ignored by the receiver
back to high idle state - or to a new start bit.
There is a maximum of 13 bits in the largest data frame.
Setting the Data Bit Size - Number of data bits you want in the frame. Use the UCSZ2:0 (UCSZ0, UCSZ1, UCSZ2) bits in the UCSRC register. Note, keeping all the bits in UCSZ2:0 not set establishes a 5-bit data bit length.
UCSZ - Character Size
UCSRC |= (1 << UCSZ1); //7-bit data length, or
UCSRC |= (2 << UCSZ0); //Alternative code for 7-bit data length
UCSRC |= (1 << UCSZ1) | (1 << UCSZ0); //8-bit data length, or
UCSRC |= (3 << UCSZ0); //Alternative code for 8-bit data length
UCSRC |= (1 << UCSZ2) | (1 << UCSZ1) | (1 << UCSZ0); //8-bit data length, or
UCSRC |= (7 << UCSZ0); //Alternative code for 8-bit data length
Huh, what's that "2, 3 and 7"?!? Well, it's about time you learned a shorthand way of setting bits. The UCSZ0 is the equavilant to the one's place in binary. If you put another number, like 7, you are actually putting the binary equivalent of the number 7 in that location which fills up the next binary digits, because the number 7 is 111 in binary.
Note: If you want to change any of this frame stuff shown below, take a look at the TXC and RXC flags and make sure they are not set. If they are set, then there is still transmissions going on. For most microcontroller projects, this may never be needed, unless you want control of the port I/O pins onthe chip where TX and RX is.
Another note: For some strange reason, UBBRH and UCSRC share the same I/O location, so you need to make sure:
URSEL bit is set in the UCSRC if you want to put stuff in that register.
Setting the Parity Mode - This is where you set the error checking feature of UART and USART communication. The parity is set only by the transmitter.
If the parity is set for "even" then there are an odd number of ones in the data bits, the transmitter will set the parity bit to a "1" so there will be an even number of ones including the parity bit.
If the parity is set for "odd" then there are an even number of ones in the data bits, the transmitter will set the parity bit to a "1" so there will be an odd number of ones including the parity bit.
UPM - Parity Mode
UCSRC |= (1 << UPM1) | (1 << UPM0); //Sets parity to ODD, or
UCSRC |= (3 << UPM0); //Alternative way to set parity to ODD
Setting the number of Stop Bits to use - Do you want one or two cabooses on the end of your train? Remember that the receiver will ignore the second stop bit, so why did they put it in there in the first place? Who knows, but I think that it adds a "bit" of breathing room for the next data frame. How do you like that pun? Huh!
USBS - Stop Bit Select
UCSRC &= ~(1 << USBS); //clears the USBS for 1 stop bit, only needed if the bit was already set
Note: If the microcontroller receiving the data frame (the choo choo train) sees a stop bit that is low (it is supposed to be high), then the Frame Error Flag Bit (FE) will be set.
Example Initialization for the USART or UART:
In this example, the baud is being set, the transmitter (TXEN - Transmitter Enable) and receiver (RXEN - Receiver Enable) is being enabled, and in the UCSRC, the URSEL is set so we can modify UCSRC, 2 stop bits used and the length of the data bits is 8-bit.
You'll notice that, first, this is a function and, second, an unsigned int is being passed into this function. The void in the beginning means that this function will not return anything when it completes. This is the same example that the Atmega324 datasheet shows. Notice that the parity is not set in this example.
{
UBBRH = (unsigned char) (baud >> 8);
//Put the remaining part of the baud number here
UBBRL = (unsigned char) baud;
//Enable the receiver and transmitter
UCSRB = (1 << RXEN) | (1 << TXEN);
//Set 2 stop bits and data bit length is 8-bit
UCSRC = (1 << URSEL) | (1 << USBS) | (3 << UCSZ0);
Note: When TXEN is set (Transmitter Enabled), the General Purpose function of that pin is not availble until the TXEN is disabled. In other words, you cannot use TX pin to light up LEDS, or receive button presses and stuff like that. Same idea goes for the RXEN and the RX pin. One other thing, the TXEN cannot be disabled while there is a pending transmission. If you are using the XCK pin for synchronous operation, then the other general purpose function at the XCK pin is disabled.
Transmit Something!!
In this next example, we will actually transmit something. We are gonig to throw our data into the UDR (USART I/O Data Register) train station. I will let you in on a secret here, because you may get confused a bit later: You will put the data you want to transmit in the UDR register, and you will also get received data from the UDR as well. That seems totally off, doesn't it. If that's true then how would we be able to transmit and receive at the same time when we only have one data register to use... I mean, if full duplex is available, why is there only one register to transmit data and receive data. Seems odd.
Well, here is the secret! Shhhhh, don't tell anyone! The UDR actually has two places. The secret locations are called TXB and RXB (these are 8-bit locations). The microcontroller is so smart that when you place information into the UDR register, the stuff you put in is actually going into the TXB location. If you take the data from the UDR register, then you are actually taking the stuff out of the RXB location. You might be asking, what if we decided to set our data-bit lenght to 9 bits, cause TXB and RXB is 8-bits only? Those bits are located in the UCSRB register and their called, you guessed it, TXB8 and RXB8. Oh, one more thing, this is a polling example which means that we are going to wait (tap our toes) for the UDRE (USART Data Register Empty) signal to say its ok to go. It's like a stop light. You can't put anything into the UDR if the system is not ready for you. Ok enough jibber jabber, lets get to the example:
{
while (! (UCSRA & (1 << UDRE)) );
//Make the 9th bit 0 for the moment
UCSRB &=~(1 << TXB8);
//If the 9th bit of the data is a 1
if (data & 0x0100)
USCRB |= (1 << TXB8);
UDR = data;
The condition in the while statement may look foriegn to you. It is seemingly complicated, but it is the same type of (and) and (not) bitwise operation that we learned in an earlier video. The ! is a (not) and & is an (and). Remember when we say register &=~(1 << bit)? We are doing an & (and) calculation and reversing the bits with the ! (not). We can also put this in a condition like this: If you want to check if a bit is (not) 0, then we use (! (register & (1 << bit)) ).
Ok, so here is an explanation of the flags that inform us about the transmit process:
Polling Resources:
TXC = Transmit complete - You can use this resource if your are doing half duplex. This flag happens when the data leaves the shift register. Remember that queue that we talked about earlier.
Interrupt Resources: Remember to set your global interrupt variable before using these cool resources!
TXCIE = Transmit Complete Interrupt Enable - Use this if you are doing half-duplex and you want the microcontroller to interrupt you so you can go and put your data into the UDR train station.
Receive Something!!
The process for the microcontroller to receive data that exists on a wire from the other microcontroller goes like this:
- The microcontroller detects a start bit
- The data bits come next and there are taken in concert with the baud rate (or XCK for sychronous) heart beat
- The data goes to the shift register (like people getting off the train and waiting in queue to get off of the train platform)
- This happens until the first stop bit is received (remember, the second stop bit is ignored)
- The contents of the data then go directly to the UDR (well, really the RXB, but we still get it from the UDR)
Here is an example how to receive data:
{
return UDR; //Get that data outa there and back to the main program!
Do you notice the unsigned char at the beginning of our function statement? That is the type of return we will provide. The UDR will be an 8-bit unsigned value (char). We use the return statement to release the data from the UDR and pass it back to the main program.
Let me know if you want to see a 9-bit example.
Ok, so here is an explanation of the flags that inform us about the receive process:
Polling Resource:
Interrupt Resource: Remember to set your global interrupt variable when using this!