Basic Scan-Mode A/D DMA (STM32H743)
A/D in Scan Mode using DMA
Overview
I wanted to setup a simple scan-mode DMA on my STM32H743ZI2 Nucleo, for a v-drum project where I want to sample about 6 different analog inputs at 10 KHz. It turns out it is pretty easy to setup, but there are a few pitfalls I ran into that can save you considerable headache.
Project Setup
Start with a new project in CubeMX based on the H743ZI2 Nucleo. 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 Connectivity, set USART3 to Asynchronous mode. By default it connects to PD8 and PD9, which sets up a serial connection via the ST-Link USB debug port. The default baud rate is 115200.
- In the Clock Configuration tab, set PLL Source to HSI and set DIVM1 to /8 and DIVN1 to 120 and leave DIVP1 at /2. Set the HPRE Prescaler to /2 and set the DxPREn dividers for the clocks that are red to /2.
- For the ADC, we will use PLL2. Set DIVM2 to /32, DIVN2 to x100 and DIVP2 to /2. Make sure the ADC Clock Mux selects PLL2P, which should show 100 MHz.
And now we can setup the ADC:
- I seem to have a preference for ADC3. Far as I know, you can use any, just adapt the pin references.
- In the ADC3 settings, enable the inputs you want. Mine are single-ended, which are easy, if you use differential inputs just be careful to use ones that don't conflict with each other.
- Set the Clock Prescaler to an available value. The time to convert each A/D channel depends on the clock rate, the prescaler, the resolution and the oversampling.
- I set the 16-bit resolution because it is available.
- Scan Conversion will be Enabled. If it doesn't let you set that now, it should flip automatically when you increase the Number of Conversions.
- Continuous and Discontinuous should be off.
- Conversion Data Management, which may only be available after you add DMA, should be "DMA One Shot Mode".
- Under conversion mode, set the number of conversions to the number you want, which probably matches the number of inputs you want to sample. These are regular conversions, not injected conversions.
- There should be a "Rank" option and drop-down for each conversion, set the Channel appropriately and its fine to leave the other values at defaults. Note that you can sample the channels in any order, this is how you do that, by putting them in the appropriate Rank.
- Go to the DMA Settings tab and Add a DMA Request. Select ADC3. In the next column you will see BDMA Channel 0 is the default. You need to change this to one of the DMA Stream options.
Do NOT accept the default "BDMA Channel 0"
See the Lessons Learned section for more info.
Software
The code generated for you to initialize the DMA, ADC and UART should be fine. You will just want to add a function call in the main() function, and a C/C++ file to implement that call.
In that new file, you will want to implement a completion callback for the DMA. It needs to have the name HAL_ADC_ConvCpltCallback with the following signature:
void HAL_ADC_ConvCpltCallback (ADC_HandleTypeDef * hadc)
And it is a wonderful idea to implement the error callback as well, HAL_ADC_ErrorCallback:
void HAL_ADC_ErrorCallback (ADC_HandleTypeDef *hadc)
At this point, you can call HAL_ADC_Start_DMA and it should perform the conversions and call your completion callback appropriately. Just know that you need to take cache precautions.
Cache Precautions
When DMA accesses memory, it typically (on parts like these) ignores the MCU's cache. This means that data you have written to memory might still be in cache and not be read by an outgoing DMA, or conversely an incoming DMA will go to main memory but if the memory is already cached it won't be updated.
This article from ST gives a good description of ways to handling caching issues.
Calibration
I had to read the HAL description and the Reference Manual description of ADC Calibration several times each to get a decent understanding.
Basically, there are three calibrations you can initiate - Single-Ended Offset Calibration, Differential Offset Calibration, and Linearity Calibration. If you are just using one type of input, either single-ended or differential but not both, then you call the HAL_ADCEx_Calibration_Start like this:
status = HAL_ADCEx_Calibration_Start (padc, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);
or
status = HAL_ADCEx_Calibration_Start (padc, ADC_CALIB_OFFSET_LINEARITY, ADC_DIFFERENTIAL_ENDED);
Or if you are using both single-ended and differential, make two calls:
status = HAL_ADCEx_Calibration_Start (padc, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);status = HAL_ADCEx_Calibration_Start (padc, ADC_CALIB_OFFSET, ADC_DIFFERENTIAL_ENDED);
so you don't do the Linearity Calibration twice.
Lessons Learned
As I said it wasn't the easiest process to get all this going, so I want to make sure these Lessons Learned are clear and obvious to the reader.
Implement the Error Callback
As described above, implementing the DMA error callback will clue you in to Transfer Errors early on, instead of thinking maybe you have and A/D problem. Also, setting a breakpoint on HAL_DMA_IRQHandler in stm32h7xx_hal_dma.c (around line 1203) can be very helpful.
Don't use the BDMA
The BDMA seems to be a special purpose DMA that can't access most devices or memories. Don't get sucked in because it is the first one that comes up in the selection list.
See this article from ST which goes into detail about bus matrix domains (and also the caching stuff). What it boils down to: don't use BDMA and expect not to get transfer errors.
As a side note, I did a bunch of googling for "H743 DMA transfer error" and found many references that warned of using tightly-coupled memory for the DMA target. This is of course correct, and is echoed in the article above, but none of them mentioned that the BDMA is in a separate domain that can't get to your normal SRAM either.
Comments
Post a Comment