Dont forget to check out the vidoe version of this tutorial on my YouTube channel.
The objective of my tutorials is not to divulge into the timing intricacies of any protocol. Only when it is necessary to program and setup the peripheral in the STM32 will I explain any low-level details of how the protocol operates.
One of the simpler communications peripherals to setup is the USART is UART mode. For this reason and because of its usefulness it proceeds GPIO. By the end of this tutorial you will learn to setup the USART in asynchronous mode thus making it a UART. Using the concepts learned here you will be able to write a reusable library for printing onto a serial monitor, that tutorial will be linked at the end of this tutorial.
The first order of business is to figure out what bus the USARTs are connected to. The image below demonstrates that UART1 is on the APB2 bus while all the rest of the USARTS are one APB1. This is a very important factor because those two busses have different peripheral clock frequencies which we will use to calculate our desired baud-rate. In addition to the baud rate calculation, knowing which bus the USART is located on helps us find it in our RCC module to enable their clocks.
So for example if we wish to use USART1 on the APB2 bus we then know by common convention that its clock enable bit will be located on the APB2 Enable Register in the RCC which translates to :
Like wise we know by convention that the specific bit will look similar in nature:
If we put it all together with a nice OR operation we get the following line of code:
RCC->APB2ENR |= RCC_APB2NER_USART1EN
Page 181 of RM0008 shows that USART1 TX and RX pins are on PA9 and PA10 respectively when REMAP =0, and PB6 and PB7 when REMAP = 1 The remap configuration bit allows us to remap our peripherals to different pins if desired. It may be the case that we are using those pins for another peripheral so there will be a conflict, at which point we would remap them, or simply use a different USART but the remap is there in case you need it. REMAP defaults to 0 at system reset.
At this point you can use the previously written GPIO driver to set up PA9 and PA10 to alternate function mode. For the sake of those who are not familiar with the driver I will program the GPIO in explicitly here.
A common configuration for UART communication is to have no parity control, 1 stop bit, and 8 bit word length. Knowing this STM has made the USART reset/default values to match this common configuration which means we have very little to do. By default the USART is also set to asynchronous mode thus making it a UART. Your frame packet ends up looking like the UART pictured below. Notice how it has no clock and just a start bit and an end bit along with your data.
Setting the baud-rate for the UART is nothing out of this world but does require math, just basic multiplication. And this where the USART you chose comes into play. The baud-rate is generated based on a value programmed into the BRR register that value is called USARTDIV. You do not directly program the baud rate into the register, for example you would not set that register to 9600 to get a baud-rate of 9600. That would be too easy and logical so that is not how they designed it. Instead you will use the formula below to generate a USARTDIV value that we will then pass to the BRR register and that is how it will generate your desired baud rate. The value that will go into the BRR register is the USARTDIV value for which we will solve shortly.
In the formula above Fck is the frequency of our peripheral clock, which is not always the speed of our system clock. If we would have chosen to use one of the USARTs that is on the APB1 bus you will notice below that the peripheral clock on that bus is 36MHz.
Baud Rate Register:
In this example we are finding the USARTDIV value for USART1 to operate at 9600 baud rate on APB2 bus. Fck is a known value of 72MHz for APB2 bus and baud-rate is a known value dictated by us and USARTDIV is unknown we algebraically manipulate the formula to get the USARTDIV alone on one side and solve for all known values. This final USARTDIV value is what we will put in the BRR register to get the desired baud-rate of 9600.
Solving for USARTDIV we get a value 468.75 we grab the whole part (468) and convert it to hex which yields 0x1D4. Then the remaining fractional part 0.75 is multiplied by 16 and that result is converted into hex. The two hex numbers are combined to yield 0x1D4C. Ultimately this value will get put into the BRR register simply as USART1->BRR = 0x1D4C Do not forget this is the answer fro UART1 on the APB2 bus. Another UART would yield a different result because it would be on the APB1 bus and the value plugged in for Fck would be 36MHz.
The final thing left to do is to enable the UART RX and TX as well as the UART itself. All these setting bits are found in Control Register 1 (CR1) . Other relevant settings are also found in CR1 but as mentioned before we will be using them at default values so there is no need to change anything with them. This register also contains interrupt related bit which I will discuss in the second part of this tutorial in which we implement interrupt based communication. The line below takes care of RX enable (RE), TX enable (TE), and UART enable (UE). Make sure UART enable is the last thing you do. Datasheet states for a lot of peripherals that they must not be enabled until configured.
USART1->CR1 |= USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;
The highlighted bit in the register are as follow:
Status Register:In order to know when our data has been sent or received we will poll the status register (SR)
The highlighted bit in the register are as follow:
- TXE: Transmit data register empty. This bit indicates that the contents of the data register (DR which is where we write the data to be sent) have been moved to the shift registers where the transmission actually takes place. Meaning the data register is empty and thus you can write new data to it for transmission, does not mean the data has been sent yet.
- TC: Transmission Complete. This bit will be set when the data transmission is actually complete.
- RXNE: Receiver Not Empty. Simply put, we have received data.
It is important to distinguish between TXE and TC, while they may be used interchangeably in the code I have provided below, they are indeed telling us two different things. Other bits in this register relate to the options we did not enable like Parity error etc.. You can check out the data sheet to learn more about those.
Data Register:As mentioned above the Data Register is where we will write our data. It has bits 0 through 8 in case you set your word length to 9 bits, otherwise you will only use bits 0-7. Writing data to the DR register starts the transmission.
Sending & Receiving:In this basic example we will wait for data to be received and then we will transmit it back, basically an echo program. So whip out your favorite USB to Serial converter dongle, and open up your favorite COM port monitor.
That does it for the basic UART, next lets implement a more efficient interrupt driven UART