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.
nmz787
Posts: 29
Joined: Sun Jul 10, 2016 7:57 am

Re: Read ADC using DMA

Post by nmz787 » Wed Nov 01, 2017 3:34 pm

manitou wrote:
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
Hi Manitou,
thanks a lot for your code snippets!
I was able to integrate them with my copy of ADC.c in my clone of MicroPython 1.9.3... now I can call the read_timed method and the buffer is returned with samples inside!

For my work, I will be attempting to modify this further to transfer ADC samples with DMA only when an associated Timer ticks. I think this would consist of (2) method calls, one to get things started with

Code: Select all

HAL_ADC_Start_DMA
and the second to stop it (

Code: Select all

HAL_ADC_Stop_DMA
).

Does anyone have any references I could use to base this work on? I am already reasonably understanding of the Timer peripherals, as I setup One-Pulse-Mode and timer master/slave setup for synchronizing several timers together.

Thanks again! I will post my code when I get somewhere interesting and relatively bug-free :)

Post Reply