openlang

TMR3 ISR loses capture events although flags are w1c and cleared

Reflow oven controller, Cortex-M0 clone, arm-none-eabi-gcc -O2, Fedora. TMR3 runs at 1 MHz. Update event is the 1 ms tick, CH1 captures mains zero-cross, CH2 compare fires the triac.

Problem: after 60 s on 50 Hz, zc_count should be about 6000. I get between 5900 and 5990, different on every run. A missed zero-cross means no gate pulse in that half-wave, so the heater power flickers. It gets worse when I raise the UART log rate.

What I checked:

  • Scope shows clean zero-cross pulses, constant 10 ms spacing. So it is not the optocoupler or noise.
  • Counters are volatile.
  • Moved the capture block above the tick block. Loss is less frequent, not gone.
  • Higher TMR3 interrupt priority changes nothing.

I don't get it. The datasheet says SR flags are write-1-to-clear, and I clear each flag as soon as I handle it. If a flag is set, the NVIC must enter the handler again. So how can an event with a set flag be missed?

void TMR3_IRQHandler(void)
{
    uint32_t sr = TMR3_SR;

    if (sr & TMR_SR_UIF) {
        TMR3_SR |= TMR_SR_UIF;
        tick_ms++;
        if ((tick_ms % 500u) == 0)
            log_due = 1;
    }
    if (sr & TMR_SR_CC1IF) {
        TMR3_SR |= TMR_SR_CC1IF;
        zc_count++;
        zc_stamp = TMR3_CCR1;
        TMR3_CCR2 = (zc_stamp + fire_delay_us) & 0xFFFFu;
    }
    if (sr & TMR_SR_CC2IF) {
        TMR3_SR |= TMR_SR_CC2IF;
        TRIAC_ON();
    }
}

SR is defined in regs.h as a plain volatile uint32_t at base + 0x10. Eventually the clone has a silicon quirk, but I can't test off-target. Only UART counters and the scope.

The conversation continues 5

Follow the reasoning
  1. Permalink

    I had something like this on a lab data logger, a dev board I used for a few weeks. My event count came up a few percent short. The handler did SR |= flag. That reads every flag currently set and writes them all back, so a flag that rose between the read and the write was cleared without being handled. Plain SR = flag fixed mine, though.

  2. Permalink

    To add one detail to bkt's point: in the posted handler the loss happens even though sr is read only once at the top. TMR3_SR |= TMR_SR_UIF performs a fresh read, so if CC1IF rose after that first read, the write-back clears it unhandled. The same applies to every |= in the handler.

    One caveat: I would confirm in the clone's reference manual that SR is really write-1-to-clear. Some timers use write-0-to-clear, and there the correct form is SR = ~flag.

  3. Permalink

    Ok, that explains the loss. Between the first read and the |= write there is a window, and 5900 to 5990 fits with more UART load widening it.

    Manual says w1c, I checked this. So TMR3_SR = TMR_SR_CC1IF only touches that one bit. Fine.

    Question: does |= on a volatile always compile to ldr, orr, str at -O2? Or can GCC fold it? I read the GCC volatile page, and it says little about compound assignment. I will check the disassembly with objdump before I change the handler.

  4. Permalink

    As far as I know, GCC will not fold it away. For a volatile object, x |= y still has to read x and write x, and each access counts as a side effect. On Cortex-M0 there is no memory-operand OR instruction, so you should see ldr, orrs, str (maybe with an extra ldr for the mask constant). On some other targets, x86 for example, the compiler can use one or instruction on memory. It is still a read-modify-write, so the problem is the same.

    The GCC page is vague here because the C standard leaves "what is an access" to the implementation. So I agree that checking the disassembly is the right method. Something like arm-none-eabi-objdump -d on the object file, and look at the handler.

    After you change to TMR3_SR = TMR_SR_CC1IF;, I would also look at whether the compiler emits one str per flag. A plain store has no read in it, so there is no window left.

    If the counts still come out short after that, the silicon quirk you mentioned becomes the next suspect.

  5. Permalink

    Thanks. I did not think about x86 or on memory still being a read-modify-write. Same window there.

    I will change all three to plain stores, check the disassembly and run the 60 s test. Eventually the quirk is next if it is still short.

Other sides of the same question

  1. STM32H7 UART TX DMA sends old bytes when D-cache is on

    STM32H7, arm-none-eabi-gcc. DMA TX of a 64 byte frame. Cache off: fine. With -DUSE_DCACHE, the analyzer shows wrong frames. T…

    Replies 8Latest activity c
  2. Callbacks from C lose the arena I set up

    A C library calls back into my Odin code, and the callback can't see the context I set up before registering it. A proc "c" g…

    Replies 5Latest activity odinc
  3. Unwrapping a C pointer with .? is an assertion

    .? on a pointer that came back from C is an assertion, not a check. It says null is impossible here. In a safety-checked buil…

    Replies 3Latest activity zig