2017-11-01 03:01 PM
Need to sample 5 channels at 20Hz. Would like an interrupt when the DMA buffer is filled (50 samples). Waiting on boards to arrive and have a few questions:
1) Does the ADC or DMA needs restarted after each conversion of the sequence (5 channels) when set to trigger off a timer with continuous disabled (i.e. HAL_ADC_Start_IT)? I'm thinking no.
2) When triggering ADC conversion off a timer using DMA, when is HAL_ADC_ConvCpltCallback called? Is it after the 5 channels are sampled once or the DMA buffer is full? Is it after the 5 channels are sampled once or the DMA buffer is full?
MCU: STM32F071CB
ADC Continuous mode: disabled
Triggered off: ADC_EXTERNALTRIGCONV_T1_TRGO
Configuration details below. Also see attached wrapper for initializing the ADC, DMA, and TIM
Thank you in advance for your help.
//ADC Configuration
adcHandle.Instance = ADC_HANDLE; adcHandle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; //HSI14 Not divisible adcHandle.Init.Resolution = ADC_RESOLUTION_12B; adcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT; adcHandle.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD; adcHandle.Init.EOCSelection = ADC_EOC_SEQ_CONV; //Read as group of channels adcHandle.Init.LowPowerAutoWait = DISABLE; adcHandle.Init.LowPowerAutoPowerOff = DISABLE; //HSI14 powers off between conversions adcHandle.Init.ContinuousConvMode = DISABLE; //Perform conversion until overrun adcHandle.Init.DiscontinuousConvMode = DISABLE; adcHandle.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO; //Conversion triggered by timer adcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; //trigger on rising edge adcHandle.Init.DMAContinuousRequests = ENABLE; //DMA adcHandle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; //Old data is overwritten adcHandle.Init.SamplingTimeCommon = PRESSURE_OVERSAMPLE_RATE_CYCLES; //Oversampling rateDMA
//Enable DMA clock
PRESSURE_DMA_CLOCK(); //Configure DMA adcDmaHandle.Instance = PRESSURE_DMA_CHANNEL; adcDmaHandle.Init.Direction = DMA_PERIPH_TO_MEMORY; adcDmaHandle.Init.PeriphInc = DMA_PINC_DISABLE; //use the same peripheral address adcDmaHandle.Init.MemInc = DMA_MINC_ENABLE; //increment after each conversion adcDmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //two bytes per sample adcDmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //two bytes per sample adcDmaHandle.Init.Mode = DMA_CIRCULAR; //do not use circular buffer adcDmaHandle.Init.Priority = DMA_PRIORITY_VERY_HIGH; //Only one DMA channel, so moot setting //Deinitialize, then initialize the DMA HAL_DMA_DeInit(&adcDmaHandle); //function always returns HAL_OK HAL_DMA_Init(&adcDmaHandle); //function always returns HAL_OK //NVIC configuration for DMA Input data interrupt /*Set pre-emptive priority to 0 (highest) as per CubeMx Example Note that subpriority is not available on M0 device... set to 0.*/ HAL_NVIC_SetPriority(PRESSURE_DMA_IRQ, PRESSURE_DMA_PREEMPT_PRIORITY, 0); HAL_NVIC_EnableIRQ(PRESSURE_DMA_IRQ); //Associate the DMA handle __HAL_LINKDMA(&adcHandle, DMA_Handle, adcDmaHandle);Timer
TIM_MasterConfigTypeDef adcTimConfig;
//Enable timer clock PRESSURE_START_TIMER_CLK(); //Configure Timer adcTimHandle.Instance = PRESSURE_START_TIMER; adcTimHandle.Init.Period = PRESSURE_TIMER_PERIOD_CYCLES; adcTimHandle.Init.Prescaler = PRESSURE_TIMER_PRESCALER; adcTimHandle.Init.ClockDivision = PRESSURE_TIMER_CLOCK_DIV; adcTimHandle.Init.CounterMode = TIM_COUNTERMODE_DOWN; adcTimHandle.Init.RepetitionCounter = 0; //not used (counts reps as way to slow down the timer even further) adcTimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //disable changing reload on the fly errorCodeHal = HAL_TIM_Base_Init(&adcTimHandle); //Set timer to trigger on over/underflow */ if(HAL_OK == errorCodeHal) { adcTimConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; //Triggers on under/overflow adcTimConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; errorCodeHal = HAL_TIMEx_MasterConfigSynchronization(&adcTimHandle, &adcTimConfig); }2017-11-01 03:37 PM
At each trigger event 5 samples will be taken, you can make the buffer for 50 or 100 samples deep, and use circular DMA. These things are initialized once and done, and if you use the HT/TC DMA interrupts you can manage half the buffer without worries of the data changing underneath you.
I wouldn't use HAL, that's your choice to make.
2017-11-02 06:26 AM
Clive, thank you for the quick response. I intend to use a 50 sample circular buffer, read between the 20Hz triggers.
Question 2: If I want the ADC to trigger on a timer and use DMA, then both of these functions need called?
HAL_ADC_Start_DMA(&adcHandle, (uint32_t*)rawPressureBuffer[0], (uint32_t)NUM_OVERSAMPLES);
HAL_ADC_Start_IT(&adcHandle);
For others referring to this thread: I looked further into the HAL callback, which I should have done before posting. Here is a quick walk through. Clive or others, please correct me if I am wrong.
HAL_ADC_ConvCpltCallback is called at the end of ADC_DMAConvCplt(), which is called after each DMA transfer:
1. Checks whether software start was used & continuous mode was disabled. If true and end of sequence (EOS) is reached, it disables the interrupt for EOC and EOS, which need to be re-enabled by calling HAL_START_ADC.
abbreviated
if(ADC_IS_SOFTWARE_START_REGULAR(hadc) && (hadc->Init.ContinuousConvMode == DISABLE) )
if( __HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOS) )
__HAL_ADC_DISABLE_IT(hadc, ADC_IT_EOC | ADC_IT_EOS);
In other cases, such as software start continuous or trigger start, it leaves everything alone.
2. Next, it calls HAL_ADC_ConvCpltCallback(), which is a weak function that can be redefined by the user.
I my case, I want to be notified when the buffer is full. If I had had a double buffer, here is where I would determine if the interrupt was called when the buffer was half or full and read the corresponding half.
void DMA1_Channel1_IRQHandler(void)
{ HAL_DMA_IRQHandler(adcHandle.DMA_Handle);//Add code here to read data out of the DMA circular buffer
}Clive,
Can you elaborate on being cautious in using HAL:
2017-11-02 10:00 AM
It is far too big and gets in its own way. It has historically been full of fresher mistakes, and still isn't rigorously tested. In my product development this generates far too much drag and friction, I can't blame third parties for product failures, or delays in fixing them, my bosses aren't interested in hearing excuses..
I've demo'd F0 stuff using SPL and register level abstractions.
2017-11-02 11:15 AM
Thank you for your perspective. Shortcuts without rigorous testing often lead to long cuts (as I have experienced with ST's sensor APIs). I'll keep this in mind as I gain experience to try to move toward eliminating the HAL libraries as a crutch.