cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F1 V1.8.0: ADC and DMA in continuous mode

TPier
Associate III

I updated the software package for the STM32F1 MCU from the 1.6.0 version to 1.8.0 and I then felt in trouble with the ADC acquisition with the DMA in continuous mode : the data sound to be shifting in the buffer.

I read 3 signals (actually RGB LED). While the signals are constant, I observed the values moving from one channel to another.

Here is the code :

  • some definitions
#define ADC_SAMPLE_COUNT        10u
#define ADC_CHANNEL_COUNT       3u
#define ADC_DATA_COUNT          (ADC_SAMPLE_COUNT * ADC_CHANNEL_COUNT)
  • the initialization (generated by STM32CubeMX)
void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /** Common config 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 3;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_5;
  sConfig.Rank = ADC_REGULAR_RANK_3;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(adcHandle->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */
 
  /* USER CODE END ADC1_MspInit 0 */
    /* ADC1 clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC1 GPIO Configuration    
    PA3     ------> ADC1_IN3
    PA4     ------> ADC1_IN4
    PA5     ------> ADC1_IN5 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_NORMAL;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
 
  /* USER CODE BEGIN ADC1_MspInit 1 */
 
  /* USER CODE END ADC1_MspInit 1 */
  }
}
  • the acquisition code
Status adc_acquire(const TickType_t timeout)
{
  TickType_t tickStart = xTaskGetTickCount();
  adc_conv_error = False;
  adc_conv_timeout = False;
  adc_conv_in_progress = True;
  adc_error_state = ADC_NO_ERROR;
  uint8_t i;
 
  memset(adc_data, 0u, ADC_DATA_COUNT * sizeof(uint16_t));
 
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *) adc_data, ADC_DATA_COUNT) == HAL_OK)
  {
    /* Wait for the end of conversions */
    adc_wait_4_conversion(timeout);
    if ( !adc_conv_error )
    {
      if ( adc_conv_timeout )
        return STATUS_TIMEOUT;
      for (i = 0u; i < ADC_DATA_COUNT; ++i)
        term_printf("%5u", adc_data[i]);
      term_printf("\r\n");
      for (i = 0u; i < ADC_CHANNEL_COUNT; ++i)
        adc_values[i] = adc_average(i);
      return STATUS_OK;
    }
  }
/*  else
  {
    adc_acquisition_stop();
  }*/
  return STATUS_FAILURE;
}
 
static void adc_wait_4_conversion(const TickType_t timeout)
{
  const TickType_t tickStart = xTaskGetTickCount();
 
  while ( adc_conv_in_progress && !adc_conv_error )
  {
    if (timeout != portMAX_DELAY)
    {
      if (xTaskGetTickCount() - tickStart > timeout)
      {
        adc_conv_timeout = True;
        adc_error_state = (ADCErrorState) (hadc1.State & ADC_ERROR_STATE_MASK);
        break;
      }
    }
    __no_operation();
  }
}
 
static uint16_t adc_average( const uint8_t index )
{
  uint8_t s;
  uint32_t average = (uint32_t) adc_data[index];
  
  for (s = 1u; s < ADC_SAMPLE_COUNT; ++s)
    average += (uint32_t) adc_data[index + (s * ADC_CHANNEL_COUNT)];
  average /= ADC_SAMPLE_COUNT;
  return average;
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  adc_conv_in_progress = False;
}
 
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
{
  adc_conv_error = True;
  adc_error_state = (ADCErrorState) (hadc1.State & ADC_ERROR_STATE_MASK);
}

While only the green LED is lit on (channel 2), I can observed the signal, that should be observed on the 2d channel, moves sometimes on the 1st channel and sometimes on the 3d channel.

Did I something wrong ?

Thanks in advance for any support

Regards.

Thierry

4 REPLIES 4
Ozone
Lead II

Perhaps a runtime issue ?

You used printf-like functions, which are often troublesome, especially within Cube code.

Not using Cube myself, I find the code hard to read and digest.

Anyway, not sure why you use continuous mode with DMA.

While it might sound easier at first, you might want to implement a loop-by-loop control later on, which requires cohesive sampling sets.

I would use a timer as ADC trigger, and configure the DMA to get a TC interrupt on one sample sequence set .

TPier
Associate III

Hi,

The printf-like function has been added afterwards for debugging purpose. It highlights the acquired values in the buffer on the serial console. It did not change the mentioned behavior.

The purpose to use DMA in continuous mode is to get a value based on 10 samples at a given measurement time, triggered from the application layer. I think it is one of the best case to use the ADC in continuous mode. It would be a pity to implement a manual acquisition loop for something that the MCU can do automatically.

Further more, it worked nice with the software package for STM32F1 MCU V1.6.0.

Regards.

> The printf-like function has been added afterwards for debugging purpose. It highlights the acquired values in the buffer on the serial console. It did not change the mentioned behavior.

Might be true in your case.

But often, users post code here that calls printf function from an interrupt callback. Which more often then not breaks the code.

> The purpose to use DMA in continuous mode is to get a value based on 10 samples at a given measurement time, triggered from the application layer. I think it is one of the best case to use the ADC in continuous mode.

You can equally well get one sample of each, using the same method.

> It would be a pity to implement a manual acquisition loop for something that the MCU can do automatically.

What would be "manually" about that ?

A timer would trigger a sequence without any MCU "assistance, and DMA works in sequence mode as well.

A known and stable equidistant sampling is the base of all control theory calculations.

> Further more, it worked nice with the software package for STM32F1 MCU V1.6.0.

Those are the woes of Cube/HAL.

But as said, your "sometimes" observed effect points to a runtime effect, i.e. a race condition.

TPier
Associate III

For more info, the trouble only happens if the DMA is configured in "Normal" mode. With the DMA in "Circular" mode, no problem.

I could isolate this with the following simple code:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
 
  if ((HAL_ADC_Start(&hadc1) != HAL_OK) ||
      (HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK) )
  {
    return 0;
  }
 
  while (1)
  {
    if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *) adc_data, ADC_DATA_COUNT) != HAL_OK)
    {
      return 0;
    }
 
    HAL_Delay(1000);
  }
}

The 1st acquisition is always good. The next ones show the channel shifting (however values remain correct, but in the wrong place in the DMA buffer).

I attempt to add a call to stop the conversions before restarting the acquisition, with the HAL_ADC_Stop_DMA function. But this leads to error for all the acquisitions but the first.

So my conclusion:

The multichannel continuous conversion mode cannot be used with the DMA in "Normal" mode ! The insidious side of this configuration is that no error is raised and data are just corrupted !

As a reminder, it is the case with the V1.8.0 version of the HAL library in the software package for STM32F1 MCU. It worked beforehand (V1.6.0).