An another bug in STM32CubeMX software. This is a perfect example where the order of called initialization functions really matters. In discussed case I will give an example where ADC was configured to go through regular conversion triggered by an event from timer and the data was transferred automatically using DMA.
Let’s focus for a moment on a following task. We have 10 independent ADC channels. We would like to take readings from them in a periodical fashion. The most straightforward way would be to trigger a conversion with software. However, if there is DMA it is possible to transfer measured data to the memory completely automatically without CPU envolvment, and this is what DMA is for. This peripheral will transfer a reading from an ADC data register every time when the reading is ready. Also, if the readings have to be read periodically why not to use a timer to make it possible?
Configuration of ADC is following
As it can be seen in the figure above the ADC was configured in scan conversion mode. This means that all channels (ranks) in regular conversion mode will be measured, one after one. The continuous conversion mode is also enabled ant it means that it will make measurements continuously without stopping. This option can be safely disabled since the ADC will be triggered with a timer TIM6. An another option which has to be enabled is DMA Continuous Requests. It means that the ADC will inform DMA to transfer data from the ADC data register to memory using DMA every time when the new data is available.
Finally, in ADC_Regular_ConversionMode three things should be set. The first is about enabling regular conversion itself. Then, the number of conversion should be set, the maximum value for the MCU used for this is 16. Lastly, the source of external trigger should be set, in this case it is TIM6 update event. After setting all of those, each rank should be configured separately.
Now, the timer can be configured. Sample configuration can be seen in the figure below
It is a basic configuration including setting the prescaler and the timer’s period. However, one obvious change has to be made to the trigger output. It has to be set to Update Event so every time when the timer gets overflown an event is generated and it triggers ADC to start a conversion.
At the very end, the DMA has to be configured. Also, a sample configuration was included below in a form of a figure.
Here, three things are of a key importance:
- Mode — circular, so the DMA overwrites the data at each pass.
- Memory address incrementation.
- Width of data — 16 bits since the ADC data register is of 2 bytes in size.
One other thing is optional, namely enabling ADC interrupts. If you would like to receive and handle ADC interrupts then a corresponding interrupt should be enabled. If the code does not relay on ADC interrupts this step can be omitted. Other configuration parameters can be tuned such as the End Of Conversion Selection which can call an interrupt every time a single conversion was made or when a full sequence was finished. However, this does not influence the bug in question.
When a Generate code button is hit CubeMX generates source code files. What is the most important part is inside main.c file. A snipped was attached below
/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); MX_ADC1_Init(); MX_DMA_Init(); MX_TIM6_Init();
To run the conversion following two lines should be added
HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_raw_data, 10); HAL_TIM_Base_Start(&htim6);
The bug is not an obvious one. The ADC+DMA+TIM configuration should work out of the box but for some reason it does not. The problem lays in the order of initialization. If the ADC is initialized after the DMA then everything works fine but not the other way around.
For the time being, the bug was reported. I will give an update when everything works. Meanwhile, I have come up with a temporary fix. Since, the CubeMX generates files in order specified in *.ioc project file then one can change it manually but this is a dirty fix. A better one, in my opinion, is to disable calling of ADC initialization function and add this in user code space as in example below.
And complementary code snipped
/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); MX_DMA_Init(); MX_TIM6_Init(); /* USER CODE BEGIN 2 */ MX_ADC1_Init(); /* USER CODE END 2 */
Now, every time when the code is regenerated all concerning ADC will work since it is initialized after DMA initialization.
This bug was spotted in STM32CubeMX v.5.4.0 version with STM32Cube MCU Package for STM32L4 Series v.1.14.0 version.