Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[STM32F7] ADC-DMA Sampling + Triple Interleaved Sampling #5053

Open
ryanmgriffiths opened this issue Aug 29, 2019 · 18 comments
Open

[STM32F7] ADC-DMA Sampling + Triple Interleaved Sampling #5053

ryanmgriffiths opened this issue Aug 29, 2019 · 18 comments

Comments

@ryanmgriffiths
Copy link

ryanmgriffiths commented Aug 29, 2019

For my high performance data acquisition project I have successfully implemented ADC sampling with the DMA in both single and triple-interleaved modes (with STMCube HAL drivers), with ADC1 as master for STM32F7 MCUs. The code is integrated into adc.c and so it uses the same config routines as read_timed with some tweaks for the DMA and has been tested on the Pyboard D.

There are still a few issues to iron out such as exiting the DMA callbacks, and the code is not well integrated into Micropython yet, however if there is interest/demand for these sampling modes I would be more than happy to help to adapt this for implementation after my project is over.

@ryanmgriffiths
Copy link
Author

Since creating this issue, I have managed to switch between triple interleaved with DMA, ADC1 with DMA and read_timed as well as being able to exit the "conversion complete" callback, it requires a simple function (2 HAL Macros) + re-initialising the ADC object to reset and configure ADC peripherals and change mode. One can structure the code such that DMA transfer is invoked by a function (i.e. re-enable the DMA interrupt and disable in the convplt callback) to reduce the overhead in ADC measurements, which is desirable in high performance applications.

@peterhinch
Copy link
Contributor

Sounds very useful.

@KarlBenjaminHorn
Copy link

It's what I'm Searching all the time for. is it possible to do the same with the original pyboard? just the DMA to increase the ADC performance and make it non blocking?

@ryanmgriffiths
Copy link
Author

It is definitely possible to use the ADC with the DMA on the Pyboard v1.1, relevant DMA streams for the STM32F405RG are the same as the STM32F7 and the chips are treated similarly in the dma.c and adc.c files.

@KarlBenjaminHorn
Copy link

that's amazing, can you upload the modified dma.c and adc.c? i would like to use it for my project.

@ryanmgriffiths
Copy link
Author

ryanmgriffiths commented Sep 23, 2019

My changes can be found on my forked version dev branch here:
https://github.com/gyrov/micropython/tree/dev/ports/stm32

Note that I also made some additions to both dma.h and irq.h, I also changed the board used in the Makefile since I am now working on a PYBD SF6. The irq.h changes are necessary for high rate sampling (ie very frequent DMA interrupts) as they preserve other board features (like the REPL). I have also left debugging printf statements in.

I have not documented the code particularly well yet, however the basic idea is you are able to:

  • Make an adc object adc = ADC(pin, mode) where mode is "TIMED" or "DMA" (since both use the same ADC we cant use both at the same time).

  • Using "DMA" we call adc.start_dma(buffer_in) where buffer_in is a 32bit buffer, this configures and starts the DMA.

  • With adc.read_dma() we fill the buffer once with ADC values.

  • Finally we stop the dma with adc.stop_dma() and can reset the adc peripheral with adc.reset although it seems to be fine if we simply reinit the adc object with "TIMED" to switch back to using timed modes.

Additionally:
It should be possible to implement the triple interleaved ADC sampling with this method as well. It also seems to be sensible to move the DMA method to another ADC in order to get around the fact that both read_timed and DMA cannot be used together, however I am not sure about pin mapping support for ADC2 and ADC3 on STM32F4/7.

@KarlBenjaminHorn
Copy link

thanks a lot, i will try to compile ant test it in the next days. Actually i was just using python and never compiled any c code for the pyboard. but now i have a reason to try.

@peterhinch
Copy link
Contributor

@GyroV When using DMA, how do you determine the ADC sampling rate?

@KarlBenjaminHorn
Copy link

that's a good question. i was looking in the code yesterday and as i understand he use no timer just fill the memory with data as fast as possible so the sampling rate should be determined by the ADC configuration.

@ryanmgriffiths
Copy link
Author

@peterhinch The sampling rate is determined by both the prescaler applied to the the APB2 clock (for 216MHz sys clock I think this is 108MHz and can be divided by 2,4,6,8 with prescalers) and the selected number of cycles per ADC sample (3,15 .... 480) giving a minimum possible 0.5us between samples and a maximum of ~35us between samples, so it is certainly only useful for high resolution measurements. At the moment I am working with 15CYCLES and DIV2 prescaler giving the 0.5us sample spacing when we take into account the 12 cycles for the 12 bit resolution. As I understand, it is only possible to reach the 2,4MSPS limit if one reduces the system clock to 144MHz with 3CYCLES and DIV2 prescaler.

