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 converters 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 the 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. Let’s use our template from the previous example. This time we are going to add a couple more files to the project called usart.c and usart.h. Here we are going to to write USART initialization function, and byte read and write routines. Let’s 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 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. The last command simply enables USART1. From this moment USART1 is up and ready. Now we can proceed to send 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 transmits and receives data bytes. You can see in both functions that it is essential to wait for transmission complete before sending the next byte or storing received data. While loops simply check for proper flags to be set.

In the 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 a single byte is a good start where you can wrap this function with loops 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 a 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 operating systems where data could be read from the keyboard and sent to display or another output device. It seems that they fit well in embedded systems too with some tweaking. GCC toolchain uses a newlib C library that originally is used in Redhat and therefore it’s not adapted for microcontroller where is no operating system. (Opposite situation is with AVR GCC tools where avrlibc is specially adapted to support AVR microcontrollers and works out of the box). So when we start dealing with OS dependable functions, we face some stubs. Stubs are called system call functions that are served by the operating system. As in microcontroller 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 the dummy. Understanding them all and writing your implementations would be too 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 the 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.

Bookmark the permalink.

6 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. Embedded Plugin Suite

    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.

  6. Hi ! I have message Heap and stack collision 🙁

Leave a Reply