Programming STM32 USART using GCC tools. Part 1

When we need some feedback from microcontroller usually we use USART. It allows to output messages, debug information to terminal screen. Also data can be sent to MCU same way. For this purpose STM32 microcontrollers have more than one USART interface allowing to have multiple streams of data output and input.

USART interface is designed to be very versatile allowing to have lots of modes including LIN, IrDA, Smart card emulation, DMA based transmissions. But for now lets focus on standard USART communications we we could send and receive messages from terminal window.

STM32F100RB microcontroller in Discovery board has three USARTs (USART1, USART2 and USART3). Other higher level STM32 microcontrollers have even more. USART1 is connected to APB2 bus while other USART’s are located on APB1. Every USART has a two DMA channels allowing data transfer between memory and Rx/Tx. Each USART has ten interrupt sources and all are mapped to same NVIC channel. So it is up to code to find out what triggered an interrupt. This is done by identifying flag in status register. Why don’t we take a real example and see how things work.

For this we are going to program USART1 in our STM32VLDiscovery board. Discovery board only comes with naked pins that we have to connect to USART level converter like RS232 or USB. For this example we are going to use widely acceptable FT232RL based TTL to USB converter. So we connect board pins to converter as follows:

STM32VLDiscovery PA9 to Rx of FT232RL board

STM32VLDiscovery PA10 to Tx of FT232RL board

STM32VLDiscovery 3.3V to VCC of FT232RL board

STM32VLDiscovery GND to GND of FT232RL board

Using STM32F10x_StdPeriph_Driver library programming task becomes very easy. Lets use our template from previous example. This time we are going to add couple more files to project called usart.c and usart.h. Here we are gonna to write USART initialization function and byte read and write routines. Lets start with Initialization:

voidUsart1Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

USART_ClockInitTypeDef USART_ClockInitStructure;

//enable bus clocks

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

//Set USART1 Tx (PA.09) as AF push-pull

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

//Set USART1 Rx (PA.10) as input floating

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_ClockStructInit(&USART_ClockInitStructure);

USART_ClockInit(USART1, &USART_ClockInitStructure);

USART_InitStructure.USART_BaudRate = 9600;

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

USART_InitStructure.USART_StopBits = USART_StopBits_1;

USART_InitStructure.USART_Parity = USART_Parity_No ;

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

//Write USART1 parameters

USART_Init(USART1, &USART_InitStructure);

//Enable USART1

USART_Cmd(USART1, ENABLE);

}

Here we have a standard situation where we have to take care of enabling peripheral clocks. We know that USART1 is located on APB2 peripheral bus as GPIOA and AFIO. All these are needed because USART1 pins alternate functions of port A. These has to be clocked in order to use them:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);


Next task is to set up USART pins where Tx pin should behave as alternate function push-pull while Rx floating input. Once pins are set we need to take care of USART clock. As we are going to use standard set up we can use function helper that does this by setting default values:

USART_ClockStructInit(&USART_ClockInitStructure);

USART_ClockInit(USART1, &USART_ClockInitStructure);

Then we can set up USART specific settings by selecting baud rate, data word size, stop, parity bits and enable Rx and Tx modes. All this is done by setting values in USART_InitStructure as seen above. Last command simply enables USART1. From this moment USART1 is up and ready. New we can proceed to sending and receiving data functions:

void Usart1Put(uint8_t ch)
{
	  USART_SendData(USART1, (uint8_t) ch);
	  //Loop until the end of transmission
	  while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
	  {
	  }
}
uint8_t Usart1Get(void){
	 while ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
		return (uint8_t)USART_ReceiveData(USART1);
}

We call them Usart1Put and Usart1Get functions that sends and receives data bytes. You can see in both functions that it is important to wait for transmission complete before sending next byte or storing received data. While loops simply check for proper flags to be set.

In main program we can call these functions to send and receive single bytes of data as follows:

#include "stm32f10x.h"
#include "usart.h"
int main(void)
{
Usart1Init();
Usart1Put('A');
Usart1Put('R');
Usart1Put('M');
while (1)  {  }
}

