Modbus协议

目录


1. Modbus协议概述

1. 主从结构与数据模型

  理解Modbus协议,首先需要掌握两个核心概念:主从通信模式四种数据模型

  • 主从通信模式 (Master-Slave): Modbus总线通常只有一台主设备(如PLC),它可以向一个或多个从设备发起请求。从设备只能被动响应,不能主动发送数据,从设备之间也不能直接通信。这种机制保证了通信的有序性。

  • 四种数据模型: Modbus协议定义了四种基本的数据类型,用于操作设备内部的不同数据区域,理解它们至关重要。

    数据类型 读写属性 物理含义
    线圈 (Coils) 1位 可读可写 控制输出,如继电器开关、指示灯
    离散输入 (Discrete Inputs) 1位 只读 采集开关状态,如限位开关、按钮
    保持寄存器 (Holding Registers) 16位 可读可写 存储设备参数,如阈值、配置、PID参数等
    输入寄存器 (Input Registers) 16位 只读 采集模拟量数据,如温度、压力、电流值

    Modbus协议根据传输介质和方式的不同,主要分为RTU、ASCII和TCP三种,它们的报文格式有显著差异。


2. Modbus RTU协议详解

2.1 报文格式

Modbus RTU报文由以下四个部分组成:

字段 长度 描述 取值范围及说明
地址域 1字节 标识目标从设备的地址 1 ~ 247 (单播地址), 0 (广播地址,从机不响应)
功能码 1字节 告诉从机要执行什么操作 1 ~ 255,128 ~ 255 用于异常响应
数据域 N字节 请求或响应的具体参数 长度可变,最大为 252字节
CRC校验 2字节 循环冗余校验 对地址、功能码和数据域进行计算。传输采用小端模式

2.2 位操作

  这类操作针对线圈和离散输入,都是1位(bit)的数据,对应物理世界的开关量。

功能码 名称 作用 请求报文数据域 响应报文数据域
0x01 读线圈 读取一组逻辑线圈的当前状态(ON/OFF) 起始地址(2B) + 线圈数量(2B) 字节数(1B) + 线圈状态(N字节,每个bit代表一个线圈)
0x02 读离散输入 读取一组离散输入(开关量输入)的当前状态 起始地址(2B) + 输入数量(2B) 字节数(1B) + 输入状态(N字节)
0x05 写单个线圈 写入一个逻辑线圈的状态为ON或OFF 线圈地址(2B) + 值(2B)。0xFF00表示ON,0x0000表示OFF 与请求报文完全一致(回显),用于确认
0x0F 写多个线圈 写入一串连续逻辑线圈的通断 起始地址(2B) + 数量(2B) + 字节数(1B) + 线圈值(N字节) 起始地址(2B) + 写入数量(2B)

功能码 0x01:读线圈

  • 请求报文:01 01 00 00 00 0A BC 0D
  • 01:从站地址
  • 01:功能码(读线圈)
  • 00 00:起始线圈地址(0x0000)
  • 00 0A:读取10个线圈(0x000A = 10)
  • BC 0D:CRC校验(低字节在前)
  • 响应报文:01 01 02 CD 01 2C AC
  • 01:从站地址
  • 01:功能码
  • 02:后续数据字节数(2字节,因为10个线圈需要2字节表示)
  • CD 01:线圈状态。CD (1100 1101) 表示线圈0-7,01 (0000 0001) 表示线圈8-9(有效位只有低2位)。解析:线圈0-7依次为1,0,1,1,0,0,1,1(最低位对应起始地址),线圈8=1,线圈9=0
  • 2C AC:CRC

功能码 0x02:读离散输入

  • 请求报文:01 02 00 C4 00 16 B8 39
  • 01:从站地址
  • 02:功能码(读离散输入)
  • 00 C4:起始输入地址(0x00C4 = 196)
  • 00 16:读取22个输入(0x0016 = 22)
  • B8 39:CRC
  • 响应报文:01 02 03 AC DB 35 22 88
  • 01:从站地址
  • 02:功能码
  • 03:数据字节数(22个输入需要3字节)
  • AC DB 35:输入状态(二进制表示略)
  • 22 88:CRC

