STM32 ADC Multiple Channels

Today in this tutorial, we will see how to read multiple channels in ADC in STM32.

For this demonstration, I am using STM32F103C8 controller and True-Studio IDE.

For the ADC purpose, I am using 3 channels as mentioned below:-

  • CHANNEL 0 –> IR sensor
  • CHANNEL 1 –> Potentiometer
  • CHANNEL 16 –> Internal Temp sensor

UPDATE

If the ADC Frequency is very high, the DMA Interrupt will execute at very high rate, and this will render the while loop Useless.

There is a different way to handle multiple channels without the DMA and you can check it at https://controllerstech.com/stm32-adc-multi-channel-without-dma/

HOW TO

In order to read the multiple channels, we need to use DMA. The benefit of it is the conversion will take place in the background and we can perform some other operation with the controller and when we need the values, we can just read them easily.

Before proceeding further, Let’s see How we have to get the value from the internal temperature sensor of the STM32. According to the reference manual (Pg- 236), The temp is given as follows

V25, Avg_Slope are given in the datasheet (Pg-79)

Also note that the ADC sampling time, while reading the temperature, needs to be maximum 17.1 us.



CubeMx Setup

  • Above is the clock section from the CubeMx. Note that I have selected the ADC clock as 14 MHz.
  • The reason behind this is that the Temp sensor sampling time needs to be 17.1 us.
  • While in the ADC setting, we have maximum sampling time as 239.5 cycles. So (239.5/14) gives us 17.1 us sampling time.

Note that 3 channels are selected and the Continuous conversion mode is enabled. Also in the new CubeMx, you don’t need to worry about scan conversion mode. It will enable and disable by it’s own, based on if you are using multi channels or only one channel.


Above is the DMA setting for the ADC. Make sure that the DMA is circular and data width is selected as WORD or HALF WORD . This is because the CubeMx uses ADC in 12 bits resolution by default and in order to store 12 bits we need the variable of the same size.



Some Insight into the Code

uint32_t value[3]; 
HAL_ADC_Start_DMA(&hadc1, value, 3); // start adc in DMA mode
  • Starts the ADC in DMA mode and the converted data is stored in ‘value‘ buffer.
  • ‘3’ is the length of data to be transferred from ADC peripheral to memory.

float temp; 
#define V25 1.43 // from datasheet 
#define VSENSE 3.3/4096 // VSENSE value
#define Avg_Slope .0043 // 4.3mV from datasheet 
float get_temp (uint32_t variable) 
{ 
 return (((V25 - (variable*VSENSE)) / Avg_Slope) + 25); // formula from datasheet
}

The above code gets the temperature of the sensor in °C. You can change the values of V25 and Avg_Slope according to your controller’s datasheet.



Result

Check out the Video Below








Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

