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.

 

STM32F103C8 – ADC dual regular simultaneous mode

A tutorial how to use ADC1 and ADC2 of an STM32F103C8 MPU in ADC dual regular simultaneous mode, to measure 6 analog input channels with DMA, „double buffer technique“ and Oversampling.

Hardware Overview
Hardware Overview

Hardware Requirements: in this tutorial I am using a “Bluepill” prototyping board, with an STM32F103C8T6 MPU, along with the following hardware:
[+] a breadboard and a few jumper wires
[+] a ST-Link V2 Clone
[+] a 10 kOhm NTC Resistor
[+] and a 100 kOhm Potentiometer (variable resistor)
I am using the STM HAL Hardware Abstraction Layer functions, CubeIDE as Development Environment and the integrated hardware configuration tool (CubeMX).

Abstract / Main Goal

This tutorial demonstrates how to continously measure 6 analog inputs with an STM32F103 MPU with ADC1 and ADC2 in dual regular simultaneous mode, using DMA and Oversamling. The 6 analog channes are: IN0, IN2 and the internal temperature sensor on ADC1 and IN1, IN3 and IN4 on ADC2.

Each ADC measures 3 analog signals in regular conversion („scan mode“). ADC2 uses the scan clock from ADC1. Data is transfered from both ADCs using the DMA Channel of ADC1. The LED on the Bluepill (PC13) is used to indicate, that the ADCs are working.

Usefull Links:

RM0008 – STM32F10x User Manual – Chapter 11.9.2 (Regular simultaneous mode) – page 230
UM1850 – Description of STM32F1 HAL and low-layer drivers – Chapter 8: HAL ADC Extension driver
AN3116 – STM32’s ADC modes and their applications – Chapter 2.1: Dual regular simultaneous mode

Cube-MX Setup: Basic Setup

In CubeMX (the graphical hardware configuration tool integrated in CubeID) configure the hardware as follows:

System Configuration
STM32F103 ADC Dual regular simultaneous mode Tutorial – System Configuration (SWD enable)

In the section „SYS“, enable serial wire debug (SWD). Serial Wire Debug allows you to debug your code with the ST-Link V2 debugger.

RCC High Speed Clock Config
STM32F103 ADC Dual regular simultaneous mode Tutorial – RCC High Speed Clock Config

In the section „RCC“ enable the High Speed Clock (HSE) by selecting „Crystal/Ceramic Resonator“ from the drop down menu. This will activate the 8 MHz Crystal on the Blupill PCB.

MPU Clock Config Overview
STM32F103 ADC Dual regular simultaneous mode Tutorial – MPU Clock Config Overview

Switch to the „Clock Configuration“ tab and make sure the high speed clock is selected and the main clock is set to 72 Mhz. Hint: the easiest way to get a propper clock setup is to enter the desired main slock speed (72 Mhz in this case) in the „HCLK“ field (in the red box), CubeMX will calculate the correct values for you automatically.

Code Generator Settings
STM32F103 ADC Dual regular simultaneous mode Tutorial – Code Generator Settings

Optional Step: Switch to the „Project“ tab, selct „Code Generator“ and check
[+] Generate peripheral initialization as a pair of ‚.c/.h‘ files per peripheral, and
[+] Set all free pins as analog (to optimize the power consumption)

Selecting „Generate peripheral initialization“ makes the code more readable and ‚de-clutters‘ the main.c – this is why I recommend using this option.

Cube-MX Setup: ADC Channel selection

Switch to the „Analog“ catetory, select ADC1 and check the channels „IN0“, „IN2“ and „Temperature sensor channels“:

CubeMX - ADC1 Channel Selection
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC1 Channel Selection

Go to ADC2 and select the channels „IN1“, „IN3“ and „IN4“:

CubeMX - ADC2 - Channel Selection
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC2 Channel selection

Add a GPIO output on PC13, name it „LED1“ and set it to „Open Drain“ mode in the GPIO setup. Now your STM32F103 should look like this:

MPU Pinout
STM32F103 ADC Dual regular simultaneous mode Tutorial – MPU Pinout

