Skip to main content
HBida
Associate III
December 14, 2019
Question

STM32L4 ADC+DMA how to get an interrupt when the buffer is filled?

  • December 14, 2019
  • 14 replies
  • 6450 views

HI, i am using STM32L476RG and need to acquire data at 1.5MSPS in a buffer, size 2048. Everything is working however i cant see how to get an interrupt once the buffer is filled.

I thought the TC flag could be used but when i check in the DMA interrupt i find this function : HAL_DMA_IRQHandler(AdcHandle.DMA_Handle); which it totally managed by the HAL, so i wonder how to get a flag or an interrupt once the buffer is fileld, because i will have to change the buffer in order to allow continuous acquisition while the filled buffer is processed with DSP instructions.

Thanks

This topic has been closed for replies.

14 replies

Tesla DeLorean
Guru
December 14, 2019

DMA should generate HT and TC interrupts if it is working. You'd need to have the correctly named callback which the HAL would dispatch back to you. ie the main IRQ Handler from the vector table calls the HAL_DMA_IRQHandler(), and it in turn dispatches to your callbacks, and then clears sources and exits.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
HBida
HBidaAuthor
Associate III
December 14, 2019

well, that was fast!

i named the callback : ADC_DMA_XferCpltCallback

i also added that statement

if(HAL_DMA_RegisterCallback(&DmaHandle, HAL_DMA_XFER_CPLT_CB_ID, ADC_DMA_XferCpltCallback) != HAL_OK) return;

right after the HAL DMA init, but for some reason it doesnt work, my own callback is never called.

But If add myself :

uint32_t flag_it = AdcHandle.DMA_Handle->DmaBaseAddress->ISR;
 uint32_t source_it = AdcHandle.DMA_Handle->Instance->CCR;
 if (((flag_it & (DMA_FLAG_TC1 << (AdcHandle.DMA_Handle->ChannelIndex & 0x1CU))) != 0U) && ((source_it & DMA_IT_TC) != 0U))
 {
	 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
 }

in the DMA IRQ handler then the LED is toggled from there, so the TC flag works.

So something must be missing with the callback...

HBida
HBidaAuthor
Associate III
December 14, 2019

i can also see that this callback is actually not weakly defined, it is used in stm32l4xx_hal_adc.c line 3492

of course if i try to define it again the compiler complains about double definition.

void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)

 It is assigned line 2250, and this callback doesnt calls any user callback.

So I wonder how we can process datas in the case of continuous conversion to large buffer if there is no way to flip the buffer?

Because with a single buffer it will never be available for data processing (being constantly used) and we cannot even know that the buffer is filled :\ . I must be missing something.

RMcCa
Senior II
December 14, 2019

One can easily make your own isrs. Look in the assembly file that contains the vector table and declare a void function with the correct name for the interrupt you want to use. When the compiler complains, simply delete the old one. You will need to test the flag bits in the isr to determine the cause of the interrupt and clear them before exiting. It's all in the data sheet.​

HBida
HBidaAuthor
Associate III
December 15, 2019

yes of course I added my own ISR to check that the TC flag was set, the problem here is that the HAL implements several mecanism in this (these) ISRs already.

Basically :

HAL_ADC_Start_DMA registers ADC_DMAConvCplt and ADC_DMAHalfConvCplt

then ADC_DMAConvCplt registers the ADC callback : hadc->ConvCpltCallback(hadc);

here is ADC_DMAConvCplt:

