3、Modbus串行链路:RTU与ASCII两种传输模式详解
好,咱们今天聊聊Modbus串行链路上最核心的一个选择题——RTU和ASCII,到底选哪个?
我记得刚入行那会儿,第一次调试一个供水系统的PLC,现场工程师扔给我一本手册,说“你看着配”。我一看,通讯参数里有个“Mode:RTU/ASCII”,当时就懵了。后来踩了不少坑,才把这两个模式彻底搞明白。今天我把这些经验掰开了揉碎了讲给你听。
3.1 两种模式的根本区别
说白了,RTU和ASCII就是两种不同的“打包方式”。
你想想看,Modbus协议要传输的数据,本质上是一串二进制数。RTU模式直接把这些二进制数“原封不动”地发出去。而ASCII模式呢?它先把每个字节拆成两个ASCII字符,再发出去。
举个例子:我要发送一个字节 0x1A。
- RTU模式:直接发
0x1A(8个bit) - ASCII模式:先转成字符
'1'和'A',也就是0x31和0x41(16个bit)
看到没?同样的信息,ASCII模式要多发一倍的字节。这就是它们最本质的区别。
核心结论:RTU是“二进制裸传”,ASCII是“字符编码传”。RTU效率高,ASCII可读性强。
3.2 RTU模式详解
RTU模式,全称Remote Terminal Unit,但咱们别被名字唬住。它就是最常用的那种模式,工业现场90%以上的Modbus设备都在用RTU。
3.2.1 报文结构
RTU的报文结构很紧凑:
| 地址码(1字节) | 功能码(1字节) | 数据区(N字节) | CRC校验(2字节) |
嗯,这里要注意,RTU没有起始位和结束位的特殊标记。它靠什么判断一帧报文结束?靠“静默时间”。
具体规则是这样的:
- 报文开始前,总线必须静默至少3.5个字符时间
- 报文结束后,总线也必须静默至少3.5个字符时间
- 帧内两个字节的间隔不能超过1.5个字符时间
我曾经在一个项目中遇到过,因为PLC的扫描周期太长,导致帧内字节间隔超过了1.5个字符时间,从站直接丢弃了报文。排查了整整一下午,最后发现是程序里一个延时函数搞的鬼。
避坑指南:我曾经在调试一个变频器时,发现RTU通讯时好时坏。后来用示波器一抓,发现主站发送完报文后,总线上的静默时间只有2.8个字符时间。从站要求3.5,差那么一点点,就是不认。所以,静默时间一定要留足余量,我一般会设到5个字符时间以上。
3.2.2 CRC校验
RTU用CRC-16校验,多项式是 0x8005。这个算法挺成熟,我就不展开讲了。直接给一个C语言实现,我在项目里一直用这个:
uint16_t crc16_modbus(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = crc >> 1;
}
}
}
return crc;
}
注意,CRC校验码是低字节在前,高字节在后。这个顺序搞反了,通讯肯定不通。我刚开始写程序时就犯过这个错,查了半天资料才发现。
3.3 ASCII模式详解
ASCII模式,说白了就是“给人看的”。每个字节都转成两个ASCII字符,然后用特殊的字符标记帧的开始和结束。
3.3.1 报文结构
ASCII模式的报文长这样:
| ':' (0x3A) | 地址码(2字符) | 功能码(2字符) | 数据区(2N字符) | LRC校验(2字符) | CRLF (0x0D 0x0A) |
看到没?开头有个冒号 ':',结尾有回车换行 CRLF。这就好办了,不用靠静默时间判断帧边界了。
举个例子,我要读取从站地址为1的设备的保持寄存器,起始地址0,读取2个寄存器:
RTU模式:01 03 00 00 00 02 C4 0B
ASCII模式:3A 30 31 30 33 30 30 30 30 30 30 30 32 46 42 0D 0A
: 0 1 0 3 0 0 0 0 0 0 0 2 F B CR LF
ASCII模式比RTU模式多了将近一倍的字节数。所以,同样的波特率下,ASCII模式的通讯效率只有RTU的一半左右。
3.3.2 LRC校验
ASCII模式用的是LRC校验,比CRC简单多了。就是把所有字节加起来,取补码:
uint8_t lrc_modbus(uint8_t *data, uint16_t len) {
uint8_t lrc = 0;
for (uint16_t i = 0; i < len; i++) {
lrc += data[i];
}
lrc = (~lrc) + 1; // 取补码
return lrc;
}
注意,LRC校验的是原始字节,不是ASCII字符。比如地址码 0x01,LRC计算时用的是 0x01,不是 '0' 和 '1'。
我的习惯:ASCII模式虽然效率低,但调试时特别好用。我经常在开发阶段先用ASCII模式,等通讯调通了,再切回RTU模式。因为ASCII报文可以直接用串口助手看,一眼就能看出数据对不对。
3.4 两种模式的对比
咱们用一张表总结一下:
| 对比项 | RTU模式 | ASCII模式 |
|---|---|---|
| 数据编码 | 二进制 | ASCII字符 |
| 帧长度 | 短(约1/2) | 长(约2倍) |
| 校验方式 | CRC-16(强) | LRC(弱) |
| 帧边界 | 静默时间 | ':' 和 CRLF |
| 通讯效率 | 高 | 低(约50%) |
| 可读性 | 差 | 好 |
| 适用场景 | 绝大多数工业现场 | 调试、低速链路 |
3.5 如何选择?
我个人习惯是:能用RTU就用RTU。原因很简单:
- 效率高,同样的波特率能传更多数据
- CRC校验比LRC可靠得多
- 绝大多数设备都支持RTU
那什么时候用ASCII呢?
- 调试阶段:方便看数据,快速定位问题
- 通讯链路质量很差:ASCII模式有明确的帧边界,不容易被干扰搞乱
- 设备只支持ASCII:有些老设备确实只支持ASCII,没办法
注意:同一个总线上,所有设备必须使用同一种模式。不能有的设备用RTU,有的用ASCII。否则,从站根本解析不了报文。
3.6 知识体系图
下面这张图,把两种模式的核心逻辑串起来了:
3.7 实际项目中的选择
最后,说说我个人的经验。在绝大多数项目中,我都会选择RTU模式。原因很简单:
- 效率高,同样的波特率能传更多数据
- CRC校验比LRC可靠得多
- 绝大多数设备都支持RTU
但是,如果遇到以下情况,我会考虑用ASCII:
- 调试阶段:方便看数据,快速定位问题
- 通讯链路质量很差:ASCII模式有明确的帧边界,不容易被干扰搞乱
- 设备只支持ASCII:有些老设备确实只支持ASCII,没办法
我的一个小技巧:在开发阶段,我会先用ASCII模式把通讯调通,然后用串口助手抓一份完整的报文。等切换到RTU模式后,再用这份报文做对比,确保数据一致。这样能省不少排查时间。
好了,关于RTU和ASCII两种模式,咱们就聊到这儿。记住一句话:RTU是效率之王,ASCII是调试之友。根据你的实际场景,选对模式,能少踩很多坑。