Skip to main content

Atmega328's USART

It is essential to have a convenient functions allowing quick serial port setup and usage. In this post I'll try to cover the basics of using the serial port, data reception & transmission + using interrupts.

Serial driver routines are very similar to supervising a NIC. Of course they are much more simpler and the speeds are lower as well, but the general approach is very similar. In the simplest model we can realize serial transmission/reception directly - by simply sending the data byte by byte waiting for the hardware to finish each transmission and poll the hardware status register before queuing/dequeuing new data. Although this approach will work, it's not very elegant and it requires constant CPU attention which consumes a lot of processing power. Using interrupts provides a lot more flexibility and save some processing power since we don't need to constantly check the USART's registers in order to know whether we have any pending data or not.

As usual we need some building blocks. Regardless of how the transmission/reception will be realized, an USART initialization routine is a must. We'll setup the port in needed mode, configure the amount of data/start/stop bits, the parity and the baud rate. Of course we need a routines to transmit/receive a byte from the port. Those will be implemented in polling fashion in the first place, then we'll introduce interrupt driven transmission/reception.

The initialization

Atmega328p's USART is a very friendly peripheral to configure. For most of our applications a standard configuration of 8 data bits, 1 stop bit + no parity bits, will be sufficient and we're going to configure the port in that manner. First thing that has to be done is as usual with every Atmega's peripheral - enable to power to it, we can use the macro from avr libc power_usart0_enable(). Next let's configure the port baud rate. This is done by setting up values in three registers. UBRR0H/UBRR0L and UCSR0A - where we enable so called double speed. Without going into details for most of the standard speeds there is a nice table provided in the datasheet, and we're going to use it. Arduino is using standard crystal of 16MHz, in order to configure the port for 57600 bps We need to write 34 to UBRR0H/UBRR0L register pair and enable the double speed (to minimize the frequency error). Next thing is to enable asynchronous mode 8N1 by writing 0x06 to the UCSR0C register and finally enable receiver and transmitter. The whole procedure looks as follow:

Data reception

Accordingly to the datasheet, we must poll for the RXC0 bit to be set in the UCSR0A status register and read the data as soon as it has been set. The data must be read as quick as possible to empty the hardware FIFO and make some place for new data, otherwise we'll drop the incoming data. The simplest reception function will look the following way:

This function is blocking of course. This means that once we request for example a single byte from the serial port, the CPU will be looping inside until the byte has been received. This may be a problem since it prevents us from doing anything else in the meantime. 

Data Transmission

It is realized in a very similar fashion, we poll the status register for the value of UDRE bit. Low value indicates that the USART is busy performing transmission, high value indicates that USART is free and we can place new data into hardware FIFO UDR0 register. The data will be immediately transmitted. For code reference have a look at the serial_poll_send() function on the previous snippet. Again, the function is blocking. It will block until a complete data buffer provided as an argument will be send. This may be a problem when our application produces a lot of data and needs some time to generate it without being interrupted. In most of the application though, blocking data transmission does not pose any issue. The interrupt driven reception is more important.

Interrupt driven IO

If we're not particularly worried about every CPU microsecond, and our application is flexible enough that it can work without problems with blocking IO during USART communication, then more or less we're done and the presented set of routines is functional complete. To get the most though from our MCU and it's USART utilizing the interrupts is a must. By sacrificing some memory for so called reception/transmission ring buffers we can perform the USART IO operations "in the background" interfering the main processing flow almost insignificantly.

Operating in such manner, as mentioned previously, is very similar to what NIC device drivers perform, the principle stays the same. For both directions (or only one if there is no need for the other) we define a ring buffer which is a queue more or less. The USART will act as a producer and the CPU will act as the consumer (in RX direction). We assume that the CPU can really read the data faster than the USART can provide thus the situation when there is no space in the ring buffer and the data must be dropped, will occur very seldom. 

In TX direction it works the opposite way, our application is the producer and USART is the consumer. If there is no space in the buffer (which means that either we have a too small transmission buffer or we produce the data too fast) the transmission will fail and will have to be retried later on.

Ring buffer

This is the essential part of the implementation. Let's define it as:


The data structure is very simple and common. One may found a lot of references describing details behind it thus I'm not going to provide a very thorough description. In principle, a "producer" pushes the data at position pointed by the head and increments it. The "consumer" takes the data pointed by tail until tail == head. The write/read operations in the ring buffer are independent. Have a look at the diagrams bellow, which depict most of the states a ring buffer may be found:

Operations on Ring buffer.

Interrupt driven data reception

Interrupt driven reception is much more widely implemented and much more useful than interrupt driven transmission. In most of the applications there is only a need really to provide this kind of input data handling while the transmission is still realized the "simpler" polled way. Let's get into details. The model is quite simple. We have an RX ring buffer which will hold the incoming data and a bunch of routines which will simply get the data and return it out of the ring, freeing the space in it. So in a sense the incoming data is buffered and waiting before it is actually read by the application. To summarize it stepwise, it would be:

