Implementing UART
The software above is just the software that I am using. It works on MacOS and Linux. However, other tools exist.
In this session, the goal is to implement the Universal Synchronous/Asyncrhonous Receiver Transmitter in just a mode to transmit data. This will result in being able to print text to a serial console that we can access over USB.
At the end of this article, I will have done a walkthrough of code that can operate the USART in transmit mode to print a message, blink and LED and delay between those action.
One tool, or a tool with similar functionality, that will be needed if GNU Screen. I can not possibly cover all tools that offer this same functionality, so I will only cover the one I use, GNU Screen. If you are not familiar with GNU Screen, it can create terminal like sessions that can be attached and detatched from. I often use it as a way to have a persistent session on a server I work on over SSH between session. In this article and in the future, we will use that as our serial console to view the text display from our microcontroller.
To be able to spawn a GNU Screen session to our arduino we will need to run the follow:
In the command we call screen
, then we define the device we want to create a session to (/dev/tty.usbserial-140
) and finally specify the baud rate (57600
).
To gracefully exit from this session, my recommend method is to hold the control key
and press a
, then release those button and press k
. A prompt will ask if you want to kill the session, hit y
to do so.
NOTE: In order to flash to the AtMega 328P, we can not have any serial connections to the device, otherwise it will fail.
A new rule is added for screen, this is mostly to add speed for testing. If you are not using GNU screen, this rule will need to be removed. The usb
section also need to be adjusted to what the device appears as on your computer.
Above is our main.c where we include our relevant headers and implement the same delay function. The only change to our delay function was that I made the inner loop count to 64 to create a longer delay and I changed that inner variable j
to an int since a long was not necessary.
Down in the main function itself, we start off by initializing the USART, setting our message we want to print and initializing the LED. Then, we hit our cyclic executive, or essentially our basic process scheduler for the program. We will toggle the LED, print the message we set and wait. When the wait is over, we go back to toggling the LED, printing the message and waiting again. We do this cycle indefinitetly.
The LED class I will not be covering here since it was covered in the previous article, however, the source is down below.
In usart.h
, we simply define our public methods for our USART driver class. We want to be able to set our message, initialize our hardware and finally trigger the print functionality we build.
Our USART is a lot more involved than our LED. We are spread across using multiple registers and appear to have a lot going on. I tried to comment to illistrate various aspects. At the start of the file, we define our registers. I added comments to cover the bits of significant registers that we are using and provided their name and in some instances a short description. Most of the bits in these registers, we will not have to use to accomplish our goals.
So, looking at our datasheet for the AtMega 328P, we need to look at the section for the USART. First register to look at is UDR0
. This register is our buffer. When we receive (which is not covered in this tutorial), the recieved information is placed in the buffer. That buffer is also the same as the transmit buffer, so it can only work one way at a time. When in transmit, we place an 8 bit value we want in that register to be transmitted to our USART interface. Important to notice, it is an 8-bit register (or 1 byte or the size of 1 char).
Next register to look at is UCSR0A
. This register, each bit represent something to the hardware, our concern for this is bit 5. This register will tell us when the UDR0
register has been cleared after transmitting. When the bit in UDRE0
is 0, our UDR0
register is empty. When we transmit over the USART, when a value has been transmitted, it will zero out our UDR0
register.
In register UCSR0B
, the bit we are concerned with is the TXEN0
bit. In order to set the USART to transmit mode, that bit needs to be set to 1.
For register UCSR0C
, we are mostly concerned with bits 1 and 2. Here we are setting out bit size. We plan to transmit 8 bits. so we need to write a 1
to both of these locations. In the datasheet, there is a table that shows different configurations for this.
Lastly for registers, UBBR0
which I have as two seperate registers denoted as ending in L
and H
for their high and low portions. This register is a 12 bit register that sets the baud rate for the USART to operate at. The layout is the full 8 bits located in the lower portion of the register and the low 4-bits of the hight register. The high 4 bits of the high register are ignored by the microcontroller.
Following the registers, I have some pre-compiler definitions used to calculate the BAUD prescalar. Going back to register UBBR0
, we often call these prescalars. We can think of them as a nob that goes to different set points. We have our microcontroller clock rate (16 MHz) and our desired Baud rate, using the calculation in BAUD_PRESCALAR
, we can calculate the proper prescaler we need to set register UBBR0
.
The first non-pre-compiler definition is defining a pointer to a character array called message
. This is file-scope and is only visibile to the methods in usart.c
. Main can not directly manipulate this variable. It can only do so by the method usart_set_message
. This creates a sort of private data member that can be changed through a public method giving us an OOP like abstraction of encapsulation.
Next, we have the usart_init
method that initializes the settings for our usart by manipulating most of the bits I touched on above covering the registers we are using.
In the usart_print
method, we start off by zeroing out our UDR0
register, we do not want to assuming it is zeroed. Then, we enable transmit mode by manipulating rh TXEN0
bit. Next, we enter a for loop for the length of the message. During each iteration we check to see if UDR0
is empty. If not, we loop and wait. Once it is, we load the next character into the UDR0
register. Once the full contents of the message have been sent out to the USART, we call on the usart_crlf
which simply transmits our carriage return feed line to go to the next line of our USART interface then we disable transmit mode.