STM32 USART sending and receiving data

Using USART for sending and receiving data

USART is the same in ‘Arduino world’ and Keil programming, but getting message out is bit different – instead using “Serial.print” we are using here custom made function “printMsg” which uses ‘vsprintf’ function for formatting whole message which is then transferred to the data register and shifted out by shift register.
For formatted print, there is cplusplus reference fro programmers.

Diagram from Reference manual RM0008:

No DMA yet, just manual TX/RX-ing

DMA on USART can help a lot, but for now I did not learn enough to give you an example how to use it. As I cover part by part of STM32 programming in Keil, I am trying to make it simple so that everyone can understand. DMA (Direct Memory Access) is one way to move data from one part of the MCU to another. But also, there is old ‘manual’ ways.

Using HTerm console

Instead Putty or Serial monitor from Arduino, I like rather to use HTerm, it is free and has everything one programmer need. You can debug your program and see output data in various formats: ASCII, decimal, hexadecimal and binary form. There are other things as is flow control, saving received data, sending saved or generated file out over the USART and many other nice things.

Here are example codes, you should to include all three files into your Keil project: main.c, pringMsg.c and printMsg.h – make it so that all three files are in the same folder. Don’t forget that we need FTDI adapter.

Example codes

main.c file:

/*********************************************
 USART1 TX on STM32 is at pin A9, RX is at pin
 A10. Use pinout, or CubeMX to find those two
 pins. 
==========STM32===========FTDI================
           GND------------GND pin
   pin A9  TX-------------RX  pin
   pin A10 RX-------------TX  pin
 *********************************************/

#include "stm32f10x.h"
#include "printMsg.h"

int len=0;
char buff[256];

void delay(long cycles)
{
  while(cycles >0)
  cycles--; // 'wasted clock cycles', not exact time
}



int main()
{
	usart_1_enable();
	printMsg("Here is message %d 0x%X \n", 65, 65);
			
	while(1)
	{
	
    readMsg();
				
		
    //this part just send back to the HTerm console - the same text as received		
		for (int i=0;i<len+1;i++)
	  {
	    USART1->DR = buff[i];
	    while(!(USART1->SR & USART_SR_TXE)); //TXE becomes true (1) when the content of DR (data register) goes into shift register
	  }
	}
}

printMsg.c file:

#include "stm32f10x.h"
#include "stdint.h"
#include <stdio.h>
#include "stdarg.h"
#include "string.h"
#include "printMsg.h"

void usart_1_enable(void)
{
  //enabling pin A9 for alternating funct. for uart/usart
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN; //clock to GPIO A enabled, alt.funct.en, USART1 clock enabled
	GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1; //port A9
	GPIOA->CRH &= ~GPIO_CRH_CNF9_0; //port A9 is TX pin
	GPIOA->CRH &= ~(GPIO_CRH_MODE10_0|GPIO_CRH_MODE10_1); //port A10 is RX pin
	GPIOA->CRH |= GPIO_CRH_CNF10_0; //port A10 is RX pin
	
	//GPIOA->CRH = 0x444444B4; // A9 is alternate output, 50 MHz, push-pull - not this time short version (!)
	//clkPer/(baudRx_16bit)=72MHZ/9600 = 7500 = 0x1D4C
	/* Remove comment line for speed that you want to use*/
	//USART1->BRR = (0xEA60); //   1200 Baud
	//USART1->BRR = (0x7530); //   2400 Baud
	//USART1->BRR = (0x3A98); //   4800 Baud
	USART1->BRR = (0x1D4C); //   9600 Baud
	//USART1->BRR = (0x1388); //  14400 Baud
	//USART1->BRR = (0xEA6) ; //  19200 Baud
	//USART1->BRR = (0x9c4) ; //  28800 Baud
	//USART1->BRR = (0x753) ; //  38400 Baud
	//USART1->BRR = (0x505) ; //  56000 Baud
	//USART1->BRR = (0x4E2) ; //  57600 Baud
	//USART1->BRR = (0x271) ; // 115200 Baud
	//USART1->BRR = (0x232) ; // 128000 Baud
	//USART1->BRR = (0x119) ; // 256000 Baud
	//USART1->BRR = (0x8C)  ; // 512000 Baud
	//USART1->BRR = (0x46)  ; // 1024000 Baud
	//USART1->BRR = (0x23)  ; // 2048000 Baud
  //USART1->BRR = (0x18)  ; // 3000000 Baud (3 MHz, max speed that HTerm can get, non-standard speed)
	
	
	USART1->CR1 |= USART_CR1_TE; //transmitter enable
	USART1->CR1 |= USART_CR1_RE; //receiver enable
	USART1->CR1 |= USART_CR1_UE; //usart enable
}

