openlang

TMR3 ISR 丢失捕获事件,尽管标志位是 w1c 且已清除

回流焊炉控制器,Cortex-M0 兼容芯片,arm-none-eabi-gcc -O2,Fedora。TMR3 跑在 1 MHz。更新事件是 1 ms 的 tick,CH1 捕获市电过零,CH2 比较输出触发 triac。

问题是,50 Hz 下跑 60 s 后,zc_count 应该在 6000 左右。实际每次都不一样,在 5900 到 5990 之间。漏掉一次过零,这个半波就没有门极脉冲,加热功率就会闪烁。提高 UART 日志速率会更严重。

我已经检查过这几项。

  • 示波器显示过零脉冲干净,间隔恒定 10 ms。所以不是光耦或噪声的问题。
  • 计数器都是 volatile。
  • 把捕获块挪到 tick 块前面。丢失变少了,但没有消失。
  • 提高 TMR3 中断优先级,没有任何变化。

我想不通。数据手册说 SR 标志是写 1 清零,我也是处理完每个标志就立刻清掉。只要标志置位,NVIC 就必须再次进入处理函数。那置位了标志的事件怎么会丢?

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 在 regs.h 里就是 base + 0x10 处一个普通的 volatile uint32_t。说不定这个克隆芯片有硅片层面的缺陷,但我没法脱离目标板测试。只有 UART 计数器和示波器。

从英语翻译查看原文

后续讨论 5

顺着彼此的依据读下去
  1. 固定链接

    我在一个实验室数据记录仪上遇到过类似的事,用的是一块我用了几周的开发板。我的事件计数少了百分之几。处理函数里写的是 SR |= flag。这会把当前所有已置位的标志读出来再全部写回,所以在读和写之间置位的标志会在没处理的情况下被清掉。不过我这边改成直接 SR = flag 就好了。

    从英语翻译查看原文
  2. 固定链接

    补充 bkt 的观点,在贴出的处理函数里,即使 sr 只在开头读了一次,丢失照样会发生。TMR3_SR |= TMR_SR_UIF 会重新读一次,所以如果 CC1IF 在第一次读取之后才置位,写回时就会把它清掉,而且没被处理。处理函数里每一处 |= 都是同样的问题。

    有一点要注意,我会去查一下这个克隆芯片的参考手册,确认 SR 是不是真的写 1 清零。有些定时器是写 0 清零,那种情况下正确写法是 SR = ~flag。

    从英语翻译查看原文
  3. 固定链接

    好,这就解释了丢失。第一次读和 |= 写之间有个窗口,5900 到 5990 也符合 UART 负载越大窗口越宽。

    手册上写的是 w1c,我确认过。所以 TMR3_SR = TMR_SR_CC1IF 只会动那一位。没问题。

    我的问题是,在 -O2 下,对 volatile 做 |= 是不是总会编译成 ldr、orr、str?还是 GCC 可以把它合并?我看了 GCC volatile 页面,里面对复合赋值说得很少。改处理函数之前,我会先用 objdump 看反汇编。

    从英语翻译查看原文
  4. 固定链接

    据我所知,GCC 不会把它优化掉。对 volatile 对象,x |= y 仍然要读 x 再写 x,每次访问都算副作用。Cortex-M0 上没有内存操作数的 OR 指令,所以你应该会看到 ldr、orrs、str(可能还多一条加载掩码常量的 ldr)。在别的一些目标上,比如 x86,编译器可以对内存用一条 or 指令。那依然是读改写,所以问题是一样的。

    GCC 那一页在这里写得含糊,是因为 C 标准把“什么算一次访问”留给了实现定义。所以我同意看反汇编是对的办法。比如对目标文件跑 arm-none-eabi-objdump -d,然后看处理函数。

    改成 TMR3_SR = TMR_SR_CC1IF; 之后,我还会看编译器是不是每个标志只生成一条 str。纯写入里没有读,所以不会再有窗口。

    如果这之后计数仍然偏少,你提到的硅片缺陷就是下一个怀疑对象。

    从英语翻译查看原文

同一个问题的其他角度

  1. STM32H7 UART TX DMA 在开启 D-cache 时发出旧字节

    STM32H7,arm-none-eabi-gcc。用 DMA 发送 64 字节的帧。关闭缓存时一切正常。加上 -DUSE_DCACHE 后,分析仪显示帧是错的。复位后的第一帧是对的。之后的帧带着上一帧的字节,按 32 字节一块。有时前面是新的,尾部…

    回复 8最近活动 c
  2. 从 C 回调进来会丢掉我设置好的 arena

    一个 C 库会回调我的 Odin 代码,而回调看不到我在注册它之前设置好的 context。proc "c" 没有隐式 context,所以得自己建一个,而 runtime.default_context() 给的是默认分配器,不是注册代码里 con…

    回复 5最近活动 odinc
  3. 用 .? 解包 C 指针其实是一个断言

    对从 C 返回的指针用 .? 是断言,不是检查。它表示这里不可能是 null。在带安全检查的构建里,null 会 panic,而在 ReleaseFast 或 ReleaseSmall 里则是未定义行为。对 malloc 来说,这个断言按定义就是假的…

    回复 3最近活动 zig