UART weird stuff, and UART DMA - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 06:26:01 am
content: Some may find this funnyhttps://www.eevblog.com/forum/microcontrollers/does-this-code-need-interrupts-disabled/msg4717493/#msg4717493The 32F417 UART is the first one I have come across where to transmit under interrupt you just enable the TX Empty (TXE) interrupt and immediately the interrupt occurs and the UART TX ISR loads the UART.On all the others you have to put in a byte from the foreground task (from main() if you like), enable TXE interrupt, and the UART then interrupts when itbecomesempty.Has anyone used DMA to feed a UART? All DMA requests I have used have been "level-sensitive" so again no need to load the initial byte. But then if loading from a circular buffer, you would need to use the DMA circular pointer mode. Has anyone implemented that (for transmit or receive)? I use DMA with SPI but that is always (in my code) with a linear buffer.

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 08:18:06 am
content: Quote from: peter-h on February 23, 2023, 06:26:01 amI use circular DMA for UART reception.What's your objective here?

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 09:17:51 am
content: I wondered how one would make this work.With an ISR loading data into a buffer and moving inptr+count (or just inptr if you use the other method (inptr-outptr and MOD) to get the queue size) you disable interrupts to read out data.With DMA you would have to disable the DMA, but more to the point when an ISR places a byte into the queue it can check if the queue is full, and throw it away. DMA won't do that for you, unless you are doing some funny stuff with updating the transfer count.

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 09:36:11 am
content: The way I use DMA reception is:Setup cirular DMA to use appropriately sized RAM bufferDMA is always onDMA will overflow the buffer - you gotta keep up or you lose a whole buffers worth of data. But with appropriate buffer size and relatively slow (compared to CPU clock) UART speeds its never an issue for me.Background code keeps a read pointer and derives a write pointer from the DMA transfer count, thus knows how much stuff is waiting in the buffer.ISR reception for UART is also fine, I like the DMA technique since I tend to use low-end F0/L0 which often have no UART FIFOs. With DMA reception I get big receive buffer for no cpu time, every little helps when running on low clock rates.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 09:41:46 am
content: QuoteOK that's an interesting approach. Some applications can tolerate that, some can't.What I was thinking of was rigging the transfer count (from the background task) so the buffer can't overflow. But one can't do that on the fly without stopping the DMA.

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 09:50:14 am
content: Quote from: peter-h on February 23, 2023, 09:41:46 amYeh, I useCOBSfor packetisation with crc's, so buffer overflow is seen as a lost packet. Meh.QuoteHow fast is your uart, how big can you make the rx buffer and is your cpu so busy it can't service rx buffer in time....will overflow ever happen? Is it unsafe if it does happen?All application dependant really.

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 09:53:06 am
content: The crux of it:Code:/** The STM32 makes receiving characters into a large circular buffer simple and* it requires no CPU time. The UART receiver DMA must be setup as CIRCULAR.*/#define CIRC_BUF_SZ 512 /* must be power of two */static_assert((CIRC_BUF_SZ > 4 && ((CIRC_BUF_SZ & (CIRC_BUF_SZ - 1)) == 0)), "CIRC_BUF_SZ must be a power of two");static volatile uint8_t rx_dma_circ_buf;/* ===================================================================================================================*/#if CIRC_BUF_SZ == 256/* optimise a little by using natural rollover of uint8_t */#define DMA_WRITE_PTR ((uint8_t)(~((uint8_t)DMA1_Channel3->CNDTR) + 1))static uint8_t rd_ptr;#else#define DMA_WRITE_PTR ((uint16_t)((CIRC_BUF_SZ - DMA1_Channel6->CNDTR) & (CIRC_BUF_SZ - 1)))static uint16_t rd_ptr;#endif/* =====================================================================================================================* uart_rxbuf_is_empty***/bool uart_rxbuf_is_empty(void) { return (rd_ptr == DMA_WRITE_PTR); }/* =====================================================================================================================* uart_rxbuf_in_waiting***/#if CIRC_BUF_SZ == 256uint16_t uart_rxbuf_in_waiting(void) { return (uint8_t)(DMA_WRITE_PTR - rd_ptr); }#elseuint16_t uart_rxbuf_in_waiting(void) { return (uint16_t)((DMA_WRITE_PTR - rd_ptr) & (CIRC_BUF_SZ - 1)); }#endif/* =====================================================================================================================* uart_rxbuf_flush***/void uart_rxbuf_flush(void) { rd_ptr = DMA_WRITE_PTR; }/* =====================================================================================================================* uart_getc_blocking* If you wish to avoid blocking, first check a char is available with uart_rxbuf_is_empty()**/#if CIRC_BUF_SZ == 256uint8_t uart_getc_blocking(void) {while (rd_ptr == DMA_WRITE_PTR)(void)0;return rx_dma_circ_buf[rd_ptr++]; /* optimise a little by using natural rollover of uint8_t */}#elseuint8_t uart_getc_blocking(void) {uint8_t c;while (rd_ptr == DMA_WRITE_PTR)(void)0;c = rx_dma_circ_buf[rd_ptr++];rd_ptr &= (CIRC_BUF_SZ - 1);return c;}#endif/* =====================================================================================================================* uart_dma_usart2mem***/static void uart_dma_usart2mem(void) {/* USART2->CR1* The DMA requests are also reset when UE = 0 so the* DMA channel must be disabled before resetting the UE bit.*//* SET_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN); */ /* enable dma subsystem clock */CLEAR_BIT(DMA1_Channel6->CCR, DMA_CCR_EN);MODIFY_REG(DMA1_CSELR->CSELR, DMA_CSELR_C6S, 0b0100 << DMA_CSELR_C6S_Pos); /* select UART2_RX for dma chan6 */DMA1_Channel6->CPAR = (uint32_t)(&(USART2->RDR));/* peripheral address */DMA1_Channel6->CMAR = (uint32_t)rx_dma_circ_buf;/* memory address */DMA1_Channel6->CNDTR = (uint32_t)sizeof(rx_dma_circ_buf);DMA1_Channel6->CCR = /*DMA_CCR_DIR*/ /*DIR=0 periph2mem*/ DMA_CCR_CIRC | DMA_CCR_MINC /*mem inc*/;SET_BIT(DMA1_Channel6->CCR, DMA_CCR_EN);SET_BIT(USART2->CR3, USART_CR3_DMAR); /* start the transfer */rd_ptr = 0;}