you can ignore the UART1 settings, as it is not used in this tutorial.

Cube-MX: ADC1 Configuration

Now it’s time to configure ADC1 and ADC2 to work in dual regular simultaneous mode: Select „ADC1“ from the „Analog“ categorie and change the settings in the „Parameter Settings“ tab as follows:

CubeMX - ADC1 Parameter Settings
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC1 Parameter Settings

[+] Mode: Dual regular simultaneous mode only
[+] Scan Conversion Mode: Enabled
[+] Continous Conversion Mode: Enabled
[+] Number Of Conversion: 3

CubeMX - ADC1 Scan Conversion Channel Setup
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC1 Scan Conversion Channel Setup

Open the scan configuration, select the channel order as shown above and select the sampling time. You can select differend samling times, but make sure all channes – on both ADCs – use the same sampling time. In this case, as the internal Temperature Sensor channel is involved, which needs 239.5 cycles for an accurate reading, the decision is simple: all channels are set to 239.5 cycles – the same samling time the temperature sensors needs – according to the STM32F1xx datasheet.

Switch to the „DMA Settings“ Tab in the ADC1 Configuration and add a DMA-Channel with the following settings:

CubeMX - ADC1 - DMA Configuration
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC1 DMA Configuration

Please note that the data width is set to „word“ for both the peripheral and the memory, as the data from ADC2 are transfered in the upper two bytes of the 32-bit ADC1 data register! Hint: when using a single continous scanning ADC, this setting should be „Half Word“ and „Word“.

In the next step check the interrupt settings in the „NVIC Settings“ tab:

CubeMX - ADC1 Interrupt Configuration
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC1 Interrupt Configuration

DMA1 channel1 global interrupt should be enabled, global interrupts are not needed in this tutorial.

Cube-MX: ADC2 Configuration

Now switch to „ADC2“, select the „Parameter Settings“ tab and edit the configuration of ADC2:

ADC2 Parameter Settings
STM32F103 ADC Dual regular simultaneous mode Tutorial – ADC2 Parameter Settings

[+] Mode should be: Dual regular simultaneous mode only, and you should not be able to change that, as the regular simultaneous mode is defined by the configuration of ADC1. You will also note, that if you try to edit the configuration of ADC2 before the configuration of ADC1 you will not be able to select this mode.
[+] Scan Conversion Mode: Enabled
[+] Continous Conversion Mode: Enabled
[+] Number Of Conversion: 3
[+] select the scan order and make sure all channels in ADC1 and ADC2 are set to the same samling time!

Now the Hardware configuration with CubeMX is complete and you can generate the code.

Code: meas.h and meas.c

Download the measurement code here. You’ll get a meas.c and a meas.h file, with the implementation of the ADC functions:

STM32F103C8 - ADC dual regular simultaneous mode tutorial - ZIP file contents
STM32F103C8 – ADC dual regular simultaneous mode tutorial – ZIP file contents

Unzip the downloaded file and copy the meas.h file into the Core\Inc directory and the meas.h file into the Core\Src directory of your project:

Project Tree
STM32F103 ADC Dual regular simultaneous mode Tutorial – Project Tree

Code: main.c

In the main.c make the follwing changes:

right at the beginning of the .c-file, add the line „#include meas.h“ in the „USER CODE Includes“ section:
/* USER CODE BEGIN Includes */
#include "meas.h"
/* USER CODE END Includes */

In the „USER CODE 0“ section, add the ADC Conversion complete Callback Function Prototypes:
/* USER CODE BEGIN 0 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc);
/* USER CODE END 0 */

In the int main(void) function, in the „USER CODE 2“ section, add the call to the „MEAS_ADC_start()“ function:
/* USER CODE BEGIN 2 */
MEAS_ADC_start(); // start the ADC conversion
/* USER CODE END 2 */

Last, but not least: add the implementation of the DMA Callback Functions in the „USER CODE 4“ section:
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
   if(hadc) MEAS_ADC1_eval(1);
}

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) {
   if(hadc) MEAS_ADC1_eval(0);
}
/* USER CODE END 4 */

