ADC sampling with DMA/interrupts

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
Post Reply
griffitsr
Posts: 5
Joined: Wed Jul 10, 2019 3:50 pm

ADC sampling with DMA/interrupts

Post by griffitsr » Thu Aug 22, 2019 6:23 pm

Hello all, hope this is the correct place for this post.

We have been working to implement ADC sampling using the DMA and conversion callbacks for high performance peak-finding and have run into some problems on our pyboard D (STM32F723IEK), namely that the serial connection appears to be dropping during interrupt sampling.

To do this sampling, we added a new method to the ADC class in adc.c and modified the adcx_init_periph to enable the continuous conversion and DMA requests.
We started out by modifying the dma.c file to add a new dma_descr_t for the ADC and a DMA_InitTypeDef with the circular buffer mode enabled.
Here is the code we are using to initialise the DMA in adcx_init_periph:

//// dma.c

// adc struct is customised version of i2c/spi structs
const dma_descr_t dma_ADC = { DMA2_Stream0, DMA_CHANNEL_0, dma_id_8, &dma_init_struct_adc };



//// adc.c

STATIC void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution){
...
static DMA_HandleTypeDef DMA_Handle;

// init the DMA with custom descriptor for ADC behaviour
dma_init(&DMA_Handle, &dma_ADC, DMA_PERIPH_TO_MEMORY, adch);

// link the DMA, replaces __HAL_LINKDMA(data, xxx, *dma)
// where we have called data->xxx = dma as required in dma.c comments
adch->DMA_Handle = &DMA_Handle;
...
}

// new custom function
STATIC void adc_read_DMA(...){
...
HAL_ADC_Start_DMA(&self->handle, bufinfo.buf,nelems);
while(1){}
return mp_const_none;
}

// callback function
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *adch){
printf("Callback");
}

Unfortunately, we cannot seem to be able to get the HAL_ADC_ConvCpltCallback to trigger for our ADC input to pin X12, or if it is, then it is causing the board serial connection to drop.
This appears to be the way to initialise the DMA IRQHandler for our channel and stream in dma.c, however it looks like the streams used for ADC1 such as DMA2_Stream0 currently have a different use.
When we tried to overwrite this one the board kept resetting, presumably calling the SD card IRQ handler and not our convcpltcallback, is there a way to attach multiple handlers to one stream to make use of the channels?

We also tried using the unused DMA2_Stream7 for our ADC but still no interrupts were observed (although the serial connection remained intact). However there doesnt seem to be an adc function on this stream/channel combination so that would explain the non-result.

Therefore it seems impossible to use ADC1 for DMA reading without serious rewrites since DMA2 stream0 and stream4 are in use. It does seem possible however to use DMA2_Stream1 to implement the
DMA interrupts with ADC3 on DMA2_Stream1 due to the line in mpconfigboard_common.h :

#define MICROPY_HW_ENABLE_DCMI (0)

which implies that

