DMA, memory to peripheral - Page 1
Forum section: electronics / general

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 04:43:46 am
content: I'm starting to use DMA, and I'm porting an application from Kinetis MK66 to NXP RT1020, and from STM32 to RT1020.As I still did not know how the DMA works, to copy memory data to GPIO ports, I routed a couple of boards without taking into account the order of the ports.To read data by DMA using SPI, I see no problems. The problem, I think it comes when I'm going to use DMA memory to peripherals (seven GPIO ports).From what I read, it seemed to me that I understand that the addresses of the ports to which I am going to copy from memory using the DMA, should have consecutive addresses, could not use ports that do not have consecutive addresses.Is this so, or can it be fixed by software, if I have not routed the ports with consecutives addresses ?On one of the boards, I can fix it easily, soldering some cables, to the ports that I do not use, and for the other ports I would make a small board that reorders them, to connect them all to an IDC connector of 2x16, to which they connect to LED panels HUB75 of my application.With the other board, I have it more complicated, because it is the evaluation board of the RT1020, and on this board there are very few accessible ports. Only the ports just for my application, but of all these, once discarded those using the SPI that I need also, I would not have any group of 7 consecutive ports. I would only have 5 consecutive ports, and the other 2 I would have to wire them, desoldering a pair of very small resistors (0402 or 0201), to weld there 2 wires.Is any software solution possible, to be able to use 7 non-contiguous ports in their addresses, to use DMA memory to peripheral?Greetings.

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 04:52:36 am
content: Usually GPIO ports are not really compatible with DMA unless hardware has some additional magic to handle it.DMA also needs a beat trigger, which in case of an SPI peripheral is a shift register empty flag. You can probably use a timer for that, but again it depends on the DMA controller and if it can do this. Otherwise DMA will just instantly go through all the values.What sort of data you are planning to write?