功能码 0x05:写单个线圈

  • 请求报文:01 05 00 AC FF 00 4C 1B
  • 01:从站地址
  • 05:功能码(写单个线圈)
  • 00 AC:线圈地址(0x00AC = 172)
  • FF 00:数据值,FF00表示ON(线圈吸合),0000表示OFF
  • 4C 1B:CRC
  • 响应报文:01 05 00 AC FF 00 4C 1B
  • 正常响应与请求完全一致(回显),从站确认操作。

功能码 0x0F:写多个线圈

  • 请求报文:01 0F 00 13 00 0A 02 CD 01 72 CB
  • 01:从站地址
  • 0F:功能码(写多个线圈)
  • 00 13:起始线圈地址(0x0013 = 19)
  • 00 0A:写入10个线圈
  • 02:后续数据字节数(2字节)
  • CD 01:线圈状态。CD (1100 1101) 控制前8个线圈(地址19-26),01 (0000 0001) 控制后2个线圈(地址27-28)
  • 72 CB:CRC
  • 响应报文:01 0F 00 13 00 0A 24 09
  • 01:从站地址
  • 0F:功能码
  • 00 13:起始地址
  • 00 0A:写入数量
  • 24 09:CRC

2.3 寄存器操作

  这类操作针对的是16位(双字节)的数据,对应模拟量输入、输出和参数设置。

功能码 名称 作用 请求报文数据域 响应报文数据域
0x03 读保持寄存器 读取一个或多个保持寄存器值 起始地址(2B) + 寄存器数量(2B) 字节数(1B) + 寄存器值(N×2字节)
0x04 读输入寄存器 读取一个或多个输入寄存器值 起始地址(2B) + 输入寄存器数量(2B) 字节数(1B) + 输入寄存器值(N×2字节)
0x06 写单个寄存器 把值写入到一个保持寄存器中 寄存器地址(2B) + 值(2B) 与请求报文完全一致(回显)
0x10 写多个寄存器 写一串连续的保持寄存器 起始地址(2B) + 数量(2B) + 字节数(1B) + 寄存器值(N×2B) 起始地址(2B) + 写入数量(2B)

功能码 0x03:读保持寄存器

  • 请求报文:01 03 00 6B 00 03 74 17
  • 01:从站地址
  • 03:功能码(读保持寄存器)
  • 00 6B:起始寄存器地址(0x006B = 107)
  • 00 03:读取3个寄存器
  • 74 17:CRC
  • 响应报文:01 03 06 02 2B 00 00 00 64 05 7A
  • 01:从站地址
  • 03:功能码
  • 06:数据字节数(3个寄存器 × 2字节 = 6字节)
  • 02 2B:寄存器0x006B的值(0x022B = 555)
  • 00 00:寄存器0x006C的值
  • 00 64:寄存器0x006D的值(0x0064 = 100)
  • 05 7A:CRC

功能码 0x04:读输入寄存器

  • 请求报文:01 04 00 08 00 01 B0 08
  • 01:从站地址
  • 04:功能码(读输入寄存器)
  • 00 08:起始输入寄存器地址(0x0008 = 8)
  • 00 01:读取1个寄存器
  • B0 08:CRC
  • 响应报文:01 04 02 00 0A 39 37
  • 01:从站地址
  • 04:功能码
  • 02:数据字节数
  • 00 0A:输入寄存器值(0x000A = 10)
  • 39 37:CRC

功能码 0x06:写单个寄存器

  • 请求报文:01 06 00 01 00 03 98 0B
  • 01:从站地址
  • 06:功能码(写单个寄存器)
  • 00 01:寄存器地址(0x0001 = 1)
  • 00 03:写入值(0x0003 = 3)
  • 98 0B:CRC
  • 响应报文:01 06 00 01 00 03 98 0B(回显)

