2. 物理层与数据链路层实战:RS232/RS485物理特性、Modbus RTU协议帧解析、CRC校验实现
各位同学,咱们今天聊点实在的。做嵌入式通信,说白了就是跟物理层和数据链路层打交道。很多新手一上来就调应用层,结果死活连不上,最后发现是电平不匹配、线序接反了,或者CRC算错了。嗯,这些坑我都踩过,今天咱们就一次性把它们讲透。
2.1 RS232与RS485:物理层的两个老伙计
先说说RS232。这玩意儿历史久远,我最早接触是在大学实验室,用DB9串口线连单片机。它的特点是全双工,收发各用一根线,再加上地线,总共三根就能干活。但有个致命弱点——传输距离短,也就15米左右,而且抗干扰能力一般。
后来做工业项目,发现现场全是RS485。为什么?因为它是差分信号,抗干扰强,距离能到1200米,还能挂32个节点。我记得有一次在工厂调试,现场电机一启动,RS232就乱码,换成RS485立马稳了。从那以后,但凡涉及工业现场,我首选RS485。
| 特性 | RS232 | RS485 |
|---|---|---|
| 传输方式 | 单端(非平衡) | 差分(平衡) |
| 最大距离 | 约15米 | 约1200米 |
| 节点数 | 1对1 | 最多32个(标准) |
| 抗干扰 | 弱 | 强 |
| 典型电平 | ±12V | ±5V(差分) |
2.2 Modbus RTU协议帧:拆开看看里面长啥样
物理层搞定了,接下来就是数据链路层。Modbus RTU是工业界用得最广的协议之一。它的帧结构其实很简单,我习惯把它拆成四段来看:
- 地址码(1字节):从站地址,范围1-247。0是广播地址。
- 功能码(1字节):告诉从站要干什么。比如03是读寄存器,06是写单个寄存器。
- 数据区(N字节):具体参数,比如起始地址、数据长度、要写入的值。
- CRC校验(2字节):低字节在前,高字节在后。这个后面细说。
举个例子,读从站1的寄存器,从地址0x0000开始读2个寄存器,请求帧就是:
01 03 00 00 00 02 C4 0B
你看,01是地址,03是功能码,00 00是起始地址,00 02是寄存器数量,C4 0B就是CRC。是不是很直观?
2.3 CRC校验实现:手写一个也不难
CRC校验是Modbus RTU的保命符。它用的是CRC-16(Modbus多项式0x8005),计算过程不复杂,但容易出错。我建议你直接拿现成的查表法,效率高,代码也简洁。
下面是我常用的CRC计算函数,C语言实现,查表法:
// CRC-16 Modbus 查表法
uint16_t crc16_modbus(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
uint16_t i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
注意,Modbus RTU发送时,CRC是低字节在前,高字节在后。比如上面算出来CRC是0x0BC4,发送顺序就是C4 0B。这个顺序搞反了,从站就会回复异常码。我刚开始做的时候就被这个坑过,排查了半天。
2.4 知识体系总览:一张图看懂
下面这张图把本章的核心逻辑串起来了。从物理层的RS232/RS485,到数据链路层的Modbus RTU帧结构,再到CRC校验,每一步都是环环相扣的。
2.5 实战调试:从波形到数据帧
最后说说调试方法。我个人的习惯是三步走:
- 看波形:用示波器抓RS485的A/B线差分信号,确认电平正常、无毛刺。
- 看数据:用USB转485模块接电脑,串口助手看原始数据,确认帧结构正确。
- 看CRC:用在线CRC计算工具验证,或者自己写个函数打印出来对比。
你想想看,如果波形不对,调软件有什么用?物理层没搞定,上层全是白费功夫。我曾经在一个项目里,从站老是回复异常码,折腾了两天,最后发现是485芯片的RE/DE引脚控制逻辑写反了。嗯,这种低级错误,犯过一次就记住了。