the main look „while(1)“ remains empty in the scope of this tutorial. You could use e.g. the variable „uint8_t MEAS_ACD1_update“ there, to check, if a new block of data is availiable.

Now the code is complete and you can complile and download the hex-file to your Bluepill.

Functional Description

All functions, necessary for this tutorial, are implemented in the meas.h and meas.c file. Please also check the documentation in these files for further details! In general, this code works as follows:

ADC1 and ADC2 are set up to sample simultanously, 3 channels each, in scan mode. This means ADC 1 samples IN0, then IN2, then the internal temperature sensor and jumps back to scan IN0 after that. ADC2 samples IN1 at the same time ADC1 samles IN0, IN3 at the same time ADC1 samples IN2 and do on.

All samples are written via DMA into a buffer, while ADC2 makes usage of the ADC1 DMA channel and writes its data into the upper 2 bytes of the 32 bit ADC sample register. To prevent interferences between the sampling and the processing of the data, I am using the „double buffer technique“. This means: the DMA buffer is set up twice as big as it would be needed and the two ADC DMA Conversion Complete Interrupt Callback functions are used.

When both DMA Callback functions are used, each time when the (internal) DMA buffer pointer reaches the half size of the buffer or when the (circular) buffer rolls over, an DMA interrupt is fired, which is catched by the two callback functions. Practically spoken, this gives us the possibility to process the data from the first half of the buffer, while the second half is filled and vice versa.

Both implementations of the DMA Callback functions, simply call the „void MEAS_ADC1_eval(uint8_t pos)“ from meas.c, where the variable „pos“ indicates which half of the buffer is ready for processing.

The functionality of the „void MEAS_ADC1_eval(uint8_t pos)“ function is to „de-clutter“ the data from the DMA-Buffer, this means:
[+] splitt the 32 value in the lower 2 bytes, which contain the ADC1 samples and the upper 2 bytes, which contain the ADC2 samples
[+] separate the data into channels
[+] accumulate all data of each channel and shrink them (aka Oversampling)
[+] convert the ADC readings to floating point voltages

Intended usage

Compile it, download it to your Bluepill and set probes to „ADC1_Bfr“ and „ADC2_Brf“, to see how the ADC readings are aligned in the buffer. Hint: pulling a channel to ground helps you to identify the sample alignment. Set a probe on „adcf“ to see the measured voltages. You can also set a probe on „CPU_Temp“ to watch the Chip temperature in °C

running / Live Expressions
STM32F103 ADC Dual regular simultaneous mode Tutorial – running

When everythins is ok, the LED1 of the bluepill is blinking with a frequency of ~ 2Hz and you can see the data as shown above …

Have fun!

STM32F103C8 – Blinking LED

Abstract

In this tutorial i will describe how to use CubeMX and STM32CubeIDE to create a „Blinky Sketch“.

As there are allready gazillions of such tutorials availiable – e.g. on youtube, which describe how to make the LED of a Bluepill blink, the main purpose of this blog entry is simply to have an article, which describes the basic setup procedure in Cube MX, which I can refer to in other articles.

Introduction

I’m a fan of the STM32F103C8 Microprocessor. The reason why I use this MPUs is simple: they are the core of the „Bluepill“ prototyping board, which is available on ebay or ali-express for a very reasonable price.

While this prototyping board is most probably not the best choice for a professional application, the MPU on it is pretty neat and has a lot of hardware capabilities packed inside that little chip, which makes it almost perfect for playing around and testing. I guess, this is the reason, why the „Bluepill“ became so popular.

For a few Euros you get a MPU with a Cortex M3 core, GPIO, analog input, U(S)ART, USB, SPI, I2C, CAN, a Real Time Clock and 4 Timers

Goals

In this tutorial I am using CubeMX (inside the STM32CubeID) to configure:

  • The Debug Interface
  • The 8 MHz Crystal Resonator on the Bluepill Board
  • the MPU Main clock
  • the “LED1” GPIO
  • and the general project settings

Hardware

Bluepill "Blinky Sketch" - Hardware
Bluepill "Blinky Sketch" - Hardware

