前阵子去客户现场调试一台第三方温控表,对方的PLC已经定了,但仪表只开放RS485串口,协议还是厂家自定义的,不是常见的Modbus RTU。我一听倒是松了口气——只要是串口能跑的协议,汇川技术InoProShop里的串口自由协议基本都能啃下来。今天这篇就把InoProShop的串口自由协议从通信帧设计到轮询调度,再到现场排错,整个捋一遍,希望能帮到正在跟各种“非标串口设备”死磕的朋友。
说简单点,串口自由协议就是PLC通过串口和外部设备通信时,不按Modbus这类标准协议来,而是完全按照对方设备手册里定义的数据帧格式去发送和接收数据。听起来很灵活,但前提是你得搞清楚对方的通信协议格式,自己拼帧、自己校验、自己解析。这个内容适合做设备集成、产线改造、非标自动化项目的电气工程师和PLC程序员,尤其适合正在用InoProShop做汇川PLC开发,又恰好遇到非标通信需求的同学。
1. 自由协议和标准协议,到底差在哪
很多刚接触PLC通信的人有个误区,觉得串口通信就是Modbus,Modbus就是串口。实际上Modbus只是串口通信协议里最通用的一种,真正到现场会发现,各种传感器、仪表、扫码枪、称重模块、变频器,它们的通信协议五花八门。有的基于Modbus改几个功能码,有的干脆完全自定义报文格式,这时候就轮到自由协议上场了。
1.1 自由协议和Modbus的区别
Modbus RTU本质上也是一种“自由协议”的约定,只不过它被广泛使用,大家默认按它的帧格式来:地址码、功能码、数据、CRC校验。而真正的自由协议,则要求你完全遵从设备手册里规定的字节顺序和含义,地址码可能是两字节,功能码可能是某个固定命令字符串,校验方式也可能是求和校验、异或校验、CRC16或者LRC,更复杂一点的还会有应答超时重发机制。
用一个不恰当的比喻:Modbus是大家说普通话,自由协议就是各地方言。讲普通话谁都会但门槛低,遇到不会说普通话的人,你必须学会对方的方言去交流。自由协议的价值就在这里——它让PLC能跟任何支持串口通信的设备对话,前提是你愿意按照对方的规则去“说话”。
在InoProShop里面,标准Modbus通信只需要简单配置一下从站地址、寄存器地址和读写字数,系统自动帮你构建报文帧。但自由协议模式下,你不仅要自己规划发送缓冲区,把每一个字节按顺序填进去,还要处理接收缓冲区的解析逻辑,校验算法要自己写,超时时间要自己控制,等于把整个串口通信的核心逻辑全部交到程序员手里。
1.2 典型应用场景有哪些
从我实际接触的项目来看,自由协议最常出现在这几类设备上:
第一类是专用仪表和设备,比如温控表、流量计、PH计、色谱仪、离子计等。很多仪表厂商都有自己的通信协议,有的是简化版Modbus,有的是纯ASCII字符协议,比如发送“READ:001”这样的字符串来读取数据。用自由协议处理这类设备,核心在于按手册拼字符串帧。
第二类是工业上下游设备联动,比如上位机、触摸屏或者视觉系统通过串口给PLC下发字符串指令。视觉系统最常见的做法就是输出一个字符串,比如“OK”或者“NG:01”,PLC收到后执行对应逻辑。这种情况用标准Modbus反而麻烦,用自由协议最顺手。
第三类是旧设备改造。很多老设备只有串口,协议极不标准,例如一个重量变送器可能用“STX+数据+ETX”这种带帧头帧尾的结构,中间数据可能不是十六进制,而是ASCII码组合。这类场景非得自由协议出马不可。
1.3 InoProShop里自由协议的硬件通道
在汇川技术InoProShop软件环境下,自由协议通信通常是在H系列或H5U系列PLC上通过内置串口或扩展串口模块实现。具体硬件通道取决于你用的PLC型号和扩展模块,但核心逻辑是一致的。
以常见的H3U为例,内置串口在自由协议模式下,通信参数(波特率、数据位、校验位、停止位)需要在系统参数里先配置好。这里有个容易踩的坑:很多人一开始设了自由协议,但串口参数和对方设备不一致,比如对方是偶校验,你这边设成了无校验,结果就是通信不稳定,时好时坏。所以硬件通道选定之后,第一步永远是对齐通信参数,再去管报文内容。
2. 通信帧设计与核心参数,先懂原理再动手
自由协议通信里最难的部分不是指令怎么用,而是帧怎么设计、校验怎么算、缓冲区怎么规划。这部分搞不清楚,通信就是玄学。
2.1 一条通信帧的构成
不管什么协议,一条完整的串口通信帧通常由这几部分组成:
- 帧头:标识一帧数据的开始,常见的有固定字节如02H、0AH,或者ASCII字符如“:”“$”
- 地址码:标识从站设备地址,可能是1字节,也可能是2字节ASCII码
- 命令码/功能码:表示要执行的操作,比如读数据、写参数、校零等
- 数据段:要交互的核心数据,长度不定
- 校验码:验证数据传输是否出错,常见有CRC16、LRC、BCC异或校验等
- 帧尾:标识一帧数据结束,常见有0DH、0AH(回车换行),或者0DH回车
举个实际例子,我之前调试过一台国产温控表,它的读温度报文格式是这样的:
| 组成 | 字节内容 | 说明 |
|---|---|---|
| 帧头 | 0x3A “:” | 一个ASCII冒号 |
| 地址 | 0x30 0x31 | 地址字符串“01” |
| 命令 | 0x52 0x44 | “RD”表示读取 |
| 数据 | 0x54 0x31 | 参数代码"T1" |
| 分隔符 | 0x2C | 逗号 |
| 结束 | 0x0D | 回车 |
这个报文实际发送的内容就是字符串":01RDT1,\r"。如果用Modbus那套思路去套,根本对不上。所以拿到设备手册,第一件事就是画一个这样的帧格式表格,把每个字节的含义、内容范围、是否固定值全部标出来,再开始编程。
2.2 CRC16校验手算与实现代码
自由协议里最常见的校验方式是CRC16,尤其是Modbus RTU协议衍生出来的各种变种,基本都用CRC16。CRC16的计算原理不复杂,但手算很容易错,实际编程中直接用代码实现更可靠。
Modbus RTU的CRC16算法可以概括为:初始值为0xFFFF,对每个字节先与CRC低字节异或,再右移8次,每次右移时如果最低位是1,则与0xA001异或。计算完后,CRC低字节在前发送。
下面这个Python函数就是标准的Modbus CRC16计算,我一般用它来验证PLC端计算结果是否正确:
def modbus_crc16(data: bytes) -> int: crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc # 以读保持寄存器报文 01 03 00 00 00 02 为例 frame = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x02]) crc = modbus_crc16(frame) low = crc & 0xFF high = (crc >> 8) & 0xFF print(f"CRC16 = 0x{crc:04X}, 发送顺序: 0x{low:02X} 0x{high:02X}")运行结果是CRC16=0xC40B,发送时低字节在前,所以报文是01 03 00 00 00 02 0B 0C。你可以用这个结果去校验自己的计算逻辑对不对,如果CRC计算不正确,对方设备会把整帧丢掉,表现出来就是“发是发了,但对方不回应”,这个坑特别容易踩。
还有一种常见校验是LRC,它在ASCII协议里用得多。计算方法是所有字节直接求和,然后取反加一。比如对":01RDT1,\r"中要计算的字节(去掉帧头冒号、帧尾回车)求和,取低8位,再取反加一就等于LRC值。LRC的计算量比CRC小很多,但安全性也低一些,适合通信距离短、干扰小的场合。
2.3 缓冲区规划与通信参数选型
自由协议通信不像Modbus那样有标准的寄存器映射,发送什么、存到哪、接收什么、怎么解析,全部需要你自己规划。我一般建议在PLC里划分专用的数据区来做缓冲,不要直接散落在各个寄存器里。
发送缓冲区建议固定200字节,从D100开始;接收缓冲区也固定200字节,从D200开始。这样做的好处是程序逻辑清晰、变量地址固定,调试时用监控表一眼就能看到发送和接收的原始字节。接收缓冲区的起始地址和长度通常在通信参数里绑定,一旦设置好,就不要在程序里乱改动。
通信参数的选型遵循一个原则:能低就不高。波特率9600虽然慢,但抗干扰能力强,传输距离远。现场有变频器、伺服这种强干扰源,把波特率设到115200就是在找死,除非通信数据量确实大,否则9600才是稳妥的选择。数据位基本固定8位、停止位1位或者2位,校验方式看对方设备手册要求,偶校验最常用,但也有设备要求无校验。
3. 在InoProShop里把自由协议跑起来
原理说得再多,不如上手操作一次。下面我按一个完整的小项目来拆解:用汇川PLC通过RS485串口读取一台第三方温控表的当前温度,协议是厂家自定义ASCII格式,波特率9600,偶校验,报文帧头为“:”,帧尾为回车,命令为“:01RDT1,\r”。
3.1 硬件接线与参数配置
第一步是接线。RS485是差分信号,两根线分别叫A+和B-,这个接到温控表对应端子上。接线本身不难,难点在现场环境。RS485的A/B线必须用双绞线或者屏蔽双绞线,不要用普通平行电线。屏蔽层单端接地,一般接在PLC侧的地线上。通信距离超过100米时,末端要加120欧姆终端电阻,这个电阻可以减少信号反射,实测能明显降低偶发通信故障。
接线完成后,打开InoProShop的工程,在系统参数里找到对应的串口配置界面。协议类型选择“自由协议”,波特率设为9600,数据位8位,校验方式选择偶校验,停止位1位。注意这些参数必须和温控表手册里的串口设置完全一致,只要有一位不一致,通信就会失败。
串口参数配置好之后,紧接着要定义通信缓冲区。InoProShop的自由协议通常需要指定发送缓冲区起始地址、接收缓冲区起始地址、允许接收的最大字节数。我的设置是发送缓冲区D100起始、接收缓冲区D200起始、最大接收长度200字节。这里要注意,最大接收长度不要设得太小,有些设备返回的数据里可能包含多个连续的帧,设太小会丢数据。
3.2 发送接收逻辑与轮询程序示例
配置完成后,核心工作就是编写发送接收逻辑。自由协议模式下,程序逻辑可以归纳为这样一个状态循环:置位发送请求、把要发送的帧按顺序填入发送缓冲区、触发发送、等待接收完成标志、解析接收缓冲区、处理数据。
以温控表为例,假设我们需要每秒读取一次温度。发送帧“:01RDT1,\r”在发送缓冲区里需要按ASCII码逐字节填入,转换成十六进制就是:
frame_hex = [0x3A, 0x30, 0x31, 0x52, 0x44, 0x54, 0x31, 0x2C, 0x0D]第一个0x3A是冒号“:”,0x30 0x31是字符“01”,0x52 0x44是“RD”,0x54 0x31是“T1”,0x2C是逗号,0x0D是回车。有些协议帧尾是0x0D 0x0A(回车+换行),这个要严格看手册,多一个换行或少一个回车都可能被对方忽略。
PLC端发送完成后,等待温控表返回数据。返回帧一般也以冒号开头,以回车结尾,中间包含当前温度值,比如返回“:01RDT1,235,\r”可能表示23.5度。解析时关键是定位温度数据的起始位置:从接收缓冲区的起始地址开始,先确认第一个字节是不是0x3A(冒号),然后逐字节找逗号0x2C的位置,逗号后面的连续ASCII数字就是温度值。
为了便于理解,下面给出一段简化的流程伪代码,实际在InoProShop里可以用ST语言或者梯形图实现:
// 定时触发:每1秒置位发送请求 IF 定时发送 THEN 发送请求 := TRUE; END_IF // 发送子程序 IF 发送请求 THEN // 将帧头写入发送缓冲区首字节 发送缓冲区[0] := 0x3A; // ":" 发送缓冲区[1] := 0x30; // "0" 发送缓冲区[2] := 0x31; // "1" ... // 写入帧尾后,触发串口发送 启动发送(缓冲区首地址 := D100, 发送长度 := 9); 发送请求 := FALSE; END_IF // 接收解析:检测到接收完成标志 IF 接收完成 THEN // 判断接收缓冲区首字节是否等于帧头 IF 接收缓冲区[0] = 0x3A THEN // 查找逗号位置 索引 := 查找字符(接收缓冲区, 0x2C); // 从索引+1开始解析ASCII数字 温度值 := 解析数值(接收缓冲区, 索引+1); END_IF // 复位接收标志,准备下次接收 接收完成 := FALSE; END_IF实际编程时,InoProShop里有对应的串口发送和接收指令,指令的具体名称和参数会因PLC型号和固件版本略有差异,最简单的办法是参考当前固件版本对应的通信指令手册。核心逻辑就是上面这个结构,理解了之后,换成什么指令都不怕。
3.3 联调测试步骤
程序下载到PLC后,先不要急着连温控表,用串口调试助手做一次“假设备”测试。方法很简单:把PLC的RS485串口通过USB转485模块接到电脑上,打开串口助手,设置同样的参数,手工发送温控表的返回帧。这一步可以验证PLC接收解析逻辑是否正确,也方便你直接观察PLC发送出来的内容是不是完全符合手册要求。
测试无问题后,再真正把RS485接到温控表上。切换成真实设备时,要注意线缆没有接错、接线端子是否拧紧。很多人一上来就用真实设备联调,一旦失败就很难判断是接线问题、参数问题还是程序问题,用串口助手隔离变量能省很多时间。
联调时推荐先发一条读命令,观察返回数据。如果返回不了,用表量一下RS485两根线之间有没有2-5V直流电压,这个电压存在说明收发芯片在工作。一点电压都没有,大概率是接线问题或串口参数配置没有生效。
4. 常见问题和排错速查
自由协议通信的报错表现就那么几种,但原因可能千奇百怪。我把自己在项目里积累的排查经验整理成了一个速查表,节省你在现场瞎折腾的时间。
4.1 完全无响应的排查路线
现象是PLC一直发送,但设备一点反应没有。这种情况先不要怀疑程序逻辑,按照下面的路线走一遍:
先确认硬件接线。用万用表量RS485的A和B之间是否有电压。正常工作时应该有两三伏的电压波动,如果没有,八成是接线松动或芯片没工作。再看看通信线是否接地正确,RS485设备之间最好共地,各设备的地线一定要连起来,否则共模电压可能损坏通信。
再确认串口参数。这里包括波特率、数据位、校验位、停止位,每一个位都反复核对。我遇到过一台设备手册写的是波特率9600、偶校验,结果实际出厂默认是19200、无校验,害得我排查了半天。遇到这种情况,优先用设备自带的调试软件测试通信,确定设备实际参数后再改PLC程序。
最后确认对方的通信地址。很多自由协议设备在报文里包含地址信息,如果地址不对,设备直接丢弃不响应。我曾因为仪表拨码地址和报文里填的地址不一致折腾了一晚上,其实只要把地址改对就通了。
4.2 数据乱码和错位的处理
有时候通信能通,但解析出来的数完全不对,比如温度显示成几百万,或者数值跳动特别夸张。这个问题多半是数据格式不匹配。
考虑一种情况:设备返回的是ASCII码“235”,PLC程序却按照十六进制字节去解析。0x32 0x33 0x35这3个字节,如果直接拼成一个十六进制数就是0x323335,转成十进制是三百多万,那数据怎么可能对。所以看到超大数、异常数,第一反应就是检查数据解析方式,ASCII字符要先减0x30再从高位到低位组合。
还有一种是返回帧里包含几个连续的数据段,比如温控表一次返回“:01RDT1,235,0,0,\r”,里面有温度、报警状态、输出功率好几个值。解析时如果只按固定偏移找数据,极容易错位。我建议先通过串口助手把一帧完整的返回报文打出来,数清楚每个逗号在第几个字节位置,再根据位置解析。不要只凭手册上的“大概位置”去写程序。
4.3 超时反馈错误与ERROR CODE对照
很多自由协议设备在收到错误指令时会返回一条固定的错误码,这在调试时特别有用。常见的错误码含义如下表,不同设备定义略有差异,但大致逻辑相近:
| 错误码 | 含义 | 排查方向 |
|---|---|---|
| 01 | 非法功能码 | 检查发送的命令码是否在设备手册允许范围内 |
| 02 | 非法数据地址 | 检查参数代码、寄存器编号是否正确 |
| 03 | 非法数据值 | 检查数据边界、字节长度是否合理 |
| 04 | 从站设备故障 | 检查仪表本身状态、传感器是否异常 |
| FF | 未知错误 | 检查报文帧格式、校验码计算 |
遇到错误码反馈,第一件事不是改PLC程序,而是用串口助手直接向设备发送报文,看看设备会不会返回相同的错误码。如果串口助手也返回错误码,说明设备的协议逻辑没毛病,问题在指令内容;如果串口助手发送正常,只有PLC发送出错,那就是程序里填的字节或校验算错了。
调试自由协议通信的时候,还有一个容易忽略的细节:很多设备要求两帧之间的间隔不能太短,推荐在200毫秒以上。PLC如果毫不停歇地轮询发送,设备会来不及处理导致数据拥堵。最粗暴的解决方法是把每次发送之间的间隔做成可调参数,现场根据实际测试调整。
4.4 从排错到上手的心得清单
结合这么多年的经验,我总结了一套做自由协议通信的固定流程,每次上手都是这个套路:
第一,拿到设备手册先画帧格式表,不急着写程序。把帧头、地址、命令、数据、校验、帧尾逐项列出来,标清每个字段是十六进制还是ASCII字符、是固定值还是变量。
第二,用串口助手模拟一次完整通信,发一帧看一帧。只要设备能正常响应,说明你已经掌握协议了,后面的事情就是把这段交互逻辑移植到PLC里。
第三,程序里留好调试开关。我习惯在PLC里加一个“调试模式”位,置ON时把当前发送帧和接收帧的原始字节全部搬到连续的寄存器里,方便用监控表查看,联调通过后再关闭。这样能省下大量翻手册核对字节的时间。
第四,通信不稳定的问题,不要一上来就怀疑协议,先排查现场干扰。变频器启动瞬间、伺服抱闸动作瞬间最容易出问题,这些场合是不是该降波特率、加屏蔽层、区分电源地线,优先级远高于调协议。
如果这些路子都走了一遍还没搞定,不妨把波特率往下降一档再看看。很多通信问题不是逻辑问题,而是物理层问题。9600不行试4800,高速通信在这种干扰大的现场确实比较吃亏,降到稳定传输为止。
我个人在实际操作中的体会是,做自由协议通信,耐住性子比技术能力更重要。把每一步分开验证,把协议帧拆到最小单位去排错,看起来慢,实际上才是最省时间的方式。我见过太多人一次性把整个通信程序写完,然后在现场抓瞎,一个字节一个字节地猜问题,那种煎熬我深有体会。记住一个原则:先让一帧数据稳定跑通,再去做整个通信轮询的架构设计。