You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何mult=3/4时while循环在条件为真时提前终止?

问题解析:循环提前终止与volatile变量的非原子读取

问题现象

当mult取值为3或4时,EXTI_Main函数中的while(TMR3_GetTick() < minimum);循环会提前终止,输出结果显示实际tick值小于设定的minimum(例如mult=3时tick=512 < 600,mult=4时tick=768 < 800)。

相关代码

EXTI_Main函数

void EXTI_Main(uint8_t mult)//range 1-4(inclusive)
{
    uint16_t minimum = 200 * mult;
    while(TMR3_GetTick() < minimum);
    printf("mult:%hhu	tick:%hu	minimum:%hu\n", mult, TMR3_GetTick(), minimum);
    //RF_Main();
    LCD_Read();
    
}

定时器tick读取代码(tmr3.c)

static volatile uint16_t msTick;//incremented using ISR
uint16_t TMR3_GetTick(void)
{
    return msTick;
}

运行输出示例

mult:0  tick:0  minimum:0
mult:1  tick:414        minimum:200
mult:2  tick:453        minimum:400
mult:3  tick:600        minimum:600
mult:4  tick:800        minimum:800
mult:0  tick:0  minimum:0
mult:1  tick:414        minimum:200
mult:2  tick:453        minimum:400
mult:3  tick:600        minimum:600
mult:4  tick:768        minimum:800
mult:0  tick:0  minimum:0
mult:1  tick:414        minimum:200
mult:2  tick:453        minimum:400
mult:3  tick:512        minimum:600
mult:4  tick:768        minimum:800
mult:0  tick:0  minimum:0

问题根源

核心原因是16位volatile变量的非原子读取:

  1. msTick是16位变量,且在中断服务程序(ISR)中被递增修改,因此被声明为volatile以防止编译器缓存其值。
  2. 在8位架构的MCU上,读取16位变量需要分两步完成:先读取高字节,再读取低字节(或反之),两步操作之间存在时间窗口。
  3. 如果ISR在这两步读取之间触发并修改了msTick,就会得到一个错误的“拼凑值”。例如:
    • 当msTick从0x01FF(511)递增到0x0200(512)时,若先读取低字节0xFF,随后ISR完成递增操作(低字节变为0x00,高字节变为0x02),再读取高字节0x02,最终得到的错误值是0x02FF(767)。
    • 这个错误值远大于mult=3时的minimum=600,导致循环错误地认为已满足终止条件而提前退出。
  4. 循环退出后,printf再次读取msTick时,得到的是正确的最新值(512),因此出现输出中tick < minimum的矛盾现象。

解决方案解析

用户采用的写法:

uint16_t tock; while((tock = TMR3_GetTick()) < minimum);

本质是通过每次循环只读取一次msTick并存储到局部变量tock,再基于tock判断循环条件。虽然没有从根本上解决非原子读取的问题,但大幅降低了错误值导致提前终止的概率:

  • 若某次读取到错误的偏小值,循环会继续执行,直到读取到正确的、满足条件的值;
  • 若读取到错误的偏大值,虽然仍会提前终止,但这种情况的发生概率极低,适用于对时间精度要求不高的场景。

更严谨的解决方案是保证msTick读取的原子性,通过关中断完成读取操作:

uint16_t TMR3_GetTick(void)
{
    uint16_t tick;
    __disable_irq(); // 关闭全局中断,防止ISR打断读取操作
    tick = msTick;
    __enable_irq();  // 恢复全局中断
    return tick;
}

这样能确保读取msTick的过程不被打断,彻底避免错误值的产生。

内容的提问来源于stack exchange,提问作者Wesley Kienhuis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 13:15:39