Read ADC using DMA
Re: Read ADC using DMA
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.
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.
Re: Read ADC using DMA
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.manitou wrote: I haven't tried any of this, and such one-off asynchronous solutions would probably require that you hack the firmware.
.
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:
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 ..The overhead of interpreted python might defeat any speedups from the asynchronous DMA ADC
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Read ADC using DMA
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).
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.
Index to my micropython libraries.
Re: Read ADC using DMA
Thanks ... I was actually thinking of using your FFT assembly code to get the freq info from the data, so that would work well.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).
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)
Re: Read ADC using DMA
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.
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.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Read ADC using DMA
I assume you've seen my FIR filters in assembler https://github.com/peterhinch/micropython-filters.gitI also have a filter that I could easily re-write in assembly
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Read ADC using DMA
Thanks, had not seen those ..pythoncoder wrote:I assume you've seen my FIR filters in assembler https://github.com/peterhinch/micropython-filters.gitI also have a filter that I could easily re-write in assembly
Re: Read ADC using DMA
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.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 ...
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
Re: Read ADC using DMA
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.
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
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
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
Re: Read ADC using DMA
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 ..)
(I love event driven code ...no waiting ..)