author: andyturk
author_rank: Frequent Contributor
timestamp: August 18, 2018, 06:23:45 am
content: I implemented a parallel SPI tranceiver once on STM32 using a timer and a 16-bit GPIO port. There were two tricky parts: 1) getting the timer to stop after a certain number of frames, and 2) "transposing" the bits back into words (and vice versa). But it worked out well and was a nice way to read a bunch of outboard ADCs that shared the same clock.I don't understand the OP's desire for "consecutive addresses" though. Most DMA controllers allow you to increment one of the two pointers by zero, so you keep transferring to or from the same address (which is typically a peripheral register). And even if the port peripherals are somehow "consecutive", the data registers almost certainly won't be.If you had to DMA to/from locations that were arbitrarily scattered in memory, you could probably do it with linked DMA transfers (if the chip supports that).

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 06:40:58 am
content: Quote from: ataradov on August 18, 2018, 04:52:36 amYes, there is no problem in that issue, I have always an external trigger signal to load buffer of DMA, for Memory to Peripheral and also an external trigger signal to Read by SPI using DMA.My problem is with the DMA, from Memory to Peripherals (seven GPIO ports), if all those ports must be in consecutive directions for it to work. I already routed the board, and those seven ports were not in consecutive directions (I did not know at that time). I do not know if that can be fixed by software, or I must do it by hardware, rewiring the board for the tests, and routing again for the final product.

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 06:44:05 am
content: I'm assuming you are talking about consecutive pins in a port? Even then, I don't see how DMA will do anything here. Typically GPIO pins are controlled by individual bits in a register, so DMA will not help.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 06:56:05 am
content: Quote from: andyturk on August 18, 2018, 06:23:45 amMaybe I did not explain myself well, or I still do not understand how the DMA works.It is about reading seven GPIO ports and dumping the read into an array, so that with each reading, it is saved in seven elements of the array.From what I have read, when configuring the DMA, we define the address of origin, destination address and width of the data to be transferred, also if we want to increase the address of origin or destination with each reading.The entry addresses (GPIO ports) are always the same, only the destination varies, which will increase by seven with each reading.If in the DMA, I configure the source address (I suppose the address of the first GPIO port), and the width of the data to be read (seven ports), I suppose that the addresses of the seven ports to be read must be consecutive, not I can take any group of seven ports, but these have to have their addresses one after the previous one.For example, in the RT1020, those seven GPIO ports to read by DMA, I understand that these could be:GPIO_AD_B1_01GPIO_AD_B1_02GPIO_AD_B1_03GPIO_AD_B1_04GPIO_AD_B1_05GPIO_AD_B1_06GPIO_AD_B1_07but I could not use this port configuration, because their addresses are not contiguous:GPIO_SD_B1_03GPIO_AD_B0_01GPIO_SD_B0_04GPIO_AD_B1_01GPIO_AD_B0_07GPIO_SD_B1_02GPIO_SD_B0_03

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 07:02:36 am
content: What makes you think that DMA can do this with GPIO ports? There are no 7 ports, there are 7 bits in the same register.You can read from GPIO pad status register (PSR) using DMA, but I don't see how this is helpful.What is your actual goal here?

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 07:03:18 am
content: Quote from: ataradov on August 18, 2018, 06:44:05 amThere is a trigger signal generated with a timer for the DMA to send the data, from memory to ports, with each pulse of that signal.And when it comes to reading data, then there is an external trigger signal for the DMA to start capturing.My problem is not that, but the configuration of the ports to send signals using DMA, from memory to peripherals. If those ports must have configuous addresses for it to work.

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 07:07:49 am
content: Not only they need to be sequential, they need to be 0-7 or 8-15 or 16-23, or 24-31. And you also need to check that GPIO handles byte writes correctly.And you won't get 7 elements in the memory, you will get one byte per sample in which bits represent states of the corresponding pins.GPIO is not designed to work with DMA.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 07:11:08 am
content: Quote from: ataradov on August 18, 2018, 07:02:36 amRead from an array (memory) and copy the data to seven GPIO ports (peripheral), using DMA. The trigger for DMA is generated by a timer.Each pulse of the timer, the DMA must copy seven elements of the array to the seven GPIO ports, then increase the address of the array to copy the next seven elements of the array with the next pulse of the timer.It is Memory to Peripheral transfer, using DMA, with increasing address of origin. It is working with STM32 and Kinetis MK66, now I need to port it to RT1020.

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 07:15:45 am
content: Quote from: luiHS on August 18, 2018, 07:11:08 amThere are no 7 GPIO ports. There are 7 bits in the port register. They are not the same thing.Quote from: luiHS on August 18, 2018, 07:11:08 amCan you show relevant code? I don't see how that is possible. What is the destination address set in the DMA?It is probably possible using bit-banding, but it does not look like RT1020 has it.