author: wek
author_rank: Frequent Contributor
timestamp: February 23, 2023, 10:27:48 am
content: This is STM32-family specific.Using DMA for Rx in this fashion, i.e. relying on NDTR, may be safe with thesingle-port DMA(i.e. where DMA elements are called "channels" and registers have a C prefix e.g. CNDTR), butnot with the dual-port DMA, where NDTR decrements at the peripheral-side transaction, the memory-side being delayed in unspecifiable way (depending on external circumstances such as bus contention) even in case when FIFO is not used.You'll find it used e.g. in Tilen Majerle's IDLE-triggered DMA-based UART Rx implementation (which appears to be part of Cube now, I don't Cube), but they are simply not aware of the problem. Cube probably covers up the problem quite effectively by its clumsiness (i.e. the code bloat inserts enough time between NDTR read and data read to allow the memory-side DMA transfer in the vast majority of cases).As you've stated in opening post, you simply can't use circular DMA with UART Tx normally (you could, if you e.g. trigger it from timer rather than from UART itself).JW

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 10:34:45 am
content: Good point. From your site, regarding dual-port:QuotePresumably this delay would be very short, so you could read twice NDTR and check they match, by which time all should be well?

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 11:34:04 am
content: One is looking only for NDTR=0 so one could read it a few times.To start with, you know how much space there is in the buffer. Say you have a 1k buffer, and you use the 2-pointer mode, so the max it can hold is 1023 bytes.Set the transaction count to 1023.Enable the DMA-UART operation.Then poll the DMA memory address and NDTR and you should see movement.I think the issuewekis referring to is that the NDTR update isdelayedand probably the read of the DMA address registers will have the same problem, but it doesn't matter if you read twice and compare.Except with very fast data when reading 2x and comparing is a potential hanging loop, but not if NDTR=0 terminates the loop.The resulting code probably needs to be fairly cunning because if you just wait for NDTR=0 then you will see no data until all 1023 have arrived, which could be never. And if you poll frequently, say every 100us, then you are wasting a lot of time in the ISR.Probably, the scenario where you are using DMA with a UART is where you are doing half duplex comms at megabit speeds (Profibus, or even Modbus) and the buffer is linear.I am reflecting on the fact that had I been using DMA for the serial ports, I would not have wasted weeks trying to fix the mysterious byte loss caused by the code loading the 1st byte into the UART directly.

author: voltsandjolts
author_rank: Unknown
timestamp: February 23, 2023, 12:10:00 pm
content: Quote from: peter-h on February 23, 2023, 11:34:04 amErm, not sure what mode you are refering to, but in the code I posted above CNDTR/NDTR can be any value, it's not waiting for it to reach zero.

author: wek
author_rank: Frequent Contributor
timestamp: February 23, 2023, 01:45:37 pm
content: Quote from: voltsandjolts on February 23, 2023, 10:34:45 amUnfortunately, no.The sequence of actions in time is:peripheral asserts request line towards DMA Stream (Stream in the dual-port DMA is what Channel in single-port DMA is, ST is bad at terminology)DMA stream is set to P->M so it requests the DMA Peripheral-port's arbitrator to access the Peripheral portarbitrator looks at other Streams and if there's no ongoing transfer from other Stream and no request from Stream of higher priority (see discussion of *T1 below, the process should be similar to this arbitrator) then grants the Peripheral port to this StreamStream accesses the bus through the Peripheral port using address in Stream's PAR, requesting read of width given by PSIZEnormally, this request would go through AHB slave port to the AHB bus matrix, but in the Dual-port DMA, Peripheral port in DMA1 goes directly to the 2xAHB-to-APB bridge, and in DMA2 it selects either this bridge or the AHB bus matrix, based if the address is within the APB2 range or notthe bridge/busmatrix performs all arbitration necessary and after granting bus all the way to the peripheral (UART_DR in this case) the datum in question is read (through any buffers and resynchronizers which may be in way) onto the data portion of busthe DMA Stream stores the data into a buffer (in direct mode - it may be portion of FIFO, just the FIFO logic is off) or into FIFONDTR is decremented, PAR is incremented if set so (incremented by 4 if set so), DMA requests are acknowledged to the peripheral which sent the requestif FIFO is off, DMA stream immediately asks the Memory port Arbiter for access to the Memory port. If FIFO is used, only after the FIFO threshold is reached (and note that FIFO may also pack, if peripheral/memory width are set so), requests to Memory port are initiated.(*T0)Memory port Arbiter arbitrates, and then after all conditions fulfilled i.e. no other Streams have ongoing transfer or higher-priority requests, grants the Memory port (*T1)AHB matrix arbitrator arbitrates access to the target memory (i.e. resolves conflicts with other busmatrix masters requesting access to the same slave bus/memory at the same time) (*T2)access to memory is granted and data are writtenMAR is incremented if set so; Stream buffer is cleared/FIFO tail is incremented; if FIFO is empty and NDTR==0, TXC is set; if FIFO is empty, (NDTR==0 AND noncircular) or CR.ENwr == 0, CR.ENrd = 0 and Stream is disabled.The problem is in (*T0), (*T1) and (*T2), which are times between NDTR increment and memory-side write of data.Note, that this DMA in fact has 3 AHB ports - 2x master ports (hence dual-port) described above, and a slave port, through which its registers are read. Transfers through these 3 ports are not interlocked mutually, so accesses to registers (including NDTR) through the slave port may happen at any point, regardless of how the whole DMA machine works.(*T0) is "easy" - don't use FIFO and then it's a small, but most importantly, fixed delay(*T1) entails a fixed delay of a couple of DMA (=AHB) clocks; plus all delays which may come from the longest possible *ongoing* transfer of *any* other active Stream (which depends on how long it takes for that stream to perform its memory-side transfer (i.e. T2 for that stream, plus extra cycles for any burst size it may be set to use, let's call this T2n for nth Stream); plus T2n of all streams which have set higher priority and are or get active during *T1; plus in the worst-case scenario, T2n of all streams which have the same set priority as our Stream and are or get active during *T1, but only once -- this is consequence of the round-robin arbitration (the particular implementation of this arbitration may have flaws resulting in other than described behaviour, this is extremely hard to discover purely by testing without access to the real design)(*T2) may again involve fixed delay in the bus-matrix arbitrator, and then delay because of all other busmatrix masters accessing this particular slave bus (memory) at the same time -- again there's a round-robin arbitration.Problem with (*T1) and (*T2) is, that they are complex with many potentially tangled timing dependencies. Also problem is, that in simple setups such as testing programs, these interdependencies may not fully exhibit themselves. Sure, there are also simple applications where (*T1) and (*T2) can be calculated/predicted.There are also other particular use cases, which may make using circular DMA based on NDTR reliable even under the above restrictions. I was able to use it with an SPI slave with continuous clocks from master, for example. With no FIFO, when NDTR indicated Nth data reception, I knew that position N-1 is already written to memory; with FIFO I could safely read position N-K where K was calculated from particular FIFO+data packing setup. This introduces, lag but in that particular application I could live with it.There is probably only one "absolute" guarantee in this matter, that is that once the Stream is disabled, CR.EN reads 0 and respective LISR/HISR.TCIF flag is set, the memory-side transfers are guaranteed to be complete. The fact that CR.EN is not a plain "storage" register but in fact after writing zero to it one has to wait until it reads back zero, is the proof that it waits until memory-write streams' FIFO/buffer toward memory sides are purged (it would be unreasonable to expect peripheral-write streams to correctly empty FIFO as it may take forever, but it may wait until an ongoing peripheral-write finishes).I am not ST insider and this description is inferred from observed behaviour and "common sense", so there may be errors in the details.JW

