自写轻量modbus库请参考我的gitee链接
modbus_master_f103rc: 700行的modbus主站代码,支持异步同步收发,03 04 06 10功能码
modbus_slave_f103c8: 300行的modbus从站代码,支持03 06 10功能码
1. 什么是 Modbus RTU?
Modbus RTU 是 Modbus 协议家族中的一种传输模式,专注于在串行通信(Serial Communication)线路上通过二进制数据格式进行高效的数据交换。它遵循主从(Master-Slave)通信模型:通常有一个主站(Master)设备(如PC或PLC)发起请求,一个或多个从站(Slave)设备(如传感器或控制器)响应该请求。
Modbus RTU 的核心优势在于:
简单可靠:协议结构极为精炼,易于理解和在嵌入式系统中实现,出错率低。
开放免费:Modbus 协议是完全开放且免授权费的,极大促进了其在工业领域的普及。
广泛支持:市场上的绝大多数工业自动化设备都内置或支持 Modbus RTU 接口,兼容性极佳。
高效传输:采用紧凑的二进制格式传输数据,相比 Modbus ASCII 模式,具有更高的传输效率。
2. 物理层:Modbus RTU 的载体
Modbus RTU 协议本身只定义了数据报文的结构,而底层的物理连接则依赖于串行通信标准。最常见的物理层标准是:
RS-485
RS-485 是 Modbus RTU 在工业应用中最常见的选择,其优势显著:
- 多点通信:支持半双工多点通信,一个主站可以连接多达32个从站(通过中继器可扩展更多)。
- 传输距离远:标准传输距离可达1200米。
- 抗干扰能力强:采用差分信号传输,对共模噪声具有良好的抑制能力,适用于工业恶劣环境。
- 布线简单:通常采用两根双绞线(A线和B线)进行连接。
3. 协议基础结构:Modbus RTU 数据帧
一个标准的 Modbus RTU 数据帧(或称报文)是主站与从站之间进行通信的基本单位。它包含以下几个关键部分:
3.1 数据帧格式概览
1 | [从站地址] [功能码] [数据字段] [CRC校验] |
复制
3.1.1 从站地址(Slave Address)- 1个字节
- 作用:用于唯一标识网络上的目标从站设备。
- 范围:通常为 1-247。
0是广播地址,发送给所有从站,但从站不响应。248-255为保留地址。
- 要求:每个从站设备在 Modbus 网络中必须配置一个唯一的地址。
3.1.2 功能码(Function Code)- 1个字节
- 作用:定义了主站希望从站执行的操作类型(如读取数据、写入数据)。
- 最常用功能码列表:
| 功能码 (十进制/十六进制) | 名称 | 数据类型 | 访问权限 | 说明 |
|---|---|---|---|---|
| 01 (0x01) | 读取线圈状态 | Bit | 读 | 读取一个或多个输出线圈(Coils)的开/关状态 |
| 02 (0x02) | 读取离散量输入状态 | Bit | 读 | 读取一个或多个离散量输入(Discrete Inputs)的开/关状态 |
| 03 (0x03) | 读取保持寄存器 | Word | 读 | 读取一个或多个保持寄存器(Holding Registers)的值 |
| 04 (0x04) | 读取输入寄存器 | Word | 读 | 读取一个或多个输入寄存器(Input Registers)的值 |
| 05 (0x05) | 强制单个线圈 | Bit | 写 | 设置单个输出线圈的状态(开/关) |
| 06 (0x06) | 预置单个寄存器 | Word | 写 | 设置单个保持寄存器的值 |
| 15 (0x0F) | 强制多个线圈 | Bit | 写 | 设置多个输出线圈的状态 |
| 16 (0x10) | 预置多个寄存器 | Word | 写 | 设置多个保持寄存器的值 |
3.1.3 数据字段(Data Field)- 变长
- 作用:包含执行特定功能所需的具体信息,如起始地址、读取或写入的数量、要写入的值等。
- 长度:根据功能码的不同而变化。
- 读取请求:通常包含起始地址和数量。
- 写入请求:通常包含起始地址和要写入的值。
3.1.4 CRC校验(Cyclic Redundancy Check)- 2个字节
- 作用:提供数据的完整性校验,用于检测在传输过程中是否发生错误。
- 算法:Modbus RTU 使用 CRC-16 算法。
- 发送方计算从从站地址到数据字段末尾的所有字节的 CRC 值,并将其附加在帧的末尾(低字节在前,高字节在后)。
- 接收方接收到数据帧后,以同样的方式对接收到的数据(不含接收到的 CRC)进行 CRC 计算。
- 如果计算结果与接收到的 CRC 值不匹配,则认为数据传输有误,从站通常会丢弃该帧不予处理或返回错误。
3.1.5 帧间静默时间(T3.5)
- 重要性:这是 Modbus RTU 协议中一个非常关键的特性,用于判断一帧数据的开始和结束。
- 定义:在 Modbus RTU 模式下,一个数据帧的发送必须以至少 3.5 个字符传输时间的静默间隔作为结束标志。当接收方检测到连续的 3.5 个字符的静默时间后,就会认为前一帧数据已经完整接收。同样,一个新帧的开始也必须以 3.5 个字符的静默时间作为前导。
- 作用:
- 帧同步:确保接收方能够准确地识别一帧的边界,避免粘包或断帧问题。
- 错误检测:如果两个字符之间的静默时间超过 1.5 个字符但小于 3.5 个字符,则认为这是一个传输错误(可能表示帧被截断)。
- 计算:
T3.5 = (3.5 * 10位 / 波特率)秒(每个字符通常包含1起始位+8数据位+1停止位 = 10位)。例如,在9600 bps下,T3.5约为3.64毫秒。
4.硬件载体:485常见电路
- 自动收发与非自动收发只是收发方向切换方案不同,多一个引脚的问题,主要看硬件成本
4.1 非自动收发

