Using the USART with AVR-GCC
I've had this three-quarters finished and sitting on my hard drive since
mid-last year, so I decided to finish it and post here for the benefit of
others. I'm the first to admit it's not very clearly written, so I may re-write
it in the future. Comment away.
This tutorial will focus on setting up the serial USART on the AVR platform.
Although other hardware AVR interfaces (eg, USI) can be configured for limited
RS-232 serial transmission and reception such alternatives will not be covered.
What is the USART?
The vast majority of devices in the current AVR family lineup contain a USART
hardware subsystem. The USART hardware allows the AVR to transmit and receive
data serially to and from other devices - such as a computer or another AVR.
The USART transmission system differs to most other digital busses in that it
does not utilize a separate pin for the serial clock. An agreed clock rate is
preset into both devices, which is then used to sample the Rx/Tx lines at
regular intervals. Because of this, the USART requires only three wires for
bi-directional communication (Rx, Tx and GND).
The RS-232 Specification
The serial USART is frequently referred to as RS-232, which is a reference to
the RS-232 specification which mandates the logic levels and control signals.
While the AVR's normal logic levels are about 3-5V, RS-232 communication uses a
low of +3V to +25V for a digital '0', and -3V to -25V for a digital '1'.
If the two USART devices are running at AVR logic levels, the Tx and Rx pins can
be connected together directly. If the devices are using RS-232 spec. voltages,
a level converter is needed.
This tutorial will assume the user is attempting to interface with a computer
RS-232 port, which uses the proper RS-232 specification logic levels.
First things first - Setting up
the Hardware
To connect your AVR to a computer via the USART port, you will need to add a
level converter to your existing circuit. The most common/popular serial level
converter is the Maxim MAX232 (datasheet avaliable
here). This small and relatively inexpensive IC will convert your AVR's 5V
logic levels to 10V RS-232 and vice versa.
Before you can hook up your MAX232, you need to first identify which pins of
your AVR are responsible for serial communication. Looking in your AVR's
datasheet, you should be able to identify the two serial port pins (named as
alternative pin functions TX and RX).
You should wire up your MAX232 to reflect the schematic here.
This will enable your AVR's USART hardware to interface with the computer's
USART hardware.
Deciding on your AVR's system
clock frequency
As mentioned above, the USART does not use an external clock. To allow the two
interfaced devices to communicate together, you need to decide on a baud rate
for communication. Also due to the timing-critical nature of the USART, your
system's clock should be stable and of a known frequency. The AVR's system clock
frequency is very important. The USART clock is derived from the system clock,
and it is used to sample the Rx line and set the Tx line at precise intervals in
order to maintain the communication.
If the system clock cannot be precisely divided down to a "magic"
frequency for perfect communications, it will have a percentage error (where a
byte fails to be read or written inside designated time frame). System clocks
for perfect USART communications should be multiples of 1.8432MHz which when
used will give a 0.00% error.
Other frequencies for the main AVR clock can be used, but as the frequency
drifts from the "perfect" multiples of 1.8432MHz, the greater the
percentage error will be (and thus the less reliable the serial communication
will be). It is generally accepted that error percentages of less than +/- 2%
are acceptable.
Looking in your AVR's datasheet in the USART section you should see a table of
common baud rates, system clocks and error percentages. You should find a
combination which suits your project's constraints and has the lowest possible
error percentage.
I will be basing the rest of this tutorial on an example setup; a MEGA16 running
at 7.3728MHz and a baud rate of 9600bps (bits per second). This combination,
because it uses a system clock which is a multiple of the magic 1.8432MHz, has
an error percentage of 0.00%).
Initializing the USART
Now you should have selected a baud rate, system clock and have your AVR set up
with a RS-232 level converter - you're all set to go! All you need is the
firmware to drive it.
First off, you need to enable both the USART's transmission and reception
circuitry. For the MEGA16, these are bits named RXEN and TXEN, and they are
located in the control register UCSRB. When set, these two bits turn on the
serial buffers to allow for serial communications:
Code:
Next, we need to tell the AVR what type of serial format we're using. The USART
can receive bytes of various sizes (from 5 to 9 bits) but for simplicity's sake
we'll use the standard 8-bits (normal byte size for the AVR). Looking again at
the MEGA16 datasheet, we can see that the bits responsible for the serial format
are named UCSZ0 to UCSZ2, and are located in the USART control register C named
UCSRC.
The datasheet very handily gives us a table showing which bits to set for each
format. The standard 8-bit format is chosen by setting the UCSZ0 and UCSZ1 bits.
Before we write to the UCSRC register however, note a curious peculiarity in our
chosen AVR, the MEGA16. To save on register addresses, the UCSRC and UBRRH
registers (the latter being explained later in this text) share the same
address. To select between the two, you must also write the URSEL bit when
writing to UCSRC:
Code:
The URSEL bit does not exist in all AVR models; check your chosen AVR's
datasheet.
There, we're almost done setting up the USART! The last thing to set for basic
serial communications is the baud rate register. This register sets the clock
divider for the USART which is used as a timebase to sample the incoming data at
the correct frequency. It also gives the timebase for sending data, so it's
vital for RS-232 serial communications.
The baud rate register is 16-bit, split into two 8-bit registers as is the case
with all 16-bit registers in the AVR device family. To set our baud rate
prescaler value, we first need to determine it. Note that the baud rate register
value is NOT the same as the baud rate you wish to use - this is a common point
of failure amongst those new to the serial subsystem. Instead, the value must be
derived from the following formula:
Where F_CPU is your AVR's system clock frequency (in Hz), and USART_BAUDRATE is
the desired communication baud rate.
Given my example project using a system clock of 7372800Hz and a baud rate of
9600, our formula gives:
To make our life easier, we can turn this formula into a set of handy macros.
F_CPU is automatically defined in AVR-GCC via your makefile, so all that is
needed is the baud rate:
Code:
This avoids "magic numbers" (unexplained constants) in our source
code, and makes changing the baud rate later on very easy - just change the
BAUD_RATE macro value. Now, we need to load this into the baud rate registers,
named UBRRH (for the high byte) and UBRRL (for the low byte). This is simple via
a simple bitshift to grab the upper eight bits of the BAUD_PRESCALE constant:
Code:
Now we're ready to rock and roll!
Sending and receiving data
Once initialized, we're ready to send and receive data. We do this by the
special register named UDR - short for "USART I/O Data Register". This
is special for two reasons; it behaves differently when it is read or written
to, and it is double buffered.
By assigning a byte to the UDR register, that byte is sent out via the AVR's Tx
line. This process is automatic - just assign a value and wait for the
transmission to complete. We can tell when the transmission is complete by
looking at the transmission completion flag inside the USART control registers.
On the MEGA16, the Transmission Complete flag is located in the control register
UCSRA, and it is named TXC. Using this information we can construct a simple
wait loop which will prevent data from being written to the UDR register until
the current transmission is complete:
Code:
However this is non-optimal. We spend time waiting after each byte which could
be better spent performing other tasks - better to check before
a transmission to see if the UDR register is ready for data. We can do this by
checking the USART Data Register Empty flag instead (called UDRE), also located
in the UCSRA control register of the MEGA16:
Code:
This is much better, as now time is only wasted before a transmission if the UDR
register is full. After a transmission we can immediately continue program
execution while the UDR byte is sent, saving time.
Now we can move on to receiving data. As mentioned before, the UDR register
behaves differently between read and write operations. If data is written to it
it is sent out via the AVR's Tx pin, however when data is received by the RX pin
of the AVR, it may be read out of the UDR register. Before reading the UDR
register however, we need to check to see if we have received a byte.
To do this, we can check the USART Receive Complete (RXC) flag to see if it is
set. Again, this is located in the UCSRA control register of the MEGA16:
Code:
Putting it all together
Right! Now it's time to put everything together! Let's make a simple program
that echoes the received characters back to the computer. First, the basic
program structure:
Code:
Next, our USART initializing sequence:
Code:
Now, an infinite loop to contain our echo code:
Code:
And some echo code to complete our example. We'll add in a local variable "ReceivedByte",
which will always hold the last received character from the computer:
Code:
And voila, we have a working serial example! Connect to this project via a
serial terminal on your computer, using 8-bit, no parity 9600 baud communication
settings and it will echo back anything you send to it.
Potential Pitfalls
There are several "gotchas" when dealing with the USART. First, make
sure your AVR is running of a reliable, known clock source with a low error
percentage at your chosen baud rate. New AVRs default to their internal
oscillators until their fuses are changed. The
internal RC oscillator is of too low a tolerance for use with the USART without
external calibration.
Check your datasheet to check for the locations of the different flags.
Different AVRs have the control flags located in different control registers (UCSRA,
UCSRB and UCSRC).
Make sure your computer terminal is connected at the exact settings your AVR
application is designed for. Different settings will cause corrupt data.
Cross your Rx and Tx wires. Your PC's Rx line should be connected to your AVR's
Tx line and vice-versa. This is because the Rx/Tx lines are labeled relative to
the device they are marked on.
Some AVRs do not have the URSEL bit to differentiate between the UCSRC and UBRRH
registers - check your datasheet.
Advanced serial communication
A more advanced method of serial communication involves the use of the USART
interrupts and is not covered here. I may cover it in a later tutorial if so
desired.
- Dean