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 一个道理,能交给机器自动干的,就别自己手搓。

相关推荐
无足鸟ICT2 小时前
【RHCA+】安装Tuned
linux·服务器·centos
starzy19902 小时前
深入理解Scala隐式类:扩展方法机制
开发语言·人工智能·scala
Pniubi2 小时前
怎么排查慢SQL,怎么解决?
java·数据库·sql
M--Y3 小时前
LangGraph 工作流模式
开发语言·javascript·python·langchain·大模型·llm·langgraph
薛之谦_3 小时前
Java 全栈知识体系:从 IDEA 高效开发到 JVM、并发与 Stream 实战
java·jvm·intellij-idea
吴可可1234 小时前
WScript Shell操作注册表详解
开发语言
纪念 2295 小时前
C++ 算法基础 (差分)
开发语言·c++·算法
测试界的小码农7 小时前
【Linux 命令实战(06):Linux 文件权限管理,掌握 chmod、chown、chgrp】
linux·运维·服务器
文丑颜不良啊7 小时前
LeetCode2761~2780题目解析
java·leetcode
艺杯羹8 小时前
1Panel 装好后 ufw 仍是 inactive:一次服务器安全盲区复盘
服务器·数据库·安全·网络安全·系统架构·实战·1panel