STM32 CubeMX "Timer + ADC + DMA"
This is a simple write-up so I don't have to figure it out again next time. The goal is to setup a periodic timer which starts an ADC measurement on the background using DMA. I'm using a NUCLEO-F303RE development board.
Timer configuration
The STM32F303RE is configured to put 72MHz at TIM4. To generate events at 10Hz, a prescaler of 7200-1 is used with a counter period of 1000-1. After the prescaler, the frequency is 10kHz. The counter reaches 1000 after 0.1 second, after which the value is reloaded. An important setting here is the "Trigger Event Selection TRGO: Update Event". This will be used to trigger the ADC.
ADC configuration
I have selected 5 channels (IN3 - IN7) on ADC2 which should all be measured in one go.
In the ADC configuration, I use 12-bit resolution. DMA Continuous Requests is enabled, because otherwise it will only measure on the first trigger from the timer. The number of conversions is set to 5, because I am measuring 5 channels. Each channels is configured individually in the 'Rank'-dropdown.
The External Trigger Conversion Source is set to "Timer 4 Trigger Out Event", which corresponds to the previously mentioned timer configuration. This way, the ADC will be started every 0.1 seconds.
To do this using DMA, the DMA is configured as follows. Data Width is chosen Half Word (16-bits), since the ADC is configured at 12-bit. Mode Circular is enabled because otherwise only one measurement will be done. Values of the previous measurement are overwritten.
Code
After generating the code with CubeMX, the following lines are important. First of all, a buffer for DMA:
/* USER CODE BEGIN 0 */ volatile uint16_t adc_buffer[5] = {0};
Just before the main loop, start the timer and DMA:
/* USER CODE BEGIN 0 */
volatile uint16_t adc_buffer[5] = {0};
Somewhere down in main.c, add the interrupt function for a completed ADC conversion:
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
/* This is called after the conversion is completed */
}
The complete code is available on GitHub.
Comments
Seng Tak - May 13, 2019 at 8:55 am
Hi Bart,
Some comments:
If you are not handling the ADC triggering timer interrupt, starting it with HAL_TIM_Base_Start will be more proper.
As a sharing from my personal mistake, and maybe good to share with future reader, that the init seq of TIM and DMA must be in the right order, that DMA first and TIM follows, else the callback sequence of the DMA written buffer will be wrong.
Cheers
Jarno Kat - January 10, 2020 at 9:20 am
Thanks for sharing this, i found it very helpful.
One question, where the “Channel 4” comes from at the timer settings? Any reason could we use any other channel?
I did not manage to trigger my ADC1 with Timer 1 Capture Compare 1 event – wasn’t patient enough to try change my settings from: TIM1: Channel1: “Output compare No output” Mode: “Toggle on match” Trigger event selection: “Update Event”
Iff anybody knows the cure please share.
Millicent - July 1, 2020 at 12:37 pm
Hi,
In your first line you say “The STM32F303RE is configured to put 72MHz at TIM4”. Is this just a default frequecy because of of the CPU? I want to implement ADC+TIM+DMA on a STM32F334R8, but I’m not sure whether the timers are configured to 72 MHz (also the frep of my CPU) or if may be some different value?
ben - August 9, 2020 at 5:40 am
Since using the output-compare of the timer, why isn’t HAL_TIM_OC_Start used instead of HAL_TIM_Base_Start ?
The clock configuration allows you to set APB1 Timer Clock, and APB2 Timer Clock, and ADC Clock.. what should the ADC clock speed be? Which is APB is used for your Timer4?
Because timer 4 starts a 5 channel SCAN conversion, does that mean that adc channel 1 and channel 5 won’t have been sampled at the same time?
Finally my version of Stm32 cube shows an additional option for the adc: Conversion Data Management : DMA One shot, or DMA Circular. Since the timer triggers a dma transfer from adc to memory.. what’s the difference in behavior between dma one shot and dma circlar?
Locke - September 17, 2020 at 9:02 am
Thanks, you code helped me figure out my issue. In my case, hadc2.Init.ContinuousConvMode = DISABLE; this is the key. If it is enabled, ADC will keep running as its maximal speed rather then 10KHz.
tomio - March 2, 2021 at 4:26 am
Thanks,, The order of starting DMA and TIM. I have been in trouble for a week with this wrong starting order.
Nikos - March 28, 2021 at 10:13 am
Hi,
here you are using the & symbol but the array name of adc_buffer is a pointer. You should see a compiler warning. HAL_ADC_Start_DMA(&hadc2, (uint32_t*)&adc_buffer, 5);
Leo H - July 19, 2022 at 12:41 pm
For me, this tutorial did not work at first. The callback was only called once. What fixed the problem was that I had to change the initialization order of the ADC and the DMA that was auto-generated. If you face the same problem, make sure to first initialize the DMA, then the ADC.
Hojat - June 19, 2023 at 1:23 pm
This solved my issue. Also, I had to call HAL_ADC_Start once at initialization time.