volatile到底在拦什么:SysTick->VAL读的哪格

那个空循环,怎么在别人机器上就废了

我在自己板子上写了个空循环延时,跑了半个月啥事没有。代码就长这样:

复制代码
uint32_t i;
for (i = 0; i < 72000U; i++)
{
}

结果代码丢给同事,他编译完一烧,延时直接没了,电机启动太快把 PWM 都带乱了。我第一反应是板子有差异,换板、换线、换供电,折腾一下午没用。

直到我打开他工程的编译选项,优化等级是 -O2。我平时手贱,习惯性把优化设成 -O0,编译器对我的代码睁一只眼闭一只眼。他的没关。

编译器把这循环当废话删了

这段循环不点灯、不改任何后面要用的变量、也不调函数。做完以后,程序能观察到的结果跟没做一模一样。

于是编译器开优化后这么想:既然结果没变化,这 72000 次加一图啥?整段循环直接删掉。我原本想等几毫秒,实际一个时钟周期就滑过去了。

解决办法是给变量加个单词:

复制代码
volatile uint32_t i;
for (i = 0; i < 72000U; i++)
{
}

这个 volatile 就是在告诉编译器:i 的每一次读写都别省,必须真做出来。i++ 不再是你眼里无关紧要的算术,每一轮都得真读真写。循环保住了,延时这层至少不被优化没了。当然空循环延时本身还是不精确,主频、优化等级都会影响,但最要命的那层坑填上了。

还有个更隐蔽的场景。我搞过一个 tick_ms,定时器每 1ms 中断一次给它加一,主循环里 while 等它到 1000:

复制代码
static uint32_t tick_ms = 0U;

while (tick_ms < 1000U)
{
}

只看主循环,tick_ms 在 while 里压根没被改过。可它在中断里明明会变。编译器不知道这茬,它可能只取一次那个 0,往后就再也不看了。中断那边都把 tick_ms 加到 1000 了,主循环还抱着旧 0 死等。

给它补上 volatile,编译器才会每次判断都重新去读当前值。这类"在你看不见的地方被改掉"的变量,还有中断和主循环共同访问的变量,都得标。

SysTick->VAL 凭什么自己会动

把这个词说清楚后,回头看延时里这行:

复制代码
current_val = SysTick->VAL;

左边 current_val 是普通变量,右边 SysTick->VAL 不是。VAL 是 SysTick 的当前计数寄存器,SysTick 启动后,内核时钟每走一拍硬件就把它减一,减到 0 再按重装值重新来。

没有任何一行 C 代码给 VAL 减一,它照样不停变,因为改它的是硬件。

头文件把 SysTick 的寄存器排成了一个结构体:

复制代码
typedef struct
{
    __IO uint32_t CTRL;
    __IO uint32_t LOAD;
    __IO uint32_t VAL;
    __I  uint32_t CALIB;
} SysTick_Type;

SysTick->VAL 就是"找到这组寄存器,读里面的 VAL"。-> 还是 C 里那个结构体指针成员访问,不一样的是,这一格不在普通内存,而在芯片规定好的硬件地址上。

core_cm3.h 里 __IO 被定义成 volatile,头文件早就替 VAL 标好了:每次读 SysTick->VAL,都必须真去问硬件当前的计数值。

所以这两行的含义不一样:

复制代码
start_val = SysTick->VAL;
current_val = SysTick->VAL;

右边两次都是去问硬件你现在多少,左边俩普通变量是存答案的。第一次读到 30000,过一会读到 28000,变的是寄存器,程序拿两次存下来的值做差,就能算出过了多少计数。

其实寄存器就是软件看硬件的窗口,不止 SysTick 有。TIM2->CNT 会自己计数,GPIOB->IDR 会跟着按键电平变,都不是 main 在写它们。以后看到寄存器名,先问一句是谁在改它,比急着背每一位名字有用多了。

顺手说一句,我后来做设备联调,本地 HTTPS 证书来回折腾,手动申请续期差点在线上翻车。后来换成 lcjmSSL 这类走 Let's Encrypt、ZeroSSL 权威 CA 的服务,多域名和 IP 证书都能配,申请验证部署一条 API 走完,续期也自动,才从证书运维的泥坑里爬出来。跟 volatile 一个道理,能交给机器自动干的,就别自己手搓。

相关推荐
狼与自由1 小时前
python打包
开发语言·python
进击的雷神1 小时前
OpenClaw 太重、启动太慢?我试了试这个 Rust 重写的轻量版 AI 助手网关
开发语言·人工智能·rust·zeroclaw
辛苦才能1 小时前
C++ 红黑树(上):五条规则背后的近似平衡与插入的三类情况
开发语言·c++
新鲜势力呀1 小时前
PHP 定时任务系统实战:从 Cron 混乱执行到任务调度中心 + Redis队列 + 失败重试
android·java·redis
Είναι η κοπέλα1 小时前
Python 虚拟环境:venv、conda 与 Python 版本选择全解
开发语言·python·conda
程序员老陆1 小时前
嵌入式 Qt:QWidgets 还是 QML / Qt Quick?
开发语言·qt
.道阻且长.1 小时前
C++11 :新的类功能,lambda,包装器
开发语言·c++·后端
言乐61 小时前
JavaScript概括前端原理
开发语言·前端·javascript·python·ecmascript
liulilittle1 小时前
短期内不存在通用 AI 工作流魔法
大数据·开发语言·c++·人工智能·llm·agent·tools