author: Siwastaja
author_rank: Super Contributor
timestamp: February 23, 2023, 02:01:28 pm
content: Quote from: voltsandjolts on February 23, 2023, 12:10:00 pmAnd waiting for NDTR to reach zero would be waste of time. One would instead use completion interrupt.As wek says, looking at NDTR to decide if enough data is available for processing (when having more is not a problem) is fine. I have done that, too.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 03:23:16 pm
content: Unfortunatelywek'samazing explanation is well above my pay grade. What does it actually mean? Does it mean that NDTR gets stuck for brief periods, or does it assume random values, perhaps even non-monotonic?voltsandjolts- sorry I was not referring to your code, which I have now tried to understand but you are again way above my level, especially with stuff likewhile (rd_ptr == DMA_WRITE_PTR)(void)0;I can see you are implementing DMA RX and have done a function which returns how many bytes have arrived.I am well familiar with circular DMA mode. A couple of years ago I did a waveform generator using this (timer -> DAC -> DMA) which generates two variable amplitude ~400Hz sinewaves, for LVDT emulation. These are phase-locked to zero crossings of an incoming reference waveform. I actually paid someone to get me started on the code and then carried on. A very interesting project. Nearly zero CPU loading; there is just one ISR involved in the phase-locking which takes 4us, so nothing. It's amazing what can be done with these chips if you are cunning.With no real FIFOs on the peripherals, and DMA controllers everywhere, the clear intention of STwasto use DMA everywhere.Back to NDTR, I am using DMA to service SPI (at up to 21MHz, so essential) and I found I could detect end of transfer equally with NDTR=0 or LISR=1. Perhapswekwill say my code is working by luck, but anyway I am now using just LISR. Note I always pay due respect in my doc towek'svery interesting website!Code:// Wait for DMA to finish. Blocking is necessary to prevent device CS=1 too early.// There could be a timeout here but a failure is impossible short of duff silicon, because// we are a Master and generating the SPI clock.// 20/11/22: A primitive timeout has been implemented anyway.volatile uint32_t limit = 0;while(true){limit--;if (limit==0) break;// Either end-transfer detection method below works but the NDTR may be less reliable// http://efton.sk/STM32/gotcha/g20.html#if 0uint16_t temp1;temp1 = DMA1_Stream0->NDTR;if ( temp1 == 0 ) break;// transfer count = 0#elseuint32_t temp2;temp2 = DMA1->LISR;if ( (temp2 & (1<<27)) !=0 ) break;// TCIF3#endif}Someone will now point out that 1<<27 is crap coding, but at least I comment it, while most coders comment almost nothingThe whole reason I was talking about detecting end of transfer was that you could prevent buffer overflow. This is not so relevant with a circular DMA mode (which will never literally overflow memory) but in linear mode where you expect packets up to say 1k so you set up a 2k buffer and make sure there is a hard stop. I've been doing comms for 40+ years and IME most megabit-speed serial applications are half duplex with basically known packet structures.

