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.
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:
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.
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.
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:
- Configure the port and install USART RX interrupt.
- When data arrives an interrupt is called.
- 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:
- If head != tail we have data available in the ring buffer.
- Return data indexed by tail.
- Increment tail.
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:
- Queue new data for transmission if there is space in the ring buffer
- Unmask UDRE interrupt
- Transmit the data from the ring buffer
- 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 email@example.com:dagon666/avr_Libpca pca
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:
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.