Why does Cortex-M0(+) implement instructions like ISB if it has no cache? - Page 1
Forum section: electronics / general

author: incf
author_rank: Frequent Contributor
timestamp: February 26, 2025, 11:58:23 pm
content: Why does Cortex-M0(+) implement instructions like ISB (Instruction Synchronization Barrier) if it has no cache?Are there any systems/SOCs that actually go through the effort to implement external caching while at the same time deliberately using the very slow/limited Cortex-M0/M0+ core?My impression is that all (most?) popular MCU's using Cortex-M0/+ type cores simply never require the user to use synchronization instructions.Do those instructions have more value when multiple Cortex-M0/+ type cores are sharing the same memory? (Which is also exceedingly rare I think)edit: I suppose maybe there are some Big.Little embedded systems that I overlooked when I asked the question

author: coppice
author_rank: Super Contributor
timestamp: February 27, 2025, 12:03:39 am
content: If any application for the M0 core, anywhere, in any strange heterogenous product you never thought of, needs a feature then everybody has it. The licencing for ARM cores means you are not allowed to tamper with the core in even the most trivial way.

author: cfbsoftware
author_rank: Regular Contributor
timestamp: February 27, 2025, 12:48:02 am
content: QuoteREF:Application Note 321 ARM Cortex-M Programming Guide to Memory Barrier InstructionsYou can see an example of its use in the Run procedure in the Oberon-RTK Kernel for the Cortex-M0+ RP2040:https://github.com/ygrayne/oberon-rtk

author: jnk0le
author_rank: Regular Contributor
timestamp: February 27, 2025, 06:52:57 pm
content: cortex-m0 implements armv6-m architecture.And the armv6-m can be used for something bigger than the M0. ARM didn't go that route (and no one else is allowed to, of course), but there is possibility still.There is also the purpose of forward compatibility, so the armv6-m programs work correctly on armv7-m.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 01, 2025, 07:36:56 pm
content: Remember that ISB and DSB instructions have absolutely nothing to do with cache. So if working with e.g. STM32H7 or something else with caches, and you want to run caches enabled, and need to flush them for whatever reason (e.g., DMA), DSB won't do that, you will need a separate flush command for that.

author: incf
author_rank: Frequent Contributor
timestamp: March 01, 2025, 07:46:50 pm
content: Quote from: Siwastaja on March 01, 2025, 07:36:56 pmThat begs the question... what are they for?After reading "REF: Application Note 321 ARM Cortex-M Programming Guide to Memory Barrier Instructions" I find myself struggling to imagine a situation on a Cortex M0 (Plus, or no plus) type device where instructions like ISB or DSB do anything except possibly make certain events occur a few clock cycles earlier than they would have.They dedicated several op-codes to this, but I don't see what is gained by it in this specific case.

