Modbus 协议实战:Modbus-RTU/TCP 采集传感器、变频器工控案例

Modbus 协议实战:Modbus-RTU/TCP 采集传感器、变频器工控案例

工控圈有句行话:没听过 Modbus,都不好意思说自己是搞自动化的。这篇从协议帧到代码实操,一次性讲透。

一、Modbus 协议简介

Modbus 是 1979 年 Modicon 公司(现施耐德电气)发明的工业通信协议,如今已成为工业自动化领域事实上的通信标准。几乎所有工控设备——PLC、变频器、传感器、电表——都支持 Modbus。

Modbus 有三种传输模式:

模式传输介质特点典型场景
Modbus-RTU串口(RS232/RS485)二进制帧,CRC校验,效率高现场设备通信
Modbus-ASCII串口ASCII字符,LRC校验,可读性好调试场景
Modbus-TCP以太网去掉CRC,加MBAP头,端口502网络化监控

实际工控项目中RTU 和 TCP 用得最多,ASCII 基本淘汰。

二、Modbus-RTU 帧结构

2.1 帧格式

一个完整的 Modbus-RTU 帧结构如下:

| 从机地址(1B) | 功能码(1B) | 数据区(NB) | CRC校验(2B) |

帧与帧之间靠时间间隔区分:两个字符之间的间隔不能超过 3.5 个字符的传输时间(在 9600bps 下约 3.6ms)。超过这个间隔就认为是新的一帧。

2.2 常用功能码

功能码操作说明
0x01读线圈读开关量输出状态(ON/OFF)
0x02读离散输入读开关量输入状态(只读)
0x03读保持寄存器读模拟量/参数(最常用)
0x04读输入寄存器读模拟量输入(只读)
0x05写单个线圈控制单个开关
0x06写单个寄存器设定单个参数值
0x0F写多个线圈批量控制开关
0x10写多个寄存器批量设定参数

举例:读保持寄存器的请求帧和响应帧:

请求:01 03 00 00 00 02 C4 0B | | | | |—CRC | | | |—寄存器数量(2个) | | |—起始地址(0x0000) | |—功能码(03 读保持寄存器) |—从机地址(01) 响应:01 03 04 01 90 00 7A 6B 3D | | | |————|—数据(2个寄存器=4字节) | | |—字节数(4) | |—功能码 |—从机地址

寄存器值 = 0x0190 和 0x007A,即十进制 400 和 122。

三、Modbus-TCP 帧结构

Modbus-TCP 用 MBAP 头替代了 RTU 的从机地址+CRC:

| MBAP头(7B) | 功能码(1B) | 数据区(NB) |

MBAP 头结构:

字段长度说明
事务标识2B请求/响应匹配标识
协议标识2B固定0x0000
长度2B后续字节数
单元标识1B从机地址(对应RTU地址)

TCP 不需要 CRC,因为 TCP 本身就保证数据完整性。默认端口502

四、libmodbus 开源库使用

4.1 安装

# Ubuntu/Debiansudoaptinstalllibmodbus-dev# 或从源码编译(推荐,版本更新)gitclone https://github.com/stephane/libmodbus.gitcdlibmodbus ./autogen.sh ./configuremake-j$(nproc)sudomakeinstallsudoldconfig

4.2 libmodbus 核心API

modbus_t*mb;// 上下文句柄mb=modbus_new_rtu("/dev/ttyS0",9600,'N',8,1);// RTU模式mb=modbus_new_tcp("192.168.1.100",502);// TCP模式modbus_set_slave(mb,1);// 设置从机地址modbus_connect(mb);// 建立连接// 读写操作uint16_tregs[10];modbus_read_registers(mb,0,2,regs);// 读保持寄存器modbus_write_register(mb,0,500);// 写单个寄存器modbus_write_registers(mb,0,2,regs);// 写多个寄存器uint8_tbits[10];modbus_read_bits(mb,0,8,bits);// 读线圈modbus_write_bit(mb,0,TRUE);// 写单个线圈modbus_close(mb);modbus_free(mb);

五、实战案例1:读取温湿度传感器 Modbus 数据

场景:某 RS485 温湿度传感器,地址01,波特率9600,8N1。温度在保持寄存器0x0000,湿度在0x0001。