void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)
{
 /* Retrieve ADC handle corresponding to current DMA handle */
 ADC_HandleTypeDef *hadc = (ADC_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
 
 /* Update state machine on conversion status if not in error state */
 if ((hadc->State & (HAL_ADC_STATE_ERROR_INTERNAL | HAL_ADC_STATE_ERROR_DMA)) == 0UL)
 {
 /* Set ADC state */
 SET_BIT(hadc->State, HAL_ADC_STATE_REG_EOC);
 
 /* Determine whether any further conversion upcoming on group regular */
 /* by external trigger, continuous mode or scan sequence on going */
 /* to disable interruption. */
 /* Is it the end of the regular sequence ? */
 if ((hadc->Instance->ISR & ADC_FLAG_EOS) != 0UL)
 {
 /* Are conversions software-triggered ? */
 if (LL_ADC_REG_IsTriggerSourceSWStart(hadc->Instance) != 0UL)
 {
 /* Is CONT bit set ? */
 if (READ_BIT(hadc->Instance->CFGR, ADC_CFGR_CONT) == 0UL)
 {
 /* CONT bit is not set, no more conversions expected */
 CLEAR_BIT(hadc->State, HAL_ADC_STATE_REG_BUSY);
 if ((hadc->State & HAL_ADC_STATE_INJ_BUSY) == 0UL)
 {
 SET_BIT(hadc->State, HAL_ADC_STATE_READY);
 }
 }
 }
 }
 else
 {
 /* DMA End of Transfer interrupt was triggered but conversions sequence
 is not over. If DMACFG is set to 0, conversions are stopped. */
 if (READ_BIT(hadc->Instance->CFGR, ADC_CFGR_DMACFG) == 0UL)
 {
 /* DMACFG bit is not set, conversions are stopped. */
 CLEAR_BIT(hadc->State, HAL_ADC_STATE_REG_BUSY);
 if ((hadc->State & HAL_ADC_STATE_INJ_BUSY) == 0UL)
 {
 SET_BIT(hadc->State, HAL_ADC_STATE_READY);
 }
 }
 }
 
 /* Conversion complete callback */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
 hadc->ConvCpltCallback(hadc);
#else
 HAL_ADC_ConvCpltCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
 }
 else /* DMA and-or internal error occurred */
 {
 if ((hadc->State & HAL_ADC_STATE_ERROR_INTERNAL) != 0UL)
 {
 /* Call HAL ADC Error Callback function */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
 hadc->ErrorCallback(hadc);
#else
 HAL_ADC_ErrorCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
 }
 else
 {
 /* Call ADC DMA error callback */
 hadc->DMA_Handle->XferErrorCallback(hdma);
 }
 }
}

So these callbacks are already doing a lot of things and the user code does not seem to be intended to interract with them.

Ultimately my goal is just to have a double buffering so that the available datas can be processed without messing with the buffer currently in use. In the end the datas will be processed with DSP functions, but for now i just want to save them to SD card (4bit with DMA).

I dont see where and how i should switch the buffer, or if there is any mecanism provided for doing that.

RMcCa
Senior II
December 14, 2019

Also, use either the buffer half/full​ interrupt with a single 2x buffer or Double buffer with full interrupt and 2x single buffer. I think the results are the same.

It's all in the data sheet......​

S.Ma
Principal
December 14, 2019

Especially because most HAL versions enable all possible interrupts in case a user callback is registered, better use of half transfer with double buffer as RMcCa mentionned, you might then be able to run continuous acquisition if you can process 2048 data chunks before next one is coming...

HBida
HBidaAuthor
Associate III
December 15, 2019

yes, the problem here is that the calls where i could register my user callback are already used internally by the HAL with it own callbacks.

The plan is indeed to use TC or HT and double buffering, but i fail to see how to implement this provided that the HAL already have an extensive usage of these two ISRs for it own internal mecanisms. I havent found any example in the HAL tree of ADC DMA with double buffering.

RMcCa
Senior II
December 15, 2019

Sorry, you misunderstood my comment. You need to get rid of all the hal crap and write your own ​low level isr. Look in startup_32xxxxx.s and find the name in the interrupt vector table that matches the dma and stream you are using.

In my code, i am using dma with adc multimode on an f730, so i use dma2_stream0 for adc1 ( table in dma description of RM ) and declare

​

void DMA2_Stream0_IRQHandler( void )

​

Function in main.c

​

In the isr you need to read LISR to find the cause of the interrupt ( either buffer half or full​ ) and then clear the bit by writing to LIFCR.

Again, the compiler or linker will complain about multiple definition of the isr, just delete/comment out the old one.

Also, write your own initialization code. It's the only way to know that it's done properly. Setting up a dma with double buffering and interrupt is only like 8 lines of simple code.​ study the RM and use the debugger, it will start to make sense.

HBida
HBidaAuthor
Associate III
December 15, 2019

oh ok that makes sense, since then i found the callback i should use (HAL_ADC_ConvCpltCallback) and it works fine with it, but you are right, the HAL adds a lot of overhead. For now the first concern now is to improve the buffer switching, since there is no double buffering mecanism in the STM32L4 DMA engine i have to do it by hand and currently it costs 1400 cycle / 28uS. Also for some reason the SD card write speed is very low, in 4 bit mode with DMA when i write a 2048 byte buffer it takes 316107 Cycles / 3951 uS, which is ±0.5MB/S.

HBida
HBidaAuthor
Associate III
December 15, 2019

so, there is no double buffering in the STM32L4 DMA, contrary to F4 and other series. So i guess the only way is to do it by hand. I noticed that HAL_ADC_ConvCpltCallback(hadc); was only weakly defined and not actually used by the HAL, so i declared it in my main, and indeed it is called at TC.

So i crudely used it to stop DMA, set the other buffer and restart DMA (the GPIO toggle is for debug, the incremented tc flag is for cycles counting). Of course there is quite an overhead to do that, with a 1024 buffer it cost about 1400 cycles (26550 with DB, 25125 without DB) or 28uS (331uS vs 313uS) . so some samples are lost in the process (sampling rate is 1.334MSPS). If anyone has a better idea it will be much appreaciated.

N.B i dont understand why the HAL has to use extensively ISRs internally for continuous ADC acquisition via DMA, what is the point of having DMA if the MCU is overwhelmed by ISRs?

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){
 
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	tc+=1;
	HAL_ADC_Stop_DMA(&AdcHandle);
	if(!bfr){bfr=1; HAL_ADC_Start_DMA(&AdcHandle, (uint32_t*) bfrA, 1024); }
	else{bfr=0; HAL_ADC_Start_DMA(&AdcHandle, (uint32_t*) bfrB, 1024); }
}