功能码 0x10:写多个寄存器

  • 请求报文:01 10 00 01 00 02 04 00 0A 01 02 92 30
  • 01:从站地址
  • 10:功能码(写多个寄存器)
  • 00 01:起始寄存器地址(0x0001)
  • 00 02:写入2个寄存器
  • 04:数据字节数(2×2 = 4字节)
  • 00 0A:第一个寄存器的值(0x000A = 10)
  • 01 02:第二个寄存器的值(0x0102 = 258)
  • 92 30:CRC
  • 响应报文:01 10 00 01 00 02 10 08
  • 01:从站地址
  • 10:功能码
  • 00 01:起始地址
  • 00 02:写入数量
  • 10 08:CRC

2.4 诊断和特殊功能

  这类功能码用于设备管理、诊断和高级操作,在日常数据采集中使用较少。

功能码 名称 作用 简要说明
0x07 读取异常状态 读取8个内部线圈的状态,用于快速获取从机状态 适用于从机状态快速巡检
0x08 诊断 用于检查通信链路,或进行其他诊断测试 常见子功能如0x0000返回查询数据(回路反馈),用于测试通信是否正常
0x0B 读取事件计数 读取从机Modbus事务处理事件计数 可用于判断通信操作是否成功
0x0C 读取通信事件记录 检索每台从机的Modbus通信事件记录 用于高级诊断和分析
0x11 报告从机标识 获取从机的类型、运行状态等标识信息 用于设备识别和状态查询
0x14 读文件记录 读取文件记录 用于访问更复杂的数据结构
0x15 写文件记录 写入文件记录 用于修改复杂的数据结构
0x16 屏蔽写寄存器 通过一个”与”掩码和一个“或”掩码修改寄存器的内容,而不改变未指定位 用于原子化地修改寄存器中的某些位
0x17 读/写多个寄存器 在一个请求中,同时执行读取和写入多个寄存器的操作 提高了读写交互的效率
  1. 功能码 0x07:读取异常状态
  • 请求报文:01 07 41 E2
  • 01:从站地址
  • 07:功能码
  • 无数据域
  • 响应报文:01 07 02 A3 F1
  • 02:异常状态字节(表示8个内部线圈的状态,具体含义由设备定义)
  • A3 F1:CRC
  1. 功能码 0x08:诊断(子功能0x0000:返回查询数据)
  • 请求报文:01 08 00 00 A5 37 55 57
  • 01:从站地址
  • 08:功能码
  • 00 00:子功能码(回路反馈)
  • A5 37:数据域(任意数据,此处为0xA537)
  • 响应报文:01 08 00 00 A5 37 55 57(回显请求数据)
  1. 功能码 0x0B:读取事件计数
  • 请求报文:01 0B 41 E7
  • 01:从站地址
  • 0B:功能码
  • 响应报文:01 0B 00 00 00 01 65 CB
  • 00 00:状态(0x0000表示成功)
  • 00 01:事件计数(已发生1个Modbus事件)
  • 65 CB:CRC
  1. 功能码 0x0C:读取通信事件记录
  • 请求报文:01 0C 00 25
  • 01:从站地址
  • 0C:功能码
  • 00 25:CRC
  • 响应报文:01 0C 06 00 00 00 01 00 02 00 03 35 86
  • 01:从站地址
  • 0C:功能码
  • 06:后续字节数(包括状态、事件计数和事件数据)
  • 00 00:状态
  • 00 01:事件计数(当前记录数)
  • 00 02:消息计数(从站已处理的Modbus消息数)
  • 00 03:事件数据(第一个事件)
  • 35 86:CRC
  1. 功能码 0x11:报告从机标识
  • 请求报文:01 11 C0 2C
  • 01:从站地址
  • 11:功能码
  • C0 2C:CRC
  • 响应报文:01 11 0C 01 02 03 48 65 6C 6C 6F 00 00 00 00 00 E1 2A
  • 01:从站地址
  • 11:功能码
  • 0C:后续字节数
  • 01:从机状态(0x01 = 正常运行)
  • 02:附加数据(设备类型标识)
  • 03:运行状态(0x03)
  • 48 65 6C 6C 6F:ASCII字符串“Hello”
  • 00 00 00 00 00:填充(根据设备定义)
  • E1 2A:CRC
  1. 功能码 0x14:读文件记录
  • 请求报文:01 14 0E 06 00 01 00 02 00 03 00 04 00 05 00 06 00 07 96 2F
  • 此报文包含子请求结构,较为复杂。简化示例:请求读取一个文件记录,子请求长度为0E(14> - 字节),子参考类型06(请求记录),文件号00 01,记录号00 02,记录长度00 03(3个寄> - 存器),然后重复另一个子请求(文件号00 04...)
  • 完整解析需参考Modbus规范,此处仅示意。
  • 响应报文:01 14 0C 07 06 00 01 00 02 00 03 00 04 00 05 00 06 45 23(略)
  1. 功能码 0x15:写文件记录
  • 请求报文:01 15 12 06 00 01 00 02 00 02 00 03 12 34 56 78 06 00 03 00 04 00 01 9A BC
  • 包含写入的数据,格式类似读文件记录。
  • 响应报文:01 15 0E 06 00 01 00 02 00 02 06 00 03 00 04 00 01 C3 D4(回显成功写入的文件号和记录号)
  1. 功能码 0x16:屏蔽写寄存器
  • 请求报文:01 16 00 02 00 F0 0F 0F CA 01
  • 01:从站地址
  • 16:功能码
  • 00 02:寄存器地址
  • 00 F0:与掩码(AND_MASK)
  • 0F 0F:或掩码(OR_MASK)
  • CA 01:CRC
  • 操作:新值 = (原值 & AND_MASK) | (OR_MASK & (~AND_MASK))
  • 响应报文:01 16 00 02 00 F0 0F 0F CA 01(回显请求)
  1. 功能码 0x17:读/写多个寄存器
  • 请求报文:01 17 00 01 00 02 00 03 00 02 04 11 22 33 44 CB 63
  • 01:从站地址
  • 17:功能码
  • 00 01:读起始地址
  • 00 02:读取2个寄存器
  • 00 03:写起始地址
  • 00 02:写入2个寄存器
  • 04:写入数据字节数(2×2)
  • 11 22:写入第一个寄存器的值
  • 33 44:写入第二个寄存器的值
  • CB 63:CRC
  • 响应报文:01 17 04 AA BB CC DD 3D 83
  • 04:读取数据字节数
  • AA BB:第一个读取寄存器的值
  • CC DD:第二个读取寄存器的值
  • 3D 83:CRC