author: hli
author_rank: Frequent Contributor
timestamp: March 01, 2025, 10:58:19 pm
content: ISB prevents instruction re-ordering, and finishes all delayed writes.It ensures that all writes which which are in the program before the ISB are completed when the ISB is finished, and that any instructions after the ISB will get to see the results of these writes. (seehttps://developer.arm.com/documentation/dui0497/a/the-cortex-m0-processor/memory-model/software-ordering-of-memory-accessesfor ther actual wording on that, and for some exampled when to use it)

author: coppice
author_rank: Super Contributor
timestamp: March 01, 2025, 11:08:07 pm
content: Quote from: hli on March 01, 2025, 10:58:19 pmI don't think anyone has an argument about the purpose of the instructions. What is not clear is what, on a processor as simple and basic as an M0, ever gets things out of order? Are these instruction just there for compatibility with more complex processors which might use the same instruction set, or do the really do something with the M0? Although its a fairly simple core it does have a pipeline, and that frequently has the ability to spring surprises about ordering.

author: ejeffrey
author_rank: Super Contributor
timestamp: March 02, 2025, 01:05:11 am
content: Even in-order pipeline stages are a cache. And while the M0+ is quite simple and only has two states (fetch and execute) the bus it is attached to can be quite complex. Without memory barriers there is no way to force memory transactions to different endpoints (such as SRAM vs a peripheral) act in order.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 02, 2025, 08:08:13 am
content: Quote from: ejeffrey on March 02, 2025, 01:05:11 amNo, pipelines are not caches. For example, Wikipedia definition is quite decent:"In computing, a cache is a hardware or software component that stores dataso that future requests for that data can be served faster"Pipelining, buffering and prefetching are not examples of caching. Caching involves fetching the stored value "back". FIFOs are not caches; synchonization is not cache; pipelining so that two operations can run in parallel with different data is not cache. Let's not create confusion by redefining commonly used terms even more broadly than their already-quite-broad meaning.These barrier instructions are needed because, as others have pointed out, buses can have different latencies end-to-end due to different buffering or different clock domain crossings. A typical use case is ensuring that data has been written to RAM before triggering a DMA transfer accessing that RAM.Caches havesimilarconsiderations with coherency, but they are not the same thing.

author: jnk0le
author_rank: Regular Contributor
timestamp: March 02, 2025, 01:05:45 pm
content: prefetch buffer + multi stage pipeline can bite in case of self modifying code even on m0Code:.balign 4adr r0, abcstrh r1, //isbabc:nop // in decode (M0) or fetch (M0+) stage when strh finishesusing isb instead of enough amount of spacing instructions makes it portable to any uarch.

author: coppice
author_rank: Super Contributor
timestamp: March 02, 2025, 06:43:55 pm
content: Is the flash in M0 and M0+ parts 32 bits or 64 bits wide? A lot of MCUs that appear 32 bits wide actually have 64 or more bits across the flash words, which they use to increase the apparent speed of the flash. Muxing and demuxing that can lead to some out of orderiness.

author: Sacodepatatas
author_rank: Regular Contributor
timestamp: March 02, 2025, 07:26:42 pm
content: Quote from: coppice on March 02, 2025, 06:43:55 pmThe STM32G0xx architecture (M0+) has a prefetch, that is, as you said, the whole 64 bits reading of flash and then executing from 2 to 4 instructions until the next read, but also they have 16 Bytes of cache to overcome the flash wait states...

author: westfw
author_rank: Super Contributor
timestamp: March 03, 2025, 07:08:53 am
content: QuoteI don't think that the flash memory width is considered part of the ARM side of the M0/M0+ definition.As you say, many implementations have "wide" flash memory, plus other forms of "flash acceleration" that are frequently not very well documented.Then you have chips like the rp2040 that actually implement an actual cache in front of flash, even though the M0 archiecture doesn't include cache. Cached memory SHOULD be pretty much invisible to the CPU, other than making memory access times rather variable. (Hmm. I suppose it's an interesting question whether the barrier instructions interface properly with the 3rd party caches...)

author: Jeroen3
author_rank: Super Contributor
timestamp: March 03, 2025, 12:58:40 pm
content: These are really low level instructions, and that are certainly there with a purpose. Except that purpose is for advanced functions.Most people don't have to worry about them. But there are also chips running multi-core M0+, or even big-little M4/M0.Then these barrier instructions come into the picture for synchronisation purposes. And for deterministic initialisation stuff.Let me cut&paste this from the arm docs. It's about armv8, but v6 should work the same. It should answer the question.Quote from:https://developer.arm.com/documentation/dui0662/b/Cortex-M0--Peripherals/Memory-Protection-Unit

author: peter-h
author_rank: Super Contributor
timestamp: March 03, 2025, 08:45:03 pm
content: QuoteThis is very interesting stuff, and makes sense, even to meThe Q is why the thousands of designs out there, which don't do any of this, don't just blow up. The answer must be that in practical scenarios you set up e.g. VTOR many many instructions before interrupts get enabled. And probably same with enabling the FPU. STM's own "HAL" code (as generated by e.g. Cube MX) doesn't do DSB either.

author: incf
author_rank: Frequent Contributor
timestamp: March 03, 2025, 10:07:44 pm
content: Quote from: peter-h on March 03, 2025, 08:45:03 pmI think ARM wraps certain registers with logic that triggers DSB/ISB behavior. Maybe chip manufacturers like ST might do something similar with certain critical peripheral registers?Hard-earned experience has likely taught them that they retain their customers better when they auto-magically handle those sorts of low level details. (Evidentially they do a good job of it)

author: Jeroen3
author_rank: Super Contributor
timestamp: March 04, 2025, 07:13:14 am
content: Quote from: peter-h on March 03, 2025, 08:45:03 pmThese instructions guarantee certain things. Often you have enough fluff around your code that the correct behaviour getsimplied.If you look at the cmsis where it is used, in the cm0plus it isn't use that much, but for example:Code:/**\brief Disable Interrupt\details Disables a device specific interrupt in the NVIC interrupt controller.\param  IRQn Device specific interrupt number.\note IRQn must not be negative.*/__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn){if ((int32_t)(IRQn) >= 0){NVIC->ICER = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));__DSB();__ISB();}}For example, using the above function without barriers would probably work fine if any of the next instructions don't do anything with the nvic or with the peripheral sending an irq.Race conditions, these instructions prevent them.Quote from: incf on March 03, 2025, 10:07:44 pmI don't think they do. Some registers are just slower because they are on a slower clock, that might imply some of this effect.I only had to use a DMB barrieronceto ensure writes to the irq flag register were finished on exception return. Which it sometimes appears as it didn't on the F103.

author: peter-h
author_rank: Super Contributor
timestamp: March 04, 2025, 09:11:20 am
content: Yeah, you are not wrong, and this is 32F417:Code:/**\brief Disable Interrupt\details Disables a device specific interrupt in the NVIC interrupt controller.\param  IRQn Device specific interrupt number.\note IRQn must not be negative.*/__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn){if ((int32_t)(IRQn) >= 0){NVIC->ICER = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));__DSB();__ISB();}}Interestingly that function is never calledAnd DSB is not found in any other NVIC code.

author: langwadt
author_rank: Super Contributor
timestamp: March 04, 2025, 02:11:40 pm
content: Quote from: Jeroen3 on March 04, 2025, 07:13:14 amseems so,https://github.com/ARM-software/CMSIS_5/issues/110

author: Jeroen3
author_rank: Super Contributor
timestamp: March 04, 2025, 02:17:23 pm
content: Hah, nice find.

author: peter-h
author_rank: Super Contributor
timestamp: March 04, 2025, 03:15:19 pm
content: Well, there is a lot of this in arm32 e.g. the WFI instructionhttps://www.eevblog.com/forum/microcontrollers/how-fast-does-st-32f417-enter-standby-mode/msg4056850/#msg4056850where you have to put it into a loop, so if there is a delay in it, it always happens, just a tiny bit later.Very few coders will do a DI() or similar and immediately expect it to be effective. There will always be pending interrupts... It's just a messy topic. But if writing in C, there will nearly always be a ton of other machine code after DSB/ISB etc.This stuff is so deep that if it was a common issue, few products would ever get finishedAnother thing is that the STM libs are done for other chips like the H7 and same code is used. You also get this issue in the infamous ETH low level code (LWIP to ETH glue) where DSB is needed on the H7 but not the F4. I spent many hours digging around this stuff.

author: newbrain
author_rank: Super Contributor
timestamp: March 06, 2025, 11:50:05 am
content: Quote from: peter-h on March 04, 2025, 03:15:19 pmTypical example: clearing interrupt flags in a ISR and then returning.If __DSB() is not used, the IRQ will still be pending at return time and the IRS will immediately be called again.Simplified actual code of my I2C driver:Code:void I2C1_EV_IRQHandler(void){FsmI2c state = tr->state;/* Check what happened! */if (i2c->ISR & I2C_ISR_TXIS){switch (state){...case TxI2CaddrR1: /* Write Address and go to R0 */--state; /* R2 -> R1 -> R0 or W2 -> W1 -> W0 == TxData */i2c->TXDR = tr->subArr[--tr->subCount]);break;...}}else if (i2c->ISR & (I2C_ISR_TC | I2C_ISR_STOPF)){...}else if (i2c->ISR & I2C_ISR_RXNE){switch (state){case RxData: /* Do not expect any other state! */*tr->dataPtr++ = i2c->RXDR;break;....}}else{/* All other error situations, including NACK */state = Error;}tr->state = state;/* Everything is complete (state == Idle) or there's an error, wrap up transaction */if (state <= Error){...}/* Avoid retriggering IRQs by forcing data tranfers to be complete. *//* This makes sure the IRQ flags are updated before exiting the ISR */__DSB();}Here, flags in I²C's ISR register are cleared automatically by the reading or writing of the data registers, but the change has not yet taken place when the return is executed.Note that caching has nothing to do with this, as peripheral registers reside in memory of Device type which is never cached.Without __DSB(), the whole ISR was called again.

