Double-Buffered DMA from ADC
Analog Streaming Data Capture
Overview
If you want to do high-powered Analog to Digital Conversion in a microcontroller, the best way is to use two buffers and cycle between them, so you can be recording data into one buffer and processing the data already in the other buffer. ST-micro's STM32F7 products, and others, make this possible.
While the API functions are available to do this, as of this writing it isn't all packaged together so you can do it easily. Here is what I found when I went through the process.
The STM32F746G Discovery board is capable of taking ADC samples at more than 1 million per second, so it could be useful as a low-bandwidth scope, and audio spectrum analyzer, and so on.
Project Setup
To sample ADC in streaming mode, start with a CubeMX project based on the Discovery board. I choose not to initialize all peripherals, and just add the ones I want, which seems simpler. I am using CubeIDE 1.5.1.
Some basic setup:
- In System Core / CORTEX_M7, enable the Prefetch and Caches
- In System Core / RCC, set HSE to Crystal/Ceramic Resonator.
- In Connectivity, set USART1 to Asynchronous mode. By default it connects to PA9 and PB7, which sets up a serial connection via the ST-Link USB debug port. The default baud rate is 115200.
Then setup clocking:
- Set PLL Source to HSE and set the PLLM to /25 and the *N to 400 and leave /P at /2.
You can go higher than 400, up to 432. The SYSCLK runs at half that, and more is generally better. But if you are using the SRAM on the Discovery board you need to limit to 400, so I usually leave it there.
The ADC3 clock is based on PCLK2, so keep an eye on that value. With this setup it ends up being 100 MHz.
For the analog input, I have chosen to use PA0, which is mapped to the A0 Arduino pin. It is accessed via ADC3 IN0. So in the Analog / ADC3 setup, check IN0.
In the Parameter Settings:
- we will leave the prescaler at PCLK2 / 4 for now. This means the ADC is clocked at 25 MHz, and samples take 600 ns. Okay, that's too much, set it to /8 for 1.2us samples.
- leave resolution at 12 bits
- I like the Left alignment for data. That way you can divide all samples by FFFF as the full scale reading, and the sample resolution doesn't matter.
These values aren't critical to the example working, just the timing of things and the way the data looks, so feel free to pick values you like.
The settings that are critical to double buffering are:
- Scan Disabled (the default)
- Continuous Enabled
- Discontinuous Disabled
- DMA Continuous Requests Enabled - but it won't let you enable it just yet.
Go to the DMA Settings page tab and click Add, and select ADC3 under DMA Request. It should show the stream DMA2_Stream_0.
Now you can go back and enable DMA Continuous Requests.
That's it, save the project and generate code.
Software
If you aren't familiar with CubeMX code generation, as long as you follow the rules of staying between the USER CODE BEGIN and END markers (mostly in main.c), you can safely use CubeMX to update your configuration and re-generate code. Since in almost all my projects I have to regenerate at some point, I find it very good to follow the rules. One simple way is to put the guts of your code in separate source files.
So here are the updates to main.c to call the rest of the project software:
/* USER CODE BEGIN PFP */
extern void adc_process (void);
/* USER CODE END PFP */
and...
/* USER CODE BEGIN WHILE */
adc_process();
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
void HAL_ADC_DB_ConvCpltCallback(ADC_HandleTypeDef* hadc, int which){buffer_to_process = which;go_ahead_process_buffer = TRUE;conversion_count[which]++;}
The STM HAL code handles callbacks by implementing 'weak' functions, which you can override if you know the correct name, and it helps to know the parameter types. So this is the callback for the Double-Buffer process. Note that it has a second parameter which indicates which buffer has just been filled.
extern HAL_StatusTypeDef HAL_ADC_Start_DB_DMA (ADC_HandleTypeDef* hadc, uint32_t* pData1, uint32_t* pData2, uint32_t Length);
NOTE
Timing
To Come
Addendum: UART Note 2
- Enable the interrupt in CubeMX in the NVIC tab for the USART,
- Change the Transmit to Transmit_IT, and remove the timeout parameter,
- Don't try to transmit again until the last one is complete. This starts by implementing the HAL_UART_TxCpltCallback callback so that you get notified when transmission is complete.
Comments
Post a Comment