11 Comments. Leave new

  • When I replicate the code, values are getting stored only in value[0] and value[1] remains empty. I am using nucleo-stm32f303re.
    Following is main.c:
    /* USER CODE BEGIN Header */
    /**
    ******************************************************************************
    * @file : main.c
    * @brief : Main program body
    ******************************************************************************
    * @attention
    *
    * Copyright (c) 2023 STMicroelectronics.
    * All rights reserved.
    *
    * This software is licensed under terms that can be found in the LICENSE file
    * in the root directory of this software component.
    * If no LICENSE file comes with this software, it is provided AS-IS.
    *
    ******************************************************************************
    */
    /* USER CODE END Header */
    /* Includes ——————————————————————*/
    #include “main.h”

    /* Private includes ———————————————————-*/
    /* USER CODE BEGIN Includes */

    /* USER CODE END Includes */

    /* Private typedef ———————————————————–*/
    /* USER CODE BEGIN PTD */

    /* USER CODE END PTD */

    /* Private define ————————————————————*/
    /* USER CODE BEGIN PD */

    /* USER CODE END PD */

    /* Private macro ————————————————————-*/
    /* USER CODE BEGIN PM */

    /* USER CODE END PM */

    /* Private variables ———————————————————*/
    ADC_HandleTypeDef hadc1;
    DMA_HandleTypeDef hdma_adc1;

    UART_HandleTypeDef huart2;

    /* USER CODE BEGIN PV */
    uint32_t dma_buffer[2];
    /* USER CODE END PV */

    /* Private function prototypes ———————————————–*/
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    static void MX_DMA_Init(void);
    static void MX_USART2_UART_Init(void);
    static void MX_ADC1_Init(void);
    /* USER CODE BEGIN PFP */

    /* USER CODE END PFP */

    /* Private user code ———————————————————*/
    /* USER CODE BEGIN 0 */

    /* USER CODE END 0 */

    /**
    * @brief The application entry point.
    * @retval int
    */
    int main(void)
    {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration——————————————————–*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART2_UART_Init();
    MX_ADC1_Init();
    /* USER CODE BEGIN 2 */
    HAL_ADC_Start_DMA(&hadc1, dma_buffer, 2);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

    HAL_Delay(1000);
    }
    /* USER CODE END 3 */
    }

    /**
    * @brief System Clock Configuration
    * @retval None
    */
    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /** Initializes the RCC Oscillators according to the specified parameters
    * in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
    Error_Handler();
    }
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_ADC12;
    PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
    PeriphClkInit.Adc12ClockSelection = RCC_ADC12PLLCLK_DIV1;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief ADC1 Initialization Function
    * @param None
    * @retval None
    */
    static void MX_ADC1_Init(void)
    {

    /* USER CODE BEGIN ADC1_Init 0 */

    /* USER CODE END ADC1_Init 0 */

    ADC_MultiModeTypeDef multimode = {0};
    ADC_ChannelConfTypeDef sConfig = {0};

    /* USER CODE BEGIN ADC1_Init 1 */

    /* USER CODE END ADC1_Init 1 */

    /** Common config
    */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 2;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
    Error_Handler();
    }

    /** Configure the ADC multi-mode
    */
    multimode.Mode = ADC_MODE_INDEPENDENT;
    if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
    {
    Error_Handler();
    }

    /** Configure Regular Channel
    */
    sConfig.Channel = ADC_CHANNEL_6;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
    Error_Handler();
    }

    /** Configure Regular Channel
    */
    sConfig.Channel = ADC_CHANNEL_7;
    sConfig.Rank = ADC_REGULAR_RANK_2;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
    Error_Handler();
    }
    /* USER CODE BEGIN ADC1_Init 2 */

    /* USER CODE END ADC1_Init 2 */

    }

    /**
    * @brief USART2 Initialization Function
    * @param None
    * @retval None
    */
    static void MX_USART2_UART_Init(void)
    {

    /* USER CODE BEGIN USART2_Init 0 */

    /* USER CODE END USART2_Init 0 */

    /* USER CODE BEGIN USART2_Init 1 */

    /* USER CODE END USART2_Init 1 */
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 38400;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart2) != HAL_OK)
    {
    Error_Handler();
    }
    /* USER CODE BEGIN USART2_Init 2 */

    /* USER CODE END USART2_Init 2 */

    }

    /**
    * Enable DMA controller clock
    */
    static void MX_DMA_Init(void)
    {

    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

    }

    /**
    * @brief GPIO Initialization Function
    * @param None
    * @retval None
    */
    static void MX_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    /* USER CODE BEGIN MX_GPIO_Init_1 */
    /* USER CODE END MX_GPIO_Init_1 */

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pin : B1_Pin */
    GPIO_InitStruct.Pin = B1_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

    /*Configure GPIO pin : LD2_Pin */
    GPIO_InitStruct.Pin = LD2_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
    }

    /* USER CODE BEGIN 4 */

    /* USER CODE END 4 */

    /**
    * @brief This function is executed in case of error occurrence.
    * @retval None
    */
    void Error_Handler(void)
    {
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    {
    }
    /* USER CODE END Error_Handler_Debug */
    }

    #ifdef USE_FULL_ASSERT
    /**
    * @brief Reports the name of the source file and the source line number
    * where the assert_param error has occurred.
    * @param file: pointer to the source file name
    * @param line: assert_param error line source number
    * @retval None
    */
    void assert_failed(uint8_t *file, uint32_t line)
    {
    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
    ex: printf(“Wrong parameters value: file %s on line %d\r\n”, file, line) */
    /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */

    Reply
  • While(1) loop does not execute in this mode

    Reply
  • Hi, I would like to know how you can send multiple ADC in single buffer and transmit over UART.

    Reply
    • yes you can. But depending on the ADC resolution, you have to manipulate data.
      For example, if the resolution is 12 bit, you can send 2 ADC values (totally 24 bits) in 3 bytes of the UART.
      Of course the data must be converted to ascii format (characters) first

      Reply
  • It works well for my project,
    Thank you very much

    Reply
  • Using ADC with DMA
    1) I Connected 2 ADC channels.
    2) And Created a timer function for 1 minute
    3) Whenever the timer call back executes, I have to start my ADC & read the ADC value for all the mentioned channels & print in the serial port (Should not use delay function)
    4) After execution, it should not measure the ADC, until it reaches the 1-minute interval

    In this, I have to start my ADC with DMA in Timer Callback, I have to print ADC values in serial port.
    I configure the ADC, DMA and Timer in proper way.
    but In this i got the output as ADC channel 0 value as 210, and channel 1 as 0.
    I don’t know where did my mistake.
    Can u please help me to solve this issue?

    Reply
  • carlos feldman
    March 1, 2020 2:33 AM

    HI!
    Very fine example.
    What I do for changing channel is to reinitiate the DAC, for the next measurement, for instance:

    Startup:

    /* USER CODE BEGIN SysInit */
    ADC_ChannelConfTypeDef sConfig = {0};

    /* USER CODE END SysInit */
    …….
    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_ADC1_Init();
    MX_RTC_Init();
    ……
    /* USER CODE BEGIN 2 */

    HAL_ADCEx_Calibration_Start(&hadc1);

    /* USER CODE END 2 */

    sConfig.Channel = ADC_CHANNEL_VREFINT;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    // HAL_ADCEx_Calibration_Start(&hadc1); // advisable to do a self calibrate at startup, you get 1.5 digits better performance
    HAL_ADC_Start(&hadc1);
    // WAIT FOR EOC
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
    // GET VALUE
    raw = HAL_ADC_GetValue(&hadc1);

    Reply
  • Hi, thank you very much from your site
    I want to do sampling at 10 kHz ADC and DMA
    Maybe guide me ?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×