Does this code need interrupts disabled? - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: January 06, 2023, 06:56:28 pm
content: I think it does but it's not obvious.The code is self explanatory. It is a get_ipqcount() function which returns how many bytes are in a circular queue. The input pointer is rx_index and the output pointer is get_index.Receive is under interrupt, and extraction from the queue is in an RTOS task so basically also under interrupt.Code:count = (int32_t)serial_receive_buffer_rx_index - (int32_t)serial_receive_buffer_get_index; // buffers of uint8_tif (count < 0) {// rx index has wrapped and is now before the get indexcount = SERIAL_RX_BUFFER_SIZE + count;}if (count > (SERIAL_RX_BUFFER_SIZE-1)) {// This should never be allowed to happencount = 0;serial_flush_rx(port);}and here is another similar one; this is get_opqspace() which returns how many bytes can be placed into a tx queueCode:count = (int32_t)serial_transmit_buffer_put_index - (int32_t)serial_transmit_buffer_tx_index;if (count < 0) {// put index has wrapped and is now before the tx indexcount = SERIAL_TX_BUFFER_SIZE + count;}if (count > (SERIAL_TX_BUFFER_SIZE-1)) {// This should never be allowed to happencount = 0;serial_flush_tx(port);}// Space is buffer size - count -1 and the -1 is needed because full buffer = buffer_size-1qsize = SERIAL_TX_BUFFER_SIZE - count -1;I am also not sure of the evaluation order of the last line of code above.(SERIAL_TX_BUFFER_SIZE - count) -1is not same asSERIAL_TX_BUFFER_SIZE - (count -1)This is someone else's code. I always do circular buffers as inptr, outptr, size, disabling ints all around, and then a 256 byte buffer can actually hold 256 bytes, whereas the above can hold only 255.

author: ejeffrey
author_rank: Super Contributor
timestamp: January 06, 2023, 08:04:20 pm
content: Yes, it's obvious:Code:count = (int32_t)serial_receive_buffer_rx_index - (int32_t)serial_receive_buffer_get_index;...if (count > (SERIAL_RX_BUFFER_SIZE-1)) { // This should never be allowed to happenEven if the access to the serial_receive_buffer_* arrays were atomic (which they are not as written, so it's 100% wrong), there is no guarantee that the access to the two different arrays is consistent. Thus, your "this should never happen" definitely can happen.