author: legacy
author_rank: Super Contributor
timestamp: August 18, 2018, 09:26:49 am
content: what is the purpose of that?

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 09:55:18 am
content: Quote from: ataradov on August 18, 2018, 07:15:45 amOk, I think I'm starting to understand. Then, when I configure DMA, I put in the source address the address of the first GPIO pin of the port to read and in the transfer source address width the number of bits to read, then when the DMA reads, each GPIO pin is a one bit of a byte.But I must always configure the Source Address with the address of the first GPIO pin and how many bits to read, and all GPIOs must be contiguous in the same port starting from the indicated address. . This is so??. I have check source code of one example and open the function to prepare the DMA, here I can see all the parameters to configure it.In the SDK there are no examples of Memory to Peripheral or Peripheral to Memory. But I suppose that really a GPIO has an address assigned to it, and therefore it must be the same if in an example of Memory to Memory, I assigns to the address of origin or destination, the address of a GPIO port. I'm right ?.* This function prepares the transfer configuration structure according to the user input.** @param config The user configuration structure of type edma_transfer_t.* @param srcAddr eDMA transfer source address.* @param srcWidth eDMA transfer source address width (bytes).* @param destAddr eDMA transfer destination address.* @param destWidth eDMA transfer destination address width (bytes).* @param bytesEachRequest eDMA transfer bytes per channel request.* @param transferBytes eDMA transfer bytes to be transferred.* @param type eDMA transfer type.* @note The data address and the width data must be consistent. For example, if the SRC* is 4 bytes, the source address must be 4 bytes aligned, or it results in* source address error (SAE).* /void EDMA_PrepareTransfer (edma_transfer_config_t * config,void * srcAddr,uint32_t srcWidth,void * destAddr,uint32_t destWidth,uint32_t bytesEachRequest,uint32_t transferBytes,edma_transfer_type_t type);

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 09:59:23 am
content: Quote from: legacy on August 18, 2018, 09:26:49 amPlay animations in HUB75 LED panels.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 10:01:51 am
content: Quote from: luiHS on August 18, 2018, 07:11:08 amThis is with STM32, Memory to Peripheral. The memory is an array, Peripheral is 6 x GPIO in port D. The size of the data is one byte, although really it only copy to 6 x GPIO (I mean 6 bits), I still do not understand this detail. This prepares the DMA to transfer 256 bits of an Array to 6 x GPIO in port D, using a timer as trigger for DMA transfer.GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;GPIO_Init(GPIOD, &GPIO_InitStruct);// DMA init (DMA2, Channel6, Stream5)DMA_Cmd(DMA2_Stream5, DISABLE);DMA_DeInit(DMA2_Stream5);DMA_InitStructure.DMA_Channel = DMA_Channel_6;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOD->ODR;DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)0;DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;DMA_InitStructure.DMA_BufferSize = 256;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 8bitDMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA2_Stream5, &DMA_InitStructure);

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2018, 10:52:59 am
content: Quote from: luiHS on August 18, 2018, 10:01:51 amYour example code initializes 7 pins of GPIOD. Anyway, on each DMA transfer cycle, one byte will be written into GPIOD->ODR. The effect is the same as if you had written the value to the register in code, all pins that have been configured as outputs will change state according to the written value. But a full byte will be written each transfer cycle.Mike Harrison did a video on using GPIO DMA to bitbang SPI that may or may not be helpful:QuoteBytes. The array does contain 256 bits for each pin, but the transfer is done in bytes.QuoteThis field should be initialized with the address of the source memory array.Also, as a sidenote, not all STM32s have identical DMA controllers. It might be useful to say which specific chip the code you're trying to port was written for.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 18, 2018, 11:44:31 am
content: Thanks for your help andersm.I have been looking in some sources of the RT1020 SDK, to see if I found out the difference betweenMemory to MemoryandMemory to PeriphalorPeriphal to Memory, since I understand that a peripheral is also assigned a memory address.This is what I have found, it seems that the only thing different is an offset (widht of source and destiny) that is applied to source and destiny when there is a Memory to Memory transfer. I still do not understand what this offset consists of.switch (type){case kEDMA_MemoryToMemory:config->destOffset = destWidth;config->srcOffset = srcWidth;break;case kEDMA_MemoryToPeripheral:config->destOffset = 0U;config->srcOffset = srcWidth;break;case kEDMA_PeripheralToMemory:config->destOffset = destWidth;config->srcOffset = 0U;break;default:break;}

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2018, 04:24:26 pm
content: Quote from: luiHS on August 18, 2018, 09:55:18 amCorrect, except the you specify the addres of a register containing all pins, not a specific pin. And size granularity is 8-bit.Quote from: luiHS on August 18, 2018, 09:55:18 amYes, and not only contiguous, but in the ranges I specified.Quote from: luiHS on August 18, 2018, 09:55:18 amCorrect. In this case you need to specify the address of the GPIO data register (DR) of the corresponding port: "DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOD->DR;" (substitute GPIOD with whatever port you want).

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 12:16:42 am
content: Ok, thanks ataradov.Little by little, I understand how the DMA works, and how it is configured, at first I found something quite complex and little documented (this last I still think).Then to transfer from or to GPIO ports, I must indicate the port address, so the address is per port, not per pin. For example, a port D has an address, and by indicating the width of the transfer to 1byte or 2 bytes, the status of GPIOs 0 to 7, or 0 to 15, is transferred. Is this correct?Then it could not be read or transferred by DMA, for example, port D, GPIO 3 to 10, it always has to be from 0 and in bytes?.About the Memory to Peripheral or Peripheral to Memory, with respect to the Memory to Memory transfer; Besides that when a Peripheral is involved, only the width of the data is indicated in the Memory (origin or destination), and not in the Peripheral, is there any other difference?switch (type){case kEDMA_MemoryToMemory:config->destOffset = destWidth;config->srcOffset = srcWidth;break;case kEDMA_MemoryToPeripheral:config->destOffset = 0U;config->srcOffset = srcWidth;break;case kEDMA_PeripheralToMemory:config->destOffset = destWidth;config->srcOffset = 0U;break;default:break;}In this example of a source that I found, I do not understand why the address of the Memory (which is an array) in a Memory to Peripheral transfer is set to zero. Should not this address be the address of the array to be transmitted?// DMA init (DMA2, Channel6, Stream5)DMA_Cmd(DMA2_Stream5, DISABLE);DMA_DeInit(DMA2_Stream5);DMA_InitStructure.DMA_Channel = DMA_Channel_6;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOD->ODR;DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)0;DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;DMA_InitStructure.DMA_BufferSize = 256;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 8bitDMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA2_Stream5, &DMA_InitStructure);

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 12:18:22 am
content: Quote from: andersm on August 18, 2018, 10:52:59 amAnd why in this example its set to zero ?. I do not understand it. Should not this address be the address of the array to be transmitted?.This is the source code for STM32F405, STM32F407, and I want to port it to NXP RT1020. Although really, it is not about porting the original source of STM32 to RT1020, but understanding how it works to be able to write my own code for RT1020.