author: peter-h
author_rank: Super Contributor
timestamp: March 06, 2025, 12:09:28 pm
content: So how come ISRs work on the 32F4?Is it because the clearing of the IRQ happens far enough back from the ISR exit, as in hereCode: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}}but then how does this one workCode:// This ISR turns off the 485 driver on p3/p4void 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);}

author: Sauli
author_rank: Contributor
timestamp: March 06, 2025, 01:07:50 pm
content: QuoteMaybe because you are executing at least a function epilogue before returning from the interrupt?

author: newbrain
author_rank: Super Contributor
timestamp: March 06, 2025, 01:29:45 pm
content: Quote from: peter-h on March 06, 2025, 12:09:28 pmSTM32F4 are Cortex-M4, they have a shorter pipeline (half? I seem to remember 3 vs 6 stages), lower frequencies (so possibly less clock speed difference between peripherals and core), no I and D caches, and fewer internal busses from core to memory and peripherals (i.e. less possibility to issue simultaneous operations with unknown completion order).The sequence of operations on an M7 is much less predictable, and side effects - as, in my case, the clearing of flags in a register when reading/writing another register - even less so.My best guess is that when the return instruction is hit, the read or write from the peripheral DR is still outstanding, and that the HW sampling of the IRQ bits by the NVIC does not count as an explicit Device Memory access, voiding the guarantees of Device Memory ordering.DSB makes sure that all such outstanding accesses are completed before the return instruction is executed, DMB would not have been enough, as it covers only explicit accesses.I'm not saying I know exactly how it all works, I was just bitten by it!

author: peter-h
author_rank: Super Contributor
timestamp: March 06, 2025, 01:54:50 pm
content: So if upgrading a project from F4 to say H7, there are loads of these gotchas to bite you in the bum.And there was me thinking that since the QFP100 pinouts are so similar ...Thankfully I have no intention of changing the CPU I use...Regarding the epilogue suggestion 2 posts back, this is the asmCode:void xxx_serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port){8052f40:b538push{r3, r4, r5, lr}8052f42:4605movr5, r08052f44:460cmovr4, r1if ( (port == 3) || (port == 4) )8052f46:1ecbsubsr3, r1, #38052f48:b2dbuxtbr3, r38052f4a:2b01cmpr3, #18052f4c:d911bls.n8052f72 <xxx_serial_transmit_complete_irq_handler+0x32>xxx_serial_485_driver_control_internal(port, 0);}}// Transmit completexxx_serial_transmit_active[port-1] = false;8052f4e:3c01subsr4, #18052f50:4b0eldrr3, [pc, #56]; (8052f8c <xxx_serial_transmit_complete_irq_handler+0x4c>)8052f52:4423addr3, r48052f54:2200movsr2, #08052f56:f883 2094strb.wr2, [r3, #148]; 0x94// 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);8052f5a:682bldrr3, [r5, #0]8052f5c:f06f 0240mvn.wr2, #64; 0x408052f60:601astrr2, [r3, #0]huart->Instance->CR1 &= ~(UART_IT_TC);8052f62:682aldrr2, [r5, #0]8052f64:68d3ldrr3, [r2, #12]8052f66:f023 5380bic.wr3, r3, #268435456; 0x100000008052f6a:f023 0340bic.wr3, r3, #64; 0x408052f6e:60d3strr3, [r2, #12]}8052f70:bd38pop{r3, r4, r5, pc}if (xxx_serial_driver_mode[port-1] == 2)8052f72:1e4asubsr2, r1, #18052f74:4b05ldrr3, [pc, #20]; (8052f8c <xxx_serial_transmit_complete_irq_handler+0x4c>)8052f76:4413addr3, r28052f78:f893 3098ldrb.wr3, [r3, #152]; 0x988052f7c:b2dbuxtbr3, r38052f7e:2b02cmpr3, #28052f80:d1e5bne.n8052f4e <xxx_serial_transmit_complete_irq_handler+0xe>xxx_serial_485_driver_control_internal(port, 0);8052f82:2100movsr1, #08052f84:4620movr0, r48052f86:f7ff fb09bl805259c <xxx_serial_485_driver_control_internal>8052f8a:e7e0b.n8052f4e <xxx_serial_transmit_complete_irq_handler+0xe>8052f8c:2000a948.word0x2000a948I see these two after the IRQ clearing8052f6e: 60d3 str r3, [r2, #12]8052f70: bd38 pop {r3, r4, r5, pc}

author: newbrain
author_rank: Super Contributor
timestamp: March 06, 2025, 04:13:58 pm
content: Quote from: peter-h on March 06, 2025, 01:54:50 pmThe store instruction is the actual clearing, so this the shortest possible path!OTOH, in my case, there's a number of things that get compiled between the clearing and actually exiting the ISR, including theCode:if (state <= Error)check: the body is never taken for the shown cases, but the test is done.So I would say that the:Quote from: Sauli on March 06, 2025, 01:07:50 pmhypothesis does not hold.

author: gpr
author_rank: Contributor
timestamp: March 06, 2025, 06:52:37 pm
content: Quote from: newbrain on March 06, 2025, 11:50:05 amIt is not shown in the code, but I suppose you clear interrupt flags somewhere. I usually clear them early in interrupt handler, and while other interrupt code is running, flags clearing has better chances to propagate to interrupt pending flag and clear it.And if stray interrupt still happens, it is usually not a problem if you check flags properly.

author: gpr
author_rank: Contributor
timestamp: March 06, 2025, 07:08:50 pm
content: Interrupt is triggered when NVIC has corresponding flag, and that flag is cleared via peripheral->NVIC path through various buses after you clear corresponding interrupt flag in peripheral.Adding DSB instruction doesn't affect this directly, it just adds a few cycles and NVIC has better chances to clear pending interrupt bit.

author: newbrain
author_rank: Super Contributor
timestamp: March 06, 2025, 10:10:52 pm
content: Quote from: gpr on March 06, 2025, 06:52:37 pmNo, I don't and am not supposed to do that directly - for the specific flags I left in the code - as I already specified in the post with the code.Clearing of the TXIS flag is a side effect of writing to to TXDR, and, conversely, reading from the RXDR clears the RXNE flag. See 47.4.9 in the RM for STM32H750.After this read or write, not much else is needed in the state machine: update the state, check it for the end of transaction (it's not), or an error, done.Quote from: gpr on March 06, 2025, 07:08:50 pmHonestly, I don't agree.It's not (only) a delay thing, it's a coherency one.Between the read of RXDR and the end of the ISR, I can see from the generated assembler:Code:ldr.wlr, [r12, #12] <-- This is the actual read from RXDRldrr3, [r1, #8]addsr0, r3, #1strr0, [r1, #8]strb.wlr, movr3, r2b.LBB3_15....LBB3_15:uxtbr0, r3strbr3, [r1, #6] <-- Save the statecmpr0, #1 <-- The if statementbhi.LBB3_18....LBB3_18:dsbsy <- DSB instructionaddsp, #8pop{r7, pc} <- return from from this ISRI count 12 instructions, including two branches, how many more should I insert?One or ten?It might depend of a heap of different factors, from the top of my head:system clock vs. peripheral clockcompiler version and optimization levelwhether caching of D and I is enabledwhat if I decide to move the code to ITCM or the data to DTCMMoving the read or write earlier might or might not work, because the compiler might have some different ideas, and would also mess with the code structure (it's a neat FSM).NOPS might be (partly) ineffective, due to the way the H7 works.I have an instinctive distrust of delays inserted to make things work.DSB is exactly what is needed: any following instruction will see the effects of of the explicit data accesses before DSB (its definition), so thepop {r7, pc}will only happen after the read has taken effect, and checking if another interrupt is pending will happen after that instruction (and before popping the IRQ stack frame, to efficiently chain IRQs).See, e.g.,here, many other articles can be found addressing this issue, but a.t.m. I can't find a very good one I read some time ago

author: gpr
author_rank: Contributor
timestamp: March 07, 2025, 01:35:45 pm
content: Quote from: newbrain on March 06, 2025, 10:10:52 pmIf DSB helps in your case, it's good. But it looks like a workaround which works, rather than a proper solution.Interrupts are generated by NVIC when corresponding pending bit is set in some register, and that's it. NVIC knows nothing about peripherals and their flags. DSB can help data to reach peripheral register, but it can't do anything beyond that to ensure that NVIC bit is cleared.I would still like to know a "proper" solution to completely mitigate stray interrupts, but I suspect there is no general proper solution for that. If interrupt handler code checks flags properly, like you do in you code with I2C_ISR_TXIS checks, there should be no problems.The worst thing which can happen is increased (doubled?) number of interrupts. And if this itself is a problem, it's time to think about something like DMA.

author: Sauli
author_rank: Contributor
timestamp: March 07, 2025, 01:59:40 pm
content: QuoteI do agree withgpr. I had (have) a case where an external interrupt is always triggered twice on a STM32F767. In this case adding a DSB at the end of the interrupt service did not help. In the end I just decided to do nothing in the interrupt service unless the request flag is set in the EXTI peripheral. Adding another write as recommended in the link provided bynewbrainmight have helped, but I did not try that.ARM says this about the NVIC operation:Level-based interrupts become pending, and then make the interrupt active. The Interrupt Service Routine (ISR) then accesses the peripheral, causing it to deassert the interrupt. If the interrupt is still asserted on return from the ISR, it becomes pending again.So, in my opinion, by the time the interrupt service returns if the request from the peripheral to the NVIC is still active at NVIC, the interrupt service will be executed again.By the way, the instruction8052f70: bd38 pop {r3, r4, r5, pc}shown in the post bypeter-his not a return from the interrupt service. It is a return from functionserial_transmit_complete_irq_handlerwhich presumably is called from the interrupt service routine.

author: peter-h
author_rank: Super Contributor
timestamp: March 07, 2025, 03:36:12 pm
content: Yes you are correct. It gets called from 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);}}which is called from hereCode:/*** @brief This function handles USART1 global interrupt - USART1_IRQn 0*/void USART1_IRQHandler(void){usart_common_IRQHandler(&huart1, 1);}and that gets called from the interrupt vector tableCode:.word USART1_IRQHandler/* USART1 */.word USART2_IRQHandler/* USART2 */.word USART3_IRQHandler/* USART3 */The asm for the IRQ handler isCode:/*** @brief This function handles USART1 global interrupt - USART1_IRQn 0*/void USART1_IRQHandler(void){8057984:b508push{r3, lr}usart_common_IRQHandler(&huart1, 1);8057986:2101movsr1, #18057988:4801ldrr0, [pc, #4]; (8057990 <USART1_IRQHandler+0xc>)805798a:f7ff ffbfbl805790c <usart_common_IRQHandler>}805798e:bd08pop{r3, pc}8057990:2000caa4.word0x2000caa4So in this case (STM's Cube IDE / MX sources) there is a ton of code which gets executed after the interrupt gets cleared, before the ISR stack frame gets popped. In some cases I shortened this stupid route and executed the ISR straight from the interrupt vector table, but there is still a fair bit of code there.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 08, 2025, 07:03:12 pm
content: Quote from: Sauli on March 07, 2025, 01:59:40 pmI wouldassumethat the designers of NVIC would not have overlooked something as fundamental as this. The DSB instructionshouldreach all the way into NVIC, because, well, it is the sole purpose of its existence, and there is no other documented robust mechanism to do the same. So if it does not reach NVIC, it sounds like a implementation mistake; a serious HW defect.NVIC is part of the core and it "understand" what it means to return from ISR. Therefore, I would obviously interpret your quoted ARM document thatNVIC is sampling* the peripheral register again when it becomes possible for a new ISR to trigger- i.e., at some point during or right after the ISR returns. And if NVIC does this, then it will most definitely see the correct flag in the register - as guaranteed by the DSB instruction executedbeforethe bx lr.*) what exactly is meant by sampling, would depend on the implementation. I have no idea if it's a direct connection, or a bus operation, and if latter, who is the initiating party. But whatever it is, DSB really should reach all the way to the destination.I'm suspecting something else must have caused the "double triggering" issue you saw. I mean, newbrainmustbe right, if not that would be a quite serious defect in the design of ARM microcontrollers, and this should be popping up more frequently. Instead of such architectural flaw, I suspect something is just going on with the STM32 peripheral - they are notoriously flaky and buggy in every imaginable way.

author: incf
author_rank: Frequent Contributor
timestamp: March 08, 2025, 07:51:46 pm
content: After reading the materials from ARM and ST I think the proper sequence for clearing a level sensitive interrupt inside of an IRQ handler goes something like:Entrance into an ISRclear the IRQ pending bit in the peripheral so that the NVIC will not get re-triggered immediatelyISB+DSB <- Edit: ARM says "read back the register in a loop and keep repeatedly clearing until it clears"clear the IRQ pending bit in the NVICISB+DSB <- Edit: ARM says "read back the register in a loop and keep repeatedly clearing until it clears"Exit from ISRI'm fairly confident that "something like" two independent sets of memory break instructions might be required between the two clear operations for everything to work portably/independent of how the peripheral/bus bridges/etc. are implemented*memory break instructions like ISB is mandatory even on cortex M0, and ISB+DSB for "big" cores like M4. The ISB is far more important than DSB.Below are the two diagrams that changed my view of how things workThe existence of a clock synchronizer for the IRQ line heading back to the processor was surprising to me. If the peripheral bus was clocked at a much slower frequency than the CPU, the IRQ line might stay asserted for many clock cycles after the write command to clear the IRQ pending flag is executed.It is not clear to me if the memory break instructions could ever fully compensate for that particular path - since it occurs outside of the CPU. (could the ARM M0 core have some signals to interface with external busses/synchronizers to ensure the memory break instructions have fully taken effect before resuming execution when an ISB/DSB is executed?)edit: ARM says to read back the peripheral and NVIC registers to deal with things like clock synchronizers

author: peter-h
author_rank: Super Contributor
timestamp: March 08, 2025, 08:23:11 pm
content: This is bloody complicated. I've printed out that 2012 ARM document and will read it over breakfastThankfully most embedded systems never disable interrupts from peripherals. Or, if they do, getting one more doesn't matter.But I can see how both myself and perhaps others have been getting away with this.I disableallints for short bits of code. But that's a different thingCode:/**\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");}In this example some might try disabling just USB interrupts but I disable the whole lot since it is easierCode:if (!error){// Output the data to USB.// USB interrupts must be disabled around this.__disable_irq();USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);USBD_CDC_TransmitPacket(&hUsbDeviceFS);__enable_irq();}

author: Jeroen3
author_rank: Super Contributor
timestamp: March 10, 2025, 07:42:30 am
content: Quote from: incf on March 08, 2025, 07:51:46 pmFrom what I have learned is that a write to a peripheral doesn't actually finish until the synchronisation is complete. The peripheral generates wait states on the system bus.This would mean that a DSB ensures the write is finished. And an ISB ensures any pre-fetches during the DSB are invalid.I wouldassumethat the peripheral IRQ-mask is just an unsynchronised AND-gate between the status register and the IRQ line.The race condition in this scenario is limited to clearing an IRQ on aslowerrunning peripheral (eg: timers).

author: peter-h
author_rank: Super Contributor
timestamp: March 10, 2025, 10:13:11 am
content: Perhaps the main thing, when disabling interrupts, is tonotdo anything to the ISR which would make it incapable of dealing with some more interrupts.It is a subtle thing but I suspect in most embedded cases it "just works" that way.

author: westfw
author_rank: Super Contributor
timestamp: March 10, 2025, 06:40:01 pm
content: QuoteI've seen a bunch of chips where peripheral synchronization apparently needs to be done separately from anything the CPU core is aware of, notably in the Atmel SAMD chips.I don't really understand it; the datasheets say that synchronization can take up to 5x clocks of the slower peripheral, which is pretty painful if you're dealing with a 32kHz RTC. (also, it's unclear how often such syncs have to occur - does a peripheral STAY synchronized for a least a slow clock? And some peripherals will apparently stall the bus till synchronized, while others need (?) an explicit check of a peripheral flag.) Grr. Just another straw on the back of the determinism camel...

author: incf
author_rank: Frequent Contributor
timestamp: March 10, 2025, 07:26:15 pm
content: "Let me fix that for you" (I think?):QuoteThe CPU appears to have no visibility of things not directly connected to the bus that it owns. Several layers of bridges/synchronizers seem to be a source of nondeterminism that the CPU cannot see except by polling it with repeated (slow) read-modify-write operations.edit: actually, not even that much is guaranteed by ARM. DSB only guarantees SCB writes complete.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 10, 2025, 07:36:22 pm
content: Quote from: incf on March 10, 2025, 07:26:15 pmOf course it has visibility - through the bus. The whole purpose of the DSB instruction is to wait until the writes are finished - to their destinations, no matter how many clock domain synchronization steps there are in the way. If you are happy with the write going out of the CPU to the nearest bus, you don't need any such barrier instructions at all (and this is indeed 99.999% of all memory operations in a typical program).