author: peter-h
author_rank: Super Contributor
timestamp: January 06, 2023, 08:14:08 pm
content: Let me clarify by posting the function which fills the bufferCode:/** Write a number of bytes to the transmit queue and start transmission if necessary* Returns false if there is insufficient space for the given length* Port is numbered 0 to 4, with 0 being the USB VCP** The port 0 processing is different for port 0 because there is flow control on tx (to PC)* which necessitates a circular buffer before it just like ports 1-4 have. The difference* is that with serial ports the flow control is the baud rate whereas with port 0 is it* how fast the PC application is reading the data out of the VCP.** This function is NON blocking, if the data fits into the TX buffer, otherwise no action**/bool serial_transmit(uint8_t port, uint8_t *data, uint16_t length){bool ok = false;if (port > 0 && port <= SERIAL_MAX_PORT){if (serial_get_opqspace(port) >= length){portENTER_CRITICAL();// prevent interrupts while updating the index// Copy the data to the circular bufferport--;// offset from 0for (uint16_t i = 0; i < length; i++){serial_transmit_buffer[serial_transmit_buffer_put_index++] = data;serial_transmit_buffer_put_index %= SERIAL_TX_BUFFER_SIZE;}if (!serial_transmit_active){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. The read of SR (RMW actually) and a write to DR also clears pending TC int.serial_port_handles->Instance->SR &= ~(UART_FLAG_TC);serial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;serial_transmit_active = true;// Enable TX empty and TX complete interrupts__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TC);}portEXIT_CRITICAL();ok = true;}}else if (port == 0){// USB CDC port. Same as port 1-4 above but no need to mess with interrupts etc// The circular buffer gets copied to CDC in the USB RTOS task (in main.c)CDC_serial_transmit(data, length);ok=true;}return ok;}What made me suspicious is that the above calls serial_get_opqspace() with interrupts enabled. But the CDC transmit function does disable them:Code:// Same as serial_transmit().void CDC_serial_transmit(uint8_t *data, uint16_t length){portENTER_CRITICAL();if (CDC_get_opqspace() >= length){for (uint16_t i = 0; i < length; i++) {cdc_transmit_buffer[cdc_transmit_buffer_put_index++] = data;cdc_transmit_buffer_put_index %= CDC_TX_BUFFER_SIZE;}}portEXIT_CRITICAL();}and cdc_get_opqspace does it correctly - I wrote that part a year or two ago and commented on itCode:// Return the space available in CDC circular transmit circular buffer// This function should be called with ints disabled (with portENTER_CRITICAL())uint16_t CDC_get_opqspace(void){// Count is buffer size - spaceuint16_t space = CDC_TX_BUFFER_SIZE - CDC_get_opqcount() -1;return space;}My point is that often there is no need for atomic code. For example a function returning input queue size is safe even if the size increases since you checked the value but before you read the data out (obviously). Similarly with a function returning output queue space; if it returns 20 but actually by the time you write to it there is space for 21, that's ok.

author: peter-h
author_rank: Super Contributor
timestamp: January 09, 2023, 02:47:33 pm
content: I guess this thread is a living example of: one should never post more than 20 lines of code if one wants a responseHowever, I don't understand thisQuoteI have four serial ports, and therefore four TX buffers and four RX buffers. The index is 1-4 (0 means something else - a USB VCP).I cannot see a problem with one RTOS thread using say port 2 and another using port 3. What onemightwant to do is transmit on port 2 and receive on port 3Anyway, this has led down another rabbit hole and the discovery that portENTER_CRITICAL() (which I have been using around various sections) does not disable interrupts! It merely sets the priority mask to 5 which disables everything needed to stop FreeRTOS switching tasks. This in turn led to the discovery that the serial port interrupts were all set to priority 0 which cannot be masked!That originated from Cube MX generated code from years ago.So I set them to priority 1, which is still fine (some timer ISRs are also 1 but those ISRs are all very short, and anyway the UARTs do have a buffer in them). Then I redefined configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY from the default value of 5 to 1 which as far as I can find out with extensive googling blocks interrupts of priority 0, and I have no such sources. I wonder if anyone knows about this stuff...

author: peter-h
author_rank: Super Contributor
timestamp: January 09, 2023, 03:45:01 pm
content: Yes I think that is the basic idea to supportnotneeding DI/EI.The suspected problem turns out to be some interaction between the mail loop and the transmit ISR.

author: Sauli
author_rank: Contributor
timestamp: January 09, 2023, 05:10:19 pm
content: QuoteTrue.QuoteThe code is not keeping count but indexes into the buffer, which is no different from using pointers.It is code like this that requires DI/EI:QuoteBut you can avoid the index from even momentarily having an illegal value by using a local variable:Code:int index = serial_transmit_buffer_put_index;serial_transmit_buffer[index++] = data;serial_transmit_buffer_put_index = index % SERIAL_TX_BUFFER_SIZE;

author: tggzzz
author_rank: Super Contributor
timestamp: January 09, 2023, 06:16:22 pm
content: Quote from: peter-h on January 09, 2023, 02:47:33 pmBy and large priority levels cause at least as many problems as they address. My preference is for three levels:extreme prejudice: watchdog/powerfailnormal interrupt: grab input, put it in a FIFO, then exit interruptnormal processing: everything elseNormal interrupt processing should be so short that they don't need to be interruptable. Run them all at the same priority. By definition, it doesn't matter if an extreme prejudice interrupt occurs.FIFO put and get operations must be atomic.That modus operandi avoids all sorts of subtle unrepeatable behaviour.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 09, 2023, 09:28:56 pm
content: Quote from: peter-h on January 06, 2023, 06:56:28 pmYou don't need to disable interrupts for this code if few conditions are met:- All writers are in the same thread (that is there never will be two different writers in concurrent threads)- All readers are in the same thread (that is there never will be two different readers in concurrent threads)- The "port" variable is not shared between threads (that is when you run the code, the "port" cannot be changed by any other thread)The flushing code (not shown?) is most likely problematic, but it is not needed anyway. Better make sure you don't overflow/underflow the buffer. If you do allow overflow/underflow to happen, this is already a bug and must be fixed. Checking for overflow/underflow after it has already happened is idiotic, so I would remove such checks completely.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 09, 2023, 09:56:38 pm
content: Quote from: peter-h on January 06, 2023, 08:14:08 pmThe concern here is that the increment should happenafterthe write. C doesn't guarantee this. ThereforeCode:serial_transmit_buffer[serial_transmit_buffer_put_index] = data;serial_transmit_buffer_put_index++;is better. It's still a small possibility that the compiler may alter the order of writes if it believes that this doesn't cause any effects. If you want to be paranoid about this, use some sort of barrier:Code:serial_transmit_buffer[serial_transmit_buffer_put_index] = data;//some_sort_of_barrier hereserial_transmit_buffer_put_index++;which may be an asm's nop or even empty asm line in the simplest case. If you have multiple cores you barrier may be more complicated because CPU may reorder memory accesses by itself. Linux has special functions for that.

author: peter-h
author_rank: Super Contributor
timestamp: January 10, 2023, 10:17:35 am
content: This seems to work fine. Is there a potential issue?Code:int index = serial_transmit_buffer_put_index;serial_transmit_buffer[index++] = data;serial_transmit_buffer_put_index = index % SERIAL_TX_BUFFER_SIZE;In the old Z80 etc days one would just do a DI/EI around stuff but the ARM doesn't have such instructions and there is no simple way to do it (some previous threads on that). The portENTER_CRITICAL() macro (part of FreeRTOS) leads to quite a bit of code and merely elevates the priority threshold.

author: Berni
author_rank: Super Contributor
timestamp: January 10, 2023, 10:37:56 am
content: Just making the relevant arrays and counters as volatile should make sure the compiler doesn't swap things around.In general circular buffers are the best idea because you have only the read side incrementing the read pointer and only the write side incrementing the write pointer. This solves the problem of having no atomic increment instruction on most modern CPUs.You still get problems if you have multiple threads writing to the buffer. For that i think the only easy solution is a mutex.(Luckily a RTOS will tend to provide those out of the box). But i find myself mostly using these kind of buffers to move data between fast interrupt routines and main code(so 1 writer, 1 reader), in that case a circular buffer just simply works on its own.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: January 10, 2023, 10:52:35 am
content: Quote from: peter-h on January 10, 2023, 10:17:35 amARMdoeshave DI/EI equivalent: CPSID I/CPSIE I

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 11:37:41 am
content: Quote from: Berni on January 10, 2023, 10:37:56 amAre you sure? Fromhttps://en.cppreference.com/w/c/language/volatile"Note that volatile variables are not suitable for communication between threads; they do not offer atomicity, synchronization, or memory ordering. A read from a volatile variable that is modified by another thread without synchronization or concurrent modification from two unsynchronized threads is undefined behavior due to a data race. "The whole area is a (poorly marked) minefield.

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 01:08:21 pm
content: "doesn't swap things around" is explained in the first part of the article you've linked to, namely, that all volate accesses are finished and none started at the sequence points (e.g. at the semicolon delimiting recordings). That's all volatile gives you.Now how many volatile accesses you have in between successive sequence points, and what exactly those accesses are, and whether that is or is not safe in context of interrupts (and multitasking is just a glorified interrupt) is up to you to judge, as that is context-dependent.There are no silver bullets in mcu programming.JW

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 01:26:50 pm
content: Quote from: wek on January 10, 2023, 01:08:21 pmThat's just as true inallprogramming.Some tools help by reducing complexity. Some tools hinder by increasing complexity - sometimes due to outdated presumptions and/or requirements for backward compatibility.

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 01:39:47 pm
content: Quote from: wek on January 10, 2023, 01:08:21 pmVolatile has a long and inglorious history in C. Don't take my word for it; listen to Dennis Ritchie, Lunis Torvalds, John Mashey...https://www.yarchive.net/comp/volatile.htmlMost people don't understand C as well as they think they do. Zealots claim this is peoples' fault rather than the language's fault.

author: Berni
author_rank: Super Contributor
timestamp: January 10, 2023, 01:55:17 pm
content: Quote from: tggzzz on January 10, 2023, 11:37:41 amYep i was using it in the premise that you can use volatile to make sure you write the data buffer before writing the new counter value. That link explains that volatile makes sure to finish all writes/reads before evaluating the next part of code. The use of that is to make sure the other thread is informed of there being new data(writing the counter) only after the data is there(in the buffer), thus avoiding a race condition.The wrong use of the volatile keyword is expecting it to make the operation atomic. There is indeed no way for the compiler to magically make things atomic on a CPU that doesn't have appropriate atomic instructions for a certain thing. Hence why it doesn't magically make multithreaded access to a variable work. So it should indeed NOT be used for that.But this is still enough to implement the circular buffer with separate read and write pointers to communicate between two threads. A lot of the time this is all you really need anyway.(Like an interrupt pumping data into a worker on the main thread)

author: NorthGuy
author_rank: Super Contributor
timestamp: January 10, 2023, 02:45:53 pm
content: Quote from: Berni on January 10, 2023, 01:55:17 pmYes, that always worked. But now, on big out-of-order CPUs, the order of writes seen by one core may be different from the sequence seen by another core. Say, you may write data first then index, but the CPU re-arranges writes, so the other core executing at the same time sees the index change, while data are still not written. So, if you're writing a Linux driver for RPi, or communicating between RPi threads, you may get very rare data corruptions, hence the need for SMP barriers.

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 02:48:42 pm
content: Quote from: tggzzz on January 10, 2023, 01:26:50 pm"Big computers" have way more silver at their disposal, and bullets are cast regardless of their individual cost, as it's easier to simply machine-gun the zombies wholesale than to deal with them individually using all the avalable tools there are (including simply running away from them, which is the gist of the circular buffer solution above, with some cheap garlic thrown to avoid corner cases).Quote from: tggzzz on January 10, 2023, 01:39:47 pmWith all due respect to these gentlemen, their experience is rather limited to said big computers. Note liberal usage of "lock" (.EQ. silver bullet) since the very second post (together with strong opinions but we knew that).Volatile is a tool very adequate to the PDP11 macroassembler C really is. Blunt, leaving all the heavy lifting to the user, hard to use without undestanding the roots (i e. compiler design) and ultimately nonportable. But also completely transparent, well controllable and lightweight, which are treats you want when your goals are minimal memory usage and hard real time execution.You want something smarter? Don't use C then, go Rust or whatever is en vogue this week.JW

author: nctnico
author_rank: Super Contributor
timestamp: January 10, 2023, 03:03:07 pm
content: Quote from: Berni on January 10, 2023, 01:55:17 pmAgreed. Back to the OP's questions: disabling IRQs is only needed if the code can be interrupt by another interrupt that uses the buffer indexes.Assuming the UART buffer has the typical interrupt space / user space interaction, it depends on whether an RTOS task switch can happen while inside the UART interrupt. I don't see how an RTOS would work if a task switch (timer) interrupt can interrupt dealing with hardware interrupt. So my guess would be that the code doesn't need disabling the interrupts because the task switch timer interrupt has a much lower prority.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 10, 2023, 03:19:36 pm
content: * push only modifies write pointer* pop only modifies read pointer* when pushing, update write pointer after writing the data; so that concurrent pop does not detect availability of new item too early* when popping, update read pointer before returning the pointer to the data; so that concurrent push detects the full condition before overwriting the returned data* unless you can make it atomic with say 256-long FIFO, calculate the magical "+1 with wrap" with local, temporary variables and make sure assignment into the global pointer or index is atomic* no volatile needed* no disabling of interrupts needed, given that:* make pushes from one thread (e.g.; if from ISRs, they have to be same priority level not to pre-empt each other)* make pops from one thread (again, if from ISRs, same priority level).* push and pop threads can be of course different from each other.Feel free to correct me, probably got something wrong anyway. In any case, I think I have had zero problems with FIFOs, they are an excellent way to transfer data asynchronously between two threads, but one needs the fixed roles of one pusher thread and one popper thread.

author: nctnico
author_rank: Super Contributor
timestamp: January 10, 2023, 03:32:34 pm
content: You'll need to use volatile in order to do something like (with read_index or write_index modified in an interrupt or different thread):while (read_index == write_index) wait_for_characters();Without volatile, the compiler will produce an endless loop that never exits.

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 03:40:13 pm
content: > * no volatile neededWithout volatile, compiler is free to assume nothing in the buffer changed (both data and indices) and optimize out related code entirely.And without volatile, compiler is free to reorder accesses, e.g "write data first then increment index" may happen in the opposite order than written in source.JW

author: gf
author_rank: Super Contributor
timestamp: January 10, 2023, 03:40:32 pm
content: Quote from: Siwastaja on January 10, 2023, 03:19:36 pmBut this requires to enforce memory ordering. Preferably and machine-independent via atomic_store_explicit(..., memory_order_release) in order to store the incremented index after the data, or alternative (for single-core machines only) by making both, the buffer and the index variable volatile.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 10, 2023, 03:45:31 pm
content: Use some memory ordering barrier and wrap the push / pop in functions.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 10, 2023, 04:04:27 pm
content: Quote from: Siwastaja on January 10, 2023, 03:19:36 pmIf you update the index before reading data, the "pusher" will think that the data position is empty and will overwrite it. You should always update indicesafteryou're done with data accesses - index update is a signal to the other side that the cell pointed to by the index is processed (read or written).Quote from: Siwastaja on January 10, 2023, 03:19:36 pmIt is. For erxample, if you access an index which is modified only by the other side in a loop, the compiler may decide that the index never changes and won't re-load it at every pass. Volatile will force re-loading.

author: gf
author_rank: Super Contributor
timestamp: January 10, 2023, 04:06:55 pm
content: Quote from: Siwastaja on January 10, 2023, 03:45:31 pmSure. I'm just surprised that people rather use volatile or proprietary barrier functions, although C11 provides an abstract (machine-independent) memory ordering model via the functions in stdatomic.h.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 10, 2023, 04:16:18 pm
content: Quote from: gf on January 10, 2023, 04:06:55 pmC programmers are slow to change their habits; they use what they learned to use, and it shows in the code. Many don't even use the useful C99 stuff such as stdint.h.

author: peter-h
author_rank: Super Contributor
timestamp: January 10, 2023, 04:18:10 pm
content: QuoteThe scenario is a foreground program (which is an RTOS task, like everything in this product after main() completes) puts TX data into that buffer, and then a TXE interrupt takes data out of it. And similarly, on the RX side, I have an RXNE interrupt putting data into the buffer and an RTOS task taking it out.Accompanying this I have functions like opqspace() and ipqcount() which do the obvious thing. Because my buffers don't have a "quesize" value (the difference between the pointers is the queue size) these functions do the pointer subtraction.This scheme wastes one byte compared to the one where you maintain a separate "quesize" value.These functions also need to do the job atomically, because the pointers chase each other round the buffer - example:Code:// Get the number of bytes available in the receive buffer for this port// Port is numbered 0 to 4, with 0 being the USB VCPuint16_t serial_get_ipqcount(uint8_t port){int32_t count;portENTER_CRITICAL();// prevent interrupts while updating the index// Port number is 1 to 4, so -1 to offset from 0count = (int32_t)serial_receive_buffer_rx_index[port-1] - (int32_t)serial_receive_buffer_get_index[port-1];if (count < 0){// rx index has wrapped and is now before the get indexcount = SERIAL_RX_BUFFER_SIZE + count;}if (count > (SERIAL_RX_BUFFER_SIZE-1)){// This should never be allowed to happencount = 0;serial_receive_buffer_rx_index[port-1] = 0;serial_receive_buffer_get_index[port-1] = 0;}portEXIT_CRITICAL();}return (uint16_t) count;}I am not calling any bit of FreeRTOS from an ISR, even though this is supposedly possible.I have changed the portENTER_CRITICAL() and portEXIT_CRITICAL() to block ints with a priority of >0 (was >5 previously as default on FreeRTOS, because the intention of these macros isonlyto stop an RTOS task switch, while allowing most/all interrupts). This in effect does a DI/EI (because I have no priority 0 interrupts) but will look into:QuoteThese appear to needCode:__asm("CPSID i");// ...some code which needs to be protected from interrupts...__asm("CPSIE i");Does that look right? I have seen other and much longer variations of the asm constructs. I don't think the above work on GCC v10. Like thisCode:/**\brief Enable IRQ Interrupts\details Enables IRQ interrupts by clearing the I-bit in the CPSR.Can only be executed in Privileged modes.*/__attribute__( ( always_inline ) ) __STATIC_INLINE void __enable_irq(void){__ASM volatile ("cpsie i" : : : "memory");}/**\brief Disable IRQ Interrupts\details Disables IRQ interrupts by setting the I-bit in the CPSR.Can only be executed in Privileged modes.*/__attribute__( ( always_inline ) ) __STATIC_INLINE void __disable_irq(void){__ASM volatile ("cpsid i" : : : "memory");}QuoteWe did this in that long "is compiler smart enough" thread, etc, but the reality is that practically nobody uses "volatile" on buffersI guess that's because most are declared "static", or "extern" at some place. But even where I have a buffer as a local variable in a function, it is still not getting removed.Makes me wonder how anybody's code ever worked... My suspicion is that compiler writers don't just want to break everybody's code.

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 04:32:04 pm
content: Quote from: peter-h on January 10, 2023, 04:18:10 pmMany choose not to use aggressive optimisation because of such issues with their code, or interaction with libraries, or because they are worried that the next compiler version will "improve" the optimisation.Then there's the definition of "worked". I have heard programmers sayin all seriousness"it works because the unit tests are showing a green light". It is just as well that there wasn't a cluebat within range!Then2there's the issue of how you test concurrent programs toproveconstraints aren't violated. Ask someone that has too much faith in unit testing to provide a test that reliablyfailsas a result ofincorrectuse of a database's ACID properties.

author: peter-h
author_rank: Super Contributor
timestamp: January 10, 2023, 05:10:00 pm
content: OK, but if somebody haswrittenchar fred ;and the compiler "somehow" determines that only first 10 bytes actually get accessed, e.g. because the only reference to fred that it finds isCode:for (i=0;i<10;i++)fred=0;would it not bemonumentally dumbto remove the other 246 bytes of fred and silently replace that definition withchar fred;I know nothing about the C language definition (which seems to be a virtual machine) but that would be a totally perverted thing to do.Apart from anything else, it would prevent you declaring a buffer bigger than you need, because the compiler might spot the excess.

author: Berni
author_rank: Super Contributor
timestamp: January 10, 2023, 05:41:39 pm
content: Yep compilers can be impressively smart and these sort of things are the reason why sometimes peoples code breaks when the optimizations are all turned on.At one point i even had the compiler optimize out a whole CRC checksum because it figured out that if i call the crc calculation function with these certain arguments, then the function ends up just computing something purely from flash memory and so the result will be the same every time. Seeing an excellent optimization opportunity the compiler went and calculated what the result of that function would be and the whole function was replaced by "return 0x75657678;" Obviously this defeats the whole point of a CRC check. Easy fix is to just define the target array as volatile in the function.There are many ways to skin a cat here. The best way just depends on what kind of cat you are skinning. Some of the particularly simple examples of letting an interrupt pump data to a main thread is as easy as a circular buffer and a few volatile keywords (as soon as you directly write interrupt service routines your code is machine specific anyway). Other examples of a OS running on a x86 machine with quad socket Xeon CPUs and you want threads running on cores located on two physically separate CPUs to play together you got a whole new kettle of fish, since here you now need architecture specific memory barriers to inform the CPU to not only write the new value out to memory but also do the whole cache invalidation dance across multiple CPU chips, so the special x86 instructions for doing that have to be placed by the compiler.Just use whatever fits the job. Don't over-complicate things for when the simple solution works fine.

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 05:43:24 pm
content: Quote from: peter-h on January 10, 2023, 05:10:00 pmThat's an amusing analogyWelcome to the wonderful world of undefined behaviour in C. GoogleC "nasal demons"for the start of an interesting trip.If there is undefined behaviour in a C program, then the entire program has no meaning. That's not completely unreasonable given the effects that can occur with incorrect pointer arithmetic. If the program has no meaning, it can do anything, and there is nothing to prevent that being "reformat the disc" or "cause dæmons to fly out of your nose". But there are many many things that are undefined behaviour in C, e.g. division by zero or adding 1 to the largest representable integer. Some compilers are better at detecting undefined behaviour than programmers, and take full advantage of that to, for example, remove entire blocks of code. Others just let the program execute the exploit code that has been "placed just beyond the end of the buffer"https://blog.regehr.org/archives/213https://markshroyer.com/2012/06/c-both-true-and-false/The language lawyers tend to claim undefined behaviour is a strength, and that programmers are faulty when they invoke it. That's right from one point of view , but is it beneficial?

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 06:06:22 pm
content: Quote from: gf on January 10, 2023, 04:06:55 pmThat's because "abstraction" also means "less control" (as in micro*control*ler). It's almost exactly the "silver bullet" from my earlier posts.In the particular case we are talking about here, i.e. ring buffer on a CM4, stdatomic probably stores/disables/restores interrupts globally (didn't try but how else?). That introduces a couple of extra instructions, but also pipeline flushes and dozens of cycles of latency for all interrupts, versus streamline execution and no extra latency.This is always the game, comfort versus control. Not all mcu applications are control-oriented, though, so in many cases you can get away with handwavings.JW

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 06:15:26 pm
content: Quote from: wek on January 10, 2023, 06:06:22 pmI am aware that you are unlikely to be a native English speaker."Abstraction" doesnotnean "less control". It means something like "more general" and "wider applicability".The game is not usefully characterised as "comfort vs control". Better charactisations are "predictable vs chaotic", or "right/correct vs wrong/incorrect".As someone (who?) once noted something to the effect that "if I had realised that it was acceptable for me to create a program that didn't work, then I could have given you the program much sooner and it would run much faster".

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 06:30:52 pm
content: Quote from: peter-h on January 10, 2023, 05:10:00 pmAnd why would you declare a buffer bigger than you need? If you do that, surely there's at least one occasion where you access the highermost element too?But rest assured, this type of optimization is highly unlikely to happen in C. The reason may be somewhat surprising - it's because there are no arrays in C.More precisely, there *are* array *declarators* (i.e. you can tell the compiler "allocate me memory for 256 chars and give me it's address, let's call it fred") but there are no array-member-specific operations. All fred (and, if you want to hear about being perverse, equally ii) occurences are immediately converted into *(fred + ii) and then used as such. And any pointer into the array shall be dereferenced correctly (provided correct target type and alignment), regardless of whether fred was used in its value's calculation or not. In other words, array members are accessed through pointers, so learning which part of array is redundant means the compiler needs to compute *all* pointers' values across the whole lifetime of the program, which is hard, unrewarding and it's unlikely generally available compilers will ever attempt to do this (although maybe the "omniscient" Hitech compiler (targeting originally 8-bit PIC and 8051, maybe still surviving as XC-8?) did it).JW

author: NorthGuy
author_rank: Super Contributor
timestamp: January 10, 2023, 06:37:42 pm
content: Quote from: tggzzz on January 10, 2023, 06:15:26 pm"Abstract" means "Detached" (from reality). For example, C standard is specified for an abstract machine which doesn't exist anywhere. This allows you program in C without knowing where your code is going to be executed.

author: wek
author_rank: Frequent Contributor
timestamp: January 10, 2023, 06:39:13 pm
content: I am aware that you are unlikely to be a native English speaker.Quote from: tggzzz on January 10, 2023, 06:15:26 pmSo let me try again:"Abstraction" almost always implies "less control" (as in micro*control*ler).More better? ;-)JW

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 10, 2023, 06:41:14 pm
content: Quote from: gf on January 10, 2023, 04:06:55 pmYeah. I've used that to implement a message queue library. You also get C threads in C11.But there is no surprise there. Many people writing C do not even use C99 features yet. Maybe in 2100 will C11 become popular.

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 08:21:12 pm
content: Quote from: SiliconWizard on January 10, 2023, 06:41:14 pmJust like COBOL66

author: peter-h
author_rank: Super Contributor
timestamp: January 10, 2023, 09:06:51 pm
content: Can I just ask some specific questions:Is either of these methods ok? I know the 2nd option is, but I haven't yet sorted out a "DI/EI"Code:// Copy the data to the circular bufferport--;// offset from 0for (uint16_t i = 0; i < length; i++){#if 1uint32_t index = serial_transmit_buffer_put_index;serial_transmit_buffer[index++] = data;serial_transmit_buffer_put_index = index % SERIAL_TX_BUFFER_SIZE;#elseDI();serial_transmit_buffer[serial_transmit_buffer_put_index++] = data;serial_transmit_buffer_put_index %= SERIAL_TX_BUFFER_SIZE;EI();#endif}Re disabling all ints, I see this in cmsis_gcc.h in my projectCode:/**\brief Enable IRQ Interrupts\details Enables IRQ interrupts by clearing the I-bit in the CPSR.Can only be executed in Privileged modes.*/__STATIC_FORCEINLINE void __enable_irq(void){__ASM volatile ("cpsie i" : : : "memory");}/**\brief Disable IRQ Interrupts\details Disables IRQ interrupts by setting the I-bit in the CPSR.Can only be executed in Privileged modes.*/__STATIC_FORCEINLINE void __disable_irq(void){__ASM volatile ("cpsid i" : : : "memory");}and it obviously compiles. Is there any downside to using these, within code where interruptsarenormally enabled?I have given up trying to construct DI/EI with portENTER_CRITICAL() and portEXIT_CRITICAL() by setting configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY=1. It ought to work if none of your interrupt sources have a priority of 0 but it broke a lot of stuff in a way so bizzare I could not even step through it, and it wasted a couple of days of my time.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 10, 2023, 09:19:40 pm
content: Quote from: tggzzz on January 10, 2023, 08:21:12 pmCOBOL666

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 10:26:06 pm
content: Quote from: SiliconWizard on January 10, 2023, 09:19:40 pmOK, OK so the autocorrupt on this fondleslab may not have Coral66 in its dictionary.Or maybe it does, and this is a long winded circuitous way of me reminding myself to understand how that language supported fixed point arithmetic and real-time behaviour.Or maybe C++666 really is the answer to the question.

author: uer166
author_rank: Super Contributor
timestamp: January 10, 2023, 11:00:51 pm
content: Quote from: tggzzz on January 09, 2023, 06:16:22 pmSame can be said about interrupts. I normally use NVIC to its' full extent and keep the inter-ISR comms atomic flags, but if youreallydon't want to have any issue with data consistency, locks, inversions, etc etc etc, use a cooperative scheduler without any interrupts. Of course that front-loads the scheduling and re-synchronization efforts, but hey life's hard.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 10, 2023, 11:04:19 pm
content: Quote from: tggzzz on January 10, 2023, 10:26:06 pmC++ is already evil.

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 11:06:55 pm
content: Quote from: uer166 on January 10, 2023, 11:00:51 pmI don't disagree, but then I'm a fan of xC on xCORE

author: tggzzz
author_rank: Super Contributor
timestamp: January 10, 2023, 11:08:20 pm
content: Quote from: SiliconWizard on January 10, 2023, 11:04:19 pmIf C++ is the answer then you need to "revisit" the question

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 11, 2023, 12:24:56 am
content: Quote- Richard Feynman

author: tggzzz
author_rank: Super Contributor
timestamp: January 11, 2023, 12:39:21 am
content: Quote from: SiliconWizard on January 11, 2023, 12:24:56 amIn that vein, the C++ Frequently Questioned Answers  seems relevanthttp://yosefk.com/c++fqa/

author: Sauli
author_rank: Contributor
timestamp: January 11, 2023, 05:42:58 am
content: QuoteIn your case the following code will work just fine:Code:// Copy the data to the circular bufferuint32_t index = serial_transmit_buffer_put_index[--port];// offset from 0for (uint16_t i = 0; i < length; i++){serial_transmit_buffer[index++] = data;index %= SERIAL_TX_BUFFER_SIZE;}serial_transmit_buffer_put_index = index;The code reading the data from the buffer does not need to know that there is new data in the buffer until it is all there.

author: ejeffrey
author_rank: Super Contributor
timestamp: January 11, 2023, 06:05:56 am
content: Quote from: peter-h on January 10, 2023, 09:06:51 pmIt's hard to say without seeing the rest of your code, but I think that if you:1) mark the buffer and put_index variables 'volatile' or in some other way implement a compiler level barrier to prevent re-ordering,2) follow the "only one task/interrupt reads and only one task/interrupt writes" restrictionthen I think either works.Note that the DI() / EI() functions *also* act as compiler barriers. They have to, to keep the compiler from moving "protected" code to outside the critical section.Saving the index update to the end as suggested by Sauli is also fine. It will also save some unnecessary stores to the volatile variable, which is also nice. Tthis is a good example of the reason you need volatile. If you don't specify serial_transmit_buffer_put_index as volatile, it's very likely that the compiler will optimizer will hoist the update out of the loop, either putting the update before or after the loop. If it goes after the loop that is fine, but if it goes before the loop then the ISR can read invalid data.Also, all the above commentary is correct: volatile is a *compiler* ordering directive. It forces the assembly/machine code to execute certain operations in the same order as the designated flow of the C code. This is fine on (most?) uniprocessor systems: pretty much every CPU architecture guarantees that a single CPU executes code "as if" each instruction is executed sequentially. In multi-core operations, volatile is basically never useful for inter-thread communication. You need atomic operations that include CPU memory fences to guarantee that other CPU cores see the accesses in the correct order. I am assuming you don't have multi-core systems in mind, but there are multi-core microcontrollers so it is certainly not something everyone can ignore.

author: Berni
author_rank: Super Contributor
timestamp: January 11, 2023, 06:37:34 am
content: Quote from: Sauli on January 11, 2023, 05:42:58 amYep this is also how i would recommend to do it.The whole idea is that until you move the write pointer over then the other thread will not try to access the extra data in the buffer, since it doesn't even know the data is there. So this means you are guaranteed to have time to finish filling the buffer. Then as the last step updating the write pointer will let the other thread know the data is there and ready to be used.The limitation here is that you are not allowed to call this function from multiple threads. If you do there is a possibility a thread switch happens during this function, leaving the buffer half written, then another thread calling the function again tramples over the half written data. The solution for this is placing the RTOS critical section around it, preventing a task switch from happening during the writing.(Not a problem since the function finishes quickly)Disabling interrupts is a more heavy handed method. This prevents all interrupts from executing and so wrecks the timing predictability of interrupts. This could cause a buffer overflow in something like a UART driver because it couldn't empty the receive buffer quickly enough. Also the RTOS makes use of interrupts in a few places.As for the volatile keyword not doing the job. You can have such cases even in single core MCUs if you make use of DMA. Those typically just need a few cache invalidation calls in the right spots or disabling caching for the region of memory where the DMA keeps its buffers. But that doesn't seam to be relevant to OPs case.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 08:12:58 am
content: QuoteIt gets used from different threads but with a different buffer index. This is for four serial ports (plus a USB VCP) and it is a bit nonsensical for different RTOS tasks to be both using a given UART (well, I've documented to not do that, although one task could do TX and another do RX quite reasonably).QuoteI did wonder about this but could not get my head around some dependencies. Let me post more detail. I have one RX function and one TX function. I am seeing an occassional loss of 1 byte by the TX function, so a 5000 byte block sent out actually sends out 4999 (sometimes 4998) out of the UART. Obviously there are multiple ways to debugging this, like transmitting the same block repeatedly and looking at the data with a digital scope, but it still takes me back to the code driving the UART.First I have taken on board but it has not changed anything:Code:// Actual buffer size is SERIAL_RX_BUFFER_SIZE-1 in order to allow buffer full/empty distinctionstatic uint8_t serial_receive_buffer;static uint8_t serial_transmit_buffer;volatile static uint32_t serial_receive_buffer_rx_index = { 0, 0, 0, 0 };volatile static uint32_t serial_receive_buffer_get_index = { 0, 0, 0, 0 };volatile static uint32_t serial_transmit_buffer_tx_index = { 0, 0, 0, 0 };volatile static uint32_t serial_transmit_buffer_put_index = { 0, 0, 0, 0 };volatile static bool serial_transmit_active = { false, false, false, false };This is the transmit function. It is fairly standard. Put the data into the circular buffer and if TX is not active, put 1 byte of it into the UART and kick off the interrupt-driven transmit.Code:/** Write a number of bytes to the transmit queue and start transmission if necessary* Returns false if there is insufficient space for the given length* Port is numbered 0 to 4, with 0 being the USB VCP** The port 0 processing is different for port 0 because there is flow control on tx (to PC)* which necessitates a circular buffer before it just like ports 1-4 have. The difference* is that with serial ports the flow control is the baud rate whereas with port 0 is it* how fast the PC application is reading the data out of the VCP.** This function is NON blocking, if the data fits into the TX buffer, otherwise no action.* However, it delays for the time g_485_DE_wait if the port is using RS485 mode (not always). Note* that interrupts are disabled during this time!**/bool serial_transmit(uint8_t port, uint8_t *data, uint16_t length){bool ok = false;if (port > 0 && port <= SERIAL_MAX_PORT){if (serial_get_opqspace(port) >= length){// Copy the data to the circular bufferport--;// offset from 0for (uint16_t i = 0; i < length; i++){#if 1uint32_t index = serial_transmit_buffer_put_index;serial_transmit_buffer[index++] = data;serial_transmit_buffer_put_index = index % SERIAL_TX_BUFFER_SIZE;#else__disable_irq();serial_transmit_buffer[serial_transmit_buffer_put_index++] = data;serial_transmit_buffer_put_index %= SERIAL_TX_BUFFER_SIZE;__enable_irq();#endif}__disable_irq();if (!serial_transmit_active){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. The read of SR (RMW actually) and a write to DR also clears pending TC int.serial_port_handles->Instance->SR &= ~(UART_FLAG_TC);serial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;serial_transmit_active = true;// Enable TX empty and TX complete interrupts__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TC);}__enable_irq();ok = true;}}else if (port == 0){// USB CDC port. Same as port 1-4 above but no need to mess with interrupts etc// The circular buffer gets copied to CDC in the USB RTOS task (in main.c)CDC_serial_transmit(data, length);ok=true;}return ok;}This is the transmit ISRCode:void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port){port--;// offset from 0// Is there anything left to send?if (serial_transmit_buffer_tx_index == serial_transmit_buffer_put_index){// Disable the TXEmpty interrupt__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);}else{// Send the next byte//huart->Instance->SR &= ~(UART_FLAG_TC);// now done only at the start of transmissionhuart->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;}}I reckon moving the write pointer only at the end should work, and it saves a little bit of exec time. Nothing actually happens at the UART until the whole block has been loaded into the buffer, anyway. And it appears I don't need to disable interrupts around the buffer fill.

author: Berni
author_rank: Super Contributor
timestamp: January 11, 2023, 09:22:12 am
content: Well those are specifics that could be down to an off by 1 error in some counter or loop, or something to do with the UART hardware of placing the data in there at the wrong time. For example you don't actually check if the UART TX buffer is empty before placing it in there. The buggy peripheral hardware on STM micros can be pretty terrible sometimes.Also i don't think it is sensible to disable global interrupts while configuring your TX interrupt. Typically this is done by simply enabling the interrupt as the very last step. Typically you also disable just that 1 interrupt you are configuring before starting to configure. In your case since you are enabling two it makes sense to enable the TX complete interrupt first, since enabling the TX empty interrupt will likely directly shoot you off into the interrupt handler(since the TX buffer is likely empty at that point), but the TX complete will only fire after you actually transmit something.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 10:02:56 am
content: Good points!There should not be a need to check TXE before placing the byte in, because the TX Complete ISR sets a flagCode:void serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){// Transmit completeserial_transmit_active[port-1] = false;// Clear and disable the TC interrupt//huart->Instance->SR &= ~(UART_FLAG_TC);// Not needed__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}I will continue to investigate. The "not needed" flag above doesn't seem to make much difference either way. One is really probing the peculiarities of the 32F417 chip at this level.

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 11:13:40 am
content: To give you an idea of how far this rabbit whole goes, in Uni we spent a year with Jean Bacon's book on the topic:https://www.amazon.co.uk/Operating-Systems-Concurrent-Distributed-International/dp/0321117891But... "You can get away with more on a single core.", stops being true once you have more than one device communicating. If you need consistent state between them, then you are back to Jean's book again.This is why in software engineering for the big distributed, multi-CPU, multi-server architectures, we use CPUs with specifically designed features, specifically designed MCU (Memory control unit) and specifical cache and shared cache mechanics in hardware . It's why we use general purpose OSs with stable efficient IPC like Linux. It's why we reuse large amounts of code in frameworks, middleware, distributed filesystems, databases, database clusters to take care of the most obvious of concerns. The only places that would be exclude a lot of this are in custom low latency / real time use cases where optimisation has removed as much of the bloat caused by the normal approach.Your usecase is all too familar to my own "hobby space" problem. Only in my case I have 4 or more I2S streams arriving on an SPI bus and I need to mix them together and forward them out another SPI port. It's callback and buffer soup. How do you handle a late buffer, how do you handle creeping buffers. If your buffer is 1ms and your streams arrive at 100uS, 500uS, 700uS, 900uS... there isn't enough time left to mix, process and send on before something somewhere runs out of data. I'm still trying to decide if I should actually "fix" anything. If the steam alignment like that does occurr, the easiest solution to fix it is to just ignore the 700uS and 900uS frames as being "late" and pick them up next time. The output buffer has an audible "CLICK" of 1ms silence and the buffers all align next time. I'm resisting the need to do any really synchronisation just by being quick. (Note buffer content alignment is not required, buffers can slip 1ms if they must just not very often!).

author: wek
author_rank: Frequent Contributor
timestamp: January 11, 2023, 11:22:05 am
content: Quote from: peter-h on January 11, 2023, 08:12:58 amShow us the ISR.JW

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 11:26:06 am
content: One thought when scanning your code. Appologies of this is miss placed.You seem to be moving singular "circular" buffers around and/or moving data between them. It might be advantagous to move the multiplicity and circularity up one level.So instead of "a" circular buffer for a port Rx/Tx use a queue of buffers. Obviously heavy on SRAM, but it makes the synchronisation easier with it being less finely grained. Instead of a circular array buffer, have an circular array OF buffers.In similar Rx/Tx In/Out etc. naming I prefer to use the term "readyBuffer" or "freeBuffer". Because all too often your Rx buffer in one function immediately becomes your Tx buffer in another. This adds unneeded confusion. "readyBuffer", such as inwhlie( readyBuffer == NULL ) {readyBuffer = ports->getReadyBuffer();}// blah blah blahports->releaseBuffer(readyBuffer);Then you can just expand the buffers/queues horizontally or vertically. More buffers or longer buffers etc.You can have a queue per port, or a combined queue for all ports and a struct which keeps track. Have a queue per output port as well. Then the main code is just routing synchronised queue pops and pushes. All the interrupt / IPC stuff is wrapped in your queue / buffer handling functions, your main code doesn't need to know.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 11:31:29 am
content: QuoteCode:void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port){port--;// offset from 0// Is there anything left to send?if (serial_transmit_buffer_tx_index == serial_transmit_buffer_put_index){// Disable the TXEmpty interrupt__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);}else{// Send the next byte//huart->Instance->SR &= ~(UART_FLAG_TC);// now done only at the start of transmissionhuart->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;}}void serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){// Transmit completeserial_transmit_active[port-1] = false;// Clear and disable the TC interrupthuart->Instance->SR &= ~(UART_FLAG_TC);__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}

author: wek
author_rank: Frequent Contributor
timestamp: January 11, 2023, 11:37:44 am
content: I mean the xxx_IRQHandler(), where these functions are called from.JW

author: tggzzz
author_rank: Super Contributor
timestamp: January 11, 2023, 12:21:34 pm
content: Quote from: paulca on January 11, 2023, 11:13:40 amYes, it is indeed a deep rabbit hole, one that is too often hidden by the layers of infrastructure between the user applications and the holeYes,thatisan excellent book, which covers very important topics that are too often omitted from university courses. (Especially in favour of which button to push in IDEs and the currently fashionable language)Which university and course used Bacon's book?

author: Sauli
author_rank: Contributor
timestamp: January 11, 2023, 12:44:14 pm
content: I am fairly certain that you don't need to use the TC interrupt at all. At least I have not used it in any STM32 code I have written.In the buffer write routine you can replace this:Code:__disable_irq();if (!serial_transmit_active){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. The read of SR (RMW actually) and a write to DR also clears pending TC int.serial_port_handles->Instance->SR &= ~(UART_FLAG_TC);serial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;serial_transmit_active = true;// Enable TX empty and TX complete interrupts__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);__HAL_UART_ENABLE_IT(port_handles, UART_IT_TC);}__enable_irq();with this:Code:// Enable transmit interrupt if it is disabled.if( __HAL_UART_GET_IT(serial_port_handles, UART_IT_TXE) == RESET){__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);}Leave the transmit interrupt handler as is. You don't need the transmit complete interrupt handler or the serial_transmit_active flags.I don't use HAL, so my code may not be 100% correct, but you get the idea.

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 12:54:53 pm
content: Quote from: tggzzz on January 11, 2023, 12:21:34 pmIt was the Open Uni. Course was M301 Concurrent software development and OOP. The course centred around 2 core items. Jean's book for theory and a rather contrived distributed banking system as individual java apps. It was a little light on detailed coding semantics, such as while it discussed exception propagation the examples and exercises where far too easy and single dimensional. It did have to cover a lot of other "box ticking" topics for the degree requirements, like profressional ethics, security, design and project management methodologies. In a year.The OU had the advantage of being "agile" and able to write and present courses much easier and faster than brick unis. I know the Queen's University under grad computing where still doing case studies on Mini-Computers and courses written on paper in 1983. Still passing out the same photocopied notes 20 years later. The OU got grief for still presenting a course (Microprocessor based systems) from 1993 which has PIC microcontroller practical module and people said it was out of date. Hmmm. How things change. Now with the "IoT" and mobile internet MCUs are really back in fashion!

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 01:13:18 pm
content: A caution with the STM32 having an NVIC and I believe interrupts are interruptable.There is an application's note somewhere around correctly disabling interrupts or modifying the int mask. Both of which are themselves critical zones subject to out-of-ordering.The issue is, there exists a potential for an interrupted function to have modified the interrupt mask register, but still get interrupted. Combinations exist where, when you disable the interrupts, carry out your critical task and re-enable them, that you are actually re-enabling them for another interrupted function as well, causing concurrency risks.The basic idea in the note was to save the interrupt mask in a local variable, change it to suite your ciritcal zone, then restore it again. This may, or may not be what the HAL functions do, it's unlikely it's what the macros do.

author: tggzzz
author_rank: Super Contributor
timestamp: January 11, 2023, 01:26:29 pm
content: Quote from: paulca on January 11, 2023, 12:54:53 pmSuch exercises have to be simplistic, since the objectives have to be understood within a reasonable time and the code marked within a short time. Nonetheless it should be possible to illustrate the core issues.When interviewing I used a simplistic example to assess a candidate's skills and to introduce them to how the company worked. The example was "a toy manufacturer sells cars pushed around roads on a floormat, and wishes to add a working traffic lights accessory. What would you suggest?" I told them there was no single right answer. I wanted to see them ask extra questions to refine the problem statement, and to present a range of possible solutions.The range of responses was illuminating inbothdirectionsQuoteI was horrified and delighted how little had changed in the embedded arena three decades after I started: still C on 8-bit micros. The differences, including going to 32 bit were merely smaller, faster, cheaper.The main differences I could see were vastly improved ADCs and DACs, and power reductions to the nanopower/energy harvesting level. (And that I could afford the dev kit!)

author: Berni
author_rank: Super Contributor
timestamp: January 11, 2023, 01:28:45 pm
content: Yep a lot of modern MCUs support nestable interrupts.This does bring a few new considerations on what is thread safe code. But you can still disable nestable interrupts to get the more classical one after the other interrupt execution. Also typically an interrupt of higher priority is needed to interrupt an interrupt, so if you keep all your interrupts on the same priority level they never get interrupted. Yet you could still make one interrupt higher priority where you do some tight timing stuff, just be careful what you write in it.Another way to get around the interrupt mask writing race condition is to instead configure the peripheral to not generate an interrupt while leaving the interrupt channel on the interrupt controller always enabled.

author: wek
author_rank: Frequent Contributor
timestamp: January 11, 2023, 01:53:31 pm
content: Quote from: paulca on January 11, 2023, 01:13:18 pmCan you please provide a link.Thanks,JW

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 02:24:32 pm
content: Quote from: wek on January 11, 2023, 01:53:31 pmCan't find the original, which was either an ST App Note or a ST community response for an ST employee, but here is an example and the basic jist.https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/

author: paulca
author_rank: Super Contributor
timestamp: January 11, 2023, 02:34:33 pm
content: Are STM32 interrupts re-enterable and recursive? That often creates some fun too. Recursive when an interrupt handlers raise interrupts. Re-entrant in that the same ISR can be entered multiple times and be servicing multiple interrupts at the same time within the same code. The compiler will give you a fresh stack and a new set of locals, but everything else is in god knows what state. Avoiding recursive interrupts is probably a way to make your life easier and re-entrancy can be controlled with semaphores if required.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 03:12:26 pm
content: QuoteThe problem is that I am trying to not post too much code, because of I do, I will get no responseThe TC interrupt is used to turn off an RS485 driver, on two of the four (or five) serial ports. That is a standard way to do that.This is thecompleteTC ISR:Code:// This ISR turns off the 485 driver on p3/p4// https://peter-ftp.co.uk/screenshots/202210015115900110.jpgvoid serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){if ( (port == 3) || (port == 4) ){// If this is an RS485 port and auto driver mode is set, disable the driver nowif (serial_driver_mode[port-1] == 2){serial_485_driver_control_internal(port, 0);}}// Transmit completeserial_transmit_active[port-1] = false;// Clear and disable the TC interrupthuart->Instance->SR &= ~(UART_FLAG_TC);__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}The function serial_485_driver_control_internal() just waggles some GPIO.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 11, 2023, 04:19:39 pm
content: Quote from: peter-h on January 11, 2023, 08:12:58 amObviously it's not because of loss of synchronization between the buffer and the index. So, disabling/not disabling interrupts has nothing to do with the problem.First, verify your receiver. If the problem is with the receiver, you'll waste the rest of eternity debugging your code for nothing. Is there any flow control? Is it working Ok? Does slowing down the baud rate fixes the errors? Do the errors augment if you push the baud rate to the limit?Verify the value of the indices at the end. This way you'll find out whether they incremented 5000 times or less. If less, the problem is with the code that puts the data into the buffer. If 5000, then the problem is with the transmitting code.Then start sending consecutive bytes - 0x00, 0x01, 0x02 etc. in a loop. Inspect what you receive on the other end. See where the losses are - at the beginning, at the end, at the buffer rollover points (change buffer size to test this) etc.

author: Sauli
author_rank: Contributor
timestamp: January 11, 2023, 04:23:15 pm
content: The problem with the serial_transmit_active flag is that it is still set for one character time after the TXE interrupt has been disabled, and the serial_transmit function wont trigger a new transmit during that time. I would still recommend using the TXE enabled bit to do that decision. You can keep the TC interrupt for turning off the RS485 driver.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 04:50:23 pm
content: No flow control; I am sending data at a rate chosen so the baud rate is not limiting. I am also watching it on a scope and timing-wise it looks OK.Indeed my debug plan is to later just keep repeating the same packets and watch the data on a scope. It's more complicated though: with 5000 byte packets, the loss (if there is any) is~1 byte, but with short packets e.g. 100 bytes the loss can be much bigger, which I don't get at all.I know the number of bytes is correct all the way through the chain, there and back (it actually goes RS232 in, 38.4k, via one box, over ETH, back via another box, RS232 loopback 115.2k, back over ETH, and back out via RS232 at 38.4k. I have byte counts at different parts of that chain and all of them are 100% rock solid, hence the data loss must be at the very end, at the final serial TX function. The equipment generating the test data and receiving it and comparing it is a custom built programmable protocol converter which I've been selling for 30 years and that works error-free if I do just a piece of wire for loopback. Yes; the timing is not the same then and there could be an issue; I will re-test with lower baud rates.This discussion has raised great points. I will test moving that flag:QuoteIt's a good point. However I don't getQuoteDo you mean using the TXE irq enable bit as the flag?

author: NorthGuy
author_rank: Super Contributor
timestamp: January 11, 2023, 05:26:22 pm
content: Quote from: peter-h on January 11, 2023, 04:50:23 pm38400 is slow. Therefore, when you send a lot, the buffer is always full. With 100 characters the buffer is half full (or half empty) and never get full.What characters do you lose? At the beginning? At the end? In the middle?How many characters do you lose when you transmit 100 characters?Quote from: peter-h on January 11, 2023, 04:50:23 pmSo, you're saying that you verified that after you transmit 5000 bytes the "serial_transmit_buffer_tx_index" is equal to (5000 %= SERIAL_TX_BUFFER_SIZE), but you only 4999 characters appear on the physical TX line?

author: Sauli
author_rank: Contributor
timestamp: January 11, 2023, 05:59:32 pm
content: QuoteYes, that is exactly what I mean.

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 07:00:19 pm
content: I tried that; no difference to the errors. AlsoQuotewould that explain a data loss? ISTM that this merely produces a little gap in the transmitted data.BTW the issue with large data loss on small packets was a redherring - caused by too short a timeout on the loopback test rig. Now I cannot reproduce any data loss on short packets.The data loss is 1 or 2 bytes.EDIT: dropping all baud rates a long way, to 9600 baud, doesn't improve things, so I think the data loss is a "structural" thing.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 11, 2023, 09:40:31 pm
content: Quote from: peter-h on January 11, 2023, 07:00:19 pmMost likely a problem with how you handle the hardware, such as killing the transmitter before the last character gets transmitted or something of that sort.

author: wek
author_rank: Frequent Contributor
timestamp: January 11, 2023, 10:11:03 pm
content: This is why I asked for the ISR.JW

author: peter-h
author_rank: Super Contributor
timestamp: January 11, 2023, 10:27:56 pm
content: Code:// Common interrupt handler for all USART interruptsstatic void usart_common_IRQHandler(UART_HandleTypeDef *huart, uint8_t port){// check if we are here because of RXNE interruptif (huart->Instance->SR & USART_SR_RXNE){//if RX is not emptyserial_receive_irq_handler(huart, port);}// check if we are here because of TXEIE interruptif (huart->Instance->SR & USART_SR_TXE){//if TX is emptyserial_transmit_irq_handler(huart, port);}// check if we are here because of the TC interruptif (huart->Instance->SR & USART_SR_TC){//if TX completeserial_transmit_complete_irq_handler(huart, port);}}

author: wek
author_rank: Frequent Contributor
timestamp: January 11, 2023, 10:59:55 pm
content: One possible problem with this may be, that if "at" this line:> // check if we are here because of the TC interrupt(i.e. between the blocks checking TXE and TXC), a higher-priority interrupt kicks in and lasts more than around two frames, TC gets set and if this is RS485, it switches off the driver.It may happen at any point so if you consistently see 1 character missing it's not likely the issue, but something worth thinking about IMO.Also, don't RMW the status register. Again it's unlikely to be the culprit here (I don't see any mechanism how it would be), but, just don't. They are rc_w0 for a reason.The problem may lay elsewhere, so as others said, perhaps start finding out which character is missing - first, last, whichever.JW

author: Sauli
author_rank: Contributor
timestamp: January 12, 2023, 06:10:52 am
content: QuoteIt may not explain the data loss, but should nevertheless be fixed. It is not too difficult to imagine a scenario where this can cause problems. For instance transmit is active while a task is writing a new message to the transmit buffer. However this task is pre-empted by a higher priority task. By the time the task resumes writing the data we are in between the last TXE interrupt and the TC interrupt. The data written to the buffer between these interrupts then remains in the buffer until the next time the buffer write routine is called. Your application may be transmitting constantly so there is only a small gap, but I think most applications only transmit occasionally and the consequences are more serious.

author: Berni
author_rank: Super Contributor
timestamp: January 12, 2023, 06:48:05 am
content: The best way to test this is to rewrite your transmitting routine into a blocking one.This way you can fill up the buffer, then call the routine to feed it to the UART in a loop. This keeps your program functionally the same apart from the task taking longer to execute since it has to wait for the data to be pumped out. This removes all the multitasking stuff and interrupts, as well as being easier to step trough with a debugger.Then once that works flawlessly start gradualy moving things into the multithreaded way. Once you pull interrupts into the mix debugging via JTAG becomes more difficult. So it is a good idea to start using GPIO pins as "interrupt indicators" by setting a pin to 1 at the start of the interrupt and setting it back to 0 at the end of it. This way you can hook a scope up to the pins and have a nice visual diagram of exactly when and how long interrupts are executing. The GPIO write is also very fast so it causes minimal effect on the timing.

author: tggzzz
author_rank: Super Contributor
timestamp: January 12, 2023, 09:03:34 am
content: Quote from: Berni on January 12, 2023, 06:48:05 amUsinganyform of debugger a real-time system is an exercise in HeisenbugsThe GPIO concept works well provided flipping one and only one bit is fast. Some MCUs have specific instructions to flip individual io bits.It can usefully be extended to marking the begin/end of anything interesting, e.g. put/get FIFO operations. If you can read back the state of a GPIO pin, you may find it useful to "if the bit is already set by something else, then I know I'm in trouble"; the potential problems with that are obvious! Reading the generated object code is a useful skill!Since the event you are looking for will be rare and complex, you may find a logic analyser with good arming/trigger/filtering is necessary to spot "if...and...then...but not..." events on the pins. That excludes anything which simply waits for a one input condition, captures everything, and relies on post-processing to filter out the 99.99999% of boring captures.

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 09:23:43 am
content: QuoteThis is just RS232.QuoteGood point. I've been aware of this but this is someone else's code from ~2 years ago. But that RMW code comes from ST Cube MX's code generator AFAIK. So I changed it to thisCode:__disable_irq();if (!kde_serial_transmit_active){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. This is done with a read of SR and a write to DR.//kde_serial_port_handles->Instance->SR &= ~(UART_FLAG_TC);// This does a read of SR without doing RMW which the line above didkde_serial_port_handles->Instance->SR;// This is a write of DRkde_serial_port_handles->Instance->DR = kde_serial_transmit_buffer[kde_serial_transmit_buffer_tx_index++];kde_serial_transmit_buffer_tx_index %= KDE_SERIAL_TX_BUFFER_SIZE;kde_serial_transmit_active = true;// Enable TX complete and TX empty interrupts. The order may matter!__HAL_UART_ENABLE_IT(kde_serial_port_handles, UART_IT_TC);__HAL_UART_ENABLE_IT(kde_serial_port_handles, UART_IT_TXE);}__enable_irq();I hope thatkde_serial_port_handles->Instance->SR;does actually do a read of SR; I have used this elsewhere. A funny feature of C!and in the TC ISR I now haveCode:// Clear and disable the TC interrupt. This is supposed to be done by a read of SR and a write to DR// but in this case we have nothing to write to DR so we clear the TC bit by writing a 0 to it but// avoiding RMW - see http://efton.sk/STM32/gotcha/g9.htmlhuart->Instance->SR = ~(UART_FLAG_TC);__HAL_UART_DISABLE_IT(huart, UART_IT_TC);So far, no change toexistence ofthe 1 byte loss. It happens once every 50-100 1k packets. But, overall the reliability has improved with every change I have done here, which is kind of weird. Normally there is onlyonething causing somethingRemoving the SR RMW ops has significantly improved reliability, practically removing errors from shorter packets (which would be more likely cases where TX gets disabled and needs re-enabling by loading a byte into DR.I've discovered that the loss is possibly related to whether the packets sent are smaller than a particular buffer along the data path. This is currently 512 bytes. That in turn might point to something way more complex which I can't really debug.I am now running with the above-suggested code which doesn't need ints disabled during the copy (in both tx and rx instances) and that seems to run wellCode:uint32_t index = serial_receive_buffer_get_index;data = serial_receive_buffer[index++];serial_receive_buffer_get_index = index % SERIAL_RX_BUFFER_SIZE;QuoteA DSO is OK. I have a LeCroy 3034. That can decode RS232, SPI, etc, but is a PITA to set up for each such thing.

author: tggzzz
author_rank: Super Contributor
timestamp: January 12, 2023, 10:13:38 am
content: Quote from: peter-h on January 12, 2023, 09:23:43 amPossibly, possibly not. I'm not familiar with the arm/trigger on that, but I doubt it has any filtering. The serial decode capabilities are irrelevant. A lot will depend on whether you canpredictthe possible cause of the problem, detect it in software, and flip a GPIO bit when it occurs.While I'm sure that scope is better than nothing, agoodlogic analyser can have beneficial capabilities for spotting rare complex patterns and complexsequences, and ignoring everything else.

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 03:31:56 pm
content: I've had an idea. I can't get my head around the exact operation of this function, but have a feeling that it contains some subtle gotcha.Code:// Get the number of bytes free in the transmit buffer (how much more data can be queued for transmission)// Port is numbered 0 to 4, with 0 being the USB VCPuint16_t serial_get_opqspace(uint8_t port){uint16_t space = 0;// total queue sizeint32_t count;if (port > 0 && port <= SERIAL_MAX_PORT){__disable_irq();// prevent interrupts while updating the index// Port number is 1 to 4, so -1 to offset from 0count = (int32_t)serial_transmit_buffer_put_index[port-1] - (int32_t)serial_transmit_buffer_tx_index[port-1];if (count < 0){// put index has wrapped and is now before the tx indexcount = SERIAL_TX_BUFFER_SIZE + count;}if (count > (SERIAL_TX_BUFFER_SIZE-1)){// This should never be allowed to happencount = 0;serial_transmit_buffer_tx_index[port-1] = 0;serial_transmit_buffer_put_index[port-1] = 0;}// Space is buffer size - count -1 and the -1 is needed because full buffer = buffer_size-1space = (SERIAL_TX_BUFFER_SIZE - count) -1;__enable_irq();}else if (port == 0){// USB CDC portspace = CDC_get_opqspace();}return space;}The function returns the space in a circular buffer. It does it by subtracting the difference between the two pointers, and there is no "queue size" variable which would be the other way to do it (that's how I normally do it because you get an immediate "queue size" value, you don't waste 1 byte buffer space, but you need DI/EI around it).The disable/enable interrupts macros above were originally not present but I put them in because I could not be sure.In particular I wonder ifcount = SERIAL_TX_BUFFER_SIZE + count;is correct and there isn't supposed to be a +1 in there.The CDC mode above (at the bottom) uses this code to get the number of bytes in the bufferCode:int32_t count = (int32_t) cdc_transmit_buffer_put_index - (int32_t) cdc_transmit_buffer_tx_index;if (count < 0) {// tx index has wrapped and is now before the get indexcount = CDC_TX_BUFFER_SIZE + count;}and to get space you then doCode:uint16_t space = CDC_TX_BUFFER_SIZE - CDC_get_opqcount() -1;where opqcount is the CDC code above.

author: paulca
author_rank: Super Contributor
timestamp: January 12, 2023, 03:49:18 pm
content: Code:int32_t count;

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 03:53:51 pm
content: I don't get itOK; maybe you meanspace = (SERIAL_TX_BUFFER_SIZE - count) -1;where space is a uint16_t but count is a int32_t, but the real world values should never produce a wrong value for space. Or maybe they can?If SERIAL_TX_BUFFER_SIZE == count, that would break due to the -1, but count should never exceed SERIAL_TX_BUFFER_SIZE - 1.

author: Sauli
author_rank: Contributor
timestamp: January 12, 2023, 06:45:16 pm
content: PeterIn your reply #29 you posted this function:Code:// Get the number of bytes available in the receive buffer for this port// Port is numbered 0 to 4, with 0 being the USB VCPuint16_t serial_get_ipqcount(uint8_t port){int32_t count;portENTER_CRITICAL();// prevent interrupts while updating the index// Port number is 1 to 4, so -1 to offset from 0count = (int32_t)serial_receive_buffer_rx_index[port-1] - (int32_t)serial_receive_buffer_get_index[port-1];if (count < 0){// rx index has wrapped and is now before the get indexcount = SERIAL_RX_BUFFER_SIZE + count;}if (count > (SERIAL_RX_BUFFER_SIZE-1)){// This should never be allowed to happencount = 0;serial_receive_buffer_rx_index[port-1] = 0;serial_receive_buffer_get_index[port-1] = 0;}portEXIT_CRITICAL();}return (uint16_t) count;}The comment says it returns the number of available bytes in the buffer. What it actually returns is the number of used bytes in the buffer.The check for count > (SERIAL_RX_BUFFER_SIZE-1) is useless because the indexes can never reach SERIAL_RX_BUFFER_SIZE. Unless you suspect that somewhere somehow a bigger value is written to the indexes, as indeed happens if you keep using code like: serial_transmit_buffer[serial_transmit_buffer_put_index++] = data;

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 07:40:27 pm
content: Yes; it returns the number of bytes available to be read outI don't understand the bit about dodgy code but guess it is to do with undefined instruction sequence, no? Now I am using the other version which does not need interrupts disabled:Code:uint32_t index = serial_transmit_buffer_put_index;serial_transmit_buffer[index++] = data;serial_transmit_buffer_put_index = index % SERIAL_TX_BUFFER_SIZE;I've made a useful discovery: adding a byte count into the TX code wherever DR gets loaded (only 2 places) shows therightnumber of bytes is getting loaded.So the UART TX DR must be loaded somewhere when TXE=0. One place is the TX ISR which is hardly possible. The other place is the TX "kick off" code, so I put in a TXE test and the missing byte is no longer missingCode:__disable_irq();// This code gets run only if TX not active and the UART can accept a byteif ( (!kde_serial_transmit_active) && ((kde_serial_port_handles->Instance->SR & USART_SR_TXE)!=0) ){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. This is done with a read of SR and a write to DR.// This does a read of SR without doing RMW which the line above did//serial_port_handles->Instance->SR;// already done in the if( above// This is the write of DRserial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];tx_bytecount++;serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;serial_transmit_active = true;// Enable TX complete and TX empty interrupts. The order may matter!__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TC);__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);}__enable_irq();I think someone above suggested this...

author: nctnico
author_rank: Super Contributor
timestamp: January 12, 2023, 08:37:56 pm
content: I don't know the internals but perhaps the problem is using the TC interrupt which fires too early. I'd check which interrupt fires when the UART is really ready to accept to new character into the TX buffer and not use any other interrupt.

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 08:57:17 pm
content: I think the normal TX interrupt (triggered by TXE) is running OK.This issue was in the code which restarts TX after TX "ended", which happens when you get the TXE interrupt but there is no data in the TX queue. Then you have to disable the TX interrupts, and when new TX data is available, you have to restart the TX process by re-enabling TX interrupts and placing a byte into the UART.

author: nctnico
author_rank: Super Contributor
timestamp: January 12, 2023, 09:29:55 pm
content: Quote from: peter-h on January 12, 2023, 08:57:17 pmIn such a case you'd need to load the byte into the UART first and then enable the interrupt. Otherwise the interrupt will fire immediately which creates a race condition.

author: peter-h
author_rank: Super Contributor
timestamp: January 12, 2023, 09:46:33 pm
content: Yes, as in thisCode:__disable_irq();// This code gets run only if TX not active and the UART can accept a byteif ( (!serial_transmit_active) && ((serial_port_handles->Instance->SR & USART_SR_TXE)!=0) ){// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TC. This is done with a read of SR and a write to DR.// This SR read was already done in the if() line above// and this is the write of DRserial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];serial_transmit_buffer_tx_index %= SERIAL_TX_BUFFER_SIZE;serial_transmit_active = true;// Enable TX complete and TX empty interrupts. The order may matter!__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TC);__HAL_UART_ENABLE_IT(serial_port_handles, UART_IT_TXE);}__enable_irq();

author: NorthGuy
author_rank: Super Contributor
timestamp: January 12, 2023, 10:39:39 pm
content: Quote from: peter-h on January 12, 2023, 09:46:33 pmWhat if it is running? Like you have loaded the last character from the previous transmission, done with it, and now start a new transmission. Then you'll lose the first byte.

author: peter-h
author_rank: Super Contributor
timestamp: January 13, 2023, 07:23:44 am
content: It should not be running because the serial_transmit_active flag gets cleared by the TC ISR:Code:void serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){// Transmit completeserial_transmit_active[port-1] = false;// Clear and disable the TC interrupt. This is supposed to be done by a read of SR and a write to DR// but in this case we have nothing to write to DR so we clear the TC bit by writing a 0 to it but// avoiding RMW - see http://efton.sk/STM32/gotcha/g9.htmlhuart->Instance->SR = ~(UART_FLAG_TC);__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}

author: Sauli
author_rank: Contributor
timestamp: January 13, 2023, 07:25:47 am
content: QuoteOk, I misunderstood the comment.I am posting here the relevant pieces of code which is my view of how this transmit stuff should be done. Experience tells me that in so many lines there is bound to be at least one error. You are welcome to point it out.Code:// Get the number of bytes stored in the receive buffer for this UART port.// UART ports are numbered from 1 on (port 0 is the USB VCP).uint16_t serial_get_ipqcount(uint8_t port){int32_t count;// These arrays are for the UART ports which are numbered starting from 1, so offset the port by -1.count = (int32_t)serial_receive_buffer_rx_index[port-1] - (int32_t)serial_receive_buffer_get_index[port-1];if (count < 0){// rx index has wrapped and is now before the get indexcount += SERIAL_RX_BUFFER_SIZE;}return (uint16_t)count;}// Get the number of free bytes in the transmit buffer for this UART port.// UART ports are numbered from 1 on (port 0 is the USB VCP).uint16_t serial_get_opqcount(uint8_t port){int32_t count;// These arrays are for the UART ports which are numbered starting from 1, so offset the port by -1.count = (int32_t)serial_transmit_buffer_put_index[port-1] - (int32_t)serial_transmit_buffer_tx_index[port-1];if (count < 0){// rx index has wrapped and is now before the get indexcount += SERIAL_RX_BUFFER_SIZE;}// Maximum number of free bytes is SERIAL_TX_BUFFER_SIZE - 1.return (uint16_t)(SERIAL_TX_BUFFER_SIZE - count - 1);}/** Write a number of bytes to the transmit queue and start transmission if necessary* Returns false if there is insufficient space for the given length* Port is numbered 0 to 4, with 0 being the USB VCP** The port 0 processing is different for port 0 because there is flow control on tx (to PC)* which necessitates a circular buffer before it just like ports 1-4 have. The difference* is that with serial ports the flow control is the baud rate whereas with port 0 is it* how fast the PC application is reading the data out of the VCP.** This function is NON blocking, if the data fits into the TX buffer, otherwise no action.* However, it delays for the time g_485_DE_wait if the port is using RS485 mode (not always). Note* that interrupts are disabled during this time!**/bool serial_transmit(uint8_t port, uint8_t *data, uint16_t length){bool ok = false;if (port > 0 && port <= SERIAL_MAX_PORT){if (serial_get_opqspace(port) >= length){// Use a local write index.uint32_t index = serial_transmit_buffer_put_index[ --port];// Copy the data to the circular bufferfor (uint16_t i = 0; i < length; i++){serial_transmit_buffer[index++] = data;if( index >= SERIAL_TX_BUFFER_SIZE){index = 0;}}// Only update the public write index after all data copied.serial_transmit_buffer_put_index = index;// No need to trigger transmit unless transmit interrupt is disabled.if( (serial_port_handles->Instance->CR1 & UART_IT_TXE) == 0){// We do not enable TC interrupt for RS485 ports here. If we did, there would be a one character time deadline// for servicing the transmit interrupts. If that deadline was missed, there would be a TC interrupt.// Also, we do a read-modify-write for CR1 to enable TXE interrupt. Protect that against simultaneous// modification in interrupt service by using read/write exclusive.while( __STREXW( __LDREXW( &serial_port_handles->Instance->CR1) | UART_IT_TXE, &serial_port_handles->Instance->CR1) == 1);}ok = true;}}else if (port == 0){// USB CDC port. Same as port 1-4 above but no need to mess with interrupts etc// The circular buffer gets copied to CDC in the USB RTOS task (in main.c)CDC_serial_transmit(data, length);ok=true;}return ok;}void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port){port--;// offset from 0// Is there anything left to send?if (serial_transmit_buffer_tx_index == serial_transmit_buffer_put_index){// Disable the TXEmpty interrupthuart->Instance->CR1 &= ~(UART_IT_TXE);// If sending to a RS485 port, enable TC interrupt.// If we are already late, the interrupt is triggered immediately.// The TC flag in the status register is cleared by a read SR write DR sequence.// Therefore we do not have to clear the flag because if it was set before,// it was cleared when the last character was written to the DR.if( (port == 2) || (port == 3)){huart->Instance->CR1 |= UART_IT_TC;}}else{// Send the next bytehuart->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];if( serial_transmit_buffer_tx_index >= SERIAL_TX_BUFFER_SIZE){serial_transmit_buffer_tx_index = 0;}}}// This ISR turns off the 485 driver on p3/p4// https://peter-ftp.co.uk/screenshots/202210015115900110.jpgvoid serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){if ( (port == 3) || (port == 4) ){// If this is an RS485 port and auto driver mode is set, disable the driver nowif (serial_driver_mode[port-1] == 2){serial_485_driver_control_internal(port, 0);}}// RS485 Transmit complete// No need to use this flag for transmit interrupt enable control// in transmit buffer write function. Other purposes for RS485// control may exist.serial_transmit_active[port-1] = false;// Disable the TC interrupthuart->Instance->CR1 &= ~(UART_IT_TC);}

author: peter-h
author_rank: Super Contributor
timestamp: January 13, 2023, 09:06:27 am
content: You have a lot of code there - thank you! I will go through it today.Never heard of __STREXW - an interesting method. What is the advantage over some form of DI/EI?I have taken note of your use of TXE in CR1 as the "tx running" flag, instead of my serial_transmit_active flag. This I think is more elegant because it keeps the flag in just one place, but I am not doing that just yet because of possible effects on the RS485 driver enable operation, which was thoroughly tested in the past and which is not only difficult to test but is also configurable (one of the ports is 2 wire 485 and the other is 4 wire 422/485). I also no longer have any of that 1-byte data loss.Everything else I implemented - thank you! Reliability is very high now.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 10:31:49 am
content: Well, I followed an implemented everything I could suggested here. The biggest difference was using the "TX int enabled" status bit as the flag which determines whether to re-enable TX ints.Why? I don't know because the flag I used previously was only ever set/reset inside an ISR.This suggests that the TX ISR was being interrupted by some other ISR. The 32F4 interrupt system is unbelievably complicated and I totally struggle trying to understand it. Reading stuff from others struggling online suggests I am not alone. In particular I wonder if a TX ISR for say UART2 can be interrupted by a TX ISR for UART1, or UART3. In previous CPUs I have used this was not possible; the entry to an ISR did a "DI".So often I would do an "EI" near the start of a 100Hz timer ISR which would otherwise block ints for 10ms.But even if one UART ISR was interrupting another UART ISR, it doesn't explain it because each ISR is supplied (from the interrupt handler) with the serial port index, so all data is kept separate. Maybe there is a tiny window where things can go wrong. For example an int is cleared by reading SR and writing DR. This is inside an ISR. But if another ISR is interrupting this one, maybe ... ? In foreground code I use__disable_irq()which isCode:/**\brief Disable IRQ Interrupts\details Disables IRQ interrupts by setting the I-bit in the CPSR.Can only be executed in Privileged modes.*/__STATIC_FORCEINLINE void __disable_irq(void){__ASM volatile ("cpsid i" : : : "memory");}and maybe this is needed also inside a UART ISR? That would be quite a different idea from all my previous experience.There is still a very rare 1 byte loss, which would be nice to fix. Perhaps one every 10MB. Could be anywhere, of course, but losing exactly 1 byte is curious. But is probably in the UART stuff because one of the serial ports is a USB VCP and that runs fine. Of course USB VCP (CDC profile) is unbelievably complex too; it was only with the help of several people that I was able to fix the ex-ST USB code properly. Byte counters in various parts of the code show no loss and I have narrowed it down to the TX code. Even if I set up a data analyser to pick up a 1999 byte packet against a million 2000 byte packets, the UART is all internal to the CPU so cannot be probed. And 100% reliability on non error corrected serial comms is impossible anyway.

author: cv007
author_rank: Frequent Contributor
timestamp: January 17, 2023, 12:52:14 pm
content: QuoteWhether its now among your set of problems or not, blindly disabling irq's without knowing the irq state will eventually lead to where you are in a disabled state for a good reason, another piece of code is called/inlined that does the same thing, and you now have irq's enabled when you thought they were disabled because all code just assumes irq's were enabled and simply enables irq's when done.There is no way one can keep track of all the potential call paths when you arrive at your interrupt protected code, so you just do not assume irq's are enabled and instead get/restore the state of irq's to keep from breaking other code that may have called you.In C++ I use something like the following, where the irq state is saved/restored in whatever scope I put the InterruptLock instance. It becomes easy to use and do not have to worry about who gets called inside the scope since everyone else is doing the same. Obviously C can do the same thing with a little less ease of use.Code:/*-----------------------------------------------------------------------------use to temporarily disable interrupts for various reasonsatomic access, etc.constructor saves PRIMASK bit 0, then interrupts disabledat end of scope deconstructor restores PRIMASKvoid myfunc(int v){InterruptLock lock; //instance name unimportantshared_var += v; //interupts off}deconstructor called at end of scopeinterrupts now restored to previous value-----------------------------------------------------------------------------*/class InterruptLock {u32 status;public:InterruptLock (): status( __get_PRIMASK() ){ __disable_irq(); }~InterruptLock () { __set_PRIMASK(status); }};This is mcu 101 and is nothing new, and maybe you are already doing this but are not showing it, or you are confident you know all the potential call paths so you are happy disabling/enabling irq's without knowing its current state. Its a lot easier to save/restore irq state than it is to figure out all the potential paths you may have arrived to a particular piece of code. Again, whether its currently a problem or not, no need to potentially add more problems in the future when there is a simple solution to avoid it.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 01:01:00 pm
content: I am 100% sure there are no multiple paths there. As you say that is MCU101. Functions which are callable from either interrupt enable context need to save/restore the state but I am not doing that anywhere.I think there are two areas which might be a problem1) __disable_irq() doesn't quite do what it is supposed to do i.e. a straight DI, and __enable_irq a straight EI, without side effects2) The UART ISRs are interruptible by other UART ISRs and there are some critical areas

author: paulca
author_rank: Super Contributor
timestamp: January 17, 2023, 01:34:36 pm
content: My understanding is that any NVIC interrupt with a lower (higher) priority number WILL interrupt a higher priority number.So if your UART2 has priority 1 and UART3 has priority 2, UART2 could interrupt UART3's interrupt.I believe if they all have equal priority they cannot interrupt each other. In that case the interrupt flags remain until it gets to it.Disabling just the UART interrupts will still leave your routine open to being interrupted by other higher priority interrupts. Even SysTick etc.Yes, I too have read the discussions and pain people get into, it seems that things are fine until they are not and the first poster usually says something like, "It's all about the priority". You go and look and all of them are set to 0. Getting from there to a sensible, well setup priority hierarchy seems to take a foundation degree in ARM NVICs.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 02:40:13 pm
content: Current interrupt priorities are:TIM6 1 (used for a general 1kHz timer decrement interrupt - ISR exec time ~10us)TIM1 1 (not enabled in current project)UARTs 1USB 2A priority of 0 is not used for anything; it appears to be non maskable! This was the default setting for UARTs in Cube MX!SysTick is 15 - this is standard for FreeRTOS. There is a huge long discussion all over the internet about this; basically FreeRTOS uses Systick to switch tasks but that interrupt must be a lower priority than any ISR, including any timer ISR. The above 1kHz timer ISR doesn't do anything related to FreeRTOS. A key Q is whether you are calling any part of FreeRTOS from an ISR, and I am not.All my ISRs, even the four UART ISRs (actually 12: 4 x RX, 4 x TX-E, 4 x TX-TC) should be interruptible by anything else. None of them are doing something like a RMW of some config register which applies to more than one UART. This is one aspect I would not put money on.I would think thatwithin a given levele.g. 1 there must be a priority hierarchy but what does that actually do? In the past when I had multiple devices on thesamepriority they could not interrupt each other's ISR but the "priority within the same priority level" (hard-wired) would determine theorderin which the ISRs are presented for execution, so e.g. if UART1 has a higher priority than UART2, then UART2's ISR would be commenced after UART1's ISR has exited.The interrupt handler is thisCode:// Common interrupt handler for all USART interruptsstatic void usart_common_IRQHandler(UART_HandleTypeDef *huart, uint8_t port){// check if we are here because of RXNE interruptif (huart->Instance->SR & USART_SR_RXNE){//if RX is not emptyserial_receive_irq_handler(huart, port);}// check if we are here because of TXEIE interruptif (huart->Instance->SR & USART_SR_TXE){//if TX is emptyserial_transmit_irq_handler(huart, port);}// check if we are here because of the TC interruptif (huart->Instance->SR & USART_SR_TC){//if TX completeserial_transmit_complete_irq_handler(huart, port);}}and that calls each of the 12 ISRs with the "huart" (basically a base address of the memory mapped register set for that UART), and "port" which is 1-4.The above function is in turn called from the following which are pointed to by the vector tableCode:/*** @brief This function handles USART1 global interrupt - USART1_IRQn 0*/void USART1_IRQHandler(void){usart_common_IRQHandler(&huart1, 1);}/*** @brief This function handles USART2 global interrupt - USART2_IRQn 0*/void USART2_IRQHandler(void){usart_common_IRQHandler(&huart2, 2);}/*** @brief This function handles USART3 global interrupt - USART3_IRQn 0*/void USART3_IRQHandler(void){usart_common_IRQHandler(&huart3, 3);}/*** @brief This function handles USART6 global interrupt - USART6_IRQn 0*/void USART6_IRQHandler(void){usart_common_IRQHandler(&huart6, 4);}and the vector table is e.g.What could possibly be wrong with this?Note I am using uarts 1,2,3,6 for the four ports. This was dictated by some shared pin constraints.

author: bugnate
author_rank: Regular Contributor
timestamp: January 17, 2023, 03:21:14 pm
content: In STM32 speak, there are really two priorities, a "pre-emption priority" and the "sub priority". The fun part is that which bits are which in the NVIC is configurable, including all or none for one or the other. See screenshot from a fresh CubeMX F417 as an example of how they want you to think about it.An exception with a lower pre-emption priority will preempt regardless of sub priority. If pre-emption priority is the same, then the ISRs will tail-chain (not preempt) according to sub priority and should run in the sequence you expect. If the sub priority is equal, IIRC they will sequence according to their IRQ number (e.g., the value of USART6_IRQn).You can effectively disable preemption by setting the number of bits allocated to preemption priority to 0 and I often do so for my smaller and tighter projects, or I otherwise try to only preempt in a more whitelist manner. I don't know how much flexibility your app or FreeRTOS allows you.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 04:20:03 pm
content: Thank you.To me that sounds like the "pre emption priority" is the 0-15 value I reference above i.e. 1 for the UARTs etc, and "sub priority" is a priority within that. I wasn't aware that the latter is configurable, but it doesn't really matter what it is in my project because all the ISRs are very short.This is the vector table portion spanning the four UARTs. UART6 is not in use right now. I am using just UART1,2.I have no idea if this is relevantor thisand finally these are the UART settingsCode:/* USART1 interrupt Init */HAL_NVIC_SetPriority(USART1_IRQn, UART_INT_PRIORITY, 0); // 37, 1, 0HAL_NVIC_EnableIRQ(USART1_IRQn); // 37QuoteI can't find where the sub priority is configured, but whatever it is, it should not matter. Even if the TIM1 ISR was interrupting the UART2 ISR, it should not matter.

author: Sauli
author_rank: Contributor
timestamp: January 17, 2023, 04:31:03 pm
content: The pre-emption priority / sub priority configuration is just some evil scheme to confuse the poor programmer. And the STM32Cube uses it's own default configuration which most programmers are not aware of. The best thing to do after the cube hardware initialization is done is to callNVIC_SetPriorityGrouping( 0);And then useNVIC_SetPriority()andNVIC_EnableIRQ()functions to configure the interrupts. And forget about the sub priorities. A lower priority number interrupt always pre-empts a higher priority number interrupt.About __disable_irq(): Do not use it within interrupt service. If you need to modify a register in interrupt service that could also be modified in a higher priority interrupt, then use LDREX/STREX.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 04:50:56 pm
content: Hmmm.Why does Cube use NVIC_SetPriorityGrouping(3), and you suggest NVIC_SetPriorityGrouping(0)?3 seems arbitrary, except that a value of 3 means you can have a few things above and below while a value of 0 means you can have only bigger values. What do bigger values do, relative to 0? If say you have 2 devices, both on priority 1 (like my four UARTs) and NVIC_SetPriorityGrouping(0) was set, does that ensure that none of the UARTs ISRs can enter each other? But surely if NVIC_SetPriorityGrouping(3) is used as theonlyvalue, won't that do the same?This is the function called with (3)Code:/**\brief Set Priority Grouping\details Sets the priority grouping field using the required unlock sequence.The parameter PriorityGroup is assigned to the field SCB->AIRCR [10:8] PRIGROUP field.Only values from 0..7 are used.In case of a conflict between priority grouping and availablepriority bits (__NVIC_PRIO_BITS), the smallest possible priority group is set.\param  PriorityGroup Priority grouping field.*/__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup){uint32_t reg_value;uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */reg_value = SCB->AIRCR; /* read old register configuration */reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change */reg_value = (reg_value |((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |(PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos) ); /* Insert write key and priority group */SCB->AIRCR = reg_value;}I am not using __disable_irq within an ISR. It would seem dumb.QuoteAFAICT I am not doing any such thing, but maybe there is some inter-dependence between the UARTs?Separately, I am seeing a strong inter-dependence between UART activity and USB CDC (which is wholly interrupt driven), causing a whole 512 byte buffer to be lost, but currently I am suspecting something else. But... ?I wantnoISR to enter any other. What is the best way to do that?

author: bugnate
author_rank: Regular Contributor
timestamp: January 17, 2023, 05:12:17 pm
content: Quote from: peter-h on January 17, 2023, 04:20:03 pmThe "NVIC_PriorityGroup_4" line allocates all bits to pre emption and none to sub priority, so the latter is moot in any case. So yes it looks like the 0-15 is all preemption.Because all of the exceptions (except USB) are the same priority (1), when multiple are pending the one with the lower IRQn is what gets run after the current ISR completes. They will not preempt (interrupt) each other, only run back-to-back. As long as there are sufficient CPU cycles for all, you should be fine in this regard.Quote from: peter-hYou are correct, but see edit note at the end.Quote from: peter-hIf your UART ISR is sufficiently blocking the USB CDC ISR (and it could, because it is higher priority), that certainly could cause you to miss a PC host timeout or such.Quote from: peter-hNVIC_SetPriorityGrouping(NVIC_PriorityGroup_0) would do this at a stroke because the 0-15 values become sub priority values instead of pre emption values. I cannot advise if it would be appropriate for your case.EDIT: NVIC_PriorityGroup_0 not "0". Suggest using the macros. I don't think "0" is valid.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 05:36:16 pm
content: QuoteThat is what I want.QuoteAha you are right:Code:/** @defgroup CORTEX_Preemption_Priority_Group CORTEX Preemption Priority Group* @{*/#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority4 bits for subpriority */#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority3 bits for subpriority */#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority2 bits for subpriority */#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority1 bits for subpriority */#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priorityQuoteThere are vast threads here on how to implement USB MSC with a sector flash write of 15msIt turns out that the USB sector (MSC; removable storage device, not CDC which is a USB VCP) ISR is not only fine to just hang in the ISR for the 15ms, but USB flash sticks do exactly that, reportedly. The timeouts are huge. And just as well since "elegant" solutions are extremely complex; in fact despite sending weeks on it, I found none that worked (like, returning a "BUSY" status to the USB host so it keeps retrying). Or a RAM cache equal in size to the whole file systemBut USB MSC is not same as USB CDC, and CDC could have a shorter timeout. That said, my USB ISRs are all very short - of the order of tens of us max. The UART ISRs are of the order of 5-10us.QuoteI can easily test that, tonight.But from what you say, it should not make any difference. However, since NVIC_PRIORITYGROUP_0 allocates no bits for pre-emption priority, can one still set the levels 1-15 as I currently do?EDIT: no luck with NVIC_PRIORITYGROUP_0It blows up FreeRTOS.

author: paulca
author_rank: Super Contributor
timestamp: January 17, 2023, 06:44:11 pm
content: I was fiddling around with those settings in CubeMX this evening after reading here and yea... setting it to 0 bit pre-emption adds mucho red lines everywhere.RTOS won't like it as it has to at least have 2 priorities surely? IIRC, especially if you want idle watchdogs. Either that or the RTOS time source wasn't getting a change to fire.

author: Sauli
author_rank: Contributor
timestamp: January 17, 2023, 07:23:55 pm
content: Have a look at the attached picture from ARMv7-M Architecture Reference Manual.What this means?- Set PRIGROUP value to 0 and you get the maximum number of pre emption levels. How many is implementation defined. You can still control the pre emption by the priorities you select for the interrupt sources.- Set PRIGROUP value to 7 and all interrupts are at the same pre emption level, i.e. an interrupt service is not interrupted by another interrupt service. This setting breaks RTOS.So, to confuse things even more, ST has decided to call PRIGROUP value 7 NVIC_PRIORITYGROUP_0 and has not even defined PRIGROUP value 0.By using ST HAL functions it is easy to lose track of which interrupts can preempt which. Therefore I once again recommend using the CMSIS functions instead.

author: peter-h
author_rank: Super Contributor
timestamp: January 17, 2023, 07:32:29 pm
content: I would expect ST spent some time porting the various open source bits to their CPU in Cube MX and HAL. Not always the right way, and rarely efficiently, but I am sure they fixed a zillion issues on the way, and mostly didn't document what they did and why. There are docs describing e.g. FreeRTOS port but not in detail.And these OS modules themselves are "old code" e.g. LWIP is > 15 years old and the coders lost interest > 10 years ago (google hits are mostly from 2011 or earlier). FreeRTOS and FatFS similarly. No support anywhere. It's all old stuff, but it is mostly good stuff which does work.So... the consensus seems to be that none of my ISRs are interrupting each other - correct?If so, I am going to draw a line under this part of the job now.

author: tggzzz
author_rank: Super Contributor
timestamp: January 17, 2023, 08:14:59 pm
content: Quote from: peter-h on January 17, 2023, 07:32:29 pmDon't guess and hope. Test and verify.On entry to an ISR set a GPIO bit, and clear it before exiting. Use a separate bit for each ISR. Trigger a scope or L.A. if more than one bit is set at any time.If necessary use resistors to form a crude analogue adder, and trigger a scope of the voltage goes above the relevant voltage.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 18, 2023, 08:09:23 am
content: Forget the sub priority bullcrap. It's only interesting in some weird corner cases where you want to optimize the order of nearly simultaneously occurring events. Specifically the case where you have event1 and even2 in such way that you DON'T want them to pre-empt each other, but STILL want to control the order in which they are processed in case they are both pending after even more important event3. I have never found any use for this feature. Use priority grouping 0, and then the priority will be just a simple number.

author: peter-h
author_rank: Super Contributor
timestamp: January 18, 2023, 09:13:45 am
content: But that breaks FreeRTOS - see above.Siwastaja- do you think the ISRs discussed here interrupt each other?In addition toCode:#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority0 bits for subpriority */and with no definition for grouping 0, should I addCode:#define NVIC_PRIORITYGROUP_5 0x00000000U /*!< 8 bits for pre-emption priority0 bits for subpriority */But surely 16 priority levels is loads, and what else will break if I change the config? Using 0 doesn't obviously blow anything up but why tempt fate? This threadhttps://community.st.com/s/question/0D50X00009XkbuPSAR/nvic-priority-groupingcovers that; I already havezerosub-priorities. The above NVIC_PRIORITYGROUP_4 comment says the same. So is there any need to change anything?I also read elsewhere that ST didn't implement what is in the ARM manuals. They still have only 4 bits for the pre-emption priority. So what is actually wrong with what I have as Cube default?A little side note. I timed the copy loop, the one which updates the index only at the end, and it takes 140ns per byteCode:// Use a local write indexvolatile uint32_t index = serial_transmit_buffer_put_index;// Copy the data to the circular bufferfor (uint16_t i = 0; i < length; i++){serial_transmit_buffer[index++] = data;if( index >= SERIAL_TX_BUFFER_SIZE){index = 0;}}// Only update the write index after all data copied.serial_transmit_buffer_put_index = index;One could split this into two memcpy() calls but (previous threads) the newlib memcpy is no faster. Is there some cunning way to use DMA, in circular buffer mode, to copy a linear buffer into a circular buffer? One would read out the final circular buffer pointer from the DMA register at the end of the transfer. This code is not time-critical, however.Removing "volatile" speeds it up from 140ns to 120ns which is roughly 2-3 clocks. I did the volatile after dire warnings from compiler experts, but is that actually meaningful? Fairly obviously the index is otherwise held in a register, but nothing else will be accessing it anyway.And the old version of the loop (start of this thread) which uses the mod % operator takes another 20% longer; unsurprisingly since that involves a division (11 clocks?).

author: Siwastaja
author_rank: Super Contributor
timestamp: January 18, 2023, 02:43:50 pm
content: STM32 MCUs just implement 4 bits of interrupt priority anyway, so you can't have 8 bits for priority. Some other MCU manucturers do have more bits available. But I agree 16 levels is usually more than enough.All that crap is some super complicated way of saying the same: "use all 4 bits for priority; disable sub-priority stuff". It's all about getting those 4 bits in the correct place in the configuration register.ISRs with same priority do not preempt each other.

author: peter-h
author_rank: Super Contributor
timestamp: January 18, 2023, 03:50:26 pm
content: QuoteIsn't that whatCode:#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority0 bits for subpriority */does? That is the default setting from Cube.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 18, 2023, 04:54:20 pm
content: I don't know, it says so in comments.I have just used the NVIC_SetPriorityGrouping() which internally handles the write to the correct part of the register. I don't understand the idea of replacing this simple interface with another one taking in weirdly named arguments - if NVIC_PRIORITYGROUP_4 means 4 bits, why does NVIC_PRIORITYGROUP_5 mean 8 bits, I have no idea. I prefer not to use confusing interfaces to a simple register.

author: peter-h
author_rank: Super Contributor
timestamp: January 18, 2023, 05:05:52 pm
content: NVIC_PRIORITYGROUP_5 was my suggestion, and the comment on it is wrong (because ST implements only 4 bits). NVIC_PRIORITYGROUP_4 is what Cube comes with - as I posted above.ST provide the said code to get somebody started without reading the 2000 page RM

author: NorthGuy
author_rank: Super Contributor
timestamp: January 18, 2023, 05:42:18 pm
content: Quote from: peter-h on January 18, 2023, 09:13:45 amYou don't need "volatile" here. "index" is a local variable which is not accessible from anywhere else.

author: paulca
author_rank: Super Contributor
timestamp: January 18, 2023, 06:06:08 pm
content: Quote from: peter-h on January 18, 2023, 05:05:52 pmIf it worked more than 50% of the time... yes.

author: paulca
author_rank: Super Contributor
timestamp: January 18, 2023, 06:09:30 pm
content: If I am understanding.If you have 4xUART interrupts and you would like them to interrupt/pre-emptively other lower priority interrupts, you'd need to move those interrupts out of "0" pre-emption.You'd want all 4 UARTs to share both their pre-emption level and sub-priority, such that each can prempt others of higher pre-empt value, but not each other.0 - Things that should never be "pended". aka RTOS/systick.1 - High priority user stuff (UARTS)2 - Can wait and pend for a while.Does that work?

author: peter-h
author_rank: Super Contributor
timestamp: January 18, 2023, 07:08:18 pm
content: QuoteGreat - so I thought. Thanks.QuoteI already have moved the four to priority (pre-emption level) 1. Sub priority, I don't care, but AIUI NVIC_PRIORITYGROUP_4 already has zero bits for sub priority.QuoteCorrect.QuoteThere is much online about this and loads of people are confused, but it seems intended that Systick priority for FreeRTOS is supposed to be very low. It is set to 15Yes it is working.It seems I have the right config although it's not obvious how one got here

author: Sauli
author_rank: Contributor
timestamp: January 18, 2023, 07:33:27 pm
content: Once more, but I promise this is the last time.The CMSIS functions in thecore_cmX.hheader are very useful. X here depends on which ARM core you are using.Including the MCU family header also brings in the CMSIS core header. In this particular case that would be:#include <stm32f4xx.h>For handling the NVIC there are a buch of functions named NVIC_something. To configure the interrupts, first of all allocate zero bits for the subpriority field:NVIC_SetPriorityGrouping( 0);Then you can assign the priority for each interrupt and enable it, for instance:NVIC_SetPriority( USART1_IRQn, 2);NVIC_EnableIRQ( USART1_IRQn);You can assing all used USARTs to the same priority level and they won't preempt each other.The number of priority levels depends on the vendor imprementation of the NVIC, but practically all support at least 8 levels (0..7). Always leave the lowest priority - that is highest level number - unused because RTOS uses it. RTOS assigns the lowest priority for it's own use, whatever the lowest level is (7/15/31/63/127/255).

author: Siwastaja
author_rank: Super Contributor
timestamp: January 18, 2023, 08:22:37 pm
content: What Sauli said. Those functions are trivially easy to use, examples of a good interface. What's best, not STM32 specific, works for all vendors. No need to look at the manual at all.After SetPriorityGrouping(0), all further interrupt priorities are simple number, the lower the more urgent. Same numbers won't preempt each other. I don't understand how one could design anything simpler than this.

author: peter-h
author_rank: Super Contributor
timestamp: January 18, 2023, 08:35:45 pm
content: QuoteThat seems to work.It was (NVIC_PRIORITYGROUP_4); which is 3:Code:#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority0 bits for subpriority */and since ST have only 4 bits for the pre-emption priority anyway,what is supposed to be the difference?Also see herehttps://freertos.org/RTOS-Cortex-M3-M4.htmlMost systems default to the wanted configuration, with the noticeable exception of the STM32 driver library. If you are using an STM32 with the STM32 driver library then ensure all the priority bits are assigned to be preempt priority bits by calling NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); before the RTOS is started.QuoteThat's because you are much more clever than I am, and like to show itI don't mind. I really enjoy working with people much smarter than me, because I learn from them.There seems to be a lot of ST expertise in Finland. Do they have a design team there?

author: ejeffrey
author_rank: Super Contributor
timestamp: January 18, 2023, 09:24:30 pm
content: Quote from: peter-h on January 18, 2023, 09:13:45 amThe `index` variable doesn't need to be volatile, as written that is a local variable that can't be accessed in any other context.What needs to be volatile are serial_transmit_buffer_put_index and serial_transmit_buffer -- the data structures that are accessed / modified from both interrupts and your application main thread.

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 08:04:28 am
content: I posted it here and will see if anyone biteshttps://community.st.com/s/question/0D53W000024XLcbSAG/is-nvicprioritygroup4-3-same-in-effect-as-0-32f417A value of 3 or 0 selects the same row, surelyThe above is 32F4 specific - see table 51 herehttps://www.st.com/resource/en/programming_manual/pm0214-stm32-cortexm4-mcus-and-mpus-programming-manual-stmicroelectronics.pdfThe confusion seems to be from the silly #defines.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 19, 2023, 09:46:56 am
content: >show unified ARM interface which has nothing to do with ST>initialization is one simple function call with argument zero>interrupt is enabled using a function which says EnableIRQ in name, and takes single argument: IRQ number>priority is set using SetPriority function which takes two arguments: IRQ number and priority level number>interrupt priorities are simple numbers where lower number preempts higher, same numbers do not preempt each other>"I don't see how this could be any simpler"<"that's because you are a genius and ST expert!"Are you serious? Why do you waste time in something like this? You definitely are mentally capable of understanding this concept, I'm 110% sure, because you are doing things many orders of magnitude more complex all the time.

author: paulca
author_rank: Super Contributor
timestamp: January 19, 2023, 10:46:02 am
content: ST Community sucks.QuoteThis is why we can't have nice tings. It's because people just don't understand software engineering. Function pre-conditions. Setting up the state. Verifiying the state. Performing the transaction, verifying the outcome, post conditions, error conditions, recovery conditions etc. etc.Not all are used in every function/unit of code, but it pays to at least check the basics. All of these things get exponentially harder as the code gets bigger, but a lot lot harder when one engineer is not enough anymore and code flux rises rapidly.If your function to enable widget X, assumes the reset state of the device from the docs and later that changes or the code calling it changes, the code it calls changes, the code might "appear" to be work fine in 99% percent of cases and in the other 1% present as a bizarre bug in a completely unrelated part of the code.... because nobody cared to stop erroneous input because they were so damn sure of themselves they never checked or reset state to the expected values before proceeding on down the call hierarchy. Nobody ever does negative testing anymore. Technically as soon as one function assumes something incorrectly the entire application is at that point corrupt and compromised and if it does not have checks in place to detect this and fail safe or recover, it will propagate through teh system and emerge in a completely unexpected place and be next to impossible to trace.I'm not directing this at anyone in particular, it's just a rant about out of date practices, particularly in the embedded space. People seem to think the small size and foot print frees them from the entirety of software engineering pracitces.Practice what I preach? From time to time yes. But MCU world is hobby space for me. I like breaking all the rules, it makes me feel free and a breath of fresh air from enterprise software were it's 90% other thing than writing code. Box ticking, etc. etc.

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 11:14:23 am
content: I don't have a problem with loading a reset value.The main problem is people not reading a post properly.I am trying to keep the questions focused, and the focus naturally moves along from one thing to another.Right now the confusion is that ST define a value NVIC_PRIORITYGROUP_4 as 3 but calling NVIC_PriorityGroupConfig(NVIC_PRIORITYGROUP_4) with this is same as NVIC_PriorityGroupConfig(0) which was suggested by others further back.I was suspicious re using 0 because of the various places where NVIC_PRIORITYGROUP_4 is recommended, including that FreeRTOS site link. But it seems to be genuine nonsense, a repetition of something from the past, and possibly relevant to other ARM CPUs, whereas with M4 NVIC_PRIORITYGROUP_4 is same as using 0. Both 011 and 000 have bit 2 = 0 in PRIGROUP - the yellow line aboveAnyway I always try the simplest possible setting and 0 works.On the ST forum this has come up before, 2 years ago. But not exactly this.

author: paulca
author_rank: Super Contributor
timestamp: January 19, 2023, 11:28:26 am
content: Sorry my rant didn't help.I read your post several times, still didn't get it.What I got hung up on is ..Code:0x5FAULAll I see isCode:magicUndefinedNumber

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 12:00:27 pm
content: To write to AIRCR, you need to load 0x5FA into a part of it. That's all that is.The function __NVIC_SetPriorityGrouping() takes the input value, ANDs it with 0x7, so just 3 bits of it are used, and load it into the 3 AIRCR bits in Table 51 above, PRIGROUP 2..0.The point I was trying to get to the bottom of was that an input value of 3 will have the same effect as an input value of 0 - given that if bit 2 of PRIGROUP is 0, the other two bits are xxI can think of two reasons why ST provide the NVIC_PRIORITYGROUP_4 #define and the FreeRTOS website recommends using it:1) Loading NVIC_PRIORITYGROUP_4 (=3 i.e. 0b011) is not the same as loading 0b000if some other code is reading back AIRCR and reading the 3 PRIGROUP bitsanddoing something based on bits 1,0 which are now 11. The cryptic comment on the FreeRTOS website, telling you to load NVIC_PRIORITYGROUP_4, suggests that this may be a possibility. And, stepping through the RTOS, they do read PRIMASK very early on but that's not the same register and it doesn't seem to get used for anything (I didn't step on for hours) except to detect if it is being called from an ISR. I see no evidence of PRIGROUP or AIRCR being accessed by FreeRTOS.2) The NVIC_PRIORITYGROUP_4 thing is fossil code from another CPU and the FreeRTOS website writer wasn't aware.3) Some other reasonIn the meantime the person who really understands this has decided I am stupid and has left the threadI now have three targets running with a (0) parameter. I've documented all this in my project document so one can go back to it if something is broken.Also the comment in CubeCode:#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority0 bits for subpriority */is actually correct. You get 4 bits (16 priority levels) for both 3 and 0 parameters. The other mystery is why there isn't a define likeCode:#define NVIC_PRIORITYGROUP_STARTUP_DEFAULT 0

author: Siwastaja
author_rank: Super Contributor
timestamp: January 19, 2023, 02:35:16 pm
content: Quote from: paulca on January 19, 2023, 11:28:26 amWell, it literally is a magic number; in this case, the idea is to make access harder by purpose to avoid accidental changes e.g. because of corrupted pointers. Of course, either #defining or // commenting about it being MAGIC would be helpful.("magic number" is an actual programming-related term, although people sometimes misuse it when discussing poor quality code.)

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 05:13:30 pm
content: These magic numbers don't actually work well, because if a program crashes, it will very quickly enter the function where you are loading the magic numberOne discovers this pretty fast when using the old parallel EEPROMs like the 28C256 which needs a magic number to enable writing. Any program crash results in a rapid corruption.On the earlier topic, it's quite amusing to google for NVIC_PRIORITYGROUP_4 and discover how many people have been blindly copying everybody else's code which in turn was copied from Cube and which in turn has an origin which you will probably never discover because ST do not participate anywhere (except rarely and usually uselessly on the ST forum).I came across something interesting here:https://www.freertos.org/FreeRTOS_Support_Forum_Archive/April_2014/freertos_Use_FreeRTOS_on_STM32f4_725438dej.htmlvTaskEnterCritical();You cannot call that function from an interrupt. In fact, the function is not part of the documented API and should not even be called directly from a task, and definitely never called directly on a Cortex-M part. If you want a critical section inside an interrupt you need to use portSETINTERRUPTMASKFROMISR()/portCLEARINTERRUPTMASKFROMISR().I am not actually doing that (disabling higher priority interrupts inside ISRs) anyway, but what is wrong with using__disable_irq()__enable_irq()inside an ISR?In the old days, Z80, H8, etc, an ISR would never be interrupted by another ISR. If you wanted that (and sometimes you did want to e.g. make a timer ISR, full of crap, to be interruptible by comms interrupts) you had to doISR start:.. ;allints are now disabled..(disable this interrupt source)ld hl, labelpush hlei ; this enables ints but only after the "reti"reti ; ints are enabled after thislabel:(rest of ISR,allints are enabled, except the one which got us here)....di(re-enablethisinterrupt source)ei ; ints are enabled after the "ret" belowretSo the ST ARM seems to do the above anyway, managed by the int "pre-emption priority" order. But if you want a critical section within an ISR, how would you do that? A google on portSETINTERRUPTMASKFROMISR finds ST didn't implement it in their FreeRTOS port.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 19, 2023, 05:19:14 pm
content: Quote from: peter-h on January 19, 2023, 05:13:30 pmIf by "crash" you mean runaway Program Counter, then yes, it won't protect against it (with clever programming, partial protection against runaway PC is possible, but this is rarely done, instead we use wrappers just like SetPriorityGrouping()).They still work somewhat, for example when writing to variables pointed to by corrupted pointers. Or massive over/underindexing; good old "off by ones" do not hit the peripheral/CPU register address space anyway.

author: paulca
author_rank: Super Contributor
timestamp: January 19, 2023, 05:45:43 pm
content: I vaguely understand the other meaning of magic numbers. I vaguely recall diagnosing X11's magic cookies which were basical auth seeds. Fixed salt hashes, the salt might be considered a magic number? Well, nobody just drops a 128bit UUID into code in hex form as if it didn't happen. Likes literally like taking a shit in the bosses drawer.If you aren't going to eat it, name it.I get even more anal and ask that people assign or comment function parameters like "true" and "false". EG:thing->doSomething( understandable, localVar, true, false, false, true, 42 );Won't pass strict code review. Not even when you take out 42 and name it.

author: paulca
author_rank: Super Contributor
timestamp: January 19, 2023, 05:49:49 pm
content: In the most part I don't actually dislike the code style in HAL. I much prefer it to Arduino. It feels like it's adult if that makes sense.But, it's obvious to me it's not all written by the same person and most of them aren't first language English either.Some of the code look like it jumped the fence into release without testing. Some of it looks like downright experimentally permenant (like half the 2.x linux kernel) and some even looks like it's been converted from C++ to C by an automated tool.But the coding style and at least the attempt at comments I have no problem with.Not that this helps you, sorry for ranting on your thread again.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 19, 2023, 05:52:59 pm
content: I call that just "undocumented". You can document it two ways, eitherconfRocket(/*enable_rocket =*/ true);orbool enable_rocket = true;confRocket(enable_rocket);Although one could argue this isn't needed if IDEs that are capable of popping up the definition are used. One could even argue there is less room for human error that way.Magic numbers just mean the generic pattern of deliberately inventing a number which is used for identification purposes (protocol identifier, prevention of unwanted transactions, etc.), but some people call undocumented values in code "magic numbers", this is like calling people you don't like "gay".

author: Sauli
author_rank: Contributor
timestamp: January 19, 2023, 06:03:45 pm
content: ARM has not made the interrupt system easy to understand.The interrupt priority registers have 8 priority bits for each interrupt source. A silicon vendor can decide how many of these are used. ST uses 4 bits. The rest are always 0. ARM has decided that the unused bits are the lower end of the 8-bit priority value. See the attached table from ARMv7-M Architecture Reference Manual.AIRCR.PRIGROUP value defines how many lowest bits of the 8-bit priority value are used for subpriority. The ST NVIC_PRIORITYGROUP_4 value (=3) sets the number of subpriority bits to 4. Which means that there are no subpriorities at all since the four lowest interrupt priority bits for ST are always 0. The same PRIGROUP value of 3 for a processor that implements more than 4 interrupt priority bits however means that there are subpriorities. Using PRIGROUP value 0 guarantees that there are no subpriorities no matter how many priority bits are implemented. The reset value of PRIGROUP by the way is 0.

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 09:19:05 pm
content: So the NVIC_PRIORITYGROUP_4 (=3) is old BS left over from code for another CPU and which just happens to work right (same as 0) on M4. And anyway is the same as the power-up reset value.And the FreeRTOS site says you have to do it but doesn't explain why, either.Happy to note that I am not stupid after allWould anyone know anything about my question abovehttps://www.eevblog.com/forum/microcontrollers/does-this-code-need-interrupts-disabled/msg4648273/#msg4648273

author: uer166
author_rank: Super Contributor
timestamp: January 19, 2023, 09:26:47 pm
content: Quote from: peter-h on January 19, 2023, 05:13:30 pmArchitecturally? A few things. Practically? Nothing inherently wrong with doing that and it'll work as expected. Upon execution of __enable_irq() you'll of course get interrupted by any pending ISRs that are higher priority than what is currently executing.

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 09:43:10 pm
content: What I was wondering is the effect relative to ARM interrupt handling which uses "magic numbers" on the stack to indicate specific operations are requested. These values have high bits set to distinguish them from any physically possible address / PC value.That is how ARM implements (or avoids, if you like) the classic old "RETI" or equivalent. You can write an ISR wholly in C without any "interrupt" or similar keyword. When an interrupt is serviced, a magic value is pushed on the stack, the ISR can do what it likes stack-wise, and when eventually the CPU gets around to popping that magic value off the stack, that is how it knows you are doing an "RETI".Quite cunning.What I was wondering is whether __disable_irq() etc has any effect on this scheme. After all, __enable_irq() enables interruptsglobally, whereas the "RETI" enables only lower priority ones.QuoteWhat are those?QuoteAnd upon the final stack pop of the "RETI" magic value, I will get interrupted by any pending ISRs that are lower priority than the ISR just finished?

author: uer166
author_rank: Super Contributor
timestamp: January 19, 2023, 09:55:02 pm
content: Quote from: peter-h on January 19, 2023, 09:43:10 pmI think you're overthinking too much.. But just to throw fuel on the fire:It adds latency jitter to higher priority ISRs that is equal to the critical value section. This should be obvious, and may or may not be a problem.It can indicate a hack/architectural issue that is better solved some other way. E.g. bit-banding for atomic flags, double buffering with atomic 32-bit variables, etc.QuoteNo, it shouldn't. A lower priority ISR shouldn't magically interrupt your running ISR, the NVIC isn't that broken.

author: peter-h
author_rank: Super Contributor
timestamp: January 19, 2023, 10:06:48 pm
content: OK, got all that.After the "RETI" the lower priority ints should execute as they normally would.Perhaps an interesting Q might be if you doisr_function fred{blahblah__disable_irqblahblah__enable_irq}that will generate a tail of__ASM volatile ("cpsie i" : : : "memory");whatever is the asm for "RET" (there isn't one as such; they do a "POP PC").and the lower priority ints will be allowed in in between the two above lines. Should not matter... In classic ISRs, ending withEIRETthe EI enables ints only after the RET.

author: peter-h
author_rank: Super Contributor
timestamp: February 22, 2023, 01:54:41 pm
content: A little update on this old saga.Having been doing other coding on this project I returned to the old problem whereby 1 byte gets lost every 50-100 megabytes.Someone pointed out to me that it could be that the TX may not have room in the TX holding register. This is even though the TX loading event "cannot happen" unless there was a preceeding TXE interrupt.Well, it is exactly what does happen!Code:// Use a local write indexuint32_t index = serial_transmit_buffer_put_index;// Copy the data to the circular bufferfor (uint16_t i = 0; i < length; i++){serial_transmit_buffer[index++] = data;if( index >= SERIAL_TX_BUFFER_SIZE){index = 0;}}// Only update the write index after all data copied.serial_transmit_buffer_put_index = index;__disable_irq();// If transmit interrupt is disabled, restart transmitif( (serial_port_handles->Instance->CR1 & UART_IT_TXE) == 0 ){// Load the first byte and enable the TXE and TC interrupts.// First clear TC. This is done with a read of SR and a write to DR.// Read SR// serial_port_handles->Instance->SR;// happens below// Hang until TX has room - should never happenwhile( (serial_port_handles->Instance->SR & UART_FLAG_TXE) == 0 ){LED(true);// this pulses a GPIO pinLED(false);}// This is the write of DRserial_port_handles->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];if( serial_transmit_buffer_tx_index >= SERIAL_TX_BUFFER_SIZE){serial_transmit_buffer_tx_index = 0;}g_comms_act[port+1]=LED_COM_TC;// indicate data flow on LED 1-4// Enable TX complete and TX empty interrupts. The order may matter! (doesn't appear to)serial_port_handles->Instance->CR1 |= UART_IT_TC;serial_port_handles->Instance->CR1 |= UART_IT_TXE;}__enable_irq();I measured the pulse width of LED() above and it is 19us at 38400 baud UART config and 38us at 19200 baud UART config. So it is something to do with some UART internal clock speed, but nothing as obvious as 1 bit time, etc.It doesn't really matter except that this wait is being done with interrupts disabled, which is going to produce unwanted jitter elsewhere. So I will try to move that check to outside the ints-disabled region.

author: wek
author_rank: Frequent Contributor
timestamp: February 22, 2023, 02:24:06 pm
content: Is this still STM32F407/417?> if( (serial_port_handles->Instance->CR1 & UART_IT_TXE) == 0 )WTF is UART_IT_TXE?(And yes, I know what it is, and it's wrong, even if it does not cause the observed behaviour; and even is factually harmless, it is still wrong.)> while( (serial_port_handles->Instance->SR & UART_FLAG_TXE) == 0 )UART_FLAG_TXE too.> I measured the pulse width of LED() aboveIs there one pulse? Many?> LED(true); // this pulses a GPIO pinI don't believe.Now to the observed behaviour:> This is even though the TX loading event "cannot happen" unless there was a preceeding TXE interrupt.What is "TX loading event"? UARTx_DR = some_data?> unless there was a preceeding TXE interrupt.I am going into crystal ball mode now: in the TX ISR, after loading data from circular buffer into UARTx_DR, you advance tail and check if tail meets head thus circular buffer is emptied and there you clear UARTx_CR1.TXEIE. But at that point, transmitter is not empty yet and it takes time until it's emptied (and then TC interrupt kicks in etc.).You are checking UARTx_CR1.TXEIE as the guard in the code above.JW

author: NorthGuy
author_rank: Super Contributor
timestamp: February 22, 2023, 03:46:26 pm
content: Quote from: peter-h on February 22, 2023, 01:54:41 pmShouldn't you be checking for TC instead?

author: NorthGuy
author_rank: Super Contributor
timestamp: February 22, 2023, 04:25:17 pm
content: See how it works:

author: peter-h
author_rank: Super Contributor
timestamp: February 22, 2023, 05:47:30 pm
content: One is always torn between posting too little (and getting replies from irritated people saying I didn't post enough info) and posting too much (and getting no replies at all)Transmit mode terminates with TX interrupt finding no more data to send:Code:void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port){port--;// offset from 0// Is there anything left to send?if (serial_transmit_buffer_tx_index == serial_transmit_buffer_put_index){// Disable the TXEmpty interrupthuart->Instance->CR1 &= ~(UART_IT_TXE);}else{// Send the next bytehuart->Instance->DR = serial_transmit_buffer[serial_transmit_buffer_tx_index++];if( serial_transmit_buffer_tx_index >= SERIAL_TX_BUFFER_SIZE){serial_transmit_buffer_tx_index = 0;}g_comms_act[port+1]=LED_COM_TC;// indicate data flow on LED 1-4}}So, after TX int has been disabled by the above, when new data has been placed into the circular buffer, you have to load 1 byte into the UART TX and enable TX interrupt. This is standard "UART stuff". Then the thing goes off by itself, transmitting under interrupt.The UART_IT_TXE bit is also being used - as suggested further back - as an indicator as to whether transmit mode is active. If 1, then you just put new data into the buffer. If 0, then you have to do the above. Previously I used a separate flag for this but it turned out to be just a copy of IT_TXE bit.TC is a separate thing here. The TC ISR is basically used just for disabling RS485 drivers:Code:// This ISR turns off the 485 driver on p3/p4// https://peter-ftp.co.uk/screenshots/202210015115900110.jpgvoid serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){if ( (port == 3) || (port == 4) ){// If this is an RS485 port and auto driver mode is set, disable the driver nowif (serial_driver_mode[port-1] == 2){serial_485_driver_control_internal(port, 0);}}// Transmit completeserial_transmit_active[port-1] = false;// Clear and disable the TC interrupt. This is supposed to be done by a read of SR and a write to DR// but in this case we have nothing to write to DR so we clear the TC bit by writing a 0 to it but// avoiding RMW on SR - see http://efton.sk/STM32/gotcha/g9.htmlhuart->Instance->SR = ~(UART_FLAG_TC);huart->Instance->CR1 &= ~(UART_IT_TC);}32F417 yesLED(true) and LED(false) set/reset a GPIO pin, producing a pulse.Maybe I have done something stupid but a) I can't see what it is and b) the issue occurs extremely rarely.To re-iterate: After TX int has been disabled (in the TXE ISR, upon finding the circular queue empty) whennewdata has been placed into the circular buffer, and the 1 byte has been loadeddirectlyinto the UART TX holding register, the UART appears to bevery occassionallydiscarding this byte, even thoughbefore thatthe TXE interrupt has been generated which could only be if there is room in the holding register. That is what TXE is: there is room for 1+ bytes. In the 32F417 it will be just one byte (although if you got the timing just right you might be able to get two bytes in, but I am not doing that). The hack (waiting for TXE=1, before loading this byte) works but the wait takes a little too long in this particular application.Putting it another way, it appears that the TXE interrupt is sometimes generatedbeforethe byte in the tx holding register has been moved to the tx shift register.The data loss is of the order of 1 byte in 10^7 bytes so in the vast majority of applications would not be noticed. This is the frequency of the pulse I see on the scope. Just one every few hours or so. Just seen one which was 52us which is even less related to 19200 baud than previous ones.If anyone can think if a better way to address this, I am all ears, otherwise I will implement the wait for TXE=1 just above the DI/EI region where an extra few tens of us doesn't matter.

author: wek
author_rank: Frequent Contributor
timestamp: February 22, 2023, 06:30:37 pm
content: > One is always torn...The best thing is to write a minimal but complete compilable code exhibiting the problem, and post it as a zip in its entirety. Then post only relevant portions in the text.For example:> void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port)Whatizit? How is it called, in what context, what is preceding and succeeding calling this function, etc.etc.---> UART_IT_TXEAs I've said, stop using it.Use symbols from the CMSIS-mandated device header when accessing/talking about registers.Use any random Cube/HAL gobbledygook when using the Cube/HAL functions, exclusively.---> if (serial_transmit_buffer_tx_index == serial_transmit_buffer_put_index)does not guarantee that there is nothing in the shift register, so clearing USART_CR1.TXEIE does not guarantee that either.As NorthGuy says too, guard the attempt to transmit by USART_CR1.TCIE (and again, don't use UART_IT_TC etc.)Or, better, don't transmit from main at all, as soon as you set USART_CR1.TXEIE and the transmitter buffer is empty, the interrupt will fire and perform the transmission.JW

author: peter-h
author_rank: Super Contributor
timestamp: February 22, 2023, 06:52:05 pm
content: QuoteInteresting. On most UARTs you get the TX interrupt only when the TXbecomesempty.Maybe that is the whole problem. I don't need to load the byte into the UART at all. I just need to enable TXE interrupt.Bloody hell. It works!Astonishing nobody has picked that up before. Such a simple thing. But as I say most UARTs don't work that way.