author: ataradov
author_rank: Super Contributor
timestamp: August 19, 2018, 12:21:37 am
content: Quote from: luiHS on August 19, 2018, 12:16:42 amCorrect.Quote from: luiHS on August 19, 2018, 12:16:42 amYep. And for writes it is not inherently guaranteed that you can do byte-sized writes. That totally depends on specific hardware implementation. It is very much possible that entire words has to be written or results may be undefined. It does not look like this is explicitly documented anywhere though.Quote from: luiHS on August 19, 2018, 12:16:42 amI don't know the specifics of that library and the device to answer this.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 12:37:17 am
content: Quote from: ataradov on August 19, 2018, 12:21:37 amOK, I found this in the source code, it seems that the source address of the array is loaded later, every time the DMA transfer is triggered. So it seems that it is an address that is different, in this case, each time a transfer is made.DMA2_Stream5->M0AR= (uint32_t)(rgbbuf + ((rgbrow * 256) + (rgbsubframe * targetSubframeSize)));DMA2->LIFCR = 0b111101;DMA2_Stream5->CR |= DMA_SxCR_EN;I looked in the code "M0AR" and found this:typedef struct{__IO uint32_t CR; /*!< DMA stream x configuration register */__IO uint32_t NDTR; /*!< DMA stream x number of data register */__IO uint32_t PAR; /*!< DMA stream x peripheral address register */__IO uint32_tM0AR; /*!< DMA stream x memory 0 address register */__IO uint32_t M1AR; /*!< DMA stream x memory 1 address register */__IO uint32_t FCR; /*!< DMA stream x FIFO control register */} DMA_Stream_TypeDef;typedef struct{__IO uint32_t LISR; /*!< DMA low interrupt status register, Address offset: 0x00 */__IO uint32_t HISR; /*!< DMA high interrupt status register, Address offset: 0x04 */__IO uint32_t LIFCR; /*!< DMA low interrupt flag clear register, Address offset: 0x08 */__IO uint32_t HIFCR; /*!< DMA high interrupt flag clear register, Address offset: 0x0C */} DMA_TypeDef;

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 05:20:06 am
content: I checked the Memory Map in the Reference Manual and I'm confused.There are four ports, GPIO1, GPIO2, GPIO3 and GPIO5 (there is no GPIO4). GPIO1 to GPIO3, each one with 32 IO pins, and GPIO5 only with 3 IO pins.If I want to send data to 7 x IO ports per DMA, does this mean that I can only use the first seven IO pins of each GPIO1, GPIO2 or GPIO3 port?For each GPIO, the pins are GPIOn_IO00 to GPIOn_IO31, where n is 1 to 3.Therefore, it is not possible to send data, for example port GPIO1_I008 to GPIO1_I015.Can I only send data to these ?, If I have understand correctly.GPIO1_I000 to GPIO1_I007 ---> 1 byteGPIO1_I000 to GPIO1_I015 ---> 2 bytesGPIO1_I000 to GPIO1_I023 ---> 3 bytesGPIO1_I000 to GPIO1_I031 ---> 4 bytesThe problem is that some of the ports are used in my circuit, so it is not possible to send data to more than 8 IO ports if I connect my 7 pins to the 2nd, 3rd or 4th byte of the corresponding GPIO.

