2022-07-31 07:50 AM
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:
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:
Any help/example on this topic would be of much help.
2022-07-31 09:31 AM
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
2022-08-02 10:45 AM
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 architectureI was thinking to start by putting everything in AXI SRAM 512kB block, do you see any problems in this?
2022-08-02 10:46 AM
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 architectureI was thinking to start by putting everything in AXI SRAM 512kB block, do you see any problems in this?
2022-08-02 07:31 PM
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).
2022-08-04 02:19 PM
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.
2022-08-08 01:21 AM
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
I 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.
2022-08-28 04:34 AM
The cache maintenance is not correct.
2022-09-03 03:26 AM
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.
2022-09-03 07:07 AM
Try to turn the cache off, check data widths, array sizes 16/32 bit. You can try to use memcpy first, lower sampling frequency.