2. 物理层基础:RS232/RS485电平转换、UART时序分析、波特率误差计算
各位同学,咱们今天聊聊物理层。说白了,就是芯片和外界通信的「最后一公里」。很多初学者觉得这层太底层,不就是两根线嘛?其实不然。我见过太多项目,协议栈写得天花乱坠,结果一上电就乱码,最后查出来是电平不匹配或者波特率算错了。嗯,这种坑,咱们今天一次性填平。
2.1 RS232与RS485:电平转换的那些事儿
先说说RS232。你想想看,单片机UART口出来的信号,一般是0~3.3V或者0~5V的TTL/CMOS电平。但RS232标准要求的是±3V~±15V。为什么搞这么高?为了抗干扰。我在一个工业现场项目里,用RS232传了50米,信号就糊了。后来换成RS485,200米稳如老狗。
RS232电平转换,最经典的芯片就是MAX232。它内部有个电荷泵,能把5V升到±10V左右。我习惯在电路里加上0.1μF的退耦电容,靠近芯片引脚放,不然容易纹波大。
关键点:RS232是「负逻辑」——逻辑1对应-3V~-15V,逻辑0对应+3V~+15V。跟TTL正好相反。很多新手在这里栽跟头。
再说RS485。它用的是差分信号,两根线(A和B)的电压差来决定逻辑。A比B高200mV以上是逻辑1,反之是逻辑0。这种差分结构,共模抑制能力很强。我曾经在一个变频器旁边布线,RS232完全没法用,RS485一点问题没有。
RS485还有个特点:半双工。也就是说,同一时刻只能发或者收。所以需要控制DE/RE引脚。我一般用单片机的一个GPIO来控制,发送时拉高DE,接收时拉低RE。注意切换时留点余量,别刚发完就切接收,容易把最后一个字节吃掉。
| 特性 | RS232 | RS485 |
|---|---|---|
| 传输方式 | 单端 | 差分 |
| 最大距离 | 约15米 | 约1200米 |
| 最大节点数 | 1对1 | 最多256个 |
| 电平范围 | ±3V~±15V | ±1.5V~±6V(差分) |
| 常用芯片 | MAX232, SP3232 | MAX485, SN75176 |
我的小技巧:RS485总线两端一定要加120Ω终端电阻。不加的话,信号反射会导致数据错位。我吃过这个亏,调试了整整一天才发现。
2.2 UART时序分析:别被波形骗了
UART的时序,说白了就是一根数据线,按约定的速率一位一位地传。起始位(低电平)、数据位(LSB或MSB先发)、校验位(可选)、停止位(高电平)。
我习惯用示波器抓波形来验证。你看,空闲时TX线是高电平。突然拉低,那就是起始位来了。然后8个数据位,最后拉高至少一个位时间作为停止位。如果停止位不够宽,接收端就会报帧错误。
这里有个容易忽略的点:数据位的顺序。大多数UART是LSB first,也就是最低位先发。但有些协议(比如Modbus RTU)明确要求LSB first,而有些自定义协议可能用MSB first。我建议你在设计协议栈时,把这个做成可配置的。
注意:UART的校验位有奇校验、偶校验、无校验三种。如果两端配置不一致,数据能通但全是错的。我曾经帮同事排查过这个问题,他配了偶校验,对方配了无校验,结果每8个字节就错一个。
还有一个细节:UART的帧格式。常见的是8N1(8数据位、无校验、1停止位),也有7E1、8O2等。我建议在协议栈里把帧格式也做成参数,而不是硬编码。这样移植到不同平台时,改个宏定义就行。
2.3 波特率误差计算:算不对就等着丢数据吧
波特率误差,是UART通信里最隐蔽的杀手。你想想看,发送端和接收端各自用自己的时钟来采样。如果时钟有偏差,累积到一定程度,采样点就偏了。
UART的采样点一般在每个数据位的中间。如果波特率误差太大,采样点会逐渐偏移,最后可能采到相邻位上去。我见过一个案例,波特率误差0.5%,短报文没问题,一传1024字节就乱码。为什么?因为累积误差超过了半个位时间。
误差计算公式很简单:
误差 = (实际波特率 - 理想波特率) / 理想波特率 × 100%
但实际中,我们更关心的是「累积误差」。假设你传N个位(起始位+数据位+校验位+停止位),每个位有误差e,那么总误差就是N×e。当N×e超过0.5个位时间时,采样点就跑到下一个位去了。
举个例子:9600波特率,每个位时间约104.17μs。如果误差是1%,那么每个位偏差1.04μs。传10个位(1起始+8数据+1停止),累积偏差10.4μs,约0.1个位时间,还能忍。但如果传100个位,累积偏差104μs,整整一个位时间,必错。
| 理想波特率 | 实际波特率(误差+1%) | 单比特时间 | 10位累积误差 | 100位累积误差 |
|---|---|---|---|---|
| 9600 | 9696 | 104.17μs | 10.4μs | 104μs |
| 115200 | 116352 | 8.68μs | 0.87μs | 8.7μs |
| 250000 | 252500 | 4.00μs | 0.40μs | 4.0μs |
那怎么算波特率误差?以STM32为例,UART时钟来自APB总线,通过分频得到目标波特率。公式是:
BRR = UART_CLK / (16 × 目标波特率)
BRR可能不是整数,取整后反算实际波特率,再算误差。我习惯写个小脚本算一遍,确保误差在±2%以内。对于异步通信,±2%是安全边界。超过这个值,长报文就容易出问题。
实战建议:在协议栈初始化时,加一个波特率误差检查函数。如果误差超过阈值,直接返回错误。这样能避免硬件配置错误导致的诡异问题。
最后说一句:如果你用内部RC振荡器做UART时钟,注意温漂。我有个项目在户外,夏天40度冬天零下20度,波特率漂了3%。后来换了外部晶振,问题解决。嗯,有些坑,踩过一次就记住了。