author: peter-h
author_rank: Super Contributor
timestamp: March 10, 2025, 09:41:50 pm
content: Been reading that ARM doc from 2012 on this.It says DMB should not be needed since all present chips (this is 2012) never do out of ordermemoryaccesses. Only in multi master cases e.g. CPU and DMA.DSB and ISB should be used for control register writing, although STM do not use it (32F4). I can see why they don't bother; they do enough stuff after the writing.It is quite a general doc and it is hard to work out what it really means. It gives a list of reasons why people get away with it - basically because you need only 1 or at most 2 instructions after the relevant access for things to sort themselves out.

author: incf
author_rank: Frequent Contributor
timestamp: March 10, 2025, 09:53:25 pm
content: Quote from: peter-h on March 10, 2025, 09:41:50 pmThis agrees with what I had seen elsewhere. My takeaway from reading certain docs was that ISB was the only instruction that was strictly required on small cortex cores (like M0). And the DSB effectively turns into a less slow, more limited. version of ISB in simple systems. (ie. it's full intended behavior was not visible in in simple systems, systems lacking the features of "big" SOCs like cache, etc., but I might be wrong)I get the distinct impression in some of the ARM documents that they are not entirely consistent with their definitions of what the instructions actually do.Edit: misread it and I was totally wrong about ISB vs DSB.Quote from: Siwastaja on March 10, 2025, 07:36:22 pmI don't know about that. When I read the AHB-Lite spec, I don't know how that would work with several layers of bridges/buffers/clock synchronizers. The bus does not appear to have signals dedicated to that function you describe. (either the slave forces to bus to wait for every operation, or it buffers it and doesn't force waiting at all*)I get the impression that bridges just buffer the writes (and let the CPU keep running - ie. lie to the AHB bus about the write actually being complete) since the peripheral bus is in a different clock domain.Internal peripheral registers like the NVIC and SCB might have the ability to interact with DSB/ISB. But I am not sure about the necessary control signals propagating through several layers of outside buses.https://www.eecs.umich.edu/courses/eecs373/readings/ARM_IHI0033A_AMBA_AHB-Lite_SPEC.pdf*Maybe HMASTLOCK in  could be repurposed for something like what you describe, but it is hard to sayedit: in  "HMASTLOCK is always LOW because the Cortex-M0+ processor can not generate any locked transactions" "Cortex ™ -M0+ Revision: r0p0 Integration and Implementation Manual"https://picture.iczhiku.com/resource/eetop/syksoZjIGKETOxxB.pdfedit 2: maybe "HPROT" Bufferable/Cacheable bits could do it?edit 3: per table 4-7 in  there are "[[hardcoded values for]] HPROT[3:2] [[Bufferable/Cacheable]] memory types when an MPU is either not present or is disabled." that depend solely on the memory address. The entire peripheral range is "bufferable"edit 4: per https://developer.arm.com/documentation/ddi0419/latest/Outside of the SCB it seems DSB is not guaranteed to do anythingif you wanted to clear a bit in a slow peripheral like the RTC, you'd have to select a MCU with a MPU and program it to disable buffering. And then hope that the AHB to APB bridge respects that. And also hope that APB has a "bufferable" flag and implement it properly. And then hope that the clock synchronizers between RTC and APB somehow respect the "bufferable" flag (which hardly seems possible when asynchronously crossing clock domains)Reading and checking after a write seems to be a common way to get around buffers/synchronizers.

