Read ADC using DMA

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
manitou
Posts: 73
Joined: Wed Feb 25, 2015 12:15 am

Re: Read ADC using DMA

Post by manitou » Sat Aug 08, 2015 3:54 pm

The ADC DMA hardware has a circular buffer option. You could provide a buffer and start the continuously running ADC, and then read the buffer (as it is being continuously updated) at your leisure. You could also be notified (you'd need a callback) or poll the EOC bit to see if a cycle through your buffer has been completed. If you were wanting to use other ADC pins whilst your DMA was running, you'd probably need to run the DMA on ADC2 or ADC3. The firmware currently uses ADC1.

I haven't tried any of this, and such one-off asynchronous solutions would probably require that you hack the firmware.

The overhead of interpreted python might defeat any speedups from the asynchronous DMA ADC.

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Read ADC using DMA

Post by ebike » Sat Aug 08, 2015 8:22 pm

manitou wrote: I haven't tried any of this, and such one-off asynchronous solutions would probably require that you hack the firmware.
.
Yes, I could hack the firmware, but I thought one of the aims of this forum was to give the maintainers of microPython, some feedback on customer use, and if more people want a feature, they would look at implementing .... that is what I am doing. :roll:

If I wanted to continuously hack the code, I might as well continue all my projects in C, and I don't want that, as I see great benefits in this project. :)

PS:
The overhead of interpreted python might defeat any speedups from the asynchronous DMA ADC
You obviously don't know what DMA is: There is no CPU usage during DMA cycles, so there is no "overhead" from any code to the rate the DMA buffer fills ..

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Read ADC using DMA

Post by pythoncoder » Sun Aug 09, 2015 5:35 am

I think @manitou is suggesting that acquiring data at a high rate may be pointless if Python can't subsequently process it at the same rate. Although you might be able to implement a fast decimation filter using the inline assembler to reduce the data rate to a more manageable level.

On the subject of firmware, when a new feature is suggested the developers face a conflict between coding it and keeping the code "micro". Inevitably a factor in this is how widely used the feature is likely to be, including its actual usability (see above).
Peter Hinch
Index to my micropython libraries.

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Read ADC using DMA

Post by ebike » Sun Aug 09, 2015 7:45 am

pythoncoder wrote:I think @manitou is suggesting that acquiring data at a high rate may be pointless if Python can't subsequently process it at the same rate. Although you might be able to implement a fast decimation filter using the inline assembler to reduce the data rate to a more manageable level.

On the subject of firmware, when a new feature is suggested the developers face a conflict between coding it and keeping the code "micro". Inevitably a factor in this is how widely used the feature is likely to be, including its actual usability (see above).
Thanks ... I was actually thinking of using your FFT assembly code to get the freq info from the data, so that would work well.
I also have a filter that I could easily re-write in assembly to process the data.

The key here for my application, is that I need to put ADC->memory as fast as the ADC will allow ,so that I get the most freq info out of the data, then I can configure a filter to do what I want ... (if I use a filter at all)

manitou
Posts: 73
Joined: Wed Feb 25, 2015 12:15 am

Re: Read ADC using DMA

Post by manitou » Sun Aug 09, 2015 1:48 pm

if you don't require asynchronous ADC acquisition (DMA), i think that you could run the ADC in continuous mode, and the C firmware could test EOC and save the ADC values before the next sample is ready. Thus you would be acquiring the data as fast as the ADC samples became available. I plan to do some experiments in a day or so ...

The sampling rate can be increased by reducing the ADC resolution (ref 13.7) and/or altering the sampling rate in SMPRx registers. The firmware uses default resolution of 12 bits, and the sampling register is configured at 15 cycles. The total sampling cycles is thus 15+12 = 27. The ADCCLK is PCLK2/2/2 or 42mhz, so a sample takes 27/42mhz 0.64 us. Though my systick measurments were 152 CPU cycles/168mhz or 0.9 us. (Timing read_timed in python for 10,000 16-bit word buffer averages out to about 1.25us per sample with the read_timed frequency at 1MHz). I changed the sampling register to 3 cycles and the resolution to 8 bits, and the systick sample rate dropped to 86 CPU cycles, or 0.51 us per sample == 2Msps in the firmware.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Read ADC using DMA

Post by pythoncoder » Tue Aug 11, 2015 5:14 am

I also have a filter that I could easily re-write in assembly
I assume you've seen my FIR filters in assembler https://github.com/peterhinch/micropython-filters.git
Peter Hinch
Index to my micropython libraries.

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Read ADC using DMA

Post by ebike » Tue Aug 11, 2015 5:26 am

pythoncoder wrote:
I also have a filter that I could easily re-write in assembly
I assume you've seen my FIR filters in assembler https://github.com/peterhinch/micropython-filters.git
Thanks, had not seen those ..

manitou
Posts: 73
Joined: Wed Feb 25, 2015 12:15 am

Re: Read ADC using DMA

Post by manitou » Mon Sep 14, 2015 9:13 pm

manitou wrote:if you don't require asynchronous ADC acquisition (DMA), i think that you could run the ADC in continuous mode, and the C firmware could test EOC and save the ADC values before the next sample is ready. Thus you would be acquiring the data as fast as the ADC samples became available. I plan to do some experiments in a day or so ...
I modified firmware adc.c to run the ADC in continuous mode in read_timed(). The ADC is enabled outside the firmware read-loop, and the read-loop doesn't do a start, but just a while-test and read. As noted above, in single-shot mode with read_timed frequency at 1 MHz, a 10,000 half-word array is filled in 12449 us (bout 1.25 us/sample). In continuous mode, it takes 10,066 us (bout 1 us/sample). With read_time at 2MHz, it takes 6,478us. (bout 0.65 us/sample). Single-shot at 2MHz is still 12,450us.

Actually, you can just disable the timer6 stuff in read_timed() for continuous mode, and we still get 6478 us for 10,000 half-words. Here are the systick times per sample in continuous mode

array('H', [3494, 84, 122, 92, 116, 92, 116, 92, 116, 92, 116, 92, 116, 92, 116, 92])

Code: Select all

         >>> import pyb
         >>> import array
         >>> adc = pyb.ADC('X1')
         >>> buf = array.array('H', bytearray(20000))
         >>> t1=pyb.micros(); n=adc.read_timed(buf,2000000); t2=pyb.micros()
         >>> t2-t1
         6478 
I may yet try some DMA experiments

manitou
Posts: 73
Joined: Wed Feb 25, 2015 12:15 am

Re: Read ADC using DMA

Post by manitou » Mon Nov 02, 2015 12:28 am

ADC with DMA hack

OK, I finally got around to hacking adc.c to use DMA to read the ADC. As above we'll do 10,000 samples.

Code: Select all

         >>> import pyb
         >>> import array
         >>> adc = pyb.ADC('X1')
         >>> buf = array.array('H', bytearray(20000))
         >>> t1=pyb.micros(); n=adc.read_timed(buf,2000000); t2=pyb.micros()
         >>> t2-t1
         6475
Not surprisingly, the 6475us is almost identical to the CONTINUOUS mode duration of 6478us -- the sampling rate of the ADC is the limiting factor. The ADC is in its default sampling configurartion: clockDIV2, and 15samples. Of course, the DMA implementation offers the opportunity to do other computing while the DMA is running, though additional functions would need to be defined for callback's or testing for DMA done.

For my tests, I just added DMA init stuff to the adc_init_single() of adc.c, and then gutted (re-purposed) read_timed() to initiate a DMA on the ADC. Here are some snippets from the hack

Code: Select all

adcHandle->Init.ContinuousConvMode    = ENABLE;    // DMA
adcHandle->Init.DMAContinuousRequests = ENABLE;    // DMA
...
	//  DMA init  ADC1 is DMA2 channel0 stream 0 or 4 use DMA2_Stream0  thd
	//  from dac.c
	__DMA2_CLK_ENABLE();
	DMA_Handle.Instance = DMA2_Stream0;
	DMA_Handle.State = HAL_DMA_STATE_READY;
    HAL_DMA_DeInit(&DMA_Handle);

    DMA_Handle.Init.Channel = DMA_CHANNEL_0;   // dac used 7 ? thd
    DMA_Handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
    DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE;
    DMA_Handle.Init.MemInc = DMA_MINC_ENABLE;
    DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    DMA_Handle.Init.Mode =  DMA_NORMAL;
    DMA_Handle.Init.Priority = DMA_PRIORITY_HIGH;
    DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;
    DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE;   // spi DMA_MBURST_INC4 ?
    DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE;
    HAL_DMA_Init(&DMA_Handle);

	__HAL_LINKDMA(adcHandle, DMA_Handle, DMA_Handle);
....

	HAL_ADC_Start_DMA(&self->handle, bufinfo.buf, nelems);
	// wait for DMA to complete:  could use ISR/callback
	while (DMA_Handle.Instance->CR & DMA_SxCR_EN);   // spin stream 0

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Read ADC using DMA

Post by ebike » Mon Nov 02, 2015 12:53 am

Excellent stuff. Be great if you implemented the callback or soft interrupt on completion ... that would make it perfect.
(I love event driven code ...no waiting ..) :D

Post Reply