cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H747 DMA Linked List (or Double Buffering) and ADC Interleaved

ABett.3
Associate II

Hi all,

I'm implementing a DAQ (data acquisition chain), using STM32H747Xi Discovery board.

What I need to do is sample as fast as possible 1 adc channel and do some light processing on the data. I need to understand how to configure the system to get the best results.

So far here's my guess:

  • ADC1 & 2 in interleaved mode (ADC 1 as master)
  • ADCs in continuous mode
  • Transfer data to Memory using DMA in Linked List (or Double Buffering)

What I would like to do is set up 2 buffers and having the M7 process the 1st while the 2nd is beeing filled by the DMA. And viceversa.

I think that using linked list is the right way to go, because it also allows to target 2 different memory banks to avoid collision.

I started off with this example from stm32cubeh7 repo, which shows ADC interleaved functioning and uses circular buffering. I also found this one on MDMA linked lists.

Now I need to understand how to "merge" them.

Here are my questions:

  • How's the flow between sw, adc and dma? Should the ADC ask the DMA to start a transaction or the 2 should be started independently by sw?
  • from stm32h7xx_hal_adc.h "In continuous mode, DMA must be configured in circular mode. Otherwise an overrun will be triggered when DMA buffer maximum pointer is reached." But I guess it is possible to use it in linked mode as well right?
  • What do you think of the LinkedList approach as opposed to the circular buffer with HalfCompletion and Completion callback?

Any help/example on this topic would be of much help.

12 REPLIES 12
Georgy Moshkin
Senior II

1) configure ADC continuous circular mode (DMA)

2) configure MEM2MEM DMA

3) initiate DMA MEM2MEM transfer in ADC HalfCplt and Cplt interrupts

4) track DMA MEM2MEM transfer complete and safely process data in your main loop

Something like this:

#define ADC_BUFFER_SIZE 1024
 
// do not forget to place these buffers in DMA-accesible RAM 
uint16_t adcBuffer[ADC_BUFFER_SIZE] ; // for ADC circular buffer
uint16_t adcBuffer2[ADC_BUFFER_SIZE]; // copied from adcBuffer using mem2mem
 
volatile uint8_t idx=255;
 
void m2mCallback(DMA_HandleTypeDef *_hdma)
{
	// you can track mem2mem complete here, something like
	if (idx==2)  {idx=3;}
}
 
// ...
HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0, HAL_DMA_XFER_CPLT_CB_ID, m2mCallback);
 
while (1)
{
	idx=0; // allow mem2mem
	while(idx!=3){asm("nop");
	// process data here
	// if processing fast enough, idx will be set to 0 before 
	// HAL_ADC_ConvHalfCpltCallback
	// will be called again
	// idx=0 - need to copy, 1 - first half copy initiated,
	// 2 - second half copy initiated,
	// 3 - second half copy mem2mem completed
}
 
 
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
	if (idx==0) {
		// memmove(  (void *)&adcBuffer2[0],
		// (void *)&adcBuffer[0],
		// sizeof(adcBuffer)/2);
		HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
				(uint32_t)&adcBuffer[0],
				(uint32_t)&adcBuffer2[0],
				sizeof(adcBuffer)/2/4);
		idx=1;
	}
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if (idx==1) {
		//memmove(  (void *)&adcBuffer2[ADC_BUFFER_SIZE/2], 
		// (void *)&adcBuffer[ADC_BUFFER_SIZE/2],
		// sizeof(adcBuffer)/2);
		HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
				(uint32_t)&adcBuffer[ADC_BUFFER_SIZE/2],
				(uint32_t)&adcBuffer2[ADC_BUFFER_SIZE/2],
				sizeof(adcBuffer)/2/4);
		idx=2;
	}
}

In this example slow memmove/memcpy replaced by MEM2MEM. Check my answer in this thread: SPI_Receive to USB_Transmit via DMA on STM32H7

ABett.3
Associate II

Hi @Georgy Moshkin​ 

Thanks for the answer.

I'll give it a try, but I have one question:

// do not forget to place these buffers in DMA-accesible RAM 

It is not clear for me how to do so (I am using platformIO as IDE).

Also, given this system architecture0693W00000QMlXUQA1.pngI was thinking to start by putting everything in AXI SRAM 512kB block, do you see any problems in this?

Hi @Georgy Moshkin​ 

Thanks for the answer.

I'll give it a try, but I have one question:

It is not clear for me how to do so (I am using platformIO as IDE).

Also, given this system architecture0693W00000QMlXUQA1.pngI was thinking to start by putting everything in AXI SRAM 512kB block, do you see any problems in this?