author: peter-h
author_rank: Super Contributor
timestamp: March 11, 2025, 08:06:19 am
content: Yes I think this is why ST put the wait loops in their startupCode:// This is the default value of CCR. Cube IDE loads potentially different values// according to debugger config// https://www.eevblog.com/forum/microcontrollers/divide-by-zero-exception-32f417-and-cube-ide/// and this overrides it. A value of 0 disables the /0 hard fault trap, for example.SCB->CCR=0;// Enable CYCCNT as a free running counter, for various timeouts where we// don't want to rely on interrupts.// Runs at B_SystemCoreClock (or SystemCoreClock - both same) Hz AFTER these are set up.// B_hang_around() uses this counter, but the delay will be 168/16 longer than the ms value// because the PLL is not yet programmed, so the CPU runs at 16MHz.// The only reason we enable CYCCNT this early is to enable the use of b_hang_around() for// delays, even though the delays will be that much longer.CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;DWT->CYCCNT = 0;// start it at zeroDWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;// The following initialisation code is an inline condensation of what was// in various functions which were called in a sequence. The sequence has been// preserved.// Some of the initialisation was duplicated via messy coding; this has been removed.// Set 5 wait states *before* selecting the 168MHz speed below. This setting is// duplicated below. ACR resets to 0, so we just load this value straight in.//FLASH->ACR = FLASH_ACR_LATENCY_5WS;// ========== This was in SystemInit() ============// FPU startup is now in startupxxx.s - see// https://www.eevblog.com/forum/microcontrollers/stm-32f4-fpu-registers-and-main()-gotcha// #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)// SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */// #endif/* Reset the RCC clock configuration to the default reset state ------------*//* Set HSION bit */RCC->CR |= (uint32_t)0x00000001;/* Reset CFGR register */RCC->CFGR = 0x00000000;/* Reset HSEON, CSSON and PLLON bits */RCC->CR &= (uint32_t)0xFEF6FFFF;/* Reset PLLCFGR register */RCC->PLLCFGR = 0x24003010;/* Reset HSEBYP bit */RCC->CR &= (uint32_t)0xFFFBFFFF;/* Disable all clock interrupts */RCC->CIR = 0x00000000;// ============ This was in SetSysClock() ============// The settings were partly overriden later by stuff in SystemClock_Config()!// Configure the System clock source, PLL Multiplier and Divider factors,// AHB/APBx prescalers and Flash settingsvolatile uint32_t StartUpCounter = 0, HSEStatus = 0;/* Enable HSE */RCC->CR |= ((uint32_t)RCC_CR_HSEON);/* Wait till HSE is ready and if Time out is reached exit */do{HSEStatus = RCC->CR & RCC_CR_HSERDY;StartUpCounter++;} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));/* Enable high performance mode, System frequency up to 168 MHz */RCC->APB1ENR |= RCC_APB1ENR_PWREN;PWR->CR |= PWR_CR_PMODE;/* HCLK = SYSCLK / 1*/RCC->CFGR |= RCC_CFGR_HPRE_DIV1;/* PCLK2 = HCLK / 4 i.e. 42MHz */RCC->CFGR |= RCC_CFGR_PPRE2_DIV4;/* PCLK1 = HCLK / 4 i.e. 42MHz */RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;// PLL Parameters// These produce a 168MHz CPU clock (336/2) and a 48MHz USB clock (336/7)/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */#define PLL_M 25#define PLL_N 336/* SYSCLK = PLL_VCO / PLL_P */#define PLL_P 2/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */#define PLL_Q 7/* Configure the main PLL */RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);/* Enable the main PLL */RCC->CR |= RCC_CR_PLLON;/* Wait till the main PLL is ready */while((RCC->CR & RCC_CR_PLLRDY) == 0) {}/* Configure Flash prefetch, Instruction cache, Data cache and wait state */FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;/* Select the main PLL as system clock source */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |= RCC_CFGR_SW_PLL;/* Wait till the main PLL is used as system clock source */while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL) {}// This bit was in SystemClock_Config(), with duff comments// Enable peripheral clocks, and wait for them to settle__HAL_RCC_PWR_CLK_ENABLE();// Scale 1 mode (default value at reset): allows the maximum value of fHCLK = 168 MHz// If this was loading a different value, it probably wants to be much earlier!__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);// Configure the Vector Table location. No change from the reset value.SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET_BOOT;// set VT locationStill reading that 2012 ARM doc. A lot of the control reg accesses have an implicit DMB.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 11, 2025, 11:18:04 am
content: Some MCUs (although that's more the exception than the rule IME) have synchronization flags on some peripheral registers (or even on some bits of some registers), like the SAMD series, that you can poll to make sure a write is completed.

author: jnk0le
author_rank: Regular Contributor
timestamp: March 11, 2025, 11:24:20 pm
content: Quote from: peter-h on March 11, 2025, 08:06:19 amThose wait loops are due to the HSE and PLL taking a significant amount of time to startup/stabilize/lock. Not related to write synchronization.

author: peter-h
author_rank: Super Contributor
timestamp: March 12, 2025, 06:29:33 am
content: Sure, but this sort of code, which is probably quite common on the registers involved, avoids the need to perform write sync.This whole area is a great example of loads of people (who like me never read the obscure ARM manual) getting away with it because their coding practices throw in lots of delays. And in most (all?) cases 1-2 instructions are enough.The peripheral writes all generate wait states back to the CPU. So one can put a byte/word into a UART andimmediatelystart reading the TX buffer status, etc.

author: Berni
author_rank: Super Contributor
timestamp: March 12, 2025, 08:34:30 am
content: You don't need any special software synchronization when working with the same peripheral.The point is that the write might just arrive a bit late. But this write keeps the APB bus busy so you can't squeeze in a read operation before this write, while another write to the same peripheral will just wait even more since the bus is busy. These write buffers are likely very small so it is likely two consecutive writes will stall the main AHB bus and so also stall the CPU. Often software won't access peripheral registers in consecutive instructions, so having such buffers can in a lot of cases completely eliminate any CPU stall due to a busy memory bus.Clock crossing synchronization is unfortunately required in digital chips as soon as you mix clock domains, otherwise you can have metastable states screw up the logic because some gates see a logic high some see a logic low. The IRQ flag could come from anywhere so there is a danger it flips state at exactly when the clock edge hits.This is less of a problem for going between the AHB and APB bus because typically all of these are divided down from the same clock, so they have a well defined phase relationship. But it is more of a problem that going from the fast bus to the slow bus can stall the fast bus for some number of clock cycles. So some optimizations are welcome (like that write buffer(

author: peter-h
author_rank: Super Contributor
timestamp: March 12, 2025, 09:16:00 am
content: It is an interesting question whether writing a byte into a UART (which runs at say 1/8 of the CPU clock) sends 8 wait states back to the CPU. I doubt it does that (it would be stupid, given the 500MHz chips we have now) but who knows?But for sure if you now want to read the UART back, youwillget all those wait states.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 12, 2025, 11:10:25 am
content: In particular, when setting the clock(s) for the peripheral bus(es), you have to understand the implications in terms of synchronization delays with the system clock. Low frequency clocks for APBs yield more sync delays.

author: Sauli
author_rank: Contributor
timestamp: March 12, 2025, 01:19:20 pm
content: Quote from ARM application note 321:QuoteI think the first example also holds true for clearing an interrupt request from a peripheral to the side effect of that reaching the NVIC. So the request can be active at NVIC even after the write has completed. How would the barrier instructions even be expected to handle this? How could they know that the active request at NVIC is just not yet cleared by the peripheral or if it actually is valid because the peripheral has another interrupt to be serviced?

author: incf
author_rank: Frequent Contributor
timestamp: March 12, 2025, 01:31:51 pm
content: Quote from: peter-h on March 12, 2025, 06:29:33 amIn practice, peripheral writes don't generate wait states. Peripheral reads do generate wait states.In most CPUs peripheral writes are buffered by something like the AHB-APB bridge (eg. STM32s). And it takes setting bits in the MPU to disable write buffering on peripherals. (Most small ARMs don't have a MPU and therefore cannot disable any buffering)

author: peter-h
author_rank: Super Contributor
timestamp: March 12, 2025, 06:32:02 pm
content: Yes I meantreadscause wait states. Writes must be buffered otherwise every time a 500MHz CPU wrote to some UART, it would lose a load of time.