void printMsg(char *msg, ...)
{
	//char buff[120]; //was 80
	va_list args;
	va_start(args,msg); 
	vsprintf(buff,msg,args);

	for(int i=0;i<strlen(buff);i++)
	{
	  USART1->DR = buff[i];
	  while(!(USART1->SR & USART_SR_TXE)); //wait for TXE, 1 = data transferred
  }
}

int readMsg(void)
{ 
  
	for( len =0;len<256;len++)
	{
    while(!(USART1->SR & USART_SR_RXNE));
	  buff[len]= USART1->DR;
    if (buff[len]==10 || buff[len]==13) break; //if enter is pressed, providing that it is 10 or 0xA (line feed), or 13 (0xD) (carriage return) 
	}
	return len; //just returning number of entered characters, not including line feed nor carriage return (!)
}

printMsg.h file:

#ifndef printMsg_h_
#define printMsg_h_

extern int len;
extern char buff[];

void usart_1_enable(void);
void printMsg(char *msg, ...);
int readMsg(void);

#endif

In printMsg.c file is RCC->APB2ENR register with everything we need to provide clock to the USART and GPIO pins (A9 is TX, A10 is RX):

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN;

Setting pins A9 (TX) and A10 (RX) for USART1:

GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1; //port A9 GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1; //port A9 GPIOA->CRH &= ~GPIO_CRH_CNF9_0; //port A9 GPIOA->CRH &= ~(GPIO_CRH_MODE10_0|GPIO_CRH_MODE10_1); //port A10 is RX GPIOA->CRH |= GPIO_CRH_CNF10_0; //port A10 is RX

Note that this time GPIO port A9 uses ‘alternate’ function instead ‘general purpose’. If you set it incorrectly, it will not send data.

Next thing is setting USART speed in Bauds.  Here is example for 9600 Baud:

USART1->BRR = (0x1D4C); // 9600 Baud

To get this number needed for Bit Rate Register BRR, You need to divide your clock with 9600, 72000000/9600=7500, but it is less confusing to use hexadecimal value at this place, so use your programmers calculator and convert 7500 into 0x1D4C. In printMsg.c file I calculated all mostly common using USART speeds, plus few not so common. The maximum speed HTerm can transmit and receive is 3000000 Bauds (~ 3 megabauds). Putty can’t cope with that, and did not tried Arduino serial monitor.

Inside printMsg() function, there is final sending part:

USART1->DR = buff[i];USART1->DR = buff[i];   while(!(USART1->SR & USART_SR_TXE));

What id does, it send first byte from our array buff[] into register USART1->DR, then we should wait until it is transferred into shift register and send out. This means that we should make waiting while loop and ‘watch’ TXE flag to appear – sending of one byte is done, now we can continue loading next byte into USART1-DR data register.

Receiving part is little bit different. We first wait flag RXNE that told us that data register is filled with one byte and ready for read.

while(!(USART1->SR & USART_SR_RXNE));while(!(USART1->SR & USART_SR_RXNE));   buff[len]= USART1->DR;

To recap: for sending byte out, we first send one byte, then wait flag XE, for receiving byte in, we first wait flag RXNE, then we can read.

On the console, there should be enabled one of three options – after typing message, when we press enter, all data are send, but we need extra byte or two to tell MCU that there is end of the message. Else, our loop may wait until whole buffer is full. So, enabling CR and/or LF (Carriage Return / Line Feed), with decimal values of 13 and 10 respectively. Inside for loop, there is:

if (buff[len]==10 || buff[len]==13) break;

Which checking whether received byte is either of those two options, CR and/or LF, then it exit or break loop. After that it just returns length of the message “return len;”. Since ‘buff[256]’ is global array, after returning back from readMsg() function, this buffer is filled with our data, and we need to know how much. Variable ‘len’ gives us exactly that number.

I made simple test ‘loopback’ code that sends everything back to the console:

for (int i=0;i<len+1;i++) for (int i=0;i<len+1;i++)   {     USART1->DR = buff[i];     while(!(USART1->SR & USART_SR_TXE)); //TXE becomes true (1) when the content of DR (data register) goes into shift register   }

Note that inside for() loop, there is ‘len+1’, that is because length of array is certain number, then we ad our CR and/or LF character that tells HTerm to go in new line.

Custom USART function

Sometimes we need just send data from our MCU inputs. For example potentiometer data. Then we have no need for formatted print or anything, just making some loop that read ADC for example, and send byte by byte out (and waiting TXE flag):

USART1->DR = pot_value;     while(!(USART1->SR & USART_SR_TXE));

Where ‘pot_value’ is some data from ADC which measure voltage on your potentiometer.

If you want to externally change something by typing, then use this:

while(!(USART1->SR & USART_SR_RXNE));
our_variable= USART1->DR;