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:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc);

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

Last, but not least: add the implementation of the DMA Callback Functions in the „USER CODE 4“ section:
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);

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


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.


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


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


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);

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