ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/10/4 7:53:20 拓冰建站 浏览量
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 等它到 1000static 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 这类走 Lets Encrypt、ZeroSSL 权威 CA 的服务多域名和 IP 证书都能配申请验证部署一条 API 走完续期也自动才从证书运维的泥坑里爬出来。跟 volatile 一个道理能交给机器自动干的就别自己手搓。