@peterhinch
Copy link
Contributor

Thank you.

@rlourette
Copy link

@GyroV nice work. Would your changes support circular DMA where an array of buffers is supplied and callbacks are generated with the index of which buffer was just filled? This way, you could process the ADC samples (FFT, FIR, or whatever) without having to miss a sample by setting up the next transfer? I am working on an application like that and I really want to use Micropython to do it as opposed to all in C++.

Rich

@ryanmgriffiths
Copy link
Author

@rlourette The HAL_ADC_DMA_init command takes a pointer to an array of uint32_t to which it adds cyclically and generates a callback after a user defined number of samples. It sounds like you are looking maybe for some threading to process data while waiting for the next callback?

@rlourette
Copy link

@GyroV, I have been digging into this further and for the pyboard (and other STM32 variants) the circular DMA only works with one buffer. The interrupts that are available during the transfer to/from peripheral are at the halfway point and end of buffer before the cycle starts again (sorry for stating the obvious, but others may not know this.) Ideally, the "real time: processing would be done at an elevated priority.

After looking into it, what you are describing sounds exactly like what I am looking for. I am currently modeling every thing up using a timer to simulate the callbacks. I am pulling the data from compiled in data for testing. The processor I am working with is one of the STM32L4 variants. The CPU will be off most of the time to save power while data is collected. For the time critical stuff, I wrote my own module that does the processing in 'C', although, I may trying using inline assembly when I am done do make the code even more portable.

The device I am eventually processing the data from is on the SPI bus. I have limited control of the clock rate, but with fiddling with the PLL settings, I have a clock rate I want. I will take a look at your code. Thanks for the comment.

Rich

@rlourette
Copy link

rlourette commented Feb 27, 2020

@grov @peterhinch, I would like to propose the following changes to the SPI.init() pyb module signature in order to add similar functionality to the SPI module:

SPI.init() - Add the parameters:

callback - called when the SPI transfer is completed on a single transfer and repeatedly for circular dma.
callbackhalf - called when half of a transfer is completed on a single transfer and repeatedly for circular dma.
callbackerror - called if there was a transfer error
dmamode - NORMAL or CIRCULAR

I am not sure what to do with send, recv, send_recv. Should these be modified to take a parameter, which specifies whether the function returns immediately after the transfer is started. Otherwise, the default blocking behavior would be performed.

Feedback please as I would like to implement this.

Thanks

Rich

@ryanmgriffiths
Copy link
Author

Apologies for not checking back here for a while but now that my studies are over I've had much more time to work on this and I think I have a working system for using the circular DMA buffer integrated into micropython. To make this approach compatible with micropython and to avoid introducing unwanted memory usage I have used two buffers supplied as array.array types in micropython. In the screenshots below, dma_buf is the buffer supplied to the DMA which is filled cyclically and continuously. In order to obtain a static screenshot of the DMA buffer and get a measurement, I use a second buffer buf_out also supplied in micropython which is filled by the DMA buffer after a callback at the C level. As far as I can tell this is stable but there are a handful of quality of life improvements to make (error handling, making out_buf 16bit instead of 32bit etc.). It would also be important to see whether the DMA buffer is changing significantly while being read and exactly what we are able to measure with this.

mainpy
putty
read_code

@bhclowers
Copy link

I'm interested in a similar project using the STM32 (pyboard D) setup and wanted to know whether the modifications proposed by gyrov have been incorporated into the main branch or whether I would still need to use his proposed modificaitons? The capacity to sample the ADC using DMA seems like a niche, but key area to expand the capability of the micropython environment.

For reference, here is the project gyrov used to support his modifications: https://github.com/gyrov/MAPIC

Cheers

@ryanmgriffiths
Copy link
Author

ryanmgriffiths commented Nov 16, 2020

I'm interested in a similar project using the STM32 (pyboard D) setup and wanted to know whether the modifications proposed by gyrov have been incorporated into the main branch or whether I would still need to use his proposed modificaitons? The capacity to sample the ADC using DMA seems like a niche, but key area to expand the capability of the micropython environment.

For reference, here is the project gyrov used to support his modifications: https://github.com/gyrov/MAPIC

Cheers

Hi, the modifications I made to produce the results from above are from this branch of my micropython fork. That project is an implementation that was designed for a particular DAQ setup which benefited from low latency sampling.

https://github.com/gyrov/micropython/tree/adc_dma_dev

cdwilson pushed a commit to cdwilson/micropython that referenced this issue Jul 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants