Using CMSIS to read/write on UART on #STM32F4

Yesterday I talked about SysTick. Today I’m going to talk about UART input/output. I’ll be using a Nucleo board, which uses UART2 to connect via USB. I’m not sure how the blackpill works in this regards. You will probably need a separate serial device and change some of the pins in my example.

We’ll be using polling and blocking to send/receive data. Using DMA or interrupts will free up clock cycles, but is more difficult to implement. We’ll keep it simple in this tutorial.

Here’s the code:

     1	#define STM32F411xE
     2	#include "stm32f4xx.h"
     3	
     4	uint32_t SystemCoreClock = 16000000;
     5	
     6	#define _USART USART2
     7	
     8	int putchar(int c)
     9	{
    10		//if(c=='\r') putchar('\n');
    11		while( !( _USART->SR & USART_SR_TXE ) ) {}; // wait until we are able to transmit
    12		_USART->DR = c; // transmit the character
    13		return c;
    14	}
    15	
    16	int puts(const char *s)
    17	{
    18		while(*s)
    19			putchar(*s++);
    20		putchar('\n');
    21		return 1;
    22	}
    23	
    24	int getchar()
    25	{
    26		while( !( _USART->SR & USART_SR_RXNE ) ) {}; // wait until something received
    27		return _USART->DR; // find out what it is
    28	}
    29	
    30	int main (void) 
    31	{
    32		// Set pins PA2 (TX) and PA3 (RX) for serial communication
    33		RCC->AHB1ENR	|= RCC_AHB1ENR_GPIOAEN; //enable RCC for port A
    34		GPIOA->MODER 	|= GPIO_MODER_MODER2_1; // PA2 is Alt fn mode (serial TX in this case)
    35		GPIOA->AFR[0] 	|= (7 << GPIO_AFRL_AFSEL2_Pos) ; // That alt fn is alt 7 for PA2
    36		GPIOA->MODER 	|= GPIO_MODER_MODER3_1; // PA3 is Alt fn mode (serial RX in this case)
    37		GPIOA->AFR[0] 	|= (7 << GPIO_AFRL_AFSEL3_Pos) ; // Alt fn for PA3 is same as for PA2
    38		
    39		RCC->APB1ENR  |=  RCC_APB1ENR_USART2EN; // enable RCC for USART2
    40	
    41		// Set the baud rate, which requires a mantissa and fraction
    42		uint32_t baud_rate = 115200;
    43		uint16_t uartdiv = SystemCoreClock / baud_rate;
    44		_USART->BRR = ( ( ( uartdiv / 16 ) << USART_BRR_DIV_Mantissa_Pos ) |
    45				( ( uartdiv % 16 ) << USART_BRR_DIV_Fraction_Pos ) );
    46	
    47		// Mpw enable the USART peripheral
    48		_USART->CR1 |= USART_CR1_RE // enable receive
    49			| USART_CR1_TE // enable transmit
    50			| USART_CR1_UE // enable usart
    51			;
    52	
    53		puts("UART test - if you can read this, then it should be working!");
    54		puts("Now type something, and I will echo it to serial");
    55		while (1) 
    56		{
    57			int c = getchar();
    58			putchar(c);
    59			if(c == '\r') putchar('\n');
    60		}
    61	}

As explained in a previous post, lines 1-4 set up some basic information about the mcu (microcontroller).

Line 6 defines a convenience definition, in case we want to use a different UART in the future.

Lines 8-14 writes a char to our selected UART. It’s surprisingly short code. SR is the status register. USART_SR_TXE is set to 1 if the transmit data register is empty. We busy-wait until it is clear, and we are able to transmit a byte. This is done line 12. Doing so resets USART_SR_TXE.

Lines 16-22 defines puts(). The implementation is intuitive enough.

Lines 24-28 defines getchar(), and operates in the reverse of putchar(). In line 26 we block until there is something to receive. Line 27 retrieves the character from the data register, and resets USART_SR_TXE.

Perhaps the most complicated bit is configuring the mcu for serial io.

Lines 32-37 configure pins PA2 and PA3 for serial communication. Line 33 enables port A. Line 34 tells the mcu that an alt function is required for the pin. rather than just being using as a general pin.

But which alt function? Each pin can be used for several alternatives. 16 different functions are potentially available. That equates with 4 bits. There are 16 pins in a port. So 16*4=64 bits need to be reserved by the mcu for configuration. The STM32 is a 32-bit machine, meaning that 2 register address are required for full configuration: a low and a high register. The low register is for pins 0..7, and the high register is to bins 8..15. CMSIS sets then up as an array.

You need to know that the alternative function for usart on pins PA2 and PA3 is 7. Line 35 does this. It sets the lower register with the value 7 for PA2, shifted to the appropriate bit offset (which is 8 = 2 * 4. 2 for pin 2, and 4 for the bit width of the alt function specification).

Lines 36-37 do likewise for PA3, which is the receive pin.

Line 39 activates the UART RCC.

Lines 41-45 set the baudrate in the BRR (Baud Rate Register). You don’t specify the baud rate directly, but have to calculate a divisor and remainder based on the system clock (although I don’t think that is strictly true. It is actually dependent on one of the peripheral buses, which might be at a different rate if you play with the clock settings.)

Lines 47-51 relate to the peripheral’s control register CR1. We state that we want to receive and transmit, and actually enable the device. The mcu has two other control registers: CR2 and CR3, which can be manipulated to provide additional features. We don’t need to set them in our example.

Lines 53-60 just print some info to the uart, and loops through getting a char from the uart and echoing it out again. Line 59 has a little tweak. When you press the Return key, the cursor moves to the beginning of the line. The mcu needs to put in a line feed. Perhaps this is better off being done in putchar().

The above code works, but you may want to slice and dice things differently for your own purposes. Maybe you only want to send data over uart and not receive it, for example. Or you might want to use a different uart and a different set of pins. Or a different baud rate. It is probably best if you don’t fiddle with the baud rate. 115200 is quite standard. 9600 is also a popular choice, but I prefer a faster speed.

One thing that might be especially worthwhile is to have a non-blocking way of receiving a char. You could split out line 26 and return the value of (_USART->SR & USART_SR_RXNE) to see if a character is waiting to be read. Then you can read it, if appropriate. You need to be careful that you aren’t doing anything too expensive, though, as you may miss character. But it’s something to think about.

I hope this post was useful to you. You can find the code here.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s