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!

10 Antworten auf „STM32F103C8 – ADC dual regular simultaneous mode“

  1. Thanks for your great tutorial.
    please, could you tell me why my ADC read (1.7) instead of zero when there is nothing is connected to the ADC pins ?
    Thank you in advance.

  2. Thank you for this article , but i applied to stm32F303 discovery .There are many config difference . No any change when debug .

  3. Thank you very much for the great tutorial. I got one question:
    Is there a way to restart ADC sampling and overwrites to ADC buffer even after it finishes one round of sampling?

    I have configured external trigger interrupt. So, I would like to start ADC sampling whenever external trigger interrupt signal is given. But in the current code, ADC sampling is done only for one round or adc data is not refreshed.

    I would greatly appreciate your help!

    1. yes, you just have to set both the sampling mode and DMA mode to „continous“, then the ADC/DMA is running „forever“. I allmost allways use this mode and if I need the data only sporadically i simply can select in the code when to use it or not. But I’m allways sure that there are the newest data in the buffers …

  4. I would like to modify your code to perform Triple Simultaneous Regular ADC. But I do not know how to split the sampled data into values for each of 3 ADC channels. Here is the part that I modified:
    #define N 512 // number of ADC sampling to repeat for each channel
    uint32_t adc123_data[3*N]; // adc data for all three channels
    uint16_t adc1_data[N]; // adc data for adc channel1
    uint16_t adc2_data[N]; // adc data for adc channel2
    uint16_t adc3_data[N]; // adc data for adc channel3

    status = HAL_ADC_Start(&hadc2);
    status = HAL_ADC_Start(&hadc3);
    status = HAL_ADCEx_MultiModeStart_DMA(&hadc1, adc123_data, (uint32_t)(N* 3));

    In your code for dual simultaneous ADC, you used:
    uint16_t bP = 0; // „bP“ = Buffer Position
    if(pos) bP = ADC_Sample_Size; // use upper half of buffer (Dual ADC)
    uint16_t i;
    uint32_t val;

    for(i=0; i> 16) & 0xffff); }

    Can you help me modify your code to split the data into those for three ADC channels?
    Many many thanks!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert