前言
作为嵌入式开发者,你是否曾困惑于50MHz时钟1秒能计数多少次?定时器的溢出时间该如何计算?波特率115200这个看似神奇的数字又是从何而来?本文将从一个最基础的问题出发,用通俗的语言、清晰的公式和完整的推导过程,带你彻底理解STM32时序计算的来龙去脉。
一、从一个最简单的问题开始
想象你手里有一颗STM32芯片,它的系统时钟被配置成了50MHz。现在我问你一个看似简单的问题:这颗芯片在1秒钟之内,它的时钟能"计数"多少次?
如果你脱口而出"五千万次",那恭喜你,你已经掌握了最基础的概念。但如果你需要犹豫一下,或者心里还在想"到底是五百万还是五千万",那么这篇文章就是为你准备的。
实际上,50MHz等于50×10⁶赫兹,而赫兹的含义就是"每秒多少次"。所以:
计数次数 = 频率 × 时间 = 50 × 10⁶ × 1 = 50,000,000 次
这个数字听起来很大,但正是这种高速振荡,才让单片机能够在极短的时间内完成复杂的任务。不过,理解这个数字只是第一步。真正有趣的问题是:这个"五千万次"和"时钟周期"之间到底是什么关系?
二、时钟周期与计数次数:互为倒数的孪生兄弟
时钟周期和计数次数,这两个概念就像是同一枚硬币的两面。时钟周期指的是时钟振荡一次所需要的时间长短 ,而计数次数指的是在一段时间内完成了多少次振荡。
它们之间的关系可以用一个极其简单的公式表达:
| 公式 | 含义 |
|---|---|
| T = 1 / f | 时钟周期 = 1 / 频率 |
| N = f × t | 计数次数 = 频率 × 时间 |
不同频率单位的周期计算
在实际工程中,频率常用MHz或GHz表示,掌握快速换算技巧能节省大量时间:
| 频率 | 换算为Hz | 周期 | 快速记忆法 |
|---|---|---|---|
| 1 MHz | 1 × 10⁶ Hz | 1 μs | 1 ÷ 1 = 1 μs |
| 10 MHz | 1 × 10⁷ Hz | 0.1 μs = 100 ns | 1 ÷ 10 = 0.1 μs |
| 50 MHz | 5 × 10⁷ Hz | 0.02 μs = 20 ns | 1 ÷ 50 = 0.02 μs |
| 72 MHz | 7.2 × 10⁷ Hz | 0.0139 μs = 13.9 ns | 1 ÷ 72 ≈ 0.0139 μs |
| 100 MHz | 1 × 10⁸ Hz | 0.01 μs = 10 ns | 1 ÷ 100 = 0.01 μs |
| 1 GHz | 1 × 10⁹ Hz | 1 ns | 1 ÷ 1 = 1 ns |
| 2 GHz | 2 × 10⁹ Hz | 0.5 ns | 1 ÷ 2 = 0.5 ns |
核心记忆点:MHz → 周期以微秒(μs)为单位;GHz → 周期以纳秒(ns)为单位。
用50MHz完整演算一遍
已知:频率 f = 50 MHz = 50 × 10⁶ Hz
第一步:求时钟周期
cpp
T = 1 / f = 1 / (50 × 10⁶) = 2 × 10⁻⁸ 秒 = 20 纳秒
第二步:求1秒内的计数次数
cpp
N = f × t = (50 × 10⁶) × 1 = 50,000,000 次
第三步:验证两者关系
cpp
N = t / T = 1 秒 / (2 × 10⁻⁸ 秒) = 50,000,000 次 ✓
这个关系可以用一个生活中的比喻来理解:想象你有一把1米长的尺子,时钟周期就像是尺子上的最小刻度,而计数次数就是这把尺子上总共有多少个刻度。
| 比喻 | 技术概念 | 50MHz示例 |
|---|---|---|
| 尺子总长度 | 总时间 | 1秒 |
| 最小刻度 | 时钟周期 | 20纳秒 |
| 刻度总数 | 计数次数 | 50,000,000个 |
如果最小刻度是1毫米,那么尺子就有1000个刻度;如果最小刻度是1厘米,那么就只有100个刻度。刻度越小,能容纳的刻度数量就越多。同样的道理,时钟周期越短,单位时间内能完成的计数次数就越多。这就是为什么频率越高的芯片性能越强------因为同样的1秒钟里,它能做的事情更多。
三、定时器:如何用计数来度量时间
理解了时钟周期和计数的关系,我们就可以进入下一个话题:定时器。定时器是STM32里面最常用的外设之一,它的本质其实就是一个计数器,只不过它是在"数"时钟周期。当你告诉定时器"数到某个数字就产生中断",它就会忠实地执行这个任务。
定时器溢出时间公式
定时器的溢出时间由三个因素决定:定时器的时钟频率、预分频器的值、以及自动重装载的值。
T_overflow = (PSC + 1) × (ARR + 1) / Timer_Clock
其中:
-
PSC (Prescaler):预分频器,16位寄存器,取值范围0~65535
-
ARR (Auto Reload Register):自动重装载值,16位寄存器,取值范围0~65535
-
Timer_Clock:定时器模块的实际输入时钟频率(单位:Hz)
为什么公式中要加1?因为这两个寄存器的值是从0开始计数的,所以实际计数的次数要比寄存器值多一次。
实战案例:1秒中断配置
假设你的定时器时钟是80MHz,你希望它每1秒钟产生一次中断。那么你需要找到两个数字,让它们的乘积除以80MHz等于1秒。
已知:
Timer_Clock = 80,000,000 Hz
目标时间 = 1秒
设 PSC = 7999,ARR = 9999,则:
cpp
T_overflow = (7999 + 1) × (9999 + 1) / 80,000,000
= 8000 × 10000 / 80,000,000
= 80,000,000 / 80,000,000
= 1 秒
常用定时器配置参考表
| 目标时间 | Timer_Clock | PSC | ARR | 验算 |
|---|---|---|---|---|
| 1 ms | 80 MHz | 7999 | 9999 | 8000×10000/80M = 1秒 ❌ |
| 1 ms | 80 MHz | 79 | 999 | 80×1000/80M = 0.001秒 ✓ |
| 10 ms | 80 MHz | 799 | 999 | 800×1000/80M = 0.01秒 ✓ |
| 1 s | 80 MHz | 7999 | 9999 | 8000×10000/80M = 1秒 ✓ |
四、波特率:通信双方的握手协议
现在我们来聊一个在实际开发中经常遇到、但又常常被误解的概念:波特率。如果你曾经用串口调试助手连接过单片机,你一定见过9600、115200这些数字。它们到底代表什么?又是怎么来的?
波特率的本质
波特率的本质是一个约定:通信双方约定好,每秒钟传输多少个比特。
| 波特率 | 含义 | 每比特时间 |
|---|---|---|
| 9600 | 每秒传输9600个比特 | 1/9600 ≈ 104.17 μs |
| 19200 | 每秒传输19200个比特 | 1/19200 ≈ 52.08 μs |
| 38400 | 每秒传输38400个比特 | 1/38400 ≈ 26.04 μs |
| 115200 | 每秒传输115200个比特 | 1/115200 ≈ 8.68 μs |
| 921600 | 每秒传输921600个比特 | 1/921600 ≈ 1.09 μs |
以115200为例,传输一个比特需要的时间是1除以115200秒,大约是8.68微秒。接收方会设置一个内部定时器,每隔8.68微秒就去采样一次数据线,这样才能正确地把对方发来的比特一个个收下来。
波特率标准的历史来源:
那么115200这个数字是怎么来的呢?它可不是随便定的。这要从早期的UART芯片说起。
当年的UART芯片通常使用1.8432MHz的晶振作为基准时钟,然后通过一系列分频得到各种波特率:
| 分频方式 | 计算结果 |
|---|---|
| 1.8432 MHz ÷ 16 = 115200 | 得到115200 |
| 115200 ÷ 12 = 9600 | 得到9600 |
所以115200实际上是9600的12倍,而9600是早期调制解调器的标准速度。这些数字一直沿用至今,成为了串行通信的事实标准。
从UART时钟到波特率的计算
现在问题来了:假设你的STM32的UART模块使用的时钟是50MHz,你想要配置115200波特率,你应该怎么做?
分频系数 = UART时钟频率 / 目标波特率
已知:
UART时钟 = 50 MHz = 50,000,000 Hz
目标波特率 = 115200
这个434的含义非常重要:它表示UART硬件需要用434个时钟周期来表示一个比特。也就是说,当UART模块想要发送一个比特时,它会把数据线保持在一个电平上,持续434个时钟周期的时间。
完整推导过程
让我们把整个推导链条完整地写出来:
| 步骤 | 计算 | 结果 |
|---|---|---|
| ① 时钟周期 | T_clk = 1 / 50MHz | 20 ns |
| ② 波特率时间 | T_bit = 1 / 115200 | ≈ 8.68 μs |
| ③ 每比特时钟数 | N = T_bit / T_clk = 50MHz / 115200 | ≈ 434 |
**所以:**计数个数 = 频率 / 波特率 = 50,000,000 / 115,200 ≈ 434
过采样与误差分析
大多数UART硬件采用的是16倍过采样。什么叫过采样呢?就是在一个比特的时间里,接收方会采样16次,取其中多数次的电平作为最终结果。
| 采样方式 | 含义 | 优缺点 |
|---|---|---|
| 1倍采样 | 每个比特只采1次 | 速度快,但抗干扰差 |
| 8倍采样 | 每个比特采8次 | 平衡速度和抗干扰 |
| 16倍采样 | 每个比特采16次 | 抗干扰强,最常用 |
既然一个比特对应434个时钟周期,而我们要在里面采样16次,那么每次采样之间的间隔就是:
cpp
采样间隔 = 434 / 16 = 27.125 个时钟周期
这个0.125的小数部分就是波特率误差的来源------因为硬件只能用整数或者简单的分数(如0.5、0.25、0.125)来近似这个值。
| 分频系数 | 是否能精确表示 | 误差 |
|---|---|---|
| 434 ÷ 16 = 27.125 | 可以(0.125 = 1/8) | 0% |
| 312.5 ÷ 16 = 19.53125 | 可以(0.53125 = 17/32) | 0% |
| 416.666 ÷ 16 = 26.041625 | 不能精确表示 | 有误差 |
五、一个极易被忽略的陷阱:UART时钟 ≠ 系统时钟
到了这里,我们已经把波特率的计算原理讲清楚了。但还有一个非常容易踩的坑,我必须专门提一下:UART模块的时钟并不等于系统时钟,尽管很多初学者会下意识地这么认为。
1、STM32的时钟树结构
STM32的时钟系统采用了一种树状结构:
html
外部晶振(HSE) / 内部振荡器(HSI)
↓
锁相环(PLL)倍频
↓
系统时钟(SYSCLK) ← 通常说的"主频"
↓
AHB总线分频
↓
APB1/APB2分频 ← 外设时钟来源于此
↓
UART模块
2、不同UART挂载在不同总线
以常见的STM32F103为例:
| 总线 | 最大频率 | 挂载的UART |
|---|---|---|
| APB1 | 36 MHz | UART2、UART3、UART4、UART5 |
| APB2 | 72 MHz | UART1 |
一个常见的错误:系统时钟配置成72MHz,然后用72MHz去计算UART2的波特率:
| 做法 | 时钟来源 | 计算结果 | 实际效果 |
|---|---|---|---|
| ❌ 错误 | 系统时钟72MHz | 72M/115200=625 | 实际波特率是理论值的2倍 |
| ✓ 正确 | APB1时钟36MHz | 36M/115200=312.5 | 波特率正确 |
3、如何确定UART时钟?
| 方法 | 操作 | 可靠性 |
|---|---|---|
| STM32CubeMX | 配置时钟树后,点击UART外设自动显示 | ⭐⭐⭐ 最推荐 |
| 数据手册 | 查"Bus mapping"章节 | ⭐⭐ 需要仔细找 |
| 参考手册 | 看时钟树图 | ⭐⭐ 需要经验 |
六、完整计算串联:从系统时钟到串口通信
让我们用一个完整的例子,把整条知识链串联起来:
案例:STM32F103,UART1,115200波特率
已知条件:
-
外部晶振:8 MHz
-
系统时钟配置:8MHz × 9倍频 = 72 MHz
-
APB2总线:不分频,保持72 MHz
-
UART1:挂在APB2总线上
计算过程:
| 步骤 | 计算 | 结果 |
|---|---|---|
| ① 确定UART时钟 | UART1时钟 = APB2时钟 = 72 MHz | 72,000,000 Hz |
| ② 计算分频系数 | 72,000,000 / 115,200 | 625 |
| ③ 16倍过采样 | 625 / 16 | 39.0625 |
| ④ 配置寄存器 | USARTDIV = 39.0625 | 整数部分39,小数部分0.0625 |
| ⑤ 实际波特率 | 72,000,000 / (16 × 39.0625) | 115,200 ✓ 精确 |
案例对比:UART2挂在APB1上
如果换成UART2(挂在APB1上),APB1最大频率是36MHz:
| 步骤 | 计算 | 结果 |
|---|---|---|
| ① 确定UART时钟 | UART2时钟 = APB1时钟 = 36 MHz | 36,000,000 Hz |
| ② 计算分频系数 | 36,000,000 / 115,200 | 312.5 |
| ③ 16倍过采样 | 312.5 / 16 | 19.53125 |
| ④ 配置寄存器 | USARTDIV = 19.53125 | 整数部分19,小数部分0.53125 |
| ⑤ 实际波特率 | 36,000,000 / (16 × 19.53125) | 115,200 ✓ 仍然精确 |
有趣的是,312.5/16=19.53125,这个小数0.53125等于17/32,也是可以用硬件精确表示的,所以也没有误差。
什么情况下会产生误差?
| UART时钟 | 115200分频系数 | 是否精确 | 实际波特率 | 误差 |
|---|---|---|---|---|
| 36 MHz | 312.5 | ✓ 精确 | 115200 | 0% |
| 48 MHz | 416.666... | ✗ 不精确 | ≈115384 | +0.16% |
| 50 MHz | 434.027... | ✗ 不精确 | ≈115207 | +0.006% |
| 54 MHz | 468.75 | ✓ 精确 | 115200 | 0% |
| 72 MHz | 625 | ✓ 精确 | 115200 | 0% |
七、实际工程中的经验总结
理论讲完了,最后分享一些实际工程中的经验。
波特率误差容忍度
| 误差范围 | 结论 | 建议 |
|---|---|---|
| < 2% | 通常可接受 | 可以正常使用 |
| 2% ~ 3% | 临界状态 | 建议调整 |
| > 3% | 不可接受 | 通信可能出现乱码 |
降低误差的方法
-
调整系统时钟频率:选择能让分频系数为整数的频率值
-
使用8倍过采样:部分STM32支持,可以减小小数部分的影响
-
选择合适的波特率:有些波特率在特定时钟下更精确
常见时钟下的精确波特率推荐
| UART时钟 | 精确波特率(无误差) | 有误差的波特率 |
|---|---|---|
| 36 MHz | 9600、19200、38400、115200 | 57600?需要验证 |
| 48 MHz | 9600、19200、38400 | 115200(有0.16%误差) |
| 50 MHz | 9600?需要验证 | 115200(误差很小) |
| 72 MHz | 9600、19200、38400、115200 | 大部分都精确 |
八、核心公式速查表
为了方便你日后查阅,我把本文涉及的所有公式汇总如下:
| 计算内容 | 公式 | 单位说明 |
|---|---|---|
| 时钟周期 | T = 1 / f | T:秒, f:Hz |
| 计数次数 | N = f × t | N:次, t:秒 |
| 定时器溢出时间 | T = (PSC+1)×(ARR+1) / Timer_Clk | Timer_Clk:Hz |
| 看门狗超时 | T = Prescaler × Reload / LSI | LSI:Hz |
| 波特率分频系数 | DIV = UART_Clk / Baudrate | 无单位 |
| 实际波特率 | Baud = UART_Clk / (16 × USARTDIV) | USARTDIV:寄存器值 |
| 波特率误差 | Error = (实际-目标)/目标 × 100% | 百分比 |
总结
从时钟周期到计数次数,从定时器溢出到波特率计算,这些知识看似零散,实际上都围绕着一个核心:用时钟去度量时间,用时间去协调通信。
理解了这一点,你就掌握了嵌入式时序设计的精髓。当你下次配置串口波特率时,希望你能想起:那一个个数字背后,是时钟周期和计数次数的精密配合;当你发现通信异常时,希望你能从最基础的时钟开始排查。
毕竟,在嵌入式世界里,一切都是时序问题。搞懂了时钟,你就搞懂了一大半。