2.5.异常响应

  当从机无法执行主机请求时(如不支持的功能码、地址非法、数据值错误等),它会返回一个异常响应帧,异常功能码:等于请求功能码 + 0x80例如,请求功能码为0x03,异常响应功能码则为0x83,常见的异常码含义如下:

异常码 名称 含义
0x01 非法功能码 从机不支持请求中的功能码。
0x02 非法数据地址 请求中的数据地址超出从机的有效地址范围。
0x03 非法数据值 请求中包含的数据值(如数量、写入值)不在允许范围内。
0x04 从机设备故障 从机在尝试执行请求操作时发生了不可恢复的错误。
0x05 确认 从机已接受请求并正在处理,但需要较长时间,以防止主机超时。
0x06 从机设备忙 从机正在处理一个长时间命令,无法接受新的请求,主机稍后可以重发。
  1. 异常响应示例(以功能码0x03为例)
  • 请求报文:01 03 00 6B 00 03 74 17(读寄存器,但地址超出范围)
  • 异常响应:01 83 02 C0 F1
  • 01:从站地址
  • 83:异常功能码(请求码0x03 + 0x80)
  • 02:异常码(0x02 = 非法数据地址)
  • C0 F1:CRC

2.6.CRC校验

   C语言版本示例方式一:

  • 初始值:0xFFFF(Modbus标准初始值)
  • 多项式:0xA001(即0x8005的反转形式)
  • 输出:直接返回计算得到的CRC值
/*crc modbus*/
uint16_t crc_modbus(const uint8_t* data, uint16_t len)
{
    uint16_t crc_value = 0xFFFF;
    
    while (len--) {
        crc_value ^= *data++;
        for (int i = 0; i < 8; i++) {
            crc_value = (crc_value & 1) ? (crc_value >> 1) ^ 0xA001 : (crc_value >> 1);
        }
    }
   // 直接返回,无需交换字节
    // 低字节在前:crc_value & 0xFF 先发送
    // 高字节在后:(crc_value >> 8) 后发送
    return crc_value;
}

   C语言版本示例方式二:

//预计算的 CRC-16 Modbus 查找表 (多项式 0x8005,反转后为 0xA001)
static const uint16_t crc_modbus_table[256] = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};

/**
 * @brief 计算 Modbus CRC-16 (查表法)
 * @param data 数据指针
 * @param len  数据长度
 * @return CRC-16 值
 */
uint16_t crc_modbus(const uint8_t* data, uint16_t len)
{
    uint16_t crc_value = 0xFFFF;

    while (len--) {
        crc_value = (crc_value >> 8) ^ crc_modbus_table[(crc_value ^ *data++) & 0xFF];
    }

    return crc_value;
}

3. Modbus TCP协议详解

3.1 报文格式

Modbus TCP报文 = MBAP Header(7字节) + PDU(功能码+数据)

MBAP Header详解

字段 长度 描述 示例 说明
事务标识符 2字节 请求编号 00 01 匹配请求与响应
协议标识符 2字节 固定为0 00 00 0代表Modbus/TCP
长度 2字节 后续字节数 00 06 长度表示从单元标识符开始的字节数
单元标识符 1字节 从站地址 01 用于网关场景

大端传输:所有多字节数据高位字节在前(如0x1234传输为0x12 0x34)。

功能码说明

类别 功能码 名称 最大数据量
位操作 0x01 读线圈 2000位
0x02 读离散输入 2000位
0x05 写单个线圈 1位
0x0F 写多个线圈 2000位
寄存器操作 0x03 读保持寄存器 125字节
0x04 读输入寄存器 125字节
0x06 写单个寄存器 1字节
0x10 写多个寄存器 123字节
高级功能 0x16 屏蔽写寄存器 1字节
0x17 读/写多个寄存器 读125字/写121字节
诊断 0x07 读取异常状态 1字节
0x08 诊断 可变
0x0B 读取事件计数 2字节
0x0C 读取通信事件记录 可变
0x11 报告从机标识 可变

3.2 位操作

所有示例中:事务标识00 01,协议标识00 00,从站地址01

功能码 0x01:读线圈

项目 报文内容
请求报文 00 01 00 00 00 06 01 01 00 09 00 05
解析 MBAP头 | 单元标识01 | 功能码01 | 起始地址00 09 | 数量00 05
响应报文 00 01 00 00 00 04 01 01 01 14
解析 MBAP头 | 功能码01 | 字节数01 | 数据14(线圈状态)

功能码 0x02:读离散输入

项目 报文内容
请求报文 00 01 00 00 00 06 01 02 00 00 00 12
响应报文 00 01 00 00 00 06 01 02 03 01 04 00
解析 字节数03 | 数据01 04 00(输入状态)

功能码 0x05:写单个线圈

项目 报文内容
请求报文 00 01 00 00 00 06 01 05 00 02 FF 00
响应报文 00 01 00 00 00 06 01 05 00 02 FF 00(回显)

功能码 0x0F:写多个线圈

项目 报文内容
请求报文 00 01 00 00 00 09 01 0F 00 13 00 0A 02 CD 00
响应报文 00 01 00 00 00 06 01 0F 00 13 00 0A

3.3 寄存器操作

功能码 0x03:读保持寄存器

项目 报文内容
请求报文 00 01 00 00 00 06 01 03 00 03 00 02
响应报文 00 01 00 00 00 07 01 03 04 00 05 00 06
解析 字节数04 | 寄存器值00 05(5)| 00 06(6)

功能码 0x04:读输入寄存器

项目 报文内容
请求报文 00 01 00 00 00 06 01 04 00 03 00 02
响应报文 00 01 00 00 00 07 01 04 04 00 0E 00 13
解析 字节数04 | 值00 0E(14)| 00 13(19)

功能码 0x06:写单个寄存器