author: wek
author_rank: Frequent Contributor
timestamp: February 23, 2023, 04:20:07 pm
content: Quote from: peter-h on February 23, 2023, 03:23:16 pmNo, the contrary. It advances (decrements as it's a downcounter) while data is not in memory yet.JW

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 04:41:35 pm
content: OK, but - trying to get an idea of timing - on a UART -> RAM move, perhaps NDTR=0 when the data has been read from the UART, or perhaps even when the UART has issued the DRQ (RXNE=1 probably), and because the UART clock is so much slower than the CPU clock, you could see NDTR=0 a number of CPU clocks before the data is in RAM. Is that correct?The code where I was waiting for NDTR=0 had a number of instructions after that pointCode:// Wait for DMA to finish. Blocking is necessary to prevent device CS=1 too early.// There could be a timeout here but a failure is impossible short of duff silicon, because// we are a Master and generating the SPI clock.// 20/11/22: A primitive timeout has been implemented anyway.volatile uint32_t limit = 0;while(true){limit--;if (limit==0) break;// Either end-transfer detection method below works but the NDTR may be less reliable// http://efton.sk/STM32/gotcha/g20.html#if 0uint16_t temp1;temp1 = DMA1_Stream0->NDTR;if ( temp1 == 0 ) break;// transfer count = 0#elseuint32_t temp2;temp2 = DMA1->LISR;if ( (temp2 & (1<<27)) !=0 ) break;// TCIF3#endif}SPI2->CR2 = 0;// TXDMAEN, RXDMAEN: 00 - both set in one go#ifdef TIMING_DEBUGTopLED(false);#endif// Clear any rx data and the overrun flag in case not all received data was read; mandatory in rxdump mode// A small delay is needed because the very last transfer may not propagate to DR.// Note that the various int pending flags have NOT been cleared; they get cleared next time this// function is called. This is done differently in SPI3_DMA_TransmitReceive(), just in case.hang_around_us(1);// Probably not necessarySPI2->DR;// since DMA RX has removed it allSPI2->DR;SPI2->SR;return (true);Any HAL code will have way more stuff all over the place.What is not obvious to me is why LISR=1 is a safer way.

author: Kleinstein
author_rank: Super Contributor
timestamp: February 23, 2023, 05:53:03 pm
content: Uart is usually relatively slow. I don't think it would even support a baud rate approaching the processor clock. So there is usually a little time to spare. If the system is not expecially busy the delay it can take from NDTR getting updated to the data actually written to memory is limited and one can just waste a little time by waiting with reading the data from the memory after NDTR is checked.Depending on the program struckture one may use the waiting time for different tasks.I somewhat clumby method to ensure that the transfer is complete should be to start another short DMA (e.g. memory to memory) with lower priority and wait for that DMA to complete.Chances are this method would add so much delay that just waiting the same time could be OK too.One may skip the extra delay or check if more data are in the buffer. So only the access of an nearly empty buffer would be slowed down a little. Chances are one would wait for new data soon anyway.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 05:58:13 pm
content: One can do ~4mbaud on a 32F417 at 168MHz. The baud rate error isn't too bad.One can also do precise enough delays with loops, if one takes care of blocking optimisation etc.Code:// Hang around for delay in microseconds__attribute__((noinline))void hang_around_us(uint32_t delay){extern uint32_t SystemCoreClock;delay *= (SystemCoreClock/4100000);asm volatile ("1: subs %, %, #1 \n"" nop \n"" bne 1b \n":  "+l"(delay));}It would be interesting to be able to put a number of the NDTR latency.Also is LISR really any better?

author: Kleinstein
author_rank: Super Contributor
timestamp: February 23, 2023, 07:06:12 pm
content: The worst case delay for NDTR depends on the use. The main part would be other higher priority DMA. AFAIK the UART RX is usually relatively high priority (usually not a problem as it is single bytes to maybe a few words at a piece). So quite often there is no high prriority DMA. The other reasons to maybe block the bus would be rapid CPU access. I have rather limited experience with STM32, but I would no expect much more than some 10 cycels (bus clock) unless there are other DMAs active.

author: paulca
author_rank: Super Contributor
timestamp: February 23, 2023, 08:01:25 pm
content: Quote from: peter-h on February 23, 2023, 09:41:46 amYou can use multibuffering instead. That way you can modify the "idle" buffer.Feed the UARTs buffers. That's all they care about. Consume the buffers out of the queue elsewhere. A queue of buffers. The UARTS don't need to know what you do with them.I use polled SPI to draw a TFT in a project. In terms of the UART it takes AGES for it to do that. However the whole while the UART interrupts are consuming buffers out of the queue and when main() gets round to it, it will process them.(Technically the UARTs are iterrupting character by character but they are filling buffers and adding them to the queue).If the whole queue fills... all 16 2048 byte buffers. The last written queue item is overwritten.You could make it dynamic allocated and deallocated, but that's just like swallowing razors.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2023, 09:08:25 pm
content: That's an interesting approach.I've used the double buffer circular buffer method in the waveform generator but eventually dropped it because it was easy to achieve an inadvertent synchronicity between the code filling the buffers and the DMA switching them, so the changes never came outIn the RX case you could switch buffers either on full (the DMA switches them) or on a timer (a timer ISR switches them, and makes a snapshot of the DMA registers at the time, with the DMA disabled obviously).Or something like that.

author: paulca
author_rank: Super Contributor
timestamp: February 23, 2023, 09:22:46 pm
content: Take a C file which gives you buffers.When UART Rx event occurs put it into a buffer it gives you. (Or flip that with DMA and give UART a buffer first)Elsewhere. Ask for a "ready" buffer. Consume, process it.For Tx, reverse. Ask for a free Tx buffer. Fill it. Hand it back.Then implement the call backs.The buffer pool/queue is just a head/tail array of buffers.Note.... it is a LOT easier to implment 2 point buffers with "request", "release" than it is to implment the one sided auto sync buffer pool. (long story).A part of code "requests" a buffer, then owns it for however long they need it and then "release" it.The buffer of course can be a struct containing the buffer itself and all the information you need about it. Include if it's errored and should be ignored. It can have it's length, and any context you want.EDIT: Pool versus Queue. Queue has ordering mechanics. Usually FIFO, but not necessarily. (hint for why the abstract gets more powerful).EDIT: "Trap for young players"When following this abstraction, avoid using terminology like "Tx" or "Rx" for component, function or buffer names. Consider "Ready", "Free", or "Inuse", "Free".

author: voltsandjolts
author_rank: Unknown
timestamp: February 24, 2023, 08:07:16 pm
content: Quote from: peter-h on February 23, 2023, 03:23:16 pmOnly trying to help and you respond with sarcasm, nice. Won't waste my time again.

author: peter-h
author_rank: Super Contributor
timestamp: February 24, 2023, 08:42:47 pm
content: Not sarcastic at all. I have no idea what (void) 0 means. I don't use pointers in my code.A number of people here call me a stupid idiot but I don't mind so long as I get some value out of it

author: thm_w
author_rank: Super Contributor
timestamp: February 24, 2023, 10:30:44 pm
content: (void)0; is "do nothing"while (rd_ptr == DMA_WRITE_PTR) {};should produce the same code

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2023, 06:31:38 am
content: Quote from: thm_w on February 24, 2023, 10:30:44 pmOr I think normal people just writewhile (rd_ptr == DMA_WRITE_PTR) ;orwhile (rd_ptr == DMA_WRITE_PTR);(edit: or if you always use braces for consistency, you put ;inside, not outside.)(void)0 means "evaluate value of this magic number 0" (it could be anything else) "and discard the result". It's bullshit obfuscated code.Similar pattern is used on purpose, for example when one wants to communicate intended non-usage of a function argument:int func(int arg1, int arg2){(void)arg1; // arg1 is evaluated, and (void) tells both the reader and compiler the result is discarded on purpose}or when reading out a peripheral register where read operation itself does something, but we don't care about the result:void uart_inthandler(){(void)USART5->RXDR; // Data is read out and ignored; (void) communicates to compiler and reader not using the result was no mistake}(void)0 tells us this:* 0 has some significance* and we want to ignore it for some reasonUnless you mean exactly that, don't add such confusing code. Just a semicolon (;) is a no-op in C.

author: westfw
author_rank: Super Contributor
timestamp: February 25, 2023, 08:14:04 am
content: The usual problem with DMA and UART receive is that in order to receive characters "asynchronously" (interactively?) you want to be able to shortcut a DMA and read whatever data is there; it's only when the system gets very loaded or data is coming in very fast that you want to approach letting the buffer fill up.ideally, I think that leads to double DMA buffers in a sort of ping-pong setup. When you say READ, it potentially disables the current DMA buffer, switches to the other biffer, and returns any data from the first buffer to the user (until it's all gone.) If there's only one character in the buffer, that's OK, because it means your datarate/cpu loading is sufficiently low that it doesn't matter that you're "wasting" cycles on single-character DMA. Ideally, it should be rare to have a full buffer, and VERY rare to have them both full (leading to lost data.)I've never quite been able to figure our whether modern microcontroller UART/DMA permits such behavior :-(

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2023, 08:58:24 am
content: I am glad there was nothing there. I thought perhaps it was a forced read of memory location 0.QuoteOnly if RXDR is a volatile, otherwise the whole thing is likely to vanishQuoteAn interesting halway stage is using UART RX -> DMA as a way to extend the (nonexistent) ST UART FIFO. Put data into a linear buffer, and transfer count = buffer size. Read the buffer from a timer ISR or from a periodic RTOS task, and reset the DMA each time. That will give you a UART which has an arbitrary RX FIFO.

author: Kleinstein
author_rank: Super Contributor
timestamp: February 25, 2023, 09:00:10 am
content: There is not much wrong with the single circular buffer. The only problem with the STM32 implementation seems to be that the periphery does not tell when the data are written to RAM. So there can be a little delay in the data and one has to wait for this not so well defined worst case delay. This is usually only a short delay and one does not really care, but it is still inconvenien to check and maybe wait extra.Using a 2nd buffer level does not help at all - the routine to copy the data to the 2nd buffer would have the same problem.Ideally one would have the option for the DMA to trigger an interrupt when the buffer is half full also for a circular buffer. AFAIK this is not supported and would need something like using a counter to count data coming in adjusting the compare level after data are read.

author: wek
author_rank: Frequent Contributor
timestamp: February 25, 2023, 09:15:12 am
content: Quote from: Kleinstein on February 25, 2023, 09:00:10 amProblem is, that the delay is not specified and not specifiable and depending on circumstances. Read: any fixed delay may be unreliable, and "testing" it won't help the problem much.Quote from: Kleinstein on February 25, 2023, 09:00:10 amIn both(*) DMA versions in STM32 this option is available, but I don't understand how would this help.JW(*) In "traditional" STM32. In 'H7 there's a third version as MDMA, and in 'U5 there is yet another version.

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2023, 09:46:02 am
content: Am I right that checking LISR has the same problem as checking NDTR=0?

author: wek
author_rank: Frequent Contributor
timestamp: February 25, 2023, 10:33:48 am
content: > Am I right that checking LISR has the same problem as checking NDTR=0?No.I say this with being fully aware of that I have no proof to this except belief.JW

author: paulca
author_rank: Super Contributor
timestamp: February 25, 2023, 10:44:34 am
content: Quote from: Siwastaja on February 25, 2023, 06:31:38 am... and I would strongly encourage you to always keep the braces.Yes, I have been the one trying to find that bug where someone added a second line to an unbraced blockSTM32 provides a handy macro if you just want somewhere to put a break point, for example.__NOP();

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2023, 11:12:09 am
content: QuoteThe rationale was, I believe, that LISR generates an interrupt and if that occurred before the RAM was updated, it would be a bit silly.But, the 32F interrupt process involves a lot of activity... saving a load of registers, etc, and that will take a lot of time. I've never measured it or even read up on it, but probably 20-30 clocks.QuoteIndeed, but what does it do here:Code:/**\brief ITM Send Character\details Transmits a character via the ITM channel 0, and\li Just returns when no debugger is connected that has booked the output.\li Is blocking when a debugger is connected, but the previous character sent has not been transmitted.\param  ch Character to transmit.\returns Character to transmit.*/__STATIC_INLINE uint32_t ITM_SendChar (uint32_t ch){if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && /* ITM enabled */((ITM->TER & 1UL ) != 0UL) ) /* ITM Port #0 enabled */{while (ITM->PORT.u32 == 0UL){__NOP();}ITM->PORT.u8 = (uint8_t)ch;}return (ch);}Code:/**\brief No Operation\details No Operation does nothing. This instruction can be used for code alignment purposes.*/#define __NOP() __ASM volatile ("nop")What does "volatile" do with a piece of asm?? Maybe they need it because this lot below is not volatile (unlike all the CPU GPIO and register declarations)Code:/**\brief Structure type to access the Instrumentation Trace Macrocell Register (ITM).*/typedef struct{__OM union{__OM uint8_t u8; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 8-bit */__OM uint16_t u16; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 16-bit */__OM uint32_t u32; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 32-bit */} PORT ; /*!< Offset: 0x000 ( /W) ITM Stimulus Port Registers */uint32_t RESERVED0;__IOM uint32_t TER; /*!< Offset: 0xE00 (R/W) ITM Trace Enable Register */uint32_t RESERVED1;__IOM uint32_t TPR; /*!< Offset: 0xE40 (R/W) ITM Trace Privilege Register */uint32_t RESERVED2;__IOM uint32_t TCR; /*!< Offset: 0xE80 (R/W) ITM Trace Control Register */uint32_t RESERVED3;__OM uint32_t IWR; /*!< Offset: 0xEF8 ( /W) ITM Integration Write Register */__IM uint32_t IRR; /*!< Offset: 0xEFC (R/ ) ITM Integration Read Register */__IOM uint32_t IMCR; /*!< Offset: 0xF00 (R/W) ITM Integration Mode Control Register */uint32_t RESERVED4;__OM uint32_t LAR; /*!< Offset: 0xFB0 ( /W) ITM Lock Access Register */__IM uint32_t LSR; /*!< Offset: 0xFB4 (R/ ) ITM Lock Status Register */uint32_t RESERVED5;__IM uint32_t PID4; /*!< Offset: 0xFD0 (R/ ) ITM Peripheral Identification Register #4 */__IM uint32_t PID5; /*!< Offset: 0xFD4 (R/ ) ITM Peripheral Identification Register #5 */__IM uint32_t PID6; /*!< Offset: 0xFD8 (R/ ) ITM Peripheral Identification Register #6 */__IM uint32_t PID7; /*!< Offset: 0xFDC (R/ ) ITM Peripheral Identification Register #7 */__IM uint32_t PID0; /*!< Offset: 0xFE0 (R/ ) ITM Peripheral Identification Register #0 */__IM uint32_t PID1; /*!< Offset: 0xFE4 (R/ ) ITM Peripheral Identification Register #1 */__IM uint32_t PID2; /*!< Offset: 0xFE8 (R/ ) ITM Peripheral Identification Register #2 */__IM uint32_t PID3; /*!< Offset: 0xFEC (R/ ) ITM Peripheral Identification Register #3 */__IM uint32_t CID0; /*!< Offset: 0xFF0 (R/ ) ITM Component Identification Register #0 */__IM uint32_t CID1; /*!< Offset: 0xFF4 (R/ ) ITM Component Identification Register #1 */__IM uint32_t CID2; /*!< Offset: 0xFF8 (R/ ) ITM Component Identification Register #2 */__IM uint32_t CID3; /*!< Offset: 0xFFC (R/ ) ITM Component Identification Register #3 */} ITM_Type;Another rabbit hole!

author: paulca
author_rank: Super Contributor
timestamp: February 25, 2023, 11:18:35 am
content: Usually, "NOP" is an ASM instruction on the CPU or in it's microcode. It will have a predetermined number of clock cycles, usually 1.I would assume the volatile is a "dont optimize me" type flag. I've also seen volatile as dubious, but through it in from time to time anyway like most people.I didn't actually expect it to resolve down to a CPU instruction, but it makes sense, if you want to force a breakpoint in ARM, you need an instruction pointer address.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2023, 11:34:18 am
content: Quote from: paulca on February 25, 2023, 10:44:34 amMaybe In 1990's? This is such common issue that all sensible compilers, with sensible warning settings, warn about this:Code:int main(){if(a == 42)printf("a");printf("b");return 0;}hrst@jorma:~$ gcc testi.c -Walltesti.c: In function ‘main’:testi.c:7:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]7 | if(a == 42)| ^~testi.c:9:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’9 | printf("b");| ^~~~~~With proper indentation, you easily see the mistake by eye and compiler sees it, too.Braces do not necessarily help; you can still do the exact same mistake. We just had a case: look at thm_w's code: their no-op ; is OUTSIDE of the braces by accident, and thus NOT under the while loop! Luckily, in this case, it does not matter since no-op is no-op and it doesn't matter where it is.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2023, 11:39:35 am
content: Quote from: paulca on February 25, 2023, 10:44:34 amThis is completely different. This adds an actual ASM instruction, making the CPU execute a NOP instruction. ; in C does nothing like that.

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2023, 11:41:59 am
content: I have been usingasm("nop")where I want actual code so a breakpoint can be set there. On 32F4 even if you have -O0 you find breakpoints often don't work because there is no code there - due to the arm32 conditional instructions, conditional C constructs often generate no code (corresponding to the source) at all, and Cube doesn't warn that where you set the bp there is no code!e.g. withif (x){abcd}else{defg}abcd and/or defg cannot have a bp set there. No warning... but if you use asm... in there, that always works.And surely asm isneveroptimised away; isn't that a cornerstone??

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2023, 11:48:56 am
content: Quote from: peter-h on February 25, 2023, 11:41:59 amIt seems that most people, me included, who add inline asm have the habit of adding inline asm with the volatile qualifier:asm volatile ("nop");It would not hurt to do the same. You can try to look into compiler documentation what happens without it, maybe there are cases where it can get optimized out.

author: paulca
author_rank: Super Contributor
timestamp: February 25, 2023, 12:04:09 pm
content: Quote from: Siwastaja on February 25, 2023, 11:34:18 amYou are a C expert. You know these things. I work in different languages, different compilers, interpreters, different editors, different IDEs on a daily basis. C is currently a hobby space thing. Java has this "potential issue" and python is a law unto it's own. Where and when I can simplify and pick one standard that span languages, it's less I need to remember!You try and define what proper indentation is across different tech stacks in an actual software engineering company and I'll sit back with popcorn and watch the sparks fly.Spaces or tabs? Mixed? It's that old war again.In my view, if I was writing the coding standards for a team it would be a "preferred" item to always use braces around any and all scope blocks. It doesn't cost much and it standardises everyone on the same style.There are other ones I see out there that I don't agree with necessarily.

author: paulca
author_rank: Super Contributor
timestamp: February 25, 2023, 12:07:35 pm
content: Quote from: Siwastaja on February 25, 2023, 11:39:35 amI think we got confused here. I was just adding the note about the NOP macro as a helpful suggestion, if you wanted to put a "literally" no operation instruction, so you could break point it. Break pointing on conditional expressions can be hit and miss on ARM due to conditional instruction varieties on the higher M numbers. I'm sure breaking on a "void" expression might result in similar frustrations.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2023, 12:17:21 pm
content: Quote from: paulca on February 25, 2023, 12:04:09 pmNo, no, and no, your speculation is just wrong - indentation style does not matter. Spaces vs tabs does not matter. GCC catches the mistake nevertheless.Even this:Code:if(a == 42) printf("a"); printf("b");testi.c:7:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]7 | if(a == 42) printf("a"); printf("b");| ^~testi.c:7:30: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’7 | if(a == 42) printf("a"); printf("b");| ^~~~~~The only case where I fail to get the warning is when there are no indentation levels at all.This has nothing to do with being an expert or not. Quite the opposite; beginners get most benefit out of compiler checks, because they make more mistakes!The real message to beginners is, always compile with at least -Wall. With GCC, this is not the default. That's the real catch for young players.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 25, 2023, 06:19:57 pm
content: Quote from: paulca on February 25, 2023, 12:04:09 pmDon't clutter your mind with inconsequential.

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2023, 08:32:51 pm
content: One day when I have nothing better to do I will rewrite the UART interrupt functions in my product to use DMA.Its not a bad order to do stuff actually. Many years ago I developed a fairly complex product (office automation - a multi port printer/plotter buffer/sharer) which used the Z280. I even wrote an RTOS for it, because it got too complicated to run in a "main loop plus interrupts plus a timer interrupt" way. There was a lot of moving about of 4k blocks around 4MB RAM. Initially I did it with the Zxx LDIR instruction but later replaced all that with DMA and it did run a lot better.That said, the chips of the era were nearly always overloaded. Today we have a 168MHz chip which does most things in 1 clock.

