2019-03-24 03:01 PM
I need to implement a single channel software triggered ADC scan in an interrupt callback paced at 10ms intervals, since the rate cannot be controlled in DMA.
I never had an issue doing this with CMSIS, yet, HAL seems to be, well, difficult is the nicest word I will use.
I am confident my code is set up correctly, because before the scheduler starts, I have a "while" test loop that run 10 times, and the values come out correctly. Yet, in the callback, it chokes to death and causes faults. Why, that's just like HAL.
Here is the init code for the ADC.
void MX_ADC1_Init(void)
{
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 2;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
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_13;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_47CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_14;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
Here's the code before launch:
MX_ADC1_Init();
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
And in the TIM2 callback @ 10ms intervals:
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 50);
accumulator[TEMP] += HAL_ADC_GetValue(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 50);
accumulator[VBAT] += HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
//when count reaches 100, divide accumulator by count and get voltages/temps
}
It's BROKE as broke can be. I have been fighting with this thing for too many hours. Dear ST, HAL has not made things easier, at all.
Can anyone please provide a clue. Where could the problem be?
If I continue to have these problems, I will have the L4 processors removed from my boards and have F4's put in, and go back to CMSIS. Where things worked and I still had hair.
Solved! Go to Solution.
2019-03-25 11:40 AM
Because you are converting more than one channel, you will be racing to get the first channel value before the ADC overwrites it with the next channel value. Probably best to use DMA, ST provides an example of timer + ADC + DMA use, which you could extend to multiple channels:
STM32Cube_FW_L4_V1.3.0\Projects\STM32L476RG-Nucleo\Examples_LL\ADC\ADC_SingleConversion_TriggerTimer_DMA
2019-03-25 11:53 AM
I have not seen this, and should not happen if it's paced in the timer interrupt. And the whole point of this thread was to get samples at an even uniform rate of time. For a processor core running at 80MHz, this should be no issue especially when using indexed pointers into arrays.
Since I let CubeMX configure things, I may take another look at it and see if i can find out why it was breaking. Pretty basic stuff that should work with no issues.
2019-03-25 12:11 PM
Well, it appears that you have about 60 ADC clock cycles to get your first channel data. That could be less than a microsecond (depends on your ADC clock rate).
2021-02-24 04:11 AM
Does this issue still present? i have faced it too on stm32l476 stm32Cube 1.5.1 and seems like polling in MultiChannel single mode does not work properly.
2021-02-24 09:19 AM
The key to success here is to realize that the ADC has only one data register. To get two samples 10 ms apart, use a timer to start the conversion. If you need to sample two ADC channels at once like Gary has done above, use DMA and save one channel at one sample, then the other channel at the next sample. If you don't use DMA and want to poll for each channel, then configure the ADC for the first channel only before starting conversion, read and save the result, then reconfigure the ADC for the second channel only before starting the second conversion.
Cheers, Hal
2021-02-24 09:36 AM
I am currently using Cube version 6.1.1. Processor is an STM32L476V. HAL libs are 1.15.1. Everything is working correctly. I have 5 channels, including 1 for internal temperature. See attached screen cap for settings. I have Timer7 running at 1ms/1KHz rate. Software:
If you're using RTOS, make sure your interrupt priorities are set up( <= 5) and you have allocated enough memory in the config file.
At beginning of program:
//start cal
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
In interrupt:
This must be at top of timer interrupt if using RTOS (otherwise ignore it)
long xHigherPriorityTaskWoken = pdFALSE;
//start
HAL_ADC_Start(&hadc1);
//get each channel in succession
HAL_ADC_PollForConversion(&hadc1, 40);
uint16_t accVAL = HAL_ADC_GetValue(&hadc1);
//do something, assign to array, etc...
HAL_ADC_PollForConversion(&hadc1, 40);
accVAL = HAL_ADC_GetValue(&hadc1);
//do something, assign to array, etc...
//every channel in config must be read, then STOP
HAL_ADC_Stop(&hadc1);
//and if using RTOS, must do this:
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
That's it...works well now.
Hope that helps.
2021-02-24 10:00 AM
That's what I did in the end, i.e. configuring the desired channel(only one) before starting the conversion. it works.
I will update my cube and hal library and try.
Thanks all.
2021-02-24 10:21 AM
Also - I tried going the DMA route and using a timer to trigger. What's the point though? If I'm already using a HW interrupt in the timer, then it might as well execute the interrupts for the ADC calls too. The ARM pipeline architecture is pretty efficient so I see no hit in efficiency, that might not be in many cases though. RTOS is convenient so when sample buffer is collected you can fire a queue message and process the data while the next buffer is collecting. For this a dual buffer in "ping-pong" is ideal so that no data is overwritten.
It would be nice if DMA could be set up for exact interval capture as required for specific operations where that's needed.
2021-02-24 12:36 PM
Using DMA with a timer triggering the ADC, you can capture ADC samples into memory without involving the CPU, depending on your system needs, this can be very important. For example, several thousand samples can be captured to digitize a waveform while the CPU is tending to other tasks / priorities.
2021-02-24 01:06 PM
No one denies the usefulness of DMA, but there are cases when it is unnecessary.
Here we are talking about whether HAL provided the correct Multichannel operation without using DMA.