2025-07-07 5:39 AM - edited 2025-07-07 7:23 AM
Hello forum,
I'm (still) working on a data acquisition system based on STM32H743XIH6 MCU, I'm pushing the 16bit ADC to the limits and I'm stuck on a corner.
Let me summarize first the actual code:
1) TIM1 trigger every 1us ADC1 acquisition
2) ADC1 acquire 4 ch and move data via DMA to a ADCbuf[4] (4ch scan @1MSa/s)
3) HAL_ADC_ConvCpltCallback is called (every 1us) when 2) is complete and does some calculations on ADCbuf[4]. (After 100k executions=4x100k samples a flag is risen triggering data output on the main loop).
Long story short, It works flawlessly up to 960kHz, at 1MHz randomly the callback is called again before the previous call get completely served. As far I have understood the problem is that 3) is called with a 1MHz occurrance and, context saving\restore, demand too much time to get the system online without losing any samples.
The solution I'm trying to follow is to relax this 1us callback timing this way:
1) TIM1 trigger every 1us ADC1 acquisition
2) ADC1 acquire 4 ch and append data via DMA to a ADCbuf[40] (ADC still acquire 4ch @ 1MSa/s)
3) When 10 acquisition are completed (every 10us), something (a callback?) is called and does some calculations on a (4x)10 samples batch. (After 10k executions, 4x100k samples, a flag is risen triggering data output on the main loop).
Below follow some code, how do i reconfigure ADC\DMA in order to get this acquisition scheme?
Thanks in advance for all the precious infos,
Luca
TIM1 config:
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 23;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 9;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
ADC config:
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_16B;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 4;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.Oversampling.Ratio = 1;
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_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSignedSaturation = DISABLE;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
Context and Callback:
//Following is placed in D2 RAM as per https://community.st.com/t5/stm32-mcus/dma-is-not-working-on-stm32h7-devices/ta-p/49498
__attribute__((section(".dma_buffer"))) uint16_t ADCbuf[4]; //ADC to DMA buffer (4ch)
...
//ADC Calibration
if(HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK){
...
//ADC Start
if(HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADCbuf, 4) != HAL_OK){
...
//Start TIM1 (ADC trigger\timebase: 1MSa/s per eachchannel -> 1MHz)
if(HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK){
...
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){ //todo: ADC callback
//Add val^2 to sum (RMS)
SumR[0] += (uint32_t)ADCbuf[0]*ADCbuf[0];
SumR[1] += (uint32_t)ADCbuf[1]*ADCbuf[1];
SumR[2] += (uint32_t)ADCbuf[2]*ADCbuf[2];
SumR[3] += (uint32_t)ADCbuf[3]*ADCbuf[3];
if(Index>=Astop){ //Buffer full (end of frame acquisition)
#ifdef OVERRUN_LOCK
if(ADCcmplt){
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);
Overrun=1;
}
#endif
//Transfer SumR and Reset
SumRO[0]=SumR[0];
SumRO[1]=SumR[1];
SumRO[2]=SumR[2];
SumRO[3]=SumR[3];
SumR[0] = 0;
SumR[1] = 0;
SumR[2] = 0;
SumR[3] = 0;
Index = Astart; //Reset Index
ADCcmplt=1;
}
else Index++;
}
2025-07-07 6:17 AM
Don't use the ADC interrupt/callback, but the DMA. Config circular DMA which is "endless". Config Half transfer and transfer complete interrupts. In each DMA interrupt callback, process the half of the buffer that is finished while the ADC keeps filling the other half.
hth
KnarfB
2025-07-07 7:40 AM - edited 2025-07-07 7:40 AM
Hi @KnarfB ,
do you mean using HAL_ADC_ConvHalfCpltCallback() beside the HAL_ADC_ConvCpltCallback() (which I'm already using)? That's interesting since I didn't realized before that I could lose samples if the ADC overwrite the very first data if I'm not serving interrupt quickly (before the new sampling group 1-10).
DMA is already circular, therefore should I just do the following 4->40 and add the Half callback?
__attribute__((section(".dma_buffer"))) uint16_t ADCbuf[40];
...
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc){
//elaborate ADCbuf[0...19]
}
...
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){
//elaborate ADCbuf[20...39]
}
...
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADCbuf, 40);
Thanks,
Luca
2025-07-07 10:41 AM
exactly.