author: paulca
author_rank: Super Contributor
timestamp: February 26, 2023, 10:51:05 am
content: It's not cluttering. It's simplifying. The "more complete" way is to use braces. Therefore having only one way in your style simplifies things. No?C+Gcc is one single, though very large, platform/eco system. I work across too many to be a pedant in any of them. I can't recall if Java will give you a warning. I don't believe it will. Nor will it warn about using an assignment operator in an if() statment.... as that feature is used quite a lot with assignment and comparison combined. while( line = buffer.readLine() != null ) {} etc.I also work in teams where skills and diligence vary, from individual, from day to day, from project to project, work item to work item. Code style is no longer "inconsequential". You should have to see what happens when 6 different coding styles all meet up at merge time towards the end of a big sprint or two. After you do that a few times you start introducing more and more forceful style requirements. It basically stops code moving around between edits which cause unnecessary merge artifacts. Standardising style also places the code style arguments "out of band" although doesn't make them go away.Ideal goal by far. Mandatory, network sourced (or git repo) code style format file for each IDE which produces identically formatted code, with a mandatory format on save. Everything from blank space, indentation, braces, whether there are spaces before or after brackets... is specified. Ideally you want merges to only should code that specifically changed and not look like a rainbow Battenberg cake!

author: NorthGuy
author_rank: Super Contributor
timestamp: February 26, 2023, 02:07:00 pm
content: Quote from: wek on February 25, 2023, 09:15:12 amYou can suspend DMA, wait for completion, read data, then resume DMA. Then you don't need to guess about the delay.UART has an internal FIFO, so there shouldn't be any problems with that.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 26, 2023, 02:37:35 pm
content: Quote from: paulca on February 26, 2023, 10:51:05 amThe time you spend thinking about the style is the time when you don't think about other things. These other things are more important - data structures, algorithms, code flow. You should be thinking about these. It is entirely possible to write perfectly styled code which is full of bugs, or bloated beyond comprehension, or written very badly with lots of hard-to-test special cases. The style is the least of concerns.