Being able to send single byte is a good start where you can wrap this function with loops in order to send strings and so on. But what about sending numbers or specially formatted strings that would include some variable data. Writing these functions would be time consuming and meaningless because this has been done long time ago and is widely used. These are called iostream functions where data is sent by using standard streams with functions that allow formatting strings as you like. I/O streams originally were designed to support operation systems where data could be read from keyboard and sent to display or other output device. It seems that they fit well in embedded systems too with some tweaking. GCC tool chain uses a newlib C library that originally is used in Redhat and there fore it’s not adapted for microcontroller where basically is no operating system. (Opposite situation is with AVR GCC tools where avrlibc is specially adapted to support AVR microcontrollers and works out of box). So when we start dealing with OS dependable functions we face some stubs. Stubs are called system call functions that are served by operating system. As in microcontroller basically there is no OS these functions face dead-end leading to compile errors. Basic trick to avoid this we need to take care of these syscalls by writing some implementation where many of them can be dummy. Understanding them all and writing your own implementations would be to hard – at least in the beginning, so there are ready sources that can be used. I prefer to start with newlib_stubs.c developed by nanoage.co.uk. This seems to work fine with I/O stream functions. There is a dozen of other system functions but most important are _write(), _read() and _sbrk(). _write function takes care of writing characters to stdout and stderr; _read() is to read from stdin; and _sbrk() is needed for malloc related functions where it is important to know if dynamic memory doesn’t collide with stack. In _read() and _write() functions we need to implement our byte send and read functions so when we call printf() function we could output string to USART:

int _read(int file, char *ptr, int len) {
    int n;
    int num = 0;
    switch (file) {
    case STDIN_FILENO:
        for (n = 0; n < len; n++) {
        	char c = Usart1Get();
            *ptr++ = c;
            num++;
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return num;
}

and for writing to stdout and stderr:

int _write(int file, char *ptr, int len) {
    int n;
    switch (file) {
    case STDOUT_FILENO: /*stdout*/
        for (n = 0; n < len; n++) {
        	Usart1Put(*ptr++ & (uint16_t)0x01FF);
        }
        break;
    case STDERR_FILENO: /* stderr */
        for (n = 0; n < len; n++) {
        	Usart1Put(*ptr++ & (uint16_t)0x01FF);
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return len;
}

simply speaking you can direct streams where ever you want (LCD, SPI, I2C, etc.) by putting send/receive byte functions here.

After setting this we can send formatted strings and read any type of data by using stdio functions:

#include "stm32f10x.h"
#include "usart.h"
#include <stdio.h>
int main(void)
{
	char ch[30];
	int32_t a1,a2;
	Usart1Init();
	printf("\r\n USART1 Test \r\n");
	printf("Enter any text: ");
	scanf("%s",ch);
	printf("\r\nYou entered: ");
	printf("%s\r\n",ch);
while (1)
  {
	printf("\r\nEnter first number: ");
	scanf("%ld",&a1);
	printf("%ld", a1);
	printf("\r\nEnter second number: ");
	scanf("%ld",&a2);
	printf("%ld\r\n", a2);
	printf("Sum: %ld + %ld = %ld\r\n",a1, a2, a1 + a2);
  }
}

And results in terminal screen:

 

In the next part we will be dealing with interrupt based buffered communications.

Read

Bookmark the permalink.

5 Comments

  1. Hello! I start to use STM32-VL-Discovery. Can you download (or take links) for this project. When I try to compiling my project with newlib_stubs.c many errors, such as ..\..\..\STM32_VL_Discovery_Libraries\newlib-1.20.0\newlib\libc\include\sys/types.h(110): error: #256: invalid redeclaration of type name “time_t” (declared at line 70 of “C:\Keil\ARM\RV31\INC\time.h”) appears. Maybe, I do not correct use this library… My goal – is write to COM-port of PC some “words” from STM32, not only symbols (chars) as function printf() work (by example).

  2. i need more basic commands in the program belongs to the beginners…

  3. Hello, I used your method but program always stop at scanf(), terminal type any word still stop, until the TX more than 1024 bytes, then the program will run next step. What ascii code will let scanf conclude ? How could I do ?

  4. What is dummy ISR When i was working on USART1……
    I have been working from two to solve this error. Can any body know about this.

  5. Code::Blocks with Embedded Plugin Suite is
    nice environment for STM32 development including ARM GCC compiler STM32 project wizard (including lot of STM32 discovery examples) debugger supporting ST-Link / V2 and Serial wire viewer for output of debug messages in runtime. All discussed jobs here can be easily performed.

Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>