AXI SRAM (0x24000000) will work. My own experience is that STM32Cube generated project had a linker file with RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K. If DMA is not working, it is usually either wrong memory address (DMA is not working on STM32H7 devices) or wrong initialization order (dma must be initialized before peripheral).

Thanks a lot for the material! This really helps:)

So I have a draft of the implementation now (still not able to test on HW).

Here my configuration for m2m transfer (did'nt find an example so I had to copy from a linked list one)

static void MDMA_Config(void)
{
  
  /*##-1- Enable the MDMA clock ###############################################*/
  __HAL_RCC_MDMA_CLK_ENABLE();
  
  /*##-2- Select the MDMA instance to be used for the transfer : MDMA_Channel0 #*/
  MDMA_Handle.Instance = MDMA_INSTANCE;
  
  HAL_MDMA_DeInit(&MDMA_Handle);
  
  /*##-3- Initialize the MDMA channel (with liked list node 0 parameters) ####*/ 
  MDMA_Handle.Init.Request              = MDMA_REQUEST_SW;
  MDMA_Handle.Init.TransferTriggerMode  = MDMA_FULL_TRANSFER;  
  MDMA_Handle.Init.Priority             = MDMA_PRIORITY_HIGH;                          
  MDMA_Handle.Init.Endianness           = MDMA_LITTLE_ENDIANNESS_PRESERVE;
 
  MDMA_Handle.Init.SourceInc            = MDMA_SRC_INC_WORD;
  MDMA_Handle.Init.DestinationInc       = MDMA_DEST_INC_WORD;  
    
  MDMA_Handle.Init.SourceDataSize       = MDMA_SRC_DATASIZE_WORD;
  MDMA_Handle.Init.DestDataSize         = MDMA_DEST_DATASIZE_WORD;
  MDMA_Handle.Init.DataAlignment        = MDMA_DATAALIGN_PACKENABLE;                            
  MDMA_Handle.Init.SourceBurst          = MDMA_SOURCE_BURST_SINGLE;
  MDMA_Handle.Init.DestBurst            = MDMA_DEST_BURST_SINGLE;
  MDMA_Handle.Init.BufferTransferLength = MDMA_BUFFER_LENGHT;
 
  if (HAL_MDMA_Init(&MDMA_Handle) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
}

And then I start it like:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *AdcHandle)
{
  
  /* Invalidate Data Cache to get the updated content of the SRAM on the second half of the ADC converted data buffer: 32 bytes */ 
  SCB_InvalidateDCache_by_Addr((uint32_t *) &aADCDualConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE/2], 4*ADCCONVERTEDVALUES_BUFFER_SIZE/2);
 
  /* Copy adc data to buffer */
  if(status == FIRST_HALF_COPY_STARTED)
  {
 	  HAL_MDMA_Start_IT(&MDMA_Handle,
                     (uint32_t)&aADCDualConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE/2],
                     (uint32_t)&aADCDualConvertedValuesCopied[ADCCONVERTEDVALUES_BUFFER_SIZE/2],
                     MDMA_BUFFER_LENGHT,
                     (sizeof(aADCDualConvertedValues)/2)/MDMA_BUFFER_LENGHT);
    status = SECOND_HALF_COPY_STARTED;
  }
  
  /* Set variable to report DMA transfer status to main program */
  ubADCDualConversionComplete = SET;
}

Here's my plan for the memory usage, do you see pitfalls?

My concern is the d1-to-d2 bridge, but I don't think it could be avoided.0693W00000QN0jQQAT.jpg

For mem2mem and others I use STM32CubeIDE generated code and it works perfectly fine. You can add MEM2MEM from the menu from the left side of PIN configurator:

System Core → DMA → ADD → in "select" drop-down list chose MEMTOMEM

0693W00000QNAxCQAX.jpgI have used WORD (4 byte) data width, so dataLength of HAL_DMA_Start_IT is divided by 4: sizeof(someBuffer)/2/4. Division by 2 is because it is half complete interrupt, and only half of data is ready for transfer. I have not used mem2mem with BDMA and cache. If something does not work I would try to disable cache and test BDMA mem2mem separately by filling input buffer with 01 02 03 04 and copying it to some zeroed memory and see if it works.

Hi @Piranha​,

thanks for the informative post.

I see that you do not mention memory 2 memory transfers and cache related issues. is there something relevant to that respect?

Update to @Georgy Moshkin​ as well, I implemented the skeleton for the application, but I'm not getting usable results yet.

At the moment I apparently broke the ADC to memory transfer, that was working before, as I get non-sense/corrupted data.

The copy buffer gets half-filled and never updated again. I wonder if the cache can be involved somehow.

Try to turn the cache off, check data widths, array sizes 16/32 bit. You can try to use memcpy first, lower sampling frequency.