4.2 自动收发电路
三极管方案可能会影响速度,除了用7CHC也可以直接用带自动收发得芯片比如MAX13487,就是比较贵


5. 收发示例
通过具体的命令和响应示例,我们可以更好地理解 Modbus RTU 的工作方式。
5.1 示例1:读取温度传感器数据(功能码 0x03 - 读取保持寄存器)
假设我们有一个地址为 1 的温度传感器,其当前温度值存储在保持寄存器(Holding Register)的地址 40001 (Modbus协议偏移地址 0x0000)。我们要读取这一个寄存器的值。
发送命令(主站 -> 从站):
1 | 01 03 00 00 00 01 84 0A |
复制
命令解析:
01:从站地址为 1。03:功能码为 3 (0x03),表示读取保持寄存器。00 00:起始寄存器地址为 0x0000 (对应 Modbus 参考地址 40001)。00 01:请求读取 1 个寄存器。84 0A:CRC-16 校验码。
设备响应(从站 -> 主站):
1 | 01 03 02 00 64 B9 30 |
复制
响应解析:
01:从站地址为 1。03:功能码为 3 (0x03),正常响应。02:数据字节数,表示接下来的数据有 2 个字节(因为读取了 1 个 16 位寄存器,即 2 个字节)。00 64:实际读取到的数据。0x0064转换为十进制是100。B9 30:CRC-16 校验码。
结果:主站成功读取到温度值为 100 (可能是摄氏度、华氏度或带小数点的整数,具体取决于设备约定)。
5.2 示例2:设置输出线圈状态(功能码 0x05 - 强制单个线圈)
要将地址为 2 的设备的输出线圈(Coil)地址 00001 (Modbus协议偏移地址 0x0000) 设置为开启状态。
发送命令(主站 -> 从站):
1 | 02 05 00 00 FF 00 8C 3A |
复制
命令解析:
02:从站地址为 2。05:功能码为 5 (0x05),表示强制单个线圈。00 00:线圈地址为 0x0000 (对应 Modbus 参考地址 00001)。FF 00:写入的值。0xFF00表示将线圈设置为 ON(开启),0x0000表示设置为 OFF(关闭)。8C 3A:CRC-16 校验码。
设备响应(从站 -> 主站):
1 | 02 05 00 00 FF 00 8C 3A |
复制
响应解析:
- 对于功能码 0x05,从站的正常响应是将其接收到的请求帧原样返回给主站,以确认操作已成功执行。
02:从站地址为 2。05:功能码为 5。00 00:线圈地址为 0x0000。FF 00:确认线圈被设置为 ON。8C 3A:CRC-16 校验码。
结果:从站的线圈 00001 被成功设置为开启状态。
6.主站与从站示例程序
- 如果使用成熟库推荐agile_modbus,因为有中文说明,用法也简单
- 也可以参考最上面我的库
6.1 主站
- 不使用库,说明一般需求比较简单,就是读个数据,直接封包发送即可,使用库请参考所需要库的API
1 | HAL_GPIO_WritePin(RS485_EN_GPIO_Port, RS485_EN_Pin, GPIO_PIN_SET) |
6.2 从站
- 如果不想用库同时需要简单从站代码实现,请参考下方代码实现,本人用了300多一点行代码实现的从站代码
- git clone https://gitee.com/workspaces_mcu/modbus_slave_f103c8.git