author: gpr
author_rank: Contributor
timestamp: March 12, 2025, 09:44:11 pm
content: What is also interesting, is how flag clear write behaves. For example, code like this:Code:dma_interrupt_handler(){if (DMA1->ISR & DMA_ISR_TCIF4) {DMA1->IFCR = DMA_IFCR_CTCIF4;do_something();}Should I wait for ISR flag to clear before returning from interrupt handler and write something like this?Code:dma_interrupt_handler(){if (DMA1->ISR & DMA_ISR_TCIF4) {// wait for flag to become zero in ISRwhile (DMA1->ISR & DMA_ISR_TCIF4) {DMA1->IFCR = DMA_IFCR_CTCIF4;}do_something();}

author: incf
author_rank: Frequent Contributor
timestamp: March 12, 2025, 10:33:28 pm
content: Technically, yes, but it is overkill for most peripherals.

author: Jeroen3
author_rank: Super Contributor
timestamp: March 13, 2025, 07:26:30 am
content: I think we need an architect to answer this question.I even question this solution now:https://www.eevblog.com/forum/microcontrollers/why-does-cortex-m0()-implement-instruction-like-isb-if-it-has-no-cache/msg5837941/#msg5837941

author: peter-h
author_rank: Super Contributor
timestamp: March 13, 2025, 08:52:00 am
content: AN321 is interesting but you need to spend hours reading it.It describes where DSB etc are needed, but also says that mostly none of these are needed in reality since any delays sort themselves out in 1 or 2 instructions. If you are coding in C, you are gonna get more than 1 or 2 instructions in most casesI can also see another reason why this does not bite lots of people in the bum: in most embedded systems, one is not doing weird stuff like disabling a peripheral interrupt and especially then assuming it will be immediate. Let's say you have a UART receiving data. If you no longer want that data, you chuck it away in the ISR (by not emptying the RX circular buffer).

author: incf
author_rank: Frequent Contributor
timestamp: March 13, 2025, 12:09:47 pm
content: Quote from: Jeroen3 on March 13, 2025, 07:26:30 amPer ARM AN321, section "4.6 Disabling Interrupts using NVIC"Code:NVIC_DisableIRQ(device_IRQn); // Disable interrupt (C macro that translates into one or two instructions to calculate and store the new register value)__DSB(); //DSB is optional on Cortex-M, but is good practice__ISB(); //ISB is absolutely required for all Cortex-M devicesIs ARM's recommended way to do it.*NVIC and all System Control Block peripheral registers are special in that they respect memory barrier instructions.The only thing that saves most folks is short pipeline length on most cores, and rareness of that 1-2 clock cycle window actually matteringExternal peripherals (eg. RTC, UART) are a totally different matter since (1) they don't respect memory barrier instructions (2) writes to the peripheral busses are usually buffered (3) IRQ pending lines for external peripherals often have clock synchronizers on them that delay the effect of clearing an ISR pending/enabled bit in the peripheral.*See "3.5 Cortex-M processor implementation specific requirements" for cortex M3/M4 vs M0 vs M0+This single screenshot/except from AN321 (plus the one or two other excepts that I posted earlier) more or less explain my original question, and all of the follow up questions about memory barriers (and things like ISRs) on Cortex-M0+

author: Siwastaja
author_rank: Super Contributor
timestamp: March 13, 2025, 12:46:27 pm
content: In other words, you are saying that DSB is only effective to limited set of ARM-supplied registers, in NVIC and SCB block, and never any peripheral register as implemented by, for example, ST;not even AHB/APB buffers/arbiters/clock domain synchronizers.I had always assumed, based on absence of documentation and rarity of problems, that special synchronization sequences of reading back peripheral registers are exceptions and documented on case-by-case basis, basically limited to when manufacturer implements a peripheral so that it uses separate clock domains for AHB/APB interface and actual storage of the registers internally, for whatever reason. So that DSB would be effective up to the point where the peripheral connects to the bus. But it seems I have been completely wrong.This is good to keep in mind for those rare corner cases where naturally occurring delays from code are not sufficient. A communication peripheral (UART, SPI, I2C...) with "clear-interrupt-flag-on-data-register-access" pattern, when used at very slow peripheral clock, while CPU runs order of magnitude faster, would be such a case.

author: langwadt
author_rank: Super Contributor
timestamp: March 13, 2025, 04:30:39 pm
content: Quote from: peter-h on March 13, 2025, 08:52:00 amfor a UART transmitting data it is not uncommon to enable TX interrupt after you put something in the buffer, and in the TX interrupt disable the TX interrupt when you send the last data in the buffer

author: peter-h
author_rank: Super Contributor
timestamp: March 13, 2025, 04:46:10 pm
content: That works as expected because when there is no more to send out, the UART will not interrupt. So your TX interrupt disable will work fine.Youcontrol the cause of the TX interrupt.Onreceiveddata, you might get more RX interrupt(s) after you disabled them.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 13, 2025, 04:48:43 pm
content: Quote from: peter-h on March 13, 2025, 04:46:10 pmSurely langwadt would mean something like "TX buffer empty" interrupt or similar. That would continue generating interrupts, so disabling it and having the flag propagate all the way into NVIC before exiting the ISR would be necessary, no?

author: langwadt
author_rank: Super Contributor
timestamp: March 13, 2025, 04:53:51 pm
content: Quote from: Siwastaja on March 13, 2025, 04:48:43 pmyep

author: peter-h
author_rank: Super Contributor
timestamp: March 13, 2025, 05:31:37 pm
content: It would be in the "TX buffer empty" ISR where one would disable further interrupts if there is no more to transmit.I suppose one could do it in the "TX buffer has room" interrupt... But in that case the next interrupt won't happen for a whole character period, no?

author: Berni
author_rank: Super Contributor
timestamp: March 14, 2025, 11:36:01 am
content: The TX buffer on the UART wouldn't pose problems because a CPU write triggers all the actions that happen with it.Where the problem pops up with peripherals that work autonomously to generate an interrupt. Like a UART RX buffer interrupt, Timer interrupt, GPIO edge interrupt etc.. As that has a random chance of triggering within the next few cycles of an CPU instruction that disabled that interrupt. However it is very rare that the softwares particular use case actually breaks if this happens, it will typically look as if that event occurred a few fractions of a microsecond earlier.But yes theoretically there is a way to detect this if your code is written in an certain way. You would need to have a write to an interrupt disabling register quickly followed by an instruction that reads memory from a location that gets written inside the interrupt handler. If you wrap this write in a function call you likely already solved the paradox as the time it takes the CPU to return from that function would already be long enough for the IRQ to make it trough before any extra code gets executed (branches are annoying for CPUs because they have to flush the pipeline and/or push/pop state so it can take a significant number of CPU cycles)