cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103 - Empty DMA buffer from ADC conversion

4ustenite
Associate II

Hi Everyone,

Probably a bit long question, but I try to give as much details as possible. Thank you in advance for your time!

I am trying to trigger the ADC1 on a STM32F103C8T6 using a timer. The ADC uses the DMA and once the conversion is completed, I try to send the buffer to my computer via USB (Virtual COM port). I work in STM32CubeIDE using the HAL.

I use TIM4 and its 4th channel as a trigger. I need to generate 500 kHz, which I managed to do. I tested it with an oscilloscope and I can get the 500 kHz square wave if I use the "PWM Generation CH4" option.

 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM4_Init 1 */
 
  /* USER CODE END TIM4_Init 1 */
  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 143;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 35;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }

Then, I have my ADC1 set up like this:

ADC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN ADC1_Init 1 */
 
  /* USER CODE END ADC1_Init 1 */
  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T4_CC4;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  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_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

Then, in the while(1) loop, I periodically (re)start the ADC and after it is started, I also start 2 other timers in one pulse mode after the start of the ADC. The size of the buffer is 4000.

while(1)
{
HAL_ADC_Start_DMA(&hadc1, (uint32_t*) Buffer, BufferSize);
	 HAL_Delay(1);
	 __HAL_TIM_ENABLE(&htim1);
	 __HAL_TIM_ENABLE(&htim3);
HAL_Delay(50);
}

And finally, I try to use the ADC conversion completed callback to push my buffer through the USB:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    CDC_Transmit_FS((uint8_t*) Buffer, BufferSize);
}

It seems that due to my inexperience, I am overlooking something.

I separated my code into two smaller codes to see what could go wrong. I tested if the other two One-pulse timers that I start in the while(1) work properly. Yes, they do. Then I wrote the same code for ADC without those timers and it seems that it works too (ADC triggered by TIM4 CH4). I tested it with a potentiometer and the ADC wonderfully sends the expected data to my terminal on my PC.

But, when I combine the two codes, something does not seem to work. I also tried to put a flag in the callback of the ADC and do the USB transfer in the while(1) and then reset the flag, but it does not work either. I also tried to stop the ADC and TIM4 inside the callback of the ADC and restart it after the USB transfer but it did not help. I still get zeroes showing up on my computer.

If anyone has any ideas, I would greatly appreciate the help! Thank you!

1 ACCEPTED SOLUTION

Accepted Solutions
Philippe Cherbonnel
ST Employee

Hello @4ustenite​ ,

There is a mistake in the ADC configuration: confusion between continuous vs periodic ADC conversions (trigger mode):

  • continuous mode: ADC performs conversions successively, automatically and without delay, after the first trigger event
  • trigger mode: ADC performs one conversion after the each trigger event (in case of trigger from timer, it will induce periodic ADC conversions)

If you use both, like in your configuration: ADC will wait for the first trigger event from timer, then will convert automatically without use further timer events.

(configuration not forbidden, but does not correspond to typical use case).

=> you must set: hadc1.Init.ContinuousConvMode = DISABLE;

Then your application should basically work.

You don't have to stop ADC at each transfer or use tricky timer start: only ADC start, then timer start and that's all.

There is an example demonstrating ADC+DMA+timer if STM32F1 FW package:

 ...\Firmware\Projects\STM3210C_EVAL\Examples\ADC\ADC_Regular_injected_groups

(it can be easily ported to your target STM32F103)

Some improvement suggestion:

You shoud manage data buffer half by half (when a half is filled by ADC+DMA, the other half is transfered by USB)

It can be done using callback functions:

- HAL_ADC_ConvHalfCpltCallback()

- HAL_ADC_ConvCpltCallback()

It will also require update of USB buffers (hcdc->TxBuffer, hcdc->TxLength) after each transfer.

Best regards

Philippe

View solution in original post

3 REPLIES 3
4ustenite
Associate II

Upon further reading and banging my head against the wall, I found the solution and I found two issues with the code.

Problem 1: It is a good idea to stop the DMA in the callback of the ADC. Otherwise the data will arrive in a broken format. The DMA is restarted in the while(1) anyway.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    HAL_ADC_Stop_DMA(&hadc1);
    CDC_Transmit_FS((uint8_t*) Buffer, BufferSize);
}

Problem 2: Automatic code generation by the STM32CubeIDE. In the int main(void){} function, the DMA has to be initialized before the ADC.

MX_DMA_Init();
MX_ADC1_Init();

Philippe Cherbonnel
ST Employee

Hello @4ustenite​ ,

There is a mistake in the ADC configuration: confusion between continuous vs periodic ADC conversions (trigger mode):

  • continuous mode: ADC performs conversions successively, automatically and without delay, after the first trigger event
  • trigger mode: ADC performs one conversion after the each trigger event (in case of trigger from timer, it will induce periodic ADC conversions)

If you use both, like in your configuration: ADC will wait for the first trigger event from timer, then will convert automatically without use further timer events.

(configuration not forbidden, but does not correspond to typical use case).

=> you must set: hadc1.Init.ContinuousConvMode = DISABLE;

Then your application should basically work.

You don't have to stop ADC at each transfer or use tricky timer start: only ADC start, then timer start and that's all.

There is an example demonstrating ADC+DMA+timer if STM32F1 FW package:

 ...\Firmware\Projects\STM3210C_EVAL\Examples\ADC\ADC_Regular_injected_groups

(it can be easily ported to your target STM32F103)

Some improvement suggestion:

You shoud manage data buffer half by half (when a half is filled by ADC+DMA, the other half is transfered by USB)

It can be done using callback functions:

- HAL_ADC_ConvHalfCpltCallback()

- HAL_ADC_ConvCpltCallback()

It will also require update of USB buffers (hcdc->TxBuffer, hcdc->TxLength) after each transfer.

Best regards

Philippe

Thank you Philippe!

Yes, I actually copied the wrong code for the ADC, I also had the continuous mode disabled. But, I did not exactly know why it solved my issue so those first 2 bullet points you wrote were extremely helpful, thank you!

I will also check that example, and later will try to implement a code for the half-by-half transfer!

Thanks again for the detailed insights!