芯片和电路要活得准,得有两把看不见的尺子。
一把量时间:程序说"过 100ms 再刷新""每 20ms 算一次控制",凭什么相信这个 100ms、20ms 是真的?另一把量电压:说某个点是 3.3V、某个点是 0V,是相对谁说的?机器周期是时间那根尺的刻度,共地是电压那根尺的零刻度。前面那些故障------OLED 的时间数字停住、OLED 不共地时黑屏、AO 读数时大时小------回头去看,多半是某一根尺没放平。
时钟这把尺
从 51 过来的人,都背过一个数
学过 51 的人对"机器周期"很熟:一个机器周期等于 12 个晶振周期。12MHz 晶振,机器周期就是 1µs;定时器初值、延时循环,全是拿这个数在算。它像一个固定的节拍器,芯片每件事都踩着它走。
我第一次接触 STM32 时,也下意识找过"STM32 的机器周期是多少"。但 Cortex-M3 没有一个统一的机器周期。
不是芯片偷懒。是流水线来了以后,"一条指令要几拍"不再是一个能写死在手册封面的数。
一条指令在流水线上走三站
Cortex-M3 把一条指令拆成三站:取指、译码、执行。工厂流水线的好处是:第一站还在取第 3 条指令,第二站已经译第 2 条,第三站正在执行第 1 条。三条指令同时各占一站,看起来就像每拍都完成一条------大多数简单指令确实是单周期的。
单周期是"理想流水线"的结果。任何一站要停下来等,这一拍就空了:
- 取指要读 Flash。Flash 比内核慢,时钟跑高以后,取指偶尔要插入等待,流水线前排就空一拍。
- 去内存读写数据、除法这类重活,本身就不止一拍。
于是同样一条 a = b + c,寄存器里算可能一拍走完;a = array[i] 要去内存取值,就得多等。机器周期这个词在 Cortex-M3 里没有固定值,因为它本来就不是一个固定值------它是"这一条指令实际花了几拍"的现场结果。
那一拍从哪里来
不管流水线怎么排,最底下那一下一下的拍子,来自同一个源头:系统时钟。这块芯片的时钟是 72MHz,意思是每秒钟有 7200 万个时钟周期,一个周期约 13.9ns。1ms 就是 72000 个这样的周期,20ms 就是 1440000 个。定时器不一定真去数这么多下------它常常先把时钟分频再数,像定时器篇里 PSC 和 ARR 配合的那样------但不管怎么分,底下数的还是这颗 72MHz 时钟的拍子。
把按键按住那次,OLED 上的时间数字停住不走了------数字背后是一个由定时中断每 1ms 加一的计数。它为什么可信?因为它和 OLED 的刷新、按键的采样共用同一根时钟。节拍器没停,只是主循环被堵住,显示任务轮不到刷新;松手后数字猛跳,跳出来的正是"这期间节拍器一直数着"的证据。
PID 的 20ms 也一样。控制器说"每 20ms 算一次",代码里那个周期数不是随手写的------往底层看,它对应 72MHz 时钟的 1440000 个周期,只是定时器先分频、再按 PSC/ARR 数出这个间隔。所有"过多久"的判断,最后都压在时钟这一根尺上。
尺子歪了会怎样
时钟不准,一切时间都失真。面包板上最常见的不是晶振坏,而是"以为自己在用同一个时钟"------程序里一个延时用毫秒计数,另一个模块自己在循环里空转数拍,两边对不上,现象就是时快时慢、偶尔卡一下。我会让所有"过多久"都去问同一个计时源,不然每个模块各拿一把尺,迟早对不上。
电压这把尺
3.3V 是相对谁说的
说一个点是 3.3V,其实省略了后半句:"比 GND 高 3.3V"。电压从来只有差值,没有绝对值。GND 就是大家约定的那个 0V 参考点------电压尺的零刻度。
芯片内部判断"高电平还是低电平",不是看它有多"高",是看它比自己的 GND 高多少。STM32 说输出高电平,是把自己的输出脚推到比自己的 GND 高 3.3V 的地方。
为什么所有模块都要共地
OLED、按键、传感器各自也有自己的 GND。如果两块板的 GND 不是同一个点,就会出这种事:STM32 说"我发高电平",OLED 拿自己的 GND 一量,发现根本没高到该高的地方;STM32 以为自己说清楚了,对方听到的却是另一句话。共地就是把两把电压尺的零刻度对齐------对齐之后,"高"才是同一个"高","3.3V"才是同一个 3.3V。
OLED 不共地可能会黑屏、按键不共地会乱跳、循迹读数飘,排查时第一个被怀疑的常常是代码,最后找到的往往是那根没接的地线。它不是"忘了接一根线"这么小的事,是两套系统在拿两把没对齐的尺子互相量。
读电压,读的也是比值
AO 读到的数字,不是电压本身,是"输入电压占参考电压的比例"。ADC 的参考电压来自芯片的模拟供电,这块板子上通常就是 3.3V:输入 1.65V,读到满量程的一半;输入 3.3V,读到满量程。黑线和白底反射的光不同,光敏模块输出的电压不同,ADC 把这个电压和 3.3V 一比,变成数字。参考电压抖,读数就跟着抖;参考电压不准,整把电压尺的刻度都是歪的。
这也是为什么模块供电要稳。降压模块输出带纹波时,同一个黑线位置每次读到的数字都在小幅晃动------不是传感器坏了,是它头顶上的参考电压自己在晃。
地面不是理想的
说"共地",容易想象成所有 GND 接在一起就万事大吉。地线本身有电阻,电流流过时会在上面产生一点压降------离电源远的那个 GND,和离电源近的那个 GND,严格说不是同一个 0V。电流越大、线越细越长,这个"地弹"越明显。面包板上大部分时候感觉不到,但电机启动、大电流瞬间抽走时,如果逻辑模块的地线走得很远很细,就可能看到莫名其妙的复位或读数跳变。
讲究的系统会把模拟部分和数字部分的地分开走,最后在电源处汇合一点,避免数字翻转的噪声串进模拟测量。面包板阶段先做到三件事:所有模块共地、电源和地线别用太细太长的飞线、测量点尽量靠近模块本身。做到这三件,大多数"玄学"故障已经不玄了。
最后橘猫说
机器要两套基准才活得准:时间靠时钟周期当刻度,电压靠共地当零刻度。STM32 没有 51 那样一个固定的机器周期,因为流水线让"一条指令几拍"成了现场结果;但再多的拍子也来自同一个时钟,所有"过多久"最终都问它。电压没有绝对值,3.3V 是"比共地点高 3.3V",ADC 读数不过是输入电压和参考电压的比值。心跳数字停住、OLED 黑屏、AO 读数飘,看着是不相干的事,追到底都是尺子的问题:一根是时间尺,一根是电压尺。
最后蜘蛛说
以六分仪为基,取天文钟作准,闻鼓则进,闻金则退,基准自古有之