I am using a Bluepill on a breadboard, together with an ST-Link V2 clone. The Datasheet of the MPU can be found here.

Create a new Project

To start with a fresh new project in STM32CubeID click on File >> New >> STM32 Project. The target selector will open and will let you choose your MPU:

Bluepill "Blinky Sketch" - Select MPU
Bluepill "Blinky Sketch" - Select MPU

Type „F103C8“ into the Part Number Filter Control, which leaves 1 entry in the MPU List (STM32F103C8T6). Click on that element ni the list and selct „Next >“ on the bottom of the page.

CubeMX will ask you for a project name and for general project settings. In this case we leave all options as is (Target-Language = C) and klick „Finish“.

CubeMX: Hardware configuration

In the next step of the project creation process we have to configure the hardware. For this purpose, CubeMX provides us a GUI where we can configure all hardware related aspects of the MPU, without reading 1000 pages of the hardware reference manual in detail and spending a significant amount of our lifetime in shifting bits into registers.

Bluepill "Blinky Sketch" - RCC Setup: High Speed Clock
Bluepill "Blinky Sketch" - RCC Setup: High Speed Clock

Step 1: enable the High Speed Clock. As the Bluepill has an 8 MHz crystal on board, it’s a good idea to use it.Simply select „Crystal/Ceramic Resonator“ in the Drop Down Menu. This also allows you to set the max. MPU Clock Speed to 72 MHz in the clock setup. Without a Crystal or Ceramic Resonator the max. MPU speed is 64 MHz. We don’t necessarily need a 72 MHz MPU speed for this very simple example, but – as mentioned above – we will need it in other projects.

Step 2: enable „SWD“ (Serial Wire Debug) in the SYS section, by selecting „Serial Wire“ from the Drop Down Menu labeled „Debug“.

Bluepill "Blinky Sketch" - Debug Interface Setup
Bluepill "Blinky Sketch" - Debug Interface Setup

Step 3: add an Output on Pin PC13 by right clicking on the Pin in the MPU Pinout overview on the right side, selct „GPIO Output“.

Bluepill "Blinky Sketch" - GPIO Overview
Bluepill "Blinky Sketch" - GPIO Overview

Select the entry for PC13 in the GPIO overvie to edit the details of this Digital IO:

Bluepill "Blinky Sketch" - LED1 GPIO Setup
Bluepill "Blinky Sketch" - LED1 GPIO Setup

The default value for GPIO Mode is „Push-Pull“. In this case we can set it to „Open Drain“, as the GPIO is on the low side (Cathode) of the LED. Don’t forget to enter the User Label for the LED, either by entering the name in the GPIO Configuration, or by right clicking on the Pin in the Pinout view and selcting „Enter User Label.

When you followed all these steps correctly, out Pinout view should look like this:

Bluepill "Blinky Sketch" - MPU Pinout
Bluepill "Blinky Sketch" - MPU Pinout

Clock Configuration

The Clocks are configured in the Tab „Clock Configuration“. The easiest way to get this job done is: enter the desired Main Clock Speed in the „HCLK (Mhz)“ field and let CubeMX do the configuration and resolve possible issues.

MPU Clock Configuration in CubeMX
MPU Clock Configuration in CubeMX

Project Settings

Bluepill "Blinky Sketch" - general Project Properties
Bluepill "Blinky Sketch" - general Project Properties

Last but not least, switch to the „Project Manager“ tab, go to the section „Code Generatur“ and activate the options „Generate peripheral initialization …“ and „Set all free pins …“ to keep the main.c more readable and minimize the power consumption of the Processor.

Now the Hardware configuration is finished. You can close the CubeMX Tab, a Dialog will pop up asking, if you want to generate code, click „yes“ and the new project is configured and created.

Generate Code Dialog
Generate Code Dialog

Write the Code

The last step is to write the code, that makes the LED blink:

Open the main.c file, scroll down to the infinite loop and between the lines while(1) { … and /* USER CODE END WHILE*/ type:

HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
HAL_Delay(500);

That’s it! Compile the progamm, download it to your bluepill and see the LED blink …