Electrical – STM32F4: How to get data from multiple channels sampled from a single ADC

adcstm32cubemxstm32f4timertrigger

So I have searched everywhere for a solution to this problem but I can find anything. I have three channels connected to ADC1 of my STM32F446ZE-nucleo and I want to sample the channels at a rate of 200 Hz each. I set up TIM2 and linked it as the interrupt for the ADC conversion, and this works fine for 1 channel. But I am struggling to get it to work for multiple channels since I don't know how to get the data from each specific channel within the HAL_ADC_ConvCpltCallback.

My setup looks like this:

/* ADC1 init function */
static void MX_ADC1_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;

    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 3;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = 3;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

/* TIM2 init function */
static void MX_TIM2_Init(void)
{

  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 41999;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 4;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

I start my ADC and timer after receiving data on USART3 like this:

// start timer and ADC interrupt
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_Base_Start(&htim2);

This is where I am struggling to get the right data:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{

    // get values
    WE_ADC_VAL = HAL_ADC_GetValue(hadc);

    uint32_t nrConv = hadc->NbrOfCurrentConversionRank;

    // populate buffer
    transmitBuf[0] = WE_ADC_VAL >> 8; // upper 8 bits
    transmitBuf[1] = WE_ADC_VAL & 0x00FF; // lower 8 bits

    // send by interrupt mode
    HAL_UART_Transmit_IT(&huart3, transmitBuf, 2);
}

I thought that hadc->NbrOfCurrentConversionRank should have info on which channel is being sampled, but it is constantly 0 (I thought it would go from 1 to 2 to 3). How would I go about to get the individual channel's data at each conversion complete interrupt? Any help would be appreciated 🙂

Best Answer

This should be a comment to ask you if you want my solution, which includes 3 ADCs x 3 Channels = 9 signals, by utilizing DMA peripheral and no HAL libraries. Since my rep is < 50, I cannot make that comment, so I will provide the code for the above :P

It utilizes a Master-Slave technique, with ADC1 set as the master and ADC2 and 3 as slaves. Interrupts occur based on TIM2 frequency, which can be modified.

Hope it helps :)


μC Board: STM32F429ZI-Discovery

IDE: μVision V5.13.0.0

enter image description here

TIM2 Enable

#define ADC_PER 4499

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_BaseStruct_ST.TIM_Prescaler = 0;
TIM_BaseStruct_ST.TIM_CounterMode = TIM_CounterMode_Up;
// 899 => 100kHz, 2249 => 40kHz, 4499 => 20kHz, 8999 => 10kHz
TIM_BaseStruct_ST.TIM_Period = ADC_PER; 
TIM_BaseStruct_ST.TIM_ClockDivision = TIM_CKD_DIV1;

TIM_TimeBaseInit(TIM2, &TIM_BaseStruct_ST);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

ADC Clock Enable

#include "stm32f4xx_adc.h"
RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC2, ENABLE);
RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC3, ENABLE);

ADC set to Triple Mode, DMA Enable, No Prescaler

ADC_CommonInitTypeDef ADC_CommonInitStructure;

ADC_CommonInitStructure.ADC_Mode          = ADC_TripleMode_RegSimult;
ADC_CommonInitStructure.ADC_Prescaler     = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
ADC_CommonInit (&ADC_CommonInitStructure);

3 conversions for each ADC, hence 9 in total. Highest resolution of 12 bits, right data alignment and event trigger by TIM2 interrupts.

ADC_InitTypeDef ADC_InitStructure;

ADC_InitStructure.ADC_ScanConvMode       = ENABLE;
ADC_InitStructure.ADC_NbrOfConversion    = 3;
ADC_InitStructure.ADC_Resolution         = ADC_Resolution_12b;
ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;

TIM_SelectOutputTrigger (TIM2, TIM_TRGOSource_Update);
ADC_InitStructure.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;

ADC_Init(ADC1, &ADC_InitStructure);
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Init(ADC3, &ADC_InitStructure);

Set each channel to the right pin, with 15 cycles for sampling the signal

ADC_RegularChannelConfig (ADC1, ADC_Channel_13, 1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC1, ADC_Channel_8,  2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC1, ADC_Channel_6,  3, ADC_SampleTime_15Cycles);

ADC_RegularChannelConfig (ADC2, ADC_Channel_0,  1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC2, ADC_Channel_10, 2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC2, ADC_Channel_14, 3, ADC_SampleTime_15Cycles);

ADC_RegularChannelConfig (ADC3, ADC_Channel_15, 1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC3, ADC_Channel_9,  2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig (ADC3, ADC_Channel_3,  3, ADC_SampleTime_15Cycles);

DMA Clock Enable.

Setup DMA data transport, base address, matrix size, half word data transfer and circular mode.

Enable Stream4 of DMA2.

#include "stm32f4xx_dma.h"
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

DMA_InitTypeDef DMA_InitStructure;

DMA_InitStructure.DMA_DIR     = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC->CDR;
DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Memory0BaseAddr    = (uint32_t)(&adc[0]);
DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;

DMA_InitStructure.DMA_BufferSize = 9;

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

DMA_Init(DMA2_Stream4, &DMA_InitStructure)

Setup and enable interrupt event of DMA

NVIC_InitTypeDef ADCNVICConfig;
ADCNVICConfig.NVIC_IRQChannel = DMA2_Stream4_IRQn;
ADCNVICConfig.NVIC_IRQChannelPreemptionPriority = 0;
ADCNVICConfig.NVIC_IRQChannelSubPriority = 1;
ADCNVICConfig.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&ADCNVICConfig);

ADC_DMACmd(ADC1, ENABLE);
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);
DMA_ITConfig(DMA2_Stream4, DMA_IT_TC, ENABLE);

DMA_Cmd(DMA2_Stream4, ENABLE); 

Setup ADC1 as Master and ADC2 and ADC3 as Slaves, enable DMA and all ADCs.

ADC_DMACmd(ADC1, ENABLE);
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_Cmd(ADC3, ENABLE);