2021-10-31 05:09 PM
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!
Solved! Go to Solution.
2021-11-02 02:20 AM
Hello @4ustenite ,
There is a mistake in the ADC configuration: confusion between continuous vs periodic ADC conversions (trigger mode):
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
2021-11-01 11:35 AM
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();
2021-11-02 02:20 AM
Hello @4ustenite ,
There is a mistake in the ADC configuration: confusion between continuous vs periodic ADC conversions (trigger mode):
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
2021-11-02 09:46 AM
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!