author: ataradov
author_rank: Super Contributor
timestamp: August 19, 2018, 05:25:29 am
content: Quote from: luiHS on August 19, 2018, 05:20:06 amNo, you can write a byte at address GPIO1 + 1.In the code it will be something like this: "(uint8_t *)&GPIOD->ODR + 1"; Or just manually specify the address: 0x401b8001 for GPIO0 byte 1.+1 can be +0, +1, +2 and +3 to get access to bits 0-7, 8-15, 16-23, and 24-31. Again, you will have to verify that GPIO peripheral handles byte writes at any offset within a word correctly.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 05:39:29 am
content: OK thanks ataradov, I will check.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 19, 2018, 07:13:15 am
content: I reviewed the Data Sheet and the Reference Manual to identify the available GPIO for RT1020 LQFP100, which is the current available, so with fewer GPIO ports than the LQFP144.I see some strange thoughts, some IO ports are not available, but the problem is that they are not contiguous.I attach images of the assignment of GPIO1, GPIO2 and GPIO3. Those that I have marked in red, are not available in the LQFP100. I see that for each GPIO there are only 16 available ports, that is 2 bytes, but they are not contiguous, there are gaps between them.For example for GPIO1, the first available port is GPIO1_6 (instead of GPIO1_0), then it continues until 15, and there is a gap without ports until 26.The question is that if I connect, for example, the first 8 ports available in GPIO1 by DMA, these are considered the 1st byte, and the next 8 ports would be the second byte?Byte 1GPIO1_6GPIO1_7GPIO1_8GPIO1_9GPIO1_10GPIO1_11GPIO1_12GPIO1_13Byte 2GPIO1_14GPIO1_15GPIO1_26GPIO1_27GPIO1_28GPIO1_29GPIO1_30GPIO1_31

author: ataradov
author_rank: Super Contributor
timestamp: August 19, 2018, 05:16:08 pm
content: Quote from: luiHS on August 19, 2018, 07:13:15 amDMA controller won't be able to do anything with this.You need to evaluate if Smart External Memory Controller (SEMC) will work for you. I still don't see the full picture of what you are doing, but it sounds like "8080 display frame buffer" mode is what you need.

author: Siwastaja
author_rank: Super Contributor
timestamp: August 20, 2018, 02:47:37 pm
content: You need to get your basic terminology right before you have any chance understanding the documentation.APORTis the collection of the bits.You don't have "first 8 ports available in GPIO1".GPIO1 is the port.

author: luiHS
author_rank: Frequent Contributor
timestamp: August 20, 2018, 07:10:23 pm
content: Quote from: Siwastaja on August 20, 2018, 02:47:37 pmI understand the concept, it is only a question of nomenclature when expressing it. A port is, for example, GPIO1, and each pin is a bit of the port, so if there are 16 input/output pins in GPIO1, each one is a bit of the port and I can read or write by DMA one or several bytes, not necessarily from the first byte, but always full bytes.My question was about the grouping and order of the bits, in this particular case, since on the Reference Manual and the Datasheet, in the ports there are gaps between bits (for LQFP100), they are not contiguous (in appearance). Probably, internally, all these bits are contiguous , and it is not a problem, in fact when counting them I see that there are 16 bits per port, that is 2 bytes, instead of the 4 bytes that should be for the LQFP144. I will do tests, and I will check with the oscilloscope to corroborate what I suspect (internally all those bits are contiguous, although in the manual there seem to be gaps between them)Knowing that, and that I can read or write in one of those bytes, although it is not the first one, I'm already clear about it. In my hardware, for the tests, I will have to reorder the routing of the pins of each port, to always catch bits that are part of the same port byte. At first I mixed bits of several ports and without order, for lack of information, I thought that each pin could be treated individually.Now I am with the tests, but I am already understanding the concepts necessary to handle the DMA, especially with parallel read / write, to or from the GPIO ports. I still have to check how to configure automatically to increase the address of origin or detiny and how the circular buffer works, I have the concepts, but I lack the practice to be able to configure everything.