author: wek
author_rank: Frequent Contributor
timestamp: February 26, 2023, 06:18:37 pm
content: Quote from: NorthGuy on February 26, 2023, 02:07:00 pmIn the dual-port DMA (and probably in the single-port, too) in STM32, you can't. What in the RM is called "suspend-resume" is an eufemism for complete abort/restart. In particular, if you'd just disable the stream(channel), wait until disable confirmed, and then reenable, the next transfer would go from/to addresses set at the previous setup, regardless of that there was address increment during previous transfers.The practical consequence is, that circular transfers are practically uncontinuable.ST's peripherals generally appear having been designed by folks - probably young engineers - with little practical experience.QuoteNot in "classic" STM32 such as the'F4 in question (unless you count the holding register as a 1-deep FIFO).JW

author: peter-h
author_rank: Super Contributor
timestamp: February 26, 2023, 06:55:34 pm
content: QuoteWould you say that about their USB or ETH peripherals too? They are damned complicated.It could well be the others are crap. Then I wonder if the chinese copy chips (e.g. ESP32) use the same ones. They would come from somewhere in some VHDL form.QuoteIf I read that right, that is astonishing. It means the address register writes which you do during DMA init actually go into "shadow" registers and when you enable DMA it gets loaded from these.In some applications this is desirable e.g. display refresh or especially time critical stuff because it saves reloading everything.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 26, 2023, 07:54:32 pm
content: Quote from: peter-h on February 26, 2023, 06:55:34 pmBut you can overwite these before resuming. Of course, you would need to return them back to normal somehow - say by restarting manually after the end of the buffer is reached.

author: wek
author_rank: Frequent Contributor
timestamp: February 26, 2023, 08:00:28 pm
content: Quote from: peter-h on February 26, 2023, 06:55:34 pmThose are not designed by ST but purchased from Synopsys - it's not a secret, it's written in the RM too.And they come from an entirely different world, and in that world, users simply don't deal with peripherals directly, so their complexity does not matter.JW

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 26, 2023, 08:27:51 pm
content: Yep, note that this is ultra common. MCU vendors buy tons of IPs and rarely design their own peripherals. They do only for those odd ones that would give them an edge competition-wise, and that's certainly not going to be gained with stuff as basic as UARTs and SPI, or even Ethernet.

author: peter-h
author_rank: Super Contributor
timestamp: February 26, 2023, 09:35:38 pm
content: OK, let me get this right.You config DMA to copy from a UART to memory address x..., max y bytes.When the transfer ends (say NDTR=0 or LISR=1), you disable DMA, then enable it again, and it writes the same UART's data again at address x..., max y bytes.Reading the RM, it seems to confirm the above i.e. the address registers arenotincremented by the transfer

author: NorthGuy
author_rank: Super Contributor
timestamp: February 26, 2023, 11:20:29 pm
content: Quote from: peter-h on February 26, 2023, 09:35:38 pmYes. When you stop in the middle (say at the address X), if you set the DMA address to X before resuming, then DMA continues writing the buffer where it left off. However, when it restarts after the end of the transfer, it'll start from X again. Obviously, you do not want that - you want to adjust the address back to the beginning of the buffer. Therefore, you cannot use circular mode. So, you would have to re-start manually and adjust the address before re-starting.Or you can see if you can re-program priorities. But to figure out if this fixes wek's phenomenon, you must be able to create a situation where the phenomenon occurs. This is not easy to do, if at all possible.

