news 2026/10/4 7:53:04

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 一个道理,能交给机器自动干的,就别自己手搓。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 7:52:03

基于RT-Thread与W25Q128的STM32 OTA远程升级方案详解

1. 为什么要把OTA放在片外Flash上做嵌入式开发的朋友应该都遇到过这种尴尬&#xff1a;产品已经量产了&#xff0c;结果现场反馈某个功能有Bug&#xff0c;或者需要加一个新协议&#xff0c;这时候只能派工程师带着ST-Link/J-Link出差去现场&#xff0c;挨个设备拆壳、接线、烧…

作者头像 李华
网站建设 2026/10/4 7:52:00

OpenRig开源模拟赛车座舱DIY全攻略:从图纸到稳定车架

我头一回看到 OpenRig 这个项目名&#xff0c;第一反应还以为是哪个油田设备厂的新品——Rig 这个词在中文里最常被翻译成钻机、设备架&#xff0c;放到舞台灯光、影视器材圈子里指代的东西也五花八门。但只要你和我一样掉进模拟赛车这个坑&#xff0c;看见 open rig 这个组合…

作者头像 李华
网站建设 2026/10/4 7:51:04

用Python绘制专属世界地图:geopandas与matplotlib完整指南

如果你手里只有一台电脑&#xff0c;也想画出一张完全属于自己的世界地图&#xff0c;不依赖在线地图服务、不交订阅费&#xff0c;那这篇文章就是给你准备的。我用了几个晚上&#xff0c;把基于Python的矢量地图绘制流程完整跑通了一遍&#xff0c;从环境搭建到出图美化&#…

作者头像 李华
网站建设 2026/10/4 7:48:27

基于Java构建网络流量分析系统:抓包、协议解析与可视化实践

简介&#xff1a;面向计算机网络课程设计、Java Web综合实训等场景&#xff0c;一套基于Java实现的跨平台网络流量监控与分析软件完整方案以压缩包形式提供。后台服务用Java编写&#xff0c;前端通过Web客户端展示&#xff0c;适配无图形界面或远程部署环境&#xff0c;并兼顾数…

作者头像 李华