RMcCa
Senior II
December 15, 2019

Does the l4 have half and buffer full interrupts? It's the same thing as double buffering, just use a buffer that is twice the required size, that way the buffer half or full​ interrupt is telling you that you can start processing the data in either lower or upper half of the buffer. Voila! Double buffering!

And once again, don't use hal. You are declaring the wrong isr. If you want a quick & lean isr you need to use the very lowest level and set/clear register bits yourself. I've just reexplained the same thing like 3 times in this thread. At some point you need to think for yourself. It's called learning.​

HBida
HBidaAuthor
Associate III
December 15, 2019

i am not so sure it is safe to access the same buffer as the DMA at the same time tho (even if it is not the samehalf). F4 serie (and others) have some specific DMA function for double buffering (one can provide two different buffers), if there is no difference between that and using the HC and TC flags with a single buffer then i wonder why they bothered to add such mecanism.

You are declaring the wrong isr

Not sure what you mean, HAL_ADC_ConvCpltCallback is defined weak and called from within ADC_DMAConvCplt, since it is not used by the HAL it is actually the callback for user (provided that ADC_DMAConvCplt is already used by the HAL). And indeed when declared it is called at the right time and does the job.

if I can actually transfer datas from first buffer half when the DMA is writing the second half, i then just use HAL_ADC_ConvHalfCpltCallback and dont switch buffers, no overhead, no samples lost.

To get rid of the HAL ISRs and use own ISR + direct register access one has to rewrite not only ISRs but the whole ADC+DMA implementation, because ADC, DMA and ISRs are tied together and a lot of things are configured / handled by the HAL. Here the problem was to get the callback with what is already in place, and the answer to that is : use HAL_ADC_ConvCpltCallback, quite simple. Using low level ISR may incidently adress the issue, but at the cost of reimplementing everything by hand, I dont see the need for that here.

RMcCa
Senior II
December 15, 2019

Ok.

Do whatever you want, but if you are interested in writing fast & efficient embedded code you are going about it the wrong way.

Over & out.​