Skip to main content
ABett.3
Associate II
July 31, 2022
Question

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

  • July 31, 2022
  • 2 replies
  • 6984 views

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.

This topic has been closed for replies.

2 replies

Georgy Moshkin
Senior
July 31, 2022

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

Check my STM32 Bootloader - flash your STM32 with AES-encrypted binaries right from web browser!
ABett.3
ABett.3Author
Associate II
August 2, 2022

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?

ABett.3
ABett.3Author
Associate II
August 2, 2022

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?

Georgy Moshkin
Senior
August 3, 2022

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).

Check my STM32 Bootloader - flash your STM32 with AES-encrypted binaries right from web browser!
ABett.3
ABett.3Author
Associate II
August 4, 2022

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