STM32F103C8 – UART with DMA Buffer and Idle Detection

Abstract

In this tutorial I will describe how to:
[+] use the U(S)ART of an STM32F103C8 MPU
[+] with a circular DMA buffer for Rx,
[+] a normal DMA buffer forTx,
[+] and how to use the Idle Line Interrupt to receive messages of unknown length

STM32F103C8 - UART idle interrupt circular DMA tutorial - Hardware Overview
STM32F103C8 - UART idle interrupt circular DMA tutorial - Hardware Overview

Hardware Requirements

I am using a “Bluepill” prototyping board, with an STM32F103C8T6 MPU, CubeMX for Hardware Configuration, CubeIDE for editing and the STM32 HAL Hardware Abstraction Layer along with the following hardware:
[+] a breadboard and a few jumper wires
[+] a ST-Link V2 Clone
[+] a simple UART to USB Converter, you can get e.g. from ebay

Usefull Links

  • RM0008 – STM32F10x User Manual – Chapter 13 (DMA Controller)
  • AN3109 – Communication peripheral FIFO emulation with DMA and DMA timeout in STM32F10x microcontrollers

Cube MX: Basic Setup

Please use CubeMX and follow the instructions in this tutorial for the configuration of RCC, Clock Settings, SWD  and the project setup. The MCU Main clock is 72 MHz in this example.

Cube MX: Preripherals

In this tutorial we need PC13 as GPIO Output and USART1 with DMA-Channels for RX and TX and global Interrupts enabled:

Please make sure the RX DMA Channel is set to „circular“ and the TX DMA Channel is set to „normal“ and dont forget to enable the global interrupt for USART1.

When you have finished the configuration in CubeMX the MCU pinpout should look like this:

STM32F103C8 - UART idle interrupt circular DMA tutorial - MCU Pinout
STM32F103C8 - UART idle interrupt circular DMA tutorial - MCU Pinout

Code: main.c

All functionality is implemented in the main.c file, except the call for the idle interrupt, which needs to be in the stm32f1xx_it.c

You can download both files (zipped) at the end of this article, if you don’t want to type it by your own.

STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private includes
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private includes

We need and for sprintf() and strlen(). The right place for the two include statements is in the „Includes section“ of the main.c-file, between the USER CODE Includes tags.

STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private defines
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private defines

We need two defines, one for the RX Buffer Size and another one for the TX Buffer size. The RX Buffer is intentionally very small, only 16 Bytes. This might be way to small for a real world application, but for this tutorial it’s just right, as I want to demonstrate the buffer rollovers and if the buffer is small I don’t have to send long strings to show the effect. In a real application you might set the buffer size to a value, that reflects your expectations of the size of the incoming commands.

STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private global variables
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private global variables

We need a couple of global variables. Place them in the Private variables (user code) section. I have made all the necessary variables global variables for this example, because it’s easier to track them with the debugger. In a real world application you would most probably not do that this way.

  • RxRollover is a counter that counts the DMA RxComplete Interrupts
  • RxCounter counts the incoming transmissions
  • RxBfrPos is used to store the Buffer Position of the last transmission
  • TxCounter counts the outgoing transmissions
  • TxBuffer is the char buffer for outgoing transmissions
  • RxBuffer is the U(S)ART receiving buffer
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private function prototypes for UART Rx and Tx Callback functions
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: private function prototypes for UART Rx and Tx Callback functions

We need the protptypes for the UART RX Complete Callback function and the UART TX Complete callback functions. You can copy and paste them from the stm32f1xx_hal_uart.h heder file. You can find this file, in the Drivers –> STM32F1xx_HAL_Driver –> Inc folder of your CubeIDE project. Place them in the „USER CODE 0“ section of the main.c-file.

Both functions are declared as „__weak“ in the stm32f1xx_hal_uart.c file and will be overwritten by your implementation in the main.c file.

Both functions are called by the DMA handler (automatically). So you don’t have to care about where to call them and why …

STM32F103C8 - UART idle interrupt circular DMA tutorial - USER CODE 2: start UART1 in DMA Mode
STM32F103C8 - UART idle interrupt circular DMA tutorial - USER CODE 2: start UART1 in DMA Mode

In the USER CODE 2 section (within the int main(void) function) we have to:

  • enable the UART Idle Interrupt
  • start the UART in DMA mode to receive incoming transmissions
  • and turn the LED1 off by setting the GPIO in a high impedance state

The LED1 is toggled with each outgoing transmission.

the infinite loop >> while (1) { … } << in the main function remains emply. All the „heavy lifting“ is done by the two DMA callback functions, which are implemented in the „USER CODE 4“ section:

STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: User Code 4 - Callback functions
STM32F103C8 - UART idle interrupt circular DMA tutorial - main.c: User Code 4 - Callback functions
STM32F103C8 - UART idle interrupt circular DMA tutorial - UART Rx Complete callback function implementation
STM32F103C8 - UART idle interrupt circular DMA tutorial - UART Rx Complete callback function implementation

There is no need to type all the code by yourself. At the end of this tutorial you can download the source code or by following this link

stm32f1xx_it.c Code

Only a small change to the stm32f1xx_it.c is neccessary:

STM32F103C8 - UART idle interrupt circular DMA tutorial - stm32f1xx_it.c:USART1 Interrupt Handler, Idle Line Interrupt handling

When the general UART IRQHandler is called, we need to check if it is the Idle Interrupt, and if yes, we call the RxComplete Callback Function.

How does it work?

This code in general works as follows:

There are 3 interrupts: the Rx Complete Interrupt (created by DMA), the Tx Complete Interrupt (also created by DMA) and the „Idle Line“ Interrupt, coded manually in the stm32f1xx_it.c.

The Rx Complete Interrupt always occurs, when the Rx Buffer rolls over. To demonstrate this, the Rx buffer size was intentionally made very low (16 bytes) for this example. We use this interrupt to count the „Buffer rollovers“. A Buffer may only roll over once before an Idle Interrupt occurs for a valid transmission. If it rolls over two or more times, we can assume, that some data have been overwritten and the transmission is corrupted.

The incoming data is checked when the idle interrupt occurs. The start position of the new incoming data stream is the last end position, which can be calculated from querying the DMA “CNDTR” register (which tells us, how many bytes are left until the buffer is full / rolls over) and subtracting that value from the (known) buffer size. This information needs to be consistent over the whole runtime of the program, so you have to store it “in a safe place” 😉 – don’t lose it …

With the information of the last known buffer position and the current buffer position we can calculate the length of the received data. We have to check, if a buffer rollover occurred during the transmission and have to take that into account. See the source code for details …

The Tx Complete Interrupt is used here only to toggle the LED of the Bluepill.

Output

I am using RealTerm to test the output.

In the first screenshot you can see the output of sending 3 times the string „01234“ (always with Carriage Return and Linefeed at the end), which makes the buffer to roll over during the 3rd transmission.

Sending 16 bytes of test +CR +LF produces an „Error 1“, as the first 3 bytes of the transmission were overwritten in the buffer.

Sending more then 32 bytes (reminder: buffer size = 16 bytes …) makes the buffer roll over 2 times and produces „Error 2“ as result.

Sending exactly 16 bytes again works fine.