2、通信协议基础(上):串口通信RS-232/RS-485原理、Modbus RTU协议帧结构、功能码详解、CRC校验计算

各位工程师朋友,咱们今天聊聊通信协议。说实话,搞SCADA系统集成,通信协议就是咱们的“通用语言”。语言不通,设备之间就没办法对话。我见过太多项目因为通信问题调试好几天,最后发现是协议配置错了。所以这一章,咱们把最基础的串口通信和Modbus RTU协议彻底搞明白。

2.1 串口通信:RS-232与RS-485

串口通信,说白了就是设备之间通过一根或两根线,一位一位地传数据。虽然现在以太网很普及,但在工业现场,串口通信依然占据半壁江山。为什么?因为它简单、可靠、成本低。

2.1.1 RS-232:点对点的老将

RS-232是最早的串口标准之一。我记得刚入行时,调试PLC用的就是RS-232接口。它的特点是:

  • 传输距离短:一般不超过15米。我在一个项目中试过用20米的线,结果数据全是乱码。
  • 点对点通信:一台设备只能和另一台设备通信,不能组网。
  • 电压信号:用-15V到-3V表示逻辑“1”,+3V到+15V表示逻辑“0”。
注意:RS-232的接地问题经常被忽略。我曾经遇到一个案例,两台设备距离稍远,地电位不同,导致通信时好时坏。后来加了光耦隔离才解决。

2.1.2 RS-485:工业现场的主力

RS-485才是工业现场的主角。它解决了RS-232的很多痛点:

  • 差分信号传输:用两根线(A和B)的电压差来表示数据。抗干扰能力强,传输距离可达1200米。
  • 支持多节点:一条总线上可以挂32个设备(理论上更多)。
  • 半双工通信:同一时刻只能发送或接收,不能同时进行。

我个人习惯,在SCADA项目中,只要距离超过30米,一律用RS-485。你想想看,现场电机一启动,电磁干扰多强,RS-232根本扛不住。

经验之谈:RS-485总线两端一定要加120欧姆的终端电阻。我见过有人省了这个电阻,结果长距离通信时信号反射严重,数据包频繁出错。

2.2 Modbus RTU协议帧结构

Modbus协议是工业通信的事实标准。它有三种模式:RTU、ASCII和TCP。咱们重点讲RTU,因为它在串口通信中用得最多。

Modbus RTU的帧结构非常简洁,就四个部分:

字段 长度 说明
地址码 1字节 从站地址,范围1-247
功能码 1字节 告诉从站要做什么操作
数据区 N字节 具体的数据内容
CRC校验 2字节 低字节在前,高字节在后

举个例子,我要读取1号从站的3个寄存器,起始地址是0x0000。那么发送的报文就是:

01 03 00 00 00 03 05 CB

拆开来看:

  • 01:从站地址
  • 03:功能码,读取保持寄存器
  • 00 00:起始地址
  • 00 03:读取3个寄存器
  • 05 CB:CRC校验码

嗯,这里要注意:Modbus RTU的报文是连续的,帧与帧之间必须有3.5个字符以上的空闲时间。否则从站会认为是一帧数据,导致解析错误。

2.3 功能码详解

功能码是Modbus协议的核心。它告诉从站:你要干什么?常用的功能码就那么几个,我列个表:

功能码 名称 作用 我常用的场景
01 读取线圈状态 读取DO(数字量输出) 读取继电器状态
02 读取离散输入 读取DI(数字量输入) 读取按钮、限位开关
03 读取保持寄存器 读取模拟量输出或参数 读取变频器频率、温度值
04 读取输入寄存器 读取模拟量输入 读取压力传感器、液位计
05 写单个线圈 控制单个DO 启动/停止电机
06 写单个寄存器 设置单个参数 设定PID目标值
15 写多个线圈 批量控制DO 同时控制多个阀门
16 写多个寄存器 批量设置参数 下载配方数据

我个人最常用的是03和06功能码。为什么?因为保持寄存器既能读又能写,灵活性最高。你想想看,一个变频器的频率,既可以通过03读取当前值,又可以通过06设定目标值,多方便。

避坑指南:我曾经遇到一个项目,用功能码01去读取数字量输入,结果一直返回0。折腾了半天才发现,数字量输入应该用功能码02。记住:01读线圈(输出),02读离散输入(输入),千万别搞混。

2.4 CRC校验计算

CRC校验是Modbus RTU的“安全锁”。它确保数据在传输过程中没有被干扰。计算方法是:

  1. 预置一个16位寄存器为0xFFFF
  2. 取第一个字节与寄存器低8位异或
  3. 右移一位,高位补0
  4. 如果移出的位是1,则与0xA001异或
  5. 重复步骤3-4,直到8位全部处理完
  6. 取下一个字节,重复步骤2-5
  7. 最后得到的寄存器值取反,就是CRC码

听起来有点复杂?我给你一个C语言实现的代码片段,直接复制就能用:

unsigned int crc16_modbus(unsigned char *buf, int len) {
    unsigned int crc = 0xFFFF;
    int i, j;
    
    for (i = 0; i < len; i++) {
        crc ^= buf[i];
        for (j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc = crc >> 1;
            }
        }
    }
    return crc;
}

调用的时候,把报文地址码、功能码、数据区传进去,返回的就是CRC校验码。注意:Modbus RTU的CRC是低字节在前,高字节在后。比如计算结果是0xCB05,发送时要先发0x05,再发0xCB。

小技巧:调试时可以用Modbus调试工具(比如ModScan)来验证你的CRC计算是否正确。我习惯先用手算一个简单的报文,再用工具对比,确保代码没问题。

好了,这一章的内容就到这里。串口通信和Modbus RTU是SCADA系统集成的基石。下一章咱们继续聊Modbus TCP和DNP3协议,到时候再分享一些我在项目中遇到的坑。记住:通信协议搞懂了,集成工作就成功了一半。