Reception:
  1. Configure the port and install USART RX interrupt.
  2. When data arrives an interrupt is called.
  3. In ISR if there is space available in the ring buffer, place the new data in the ring and increment the head index.

Application reading data:
  1. If head != tail we have data available in the ring buffer.
  2. Return data indexed by tail.
  3. Increment tail.
Let's have a look on the code bellow:




Interrupt driven data transmission

It's very similar to the reception. We will use the UDRE empty interrupt which is triggered when the data transmission FIFO is empty. This means that when the CPU and USART are idle this will be called ALWAYS. Now that's not exactly what we want. Unlike in case of data reception interrupt, we will unmask the UDRE interrupt only when there is data available in the TX buffer and mask it as soon as the last byte has been sent. Let's list the steps of the algorithm:

Application:
  1. Queue new data for transmission if there is space in the ring buffer
  2. Unmask UDRE interrupt


ISR:
  1. Transmit the data from the ring buffer
  2. Mask the UDRE interrupt if there are no new characters to be send


And the code:

The functions and code snippets presented are taken directly from the libpca library (with small modifications), I kept the function names in tact to be compatible with the library as well. The complete usage example will use the library, we'll create something simple, the serial echo program. As usual let's clone a fresh copy of the library and create a directory for our project:

git clone git@github.com:dagon666/avr_Libpca pca
mkdir serial_echo
cd serial_echo

Our main.c will look the following way:

libpca defines an enumeration of standard serial speeds. User can specify the speed manually (by providing the number), or using the enumeration. In the above example the reception is realized using interrupts and the transmission using polling. In order for that to work one mustn't forget about enabling the RX interrupt in the library configuration file (include/config.h):

#define SERIAL_IMPLEMENT_RX_INT 1

After compiling our program, we can test it with the terminal emulator of choice. I'll use screen, my Arduino's serial port is visible as /dev/ttyACM0, I connect to it by typing:

screen /dev/ttyACM0 57600

Now whatever you'll type should be echoed back by the Arduino. That's all, we've mastered the serial port.  The libpca project can be downloaded from here:

https://googledrive.com/host/0ByE_WFvvg-guTzE4TmFwMDhaZTA/serial_echo.tgz

We're ready to do some more complex stuff now. Now as long as transferring text information through the serial line is quite easy and error prone, binary data transfers may be a little bit trickier. I'll try to cover the binary data transmission quirks in my next post as this knowledge will be needed in order to introduce you to some of my projects.




Comments

Popular posts from this blog

RTC without RTC ? How good is my crystal ?

Clock - I think that it's the most popular idea for a project with the MCU . It's not that hard from both software and hardware side to realize but not that trivial as well, not mentioning the fact that once realized it sits somewhere in the house, constantly reminding about itself and brings a kind of satisfaction :). There are a lot of options really to implement, a buzzer alarm, perhaps a thermometer, display and buttons servicing, perhaps maybe even serial port usage for NTP synchronization. But ... The most challenging thing - like in every clock is the time reference source. There are a couple of options: well calibrated frequency source acting as an interrupt source Real Time Clock module with external crystal ( < 20 ppm ) internal clock These days RTC chips are so cheap and widely available that they are really the only reasonable choice, but if we're only planning to play around a bit with the software without paying much attention to accura...

Arduino R-2R ladder Audio DAC

There is a lot of projects out there which use  R-2R ladder and an Arduino to recreate sounds from either SD card or short audio clips programmed directly to MCU's flash memory. Although using SD card is fairly reasonable and provides a lot of flexibility it's not very challenging (from the software point of view) and it requires some additional hardware. Believe it or not we already have all the needed hardware in the Arduino itself. Assumptions In this project I'll play mp3 or any other multimedia files from the PC using Arduino. Bellow are the details: Play PCM 8kHz 8bit Audio with Arduino Audio samples will be transfered via USART from the PC in binary format using SLIP Audio files will be decoded on the PC side and only the RAW data will be send to Arduino Timing Arduino is a powerful machine, powerful enough that it's possible to play audio with even higher sampling rates and bit-resolutions than assumed above, the bottleneck in this...

Simple Serial Port Command Line Interface (CLI)

It's often very useful to have some sort of interface through which a basic management can be done of our application. Whether you're designing a giant Marquee with a LED display or a plotter or you simply need to get some diagnostics from your device occasionally, a simple CLI system can come very handy.  Of course, designing something similar to "bash" or any other unix shell is completely out of scope since those applications are huge and are simply an overkill for our needs. It's pretty simple though to create some basic, yet flexible & easilly extensible CLI system. First thing needed is a command type definition. This will bind a keyword with an actual underlying routine executed for that keyword typed in the CLI . typedef struct _t_cmd { const char *cmd; void (*fh)(void*); } t_cmd; The command type is pretty simple. There's the CLI command/keyword pointer, it holds a pointer to the execution function and that's it. OK, so...