[#if MICROPY_HW_ENABLE_DCMI]
const dma_descr_t dma_DCMI_0 = { DMA2_Stream1, DMA_CHANNEL_1, dma_id_9, &dma_init_struct_dcmi };

is ignored by the compiler, freeing up the stream for ADC DMA usage. The complication here is that we are required to rewrite the adc.c file to use ADC3 for this and we are uncertain
how to access the correct pin mappings, furthermore there could already be a handler associated with this stream and we are not sure what its name is or where to find it. We are also puzzled as to where
the callback functions are for the other DMA interrupts.

Additionally we have tried implementing the normal STM32 HAL ADC interrupts, which dont rely on the DMA IRQHandler to more apparent success.
For this, one is required to define the ADC_IRQHandler(), however it is not obvious where these IRQ Handlers should be placed, after some trial and error we used the following code:

////stm32_it.c

extern ADC_HandleTypeDef ADCHandle;
...
void ADC_IRQHandler(void){
HAL_ADC_IRQHandler(&ADCHandle);
}

and we put the ADC_IRQHandler in the header too.
Then we have configured the watchdog in adc.c like so:

////adc.c

adcx_init_periph(...){
...
adch->Init.ContinuousConvMode = ENABLE;
adch->Init.ExternalTrigConv = ADC_SOFTWARE_START;
...
}

adc_make_new(...){
HAL_NVIC_SetPriority(ADC_IRQn,0,0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
...
}

// CUSTOM FUNCTION again a copy of read timed
adc_read_IT(...){
...
HAL_ADC_Start_IT(&self->handle);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *adch){
HAL_NVIC_DisableIRQ(ADC_IRQn)
if(counter>20000000){
led_toggle(PYB_LED_RED);
}
else{
counter++;
}
HAL_NVIC_EnableIRQ(ADC_IRQn);
}

which turned the red LED on after a small delay, which proves that the interrupts are working. This particular callback was necessary because the board was becoming unresponsive when interrupts were enabled, so we couldn't monitor the serial port for print statements. Perhaps this is also what was happening in our DMA interrupts as well.

What are your thoughts on this, I need to do some more testing on the DMA interrupts tomorrow to see if they are triggering, however it seems our biggest problem is that the serial connection breaks down when the ADC interrupts are enabled. This is of course not ideal for any application; do you have any ideas on the causes of the serial connection
dropping and the board "malfunctioning" (according to windows) and does the approach to the DMA handling seem to be sound?

Thank you for your assistance.
-Ryan

User avatar
dhylands
Posts: 3415
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ADC sampling with DMA/interrupts

Post by dhylands » Thu Aug 22, 2019 10:18 pm

Often, if the system becomes unresponsive when enabling interrupts it happens because an interrupt fires and the reason for it firing doesn't get cleared, so the interrupt immediately fires again. This means that the foreground never gets a chance to run.

OutoftheBOTS_
Posts: 784
Joined: Mon Nov 20, 2017 10:18 am

Re: ADC sampling with DMA/interrupts

Post by OutoftheBOTS_ » Fri Aug 23, 2019 4:50 am

I am certainly a noob but have played a little with DMA, ADC and interrupts. So far the HAL libraries have made another layer of confusion so I have mainly played with writing directly to the registers but am slowly trying to learn to use HAL. Also my experence is mainly wiht STMF4 MCUs.

Now if I understand right you want to take a number of ADC sample really fast so your using DMA to transfer them to a circular buffer.

I did a while ago do something similar in C. But instead of using a circular buffer I just used a buffer and told the DMA to do a set number of transfers then setup the ADC to do that many conversions. I just then had a small loop that waited for the DMA (by checking the status regiters if the DMA) to finish then I made use of the data in the buffer then repeated it again. So doing this didn't require using any interrupts.

My limited experience with using interrupts on the STM32 with NVIC I found that when the interrupt fired it sets a bit in 1 of the register that disables the interrupt till that bit is reset. So I normally run all the code for the interrupt then at the very end just before it is about to return from the interrupt I reset the needed bit to re-enable the interrupt. Looking at the code for an interrupt I wrote awhile ago for a STM32F1 on exit of the interrupt there was these 2 lines EXTI->PR |= EXTI_PR_PR0; and EXTI->IMR |= EXTI_IMR_MR0;

griffitsr
Posts: 5
Joined: Wed Jul 10, 2019 3:50 pm

Re: ADC sampling with DMA/interrupts

Post by griffitsr » Sat Aug 24, 2019 9:18 am

Is it possible that other applications such as the network/wireless access point may also be affected by this? When testing the network it seems that ARP requests are not being answered.

griffitsr
Posts: 5
Joined: Wed Jul 10, 2019 3:50 pm

Re: ADC sampling with DMA/interrupts

Post by griffitsr » Sat Aug 24, 2019 1:02 pm

Ok, I have solved this problem by reducing the ADC interrupt priority to preserve network (pendsv?) and serial interrupts and now I am able to sustain the foreground functions. Will still have to try this with the DMA interrupts however and not sure how great the effect will be on the rate.

griffitsr
Posts: 5
Joined: Wed Jul 10, 2019 3:50 pm

Re: ADC sampling with DMA/interrupts

Post by griffitsr » Tue Aug 27, 2019 4:23 pm

dhylands wrote:
Thu Aug 22, 2019 10:18 pm
Often, if the system becomes unresponsive when enabling interrupts it happens because an interrupt fires and the reason for it firing doesn't get cleared, so the interrupt immediately fires again. This means that the foreground never gets a chance to run.
I have managed to get both the ADC EOC interrupts and DMA ADC interrupts working since making this thread, to do this I slightly modified the dma.c file to include new descriptor for DMA-ADC. I started to use DMA2_Stream4 since it seems that the SPI4/5 are not defined on many boards (including the STM32F72/3 for the PYBD that I am working on) and it works with ADC1 on my board.

Note that DMA is initialised in circular buffer mode with continuous ADC conversion enabled and it should transfer 30 values into the buffer and then give an interrupt.

Code: Select all

//  IN DMA.C - CUSTOM DMA DESCRIPTOR
const dma_descr_t dma_ADC = { DMA2_Stream4, DMA_CHANNEL_0, dma_id_12, &dma_init_struct_adc };
// IN ADC.C

// DMA CALLBACK FUNCTION
uint32_t counter=0;

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *adch){
NVIC_DisableIRQ(DMA2_Stream4_IRQn);
if(counter==1000){
led_toggle(PYB_LED_RED);
}
else if (counter == 10000){
led_toggle(PYB_LED_GREEN);
}
counter++;
NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}

//in adcx_init_periph

static DMA_HandleTypeDef DMAHandle;

// deinit the dma descr for unused(?) SPI DMA Interrupts
dma_deinit(&dma_SPI_4_TX);
dma_deinit(&dma_SPI_5_TX);

// init DMA with ADC config
dma_init(&DMAHandle, &dma_ADC, DMA_PERIPH_TO_MEMORY, adch);

// link the DMA to ADC
adch->DMA_Handle = &DMAHandle;

// Start ADC linked with DMA in new function
HAL_ADC_Start_DMA(&self->handle, (uint32_t*)buffer , 30);

However, I have run into a fairly significant issue in that I can reliably fire off ~10000 interrupts before they stop working (i.e Red LED turns on but not the green if counter requirement too high), I have tried changing the DMA priority to be higher, removed the IRQ_ENTER()/IRQ_EXIT() in IRQHandler in dma.c, played around with the ADC init properties - including cycles per sample- but I cannot seem to pin down the cause of this limitation. I don't think there is enough time to lose the serial connection, so putty remains connected etc. It is worth noting that I did not see this issue when playing around with normal ADC EOC interrupts and it seems like the DMA is turning off after a certain amount of time or hanging on a callback when it is interrupted by something else.

In the end there will be extra overhead in the callback to ship data out so perhaps this problem is to do with an overloaded DMA and it will not affect my end goal.

Post Reply