项目 报文内容
请求报文 00 01 00 00 00 06 01 06 00 20 00 06
响应报文 00 01 00 00 00 06 01 06 00 20 00 06(回显)

功能码 0x10:写多个寄存器

项目 报文内容
请求报文 00 01 00 00 00 0B 01 10 00 03 00 02 04 04 D2 0D 80
解析 数据04 D2(1234)| 0D 80(3456)
响应报文 00 01 00 00 00 06 01 10 00 03 00 02

3.4 高级功能

功能码 0x16:屏蔽写寄存器

项目 报文内容
请求报文 00 04 00 00 00 08 01 16 00 00 00 0F 0F 00
解析 AND_MASK=0x000F,OR_MASK=0x0F00
响应报文 00 04 00 00 00 08 01 16 00 00 00 0F 0F 00(回显)

功能码 0x17:读/写多个寄存器

项目 报文内容
请求报文 00 01 00 00 00 0F 01 17 00 03 00 02 00 20 00 02 04 00 06 00 04
响应报文 00 01 00 00 00 07 01 17 04 04 D1 0D 7F

3.5 诊断功能

功能码 0x07:读取异常状态

项目 报文内容
请求报文 00 01 00 00 00 03 01 07
响应报文 00 01 00 00 00 04 01 07 01 02

功能码 0x08:诊断

项目 报文内容
请求报文 00 01 00 00 00 06 01 08 00 00 12 34
响应报文 00 01 00 00 00 06 01 08 00 00 12 34(回显)

功能码 0x11:报告从机标识

项目 报文内容
请求报文 00 01 00 00 00 02 01 11
响应报文 00 01 00 00 00 0A 01 11 07 FF 44 65 76 69 63 65
解析 长度00 0A(10字节)\| 单元标识01 \| 功能码11 \| 字节数07(7字节)\| 状态FF \| 标识44 65 76 69 63 65("Device",6字节)

3.6 异常处理

异常响应格式

当服务器无法执行请求时,返回异常响应帧:

  • TCP格式:MBAP头 + 功能码+0x80 + 异常码

异常码说明

异常码 名称 含义
01 (0x01) 非法功能码 服务器不支持请求中的功能码
02 (0x02) 非法数据地址 请求中的数据地址超出服务器的有效地址范围
03 (0x03) 非法数据值 请求中包含的数据值不在允许范围内
04 (0x04) 服务器设备故障 服务器执行操作时发生不可恢复的错误
05 (0x05) 确认 服务器已接受请求并正在处理,需要较长时间
06 (0x06) 服务器设备忙 服务器正在处理长时间命令,无法接受新请求

异常示例(TCP)

项目 报文内容
请求报文 00 01 00 00 00 06 01 03 00 6B 00 03
异常响应 00 01 00 00 00 03 01 83 02
解析 异常功能码83(03+80)| 异常码02(非法数据地址)

4. Modbus ASCII协议详解

  Modbus ASCII将所有数据转换为ASCII字符传输,报文文本可读,便于调试。

4.1 报文帧结构

字段 长度 描述
起始符 1字符 冒号 : (0x3A)
地址域 2字符 从站地址的ASCII十六进制表示
功能码 2字符 功能码的ASCII十六进制表示
数据域 N×2字符 数据的ASCII十六进制表示
LRC校验 2字符 纵向冗余校验的ASCII十六进制表示
结束符 2字符 回车 \r (0x0D) + 换行 \n (0x0A)

4.2 示例

读取保持寄存器(与RTU示例相同功能)

项目 报文内容
请求报文 :010300000003F9\r\n
解析 :起始符 | 01地址 | 03功能码 | 0000起始地址 | 0003数量 | F9LRC | \r\n结束

4.3 LRC计算

  LRC(Longitudinal Redundancy Check,纵向冗余校验)是对报文中地址、功能码和数据域的字节值进行累加后取补码。

uint8_t lrc(uint8_t *data, uint16_t len) {
    uint8_t sum = 0;
    for (uint16_t i = 0; i < len; i++) {
        sum += data[i];
    }
    return ((~sum) + 1) & 0xFF;  // 或者直接 return -sum
}

本文章使用limfx的vscode插件快速发布