#include<stdio.h>#include<modbus.h>intmain(){modbus_t*mb;uint16_tregs[2];intrc;// 初始化RTU连接mb=modbus_new_rtu("/dev/ttyS0",9600,'N',8,1);if(mb==NULL){fprintf(stderr,"Failed to create modbus context\n");return-1;}modbus_set_slave(mb,1);if(modbus_connect(mb)==-1){fprintf(stderr,"Connection failed: %s\n",modbus_strerror(errno));modbus_free(mb);return-1;}// 读取地址0x0000开始的2个保持寄存器rc=modbus_read_registers(mb,0x0000,2,regs);if(rc==-1){fprintf(stderr,"Read failed: %s\n",modbus_strerror(errno));}else{// 假设传感器输出:温度×10存于寄存器0,湿度×10存于寄存器1floattemp=regs[0]/10.0f;floathumi=regs[1]/10.0f;printf("Temperature: %.1f C\n",temp);printf("Humidity: %.1f %%\n",humi);}modbus_close(mb);modbus_free(mb);return0;}

编译:gcc modbus_sensor.c -o modbus_sensor -lmodbus

六、实战案例2:控制变频器启停与频率设定

场景:变频器地址02,波特率9600,8E1。控制字寄存器地址0x2000,频率设定寄存器地址0x2001。

#include<stdio.h>#include<modbus.h>#include<unistd.h>#defineVFD_ADDR2#defineCTRL_REG0x2000// 控制字寄存器#defineFREQ_REG0x2001// 频率设定寄存器// 控制字定义(以某品牌变频器为例)#defineCMD_STOP0x0006// 停止#defineCMD_START0x0007// 正转启动#defineCMD_REVERSE0x000F// 反转启动intmain(){modbus_t*mb;intrc;mb=modbus_new_rtu("/dev/ttyS0",9600,'E',8,1);if(mb==NULL)return-1;modbus_set_slave(mb,VFD_ADDR);if(modbus_connect(mb)==-1){modbus_free(mb);return-1;}// 设置频率:25.0Hz → 寄存器值250(频率×10)rc=modbus_write_register(mb,FREQ_REG,250);if(rc==-1){fprintf(stderr,"Set frequency failed: %s\n",modbus_strerror(errno));}else{printf("Frequency set to 25.0 Hz\n");}// 启动变频器rc=modbus_write_register(mb,CTRL_REG,CMD_START);if(rc==-1){fprintf(stderr,"Start failed: %s\n",modbus_strerror(errno));}else{printf("VFD started\n");}// 运行10秒后停止sleep(10);modbus_write_register(mb,CTRL_REG,CMD_STOP);printf("VFD stopped\n");modbus_close(mb);modbus_free(mb);return0;}

注意:不同品牌变频器的控制字定义和寄存器地址不同,务必查阅对应型号的 Modbus 通信手册。

七、CRC 校验原理与手动计算

Modbus-RTU 使用 CRC-16 校验(多项式 0xA001),计算步骤:

  1. 预置 CRC 寄存器为 0xFFFF
  2. 取帧中第一个字节与 CRC 低8位异或
  3. 如果最低位为1,右移一位并异或 0xA001;为0则直接右移
  4. 重复步骤3共8次(处理完一个字节)
  5. 对帧中每个字节重复步骤2~4
  6. 最终 CRC 寄存器的值即为校验码,低字节在前,高字节在后

C 语言实现:

uint16_tmodbus_crc16(uint8_t*data,intlen){uint16_tcrc=0xFFFF;for(inti=0;i<len;i++){crc^=data[i];for(intj=0;j<8;j++){if(crc&0x0001)crc=(crc>>1)^0xA001;elsecrc>>=1;}}returncrc;}

手动验证:对帧01 03 00 00 00 02计算 CRC,结果应为0x0BC4,发送时低字节在前:... C4 0B

八、Modbus 调试工具推荐

8.1 modpoll

命令行工具,快速测试 Modbus 设备:

# 读RTU设备地址1的保持寄存器0,数量2modpoll-mrtu-b9600-pnone-a1-r1-c2/dev/ttyS0# 读TCP设备地址1的保持寄存器0modpoll-mtcp-a1-r1-c2192.168.1.100

8.2 QModMaster

图形化 Modbus 主站工具,支持 RTU/TCP,可以图形界面配置参数、查看数据,适合现场调试。

8.3 Modbus Poll / Modbus Slave

经典调试组合:Poll 模拟主站,Slave 模拟从站,方便在没有真实设备时测试程序。

九、小结

Modbus 协议本身非常简单——就是"地址+功能码+数据+CRC"的字节流。但它的工程价值极高:

  • RTU 场景:用 libmodbus 几行代码就能读传感器、控变频器
  • TCP 场景:换一个初始化函数,其余 API 完全一样
  • 调试:modpoll + QModMaster 足以覆盖现场大部分需求

掌握 Modbus,等于拿到了工控通信的入场券。