author: peter-h
author_rank: Super Contributor
timestamp: February 27, 2023, 06:13:23 am
content: Can I get this right.If the transfer isstopped before NDTR=0, the DMA reloads the address registers from the values you previously wrote in.If the transfer comes to its natural end (NDTR=0), the DMA doesn't reload the address registers and the transfer will run to/from the address after the last one written/read?

author: westfw
author_rank: Super Contributor
timestamp: February 27, 2023, 06:45:25 am
content: QuoteMy impression is that most of the simple ST peripherals are essentially "reference design" VHDL (or whatever) "8250 compatible UART" and similar. Maybe that's OK; lots of consistency. But not really matching the features (or quality?) of many more original implementations.

author: wek
author_rank: Frequent Contributor
timestamp: February 27, 2023, 07:24:46 am
content: Quote from: NorthGuy on February 26, 2023, 11:20:29 pmIt's surprisingly simple.Attached program for the classic Disco'F4 ('F407), a jumper loops back UART Tx to UART Rx. Clocks are at default, no need to go fancy here. DMA set up in the usual way for UART Rx, to store bytewise from data register to memory buffer rx_buf and increment memory-side pointer register, noncircular, FIFO off.I transmit a byte (from an incremented variable) manually to UART Tx. Then in a loop I watch NDTR, and when it changes, I then read out the content of rx_buf at the index where FIFO is supposed to store it, and store it into the same index in rx_buf2. Then I blink a LED using a trivial loopdelay (the blinking is confusingly through timer used in fact as simple pin-switcher, this is leftover from some other test), and repeat 16x (I did not bother to do any serious ending, so NDTR ends at 0 and the whole program gets stuck at waiting for it to change).The result isCode:(gdb) p /x rx_buf$2 = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe,0xf, 0x10}(gdb) p /x rx_buf2$3 = {0x1, 0x2, 0x0, 0x4, 0x5, 0x0, 0x7, 0x8, 0x0, 0xa, 0xb, 0x0, 0xd, 0xe,0x0, 0x10}zeros in rx_buf2 are the premature reads from rx_buf (which being global variable was zeroed by the startup code).The original blinky test had this thing that if you press blue button, timing changes, blinking slows down. So I press down the blue button, reset, et voila:Code:(gdb) p /x rx_buf2$5 = {0x1, 0x2, 0x3, 0x0, 0x0, 0x6, 0x7, 0x8, 0x0, 0x0, 0xb, 0xc, 0xd, 0x0,0x0, 0x10}There are no other DMA streams involved, so I don't see how priority would change anything here.The key portion is this:Code:while(1) {n++;80002c4:6823ldrr3, [r4, #0]80002c6:3301addsr3, #180002c8:6023strr3, [r4, #0]USART2->DR = n;80002ca:6823ldrr3, [r4, #0]80002cc:f8cc 3004str.wr3, [ip, #4]while(old_ndtr == DMA1_Stream5->NDTR);80002d0:684bldrr3, [r1, #4]80002d2:4293cmpr3, r280002d4:d0fcbeq.n80002d0 <main+0xfc>rx_buf2 = rx_buf;80002d6:f818 2000ldrb.wr2, [r8, r0]80002da:4b16ldrr3, [pc, #88]; (8000334 <main+0x160>)80002dc:b2d2uxtbr2, r280002de:f80a 2000strb.wr2, [sl, r0]buf_idx++;80002e2:3001addsr0, #1old_ndtr = DMA1_Stream5->NDTR;80002e4:f8d9 2004ldr.wr2, [r9, #4]JW

author: wek
author_rank: Frequent Contributor
timestamp: February 27, 2023, 08:14:00 am
content: Quote from: peter-h on February 27, 2023, 06:13:23 amNo, NDTR and the pointer registers simply reload in both cases.See attached code, a regurgitation of the previous one. It sets NDTR to 8 and first runs down until NDTR == 5, then aborts/restarts DMA without touching NDTR and the pointer registers, runs down until "natural completion" (i.e. xSR.TCx == 1), and then again restarts without touching NDTR. The counter which gets transferred through USART keeps counting up all the time."Samples" of rx_buf are taken manually, at random spacing, but I believe the gist of the thing is clear:Code:(gdb) p /x rx_buf$84 = {0x1, 0x2, 0x0 <repeats 14 times>}(gdb)$85 = {0x1, 0x2, 0x3, 0x0 <repeats 13 times>}(gdb)$86 = {0x1, 0x2, 0x3, 0x0 <repeats 13 times>}(gdb)$87 = {0x4, 0x2, 0x3, 0x0 <repeats 13 times>}(gdb)$88 = {0x4, 0x5, 0x3, 0x0 <repeats 13 times>}(gdb)$89 = {0x4, 0x5, 0x3, 0x0 <repeats 13 times>}(gdb)$90 = {0x4, 0x5, 0x6, 0x0 <repeats 13 times>}(gdb)$91 = {0x4, 0x5, 0x6, 0x7, 0x0 <repeats 12 times>}(gdb)$92 = {0x4, 0x5, 0x6, 0x7, 0x8, 0x0 <repeats 11 times>}(gdb)$93 = {0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0}(gdb)$94 = {0xc, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0}(gdb)$95 = {0xc, 0xd, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0}(gdb)$96 = {0xc, 0xd, 0xe, 0x7, 0x8, 0x9, 0xa, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0}(gdb)$97 = {0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0, 0x0}(gdb)JW

author: paulca
author_rank: Super Contributor
timestamp: February 27, 2023, 12:44:29 pm
content: Quote from: NorthGuy on February 26, 2023, 02:37:35 pmI agree 100% until you have more than you and the compiler as audience. But if you put that line across in a job interview for software engineer, you will not get the job. You just won't be fit to work in a team.Coding/hacking/bashing out some MCU code is not the same as software engineering. The biggest point is... BIG code, BIG data, BIG platforms, many people, many teams. Way more than half the effort is in communications, not in making it work.Like a plumber could come into your house and throw some pipes in willy nilly and it will work perfectly. It's not a professional job. The professional will do it neat, tidy, to any standards that apply AND he will make it easy to access, to fix, to understand by others later etc. etc. Partly about pride too. Leaving a job knowing, to the best of your ability, the next guy along to work on it will think, "Nice professional job"I'm sure if you stated the above line in a job interview for an electrical/electronics engineer you would also be shown the door. I see convention and style picked at CONSTANTLY on here in terms of PCBs and Schematics. I'm sure the suggestion that none of that is important and all that matter is, does the circuit work... will not go down well. Again, if it's just you, nobody cares except you. If you are one of a team of 6 of a program of 4 teams.... people will care.At interview. If you can't do the basics of making it work, you are worthless. If you believe that all that is important is making it work, you are a potential liability or at least missing part of the pie.

author: peter-h
author_rank: Super Contributor
timestamp: February 27, 2023, 03:11:56 pm
content: Interesting discussion!It means that some code in a typical DMA application can be loaded just once:Code:DMA1_Stream5->NDTR = num_values;// buffer sizeDMA1_Stream5->PAR = (uint32_t) (&DAC1->DHR12RD);// destination (DAC) address, 32 bit DAC modeDMA1_Stream5->M0AR = (uint32_t) buffer0;// base of DMA table 0DMA1_Stream5->M1AR = (uint32_t) buffer1;// base of DMA table 1This can be quite significant since the arm32 peripherals are very slow - not only because they are running at a fraction of the CPU clock but also because there are multiple clocks involved in syncing register reads/writes.And probably lots of other registers too.Old 1980s DMA controllers had this shadow register setup, but they were dedicated chips.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 27, 2023, 03:38:39 pm
content: Yes, the whole idea is that you can configure the DMA once. In circular mode, you just enable it once, too; in non-circular mode, you enable it for every transfer.The requirement of clearing the interrupt flags before enabling incurs one extra register write besides the EN bit, but it's not too bad; two writes in total.I would say, if you need to rely on exact data count during on-going transfer, you are just operating beyond what the DMA was designed for. Reconsider the design. If you absolutely must do this, could you design it so that you allow one extra transfer when evaluating the NDTR? AFAIK, the DMA does not buffer more than one request to memory, so the problem as discussed here is limited to off-by-one. I would rather just delay the processing by one data item, instead of trying to work around the (uncertain) delay.

author: wek
author_rank: Frequent Contributor
timestamp: February 27, 2023, 05:14:08 pm
content: Code:This can be quite significant since the arm32 peripherals are very slow - not only because they are running at a fraction of the CPU clock but also because there are multiple clocks involved in syncing register reads/writes.This is most probably not case of DMA registers, as they sit directly on an AHB bus, and are not behind an AHB/APB bridge which is the primary source of such slowdown.JW

author: peter-h
author_rank: Super Contributor
timestamp: February 27, 2023, 05:54:14 pm
content: I am glad to hear thatBut are the DMAs running at say 168MHz?

author: NorthGuy
author_rank: Super Contributor
timestamp: February 27, 2023, 06:21:46 pm
content: Quote from: Siwastaja on February 27, 2023, 03:38:39 pmHow else would you use the circular mode?

author: Siwastaja
author_rank: Super Contributor
timestamp: February 27, 2023, 07:04:59 pm
content: Quote from: NorthGuy on February 27, 2023, 06:21:46 pmCircular mode can be used to automatically update memory, if atomic units are at most 4 bytes and aligned, so that DMA updates each variable in one piece. If there is interdependency between variables in a packet (so they need to be from the same "sample"), this does not work, but this often is not the case. I have used this pattern multiple times due to extreme simplicity, for example, to transfer current/voltage setpoints and measured values between MCUs. Then NDTR is completely meaningless.Another way to use circular mode without looking at exact value of NDTR is to implement double buffering by dividing the memory in two and use half transfer and transfer complete interrupts. Which of the two interrupt flags is active tells which of the buffers is now accessible by CPU, while the DMA is accessing the other.

author: peter-h
author_rank: Super Contributor
timestamp: February 27, 2023, 07:28:07 pm
content: What do you load into NDTR if you want continuous circular loading?Doesn't a 32 bit CPU store also write the 32 bits atomically (possibly if assuming 4-alignment)?

author: Siwastaja
author_rank: Super Contributor
timestamp: February 27, 2023, 07:34:32 pm
content: Quote from: peter-h on February 27, 2023, 07:28:07 pmSize of the datagram (memory region you are accessing) (in word size used by DMA, so you would usually divide by 4).QuoteYes, if your variables are correctly aligned, both DMA and CPU will access them atomically. For example,volatile struct __attribute__((align(4))){uint32_t time;uint16_t volts;int16_t amps;uint8_t status;uint8_t padding; // to be explicit}All of them are updated by DMA atomically, and can be accessed by CPU without a problem, butstatuscould be from a different frame thanvolts, for example. If this does not matter to you, then things are pretty simple: just configure continuous DMA and enjoy the automagic memory sharing with no CPU intervention nor any parsers.

author: peter-h
author_rank: Super Contributor
timestamp: February 27, 2023, 08:58:57 pm
content: QuoteIf they are not 4-aligned, and taking the 32F4 which supports unaligned access, would a 32 bit store really not be atomic from- an interrupt- a DMAI can see the latter may not be atomic (DMA has been like that since for ever; even in the Z80 DMA days, the DMA could cycle steal within instructions, IIRC) but the former??

author: NorthGuy
author_rank: Super Contributor
timestamp: February 27, 2023, 10:44:56 pm
content: Quote from: Siwastaja on February 27, 2023, 07:34:32 pmBut what if you start when the peer is already transmitting amps, but you will record it as the first member - time, or if you encounter any glitch on the UART line - everything will be shifted off permanently.Quote from: Siwastaja on February 27, 2023, 07:04:59 pmSure, but this works more like a ping-pong buffer - you will only get the results when the entire buffer (or half of it) is filled in. For example, when you receive a single character, you'll never know untill the rest is received, possibly days after it was sent.To replace a traditional circular buffer you need to access NDTR.The fact that you can access NDTR already means that the original designers of the module thought that such access might have been useful. Simply put, the use of AXI streams broke the functionality and they didn't figure out that it needs to be fixed.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 28, 2023, 06:33:39 am
content: Quote from: NorthGuy on February 27, 2023, 10:44:56 pmOh! Sorry; because the UART dropped off the discussion long time ago, my mind shifted on SPI, which has hardware packet delimitation signal.

author: paulca
author_rank: Super Contributor
timestamp: February 28, 2023, 11:06:43 am
content: Quote from: NorthGuy on February 27, 2023, 10:44:56 pmDid you have a crack with the multi-buffering DMA? Maybe even a scrap book project with MX and HAL just to test it.I have no used it in anger, but when it came to double buffers being nearly what I want, just not quite flexible enough it did look like the better option.I still honestly don't quite track what you are doing, I know there have been several threads around UART and your struggling, but I've lost the thread of the project and it's purpose. Normally when I find myself wandering around in a project in random directions trying to whack a mole down, it's time to stop, sigh, take a walk and try and think about it in a completely different way. If I end up in the same place, fine. However sometimes I end up in a much better place. Example being the DSP buffering system I posted about, which by the end of the thread got completely deleted and a completely different approach taken.I also realise you have gone over it dozens of times and are sick of doing so.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 28, 2023, 01:44:11 pm
content: Quote from: paulca on February 28, 2023, 11:06:43 amYou confuse me for someone else.

author: paulca
author_rank: Super Contributor
timestamp: February 28, 2023, 02:21:49 pm
content: Quote from: NorthGuy on February 28, 2023, 01:44:11 pmOops. Sorry I meant that for peter-h, not you.

author: peter-h
author_rank: Super Contributor
timestamp: February 28, 2023, 07:53:57 pm
content: My UART issue was fixed by wek's suggestion that interrupt driven TX does not need a byte placed into the UART (and then enabling TX int). That is for many past UARTs. For the 32F4 ones you just enable TX int and off it goes.Spent weeks on that. I inherited that code. Funnily enough it lost only 1 byte every few million.Had I used DMA to service the UARTs I would have never had this issue.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 01, 2023, 07:41:10 am
content: Inherited code is notoriously painful to deal with, so I don't blame peter-h for that. It is often easier to write from scratch.For microcontroller projects, code that is easiest to reuse is pure algorithmic stuff.