Setting a quad buffer in STM32 - Page 1
Forum section: electronics / general

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 14, 2020, 12:37:51 am
content: As you can see in some of my previous posts, I'm still learning and struggling with some concepts within embedded programming. This time I'm asking for some support in setting a quad buffer in STM32. I have tested ADC+DMA+TIM in STM32 using HAL and it is working good. Also, I tested Half Transfer and Transfer Complete Interrupts which are working fine. The code below was written for a PSoc device. It sets the DMA to fire an interrupt every 1/4 buffer is filled. I'm not familiar with Cypress, that's why it is harder for me to translate it to STM32. But if I fully understand the idea behind, then I think I can do it in STM32. How is the buffer manipulation being done? Is it a single buffer that changes some pointer? In STM32 I think I could declare two buffers and use HT and TC interrupts and play withCode:HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_buffer, ADC_BUF_SIZE)), but I have trouble to understand buffer allocation in this code.Code:void DMA1_Config(void){uint8 DMA1_Chan;uint8 DMA1_TD;#define TRANSFER_COUNT (NO_SAMPLES * 2)#define BUFFER_1 (NO_SAMPLES)#define BUFFER_2 (NO_SAMPLES * 2)#define BUFFER_3 (NO_SAMPLES * 3)#define DMA_BYTES_PER_BURST (2u)#define DMA_REQUEST_PER_BURST (1u)#define DMA_SRC_BASE (CYDEV_PERIPH_BASE)#define DMA_DST_BASE (CYDEV_SRAM_BASE)DMA1_Chan = DMA1_DmaInitialize(DMA_BYTES_PER_BURST, DMA_REQUEST_PER_BURST, HI16(DMA_SRC_BASE), HI16(DMA_DST_BASE));DMA1_TD = CyDmaTdAllocate();DMA1_TD = CyDmaTdAllocate();DMA1_TD = CyDmaTdAllocate();DMA1_TD = CyDmaTdAllocate();CyDmaTdSetConfiguration(DMA1_TD, TRANSFER_COUNT, DMA1_TD, DMA1__TD_TERMOUT_EN | TD_INC_DST_ADR);CyDmaTdSetConfiguration(DMA1_TD, TRANSFER_COUNT, DMA1_TD, DMA1__TD_TERMOUT_EN | TD_INC_DST_ADR);CyDmaTdSetConfiguration(DMA1_TD, TRANSFER_COUNT, DMA1_TD, DMA1__TD_TERMOUT_EN | TD_INC_DST_ADR);CyDmaTdSetConfiguration(DMA1_TD, TRANSFER_COUNT, DMA1_TD, DMA1__TD_TERMOUT_EN | TD_INC_DST_ADR);// TargetCyDmaTdSetAddress(DMA1_TD, LO16((uint32)ADC_1_DEC_SAMP_PTR), LO16((uint32)(&Buffer)));CyDmaTdSetAddress(DMA1_TD, LO16((uint32)ADC_1_DEC_SAMP_PTR), LO16((uint32)(&Buffer)));CyDmaTdSetAddress(DMA1_TD, LO16((uint32)ADC_1_DEC_SAMP_PTR), LO16((uint32)(&Buffer)));CyDmaTdSetAddress(DMA1_TD, LO16((uint32)ADC_1_DEC_SAMP_PTR), LO16((uint32)(&Buffer)));CyDmaChSetInitialTd(DMA1_Chan, DMA1_TD);CyDmaChEnable(DMA1_Chan, 1);// Enable DMA channel}Code:#define NO_SAMPLES 200u // number of samples to acquire (1/4 buffer)int16 Buffer[4 * NO_SAMPLES];uint8 Window;double calc_final(){uint64 AccMS64 = 0;uint16 offset1=0, offset2=0;uint16 i;int16 v;switch (BufferSection){case 0: // -->0..1..2--3offset1 = 3 * NO_SAMPLES;offset2 = 0;BufferSection=1; // next sectionbreak;case 1: // --0-->1..2..3offset1 = 0;offset2 = NO_SAMPLES;BufferSection=2; // next sectionbreak;case 2: // ..0--1-->2..3offset1 = NO_SAMPLES;offset2 = 2 * NO_SAMPLES;BufferSection=3; // next sectionbreak;case 3: // ..0..1--2-->3offset1 = 2 * NO_SAMPLES;offset2 = 3 * NO_SAMPLES;BufferSection=0; // next sectionbreak;default: // should not come hereBufferSection=0;}for( i=0; i<NO_SAMPLES; i++) {v = Buffer[i+offset1];AccMS64 += (uint64)(v*v) * Window[(NO_SAMPLES-1)-i];}for( i=0; i<NO_SAMPLES; i++) {v = Buffer[i+offset2];AccMS64 += (uint64)(v*v) * Window;}

author: jnz
author_rank: Frequent Contributor
timestamp: October 14, 2020, 04:00:47 am
content: I don’t know how to fix your problem. Mainly because I don’t understand what the goal is. Do you actually need to store 4 “sets” of ADC readings? Or could you just be smarter about how you are dealing with them?If who ever wrote this were my employee...Code:int16 Buffer[4 * NO_SAMPLES];Would get you in trouble. Shouldn’t be a signed looking at the rest of your code. I wouldn’t prefer you start variables with a cap. NUM_SAMPLES is more appealing to a read despite yea NO works for number.Other places, there is a concept of standards like uint8_t but use “double” in the same file. Also, IDK about your compiler or stdlibs, but uint8 I would insist is not as standard or accepted as uint8_t like size_t.... wait... holy crap I just tried to understand what the intention is with the buffer. I don’t know who wrote that, but I doubt it came out of cypress!I’m sorry. Lol I read it a few times and honestly have no idea what your INTENT is with that calc final is.... I’m interested to see what smarter people write, but I’d recommend you start a LOT smaller and work up. You’ve taken too big a bite.EDIT: Because I realize OP wrote none of it.

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 14, 2020, 04:40:50 am
content: Sorry. I will have another look and see if I can come up with a better question. Meanwhile, the code above was taken from the project attached below where RMS is calculated using a window function. I have calculated RMS using other methods and I just wanted to learn the concept of windows going through this example. Maybe if you have a look at the whole project you can understand what I meant.

author: newbrain
author_rank: Super Contributor
timestamp: October 14, 2020, 07:39:32 am
content: Not much time to answer more fully, maybe later...As a first note though:I understand the idea behind the code: it collects samples then applies a symmetric window to the last acquired buffer and the previous one..The code exploits the PSoC DMA features, which are quite different from the STM32.Briefly: PSoC DMA allows you to prepare a number of DMA transfer descriptors and chain them in a linked list, e.g.:CyDmaTdSetConfiguration(DMA1_TD, TRANSFER_COUNT,DMA1_TD, DMA1__TD_TERMOUT_EN | TD_INC_DST_ADR);to chain the 3rd and 4th transfers.When the DMA transfer described in DMA1_TD is complete, the next trigger event will automatically start using DAM1_TD, and so on, going round the circular list.This is very flexible and can take care of complicated schemes.Many DMA engines work in a similar way (e.g. TI MSP432, NXP iMX.RT and, IIRC, LPC).This is not supported by STM32 DMA, though, so you'll have to emulate it manually (maybe modifying the DMA channel setting in the complete interrupt?).

author: Siwastaja
author_rank: Super Contributor
timestamp: October 16, 2020, 03:50:39 pm
content: The new(ish) STM32H7 series has this new MDMA block that's supposed to allow complicated linking of DMA transfers. I have read the relevant reference manual sections a few times to get a grasp of it so that I could learn to use it whenever a need arises, but so far, no need.Note that there is no single "STM32 DMA" but at least two different ones, likely more. The H7 funnily combines two different DMA engines on same chip Just For Fun. What's called "BDMA" there is similar (or exactly the same?) to the DMA available in the smallest STM32F0 parts. (H7 also FINALLY solves the problem of not having DMA channel mappings available by allowing any combination of peripherals using DMA.)Most STM32 DMA engines, maybe ignoring the simplest in F0 parts, do provide you with "double buffering". This simply means you get two memory address registers instead of one (M0AR and M1AR if I recall correctly), and once you enable double buffer mode, the DMA alternatively writes to either one of these memory locations, and you can see from a status register which one is in use.OTOH, starting a DMA transfer on STM32 is just a few clock cycles; you clear the flags and enable the DMA, plus you likely want to set a config register or two. Doing this in an interrupt with 12 cycles of added entry latency usually isn't a performance bottleneck, even if you don't end up using the double buffer mode but manage changing the memory address and re-enabling DMA yourself.Oftentimes, you can also just configure the DMA to run continuously over any arbitrary-length table and then manage where you read this yourself. For example:Code:typedef struct __attribute__((packed)){int16_t some_thing;uint16_t another_thing_from_another_channel;} adc_datum_t;adc_datum_t ringbuf;// DMA config:// 16-bit peripheral// 16-bit memory// NDTR = 16*2 (this is the number of ADC transactions that fill the whole ringbuf; 16 elements in array, 2 ADC readings e.a.// OR, more optimally to save AHB/APB bus cycles:// 16-bit peripheral// 32-bit memory// NDTR = 16*2/2Now at any point, look at the current value of NDTR to know what is the latest index written at, and then loop back to any past window length you want. Or, get a half transfer or full transfer interrupt.If you do it like this, no "buffer swapping" needed; just write to a ringbuf and "swap" the place you read it at. You can think it as ten or hundred "buffers" if you like, that are adjacent in memory and finally, due to using DMA in continuous mode, wrap around to the first "buffer". You can even declare it as two-dimensional array like buffers and just give the address of buffers to the DMA in continuous mode, with NDTR set to fill 4*256 elements.