Modbus 协议_1

6 功能码描述

本篇简要解读几个 MODBUS 应用协议规范 中的功能码和作用,更多详细内容参考协议手册。
百问网提供的全场景工业互联设备管理系统解决方案,其中有对 Modbus 详细的视频课程讲解和功能案例测试。

6.1 01 (0x01) 读取线圈 (Read Coils)

此功能码用于从远程设备读取 1 到 2000 个连续的线圈状态。请求 PDU(协议数据单元)指定起始地址,即第一个指定线圈的地址,以及线圈的数量。在 PDU 中,线圈从零开始寻址。因此,编号为 1 到 16 的线圈在 PDU 中的寻址方式为 0 到 15。

在响应消息中,线圈按每个位存储在数据字段中,每个线圈占用一个位。状态以 1 表示开启 (ON),以 0 表示关闭 (OFF)。第一个数据字节的最低有效位 (LSB) 包含查询中指定的输出状态。其他线圈按字节顺序存储,先从该字节的高位开始,接着按顺序存储在后续字节中。

如果返回的输出数量不是 8 的倍数,最终数据字节中的剩余位将用零填充(填充到字节的高位)。字节计数字段指定完整数据字节的数量。


功能码01 是 Modbus 协议中最常用的功能之一,用于读取远程设备的线圈状态。在 Modbus 中,线圈是表示离散(开关)状态的寄存器,每个线圈要么为“开”(ON) 状态(表示为1),要么为“关”(OFF) 状态(表示为0)。此功能码的应用场景广泛,尤其是在自动化系统中用来读取开关设备的状态。

  1. 起始地址和数量:请求数据包(PDU)中需要提供起始地址和线圈数量。起始地址是第一个线圈的编号,Modbus 协议中从 0 开始寻址,即线圈1的地址是0,线圈16的地址是15。

  2. 响应格式:响应消息包含了请求的线圈状态。每个线圈的状态用一个位来表示,多个线圈的状态会被打包成连续的位。例如,如果请求的是16个线圈的状态,则这16个状态会按位存储在数据字段中。如果状态的数量不是8的倍数,最后一个字节的剩余位将被用零填充。

  3. 字节计数:字节计数字段表示数据中包含的完整字节数量,它可以帮助接收方知道接下来的数据长度,并能正确解析数据。

这种方法使得 Modbus 协议能够高效地传输多个线圈的状态信息,而无需为每个线圈使用单独的字节或数据包。

示例一:读取离散输出 20-38 的请求示例

输出 20 到 38 的状态在响应消息中如下所示:

输出 27 到 20 的状态作为字节值 CD(十六进制)或 1100 1101(二进制)展示。在这个字节中,输出 27 是该字节的最高有效位 (MSB),而输出 20 是最低有效位 (LSB)。

根据约定,字节中的位从左到右表示从 MSB 到 LSB。因此,响应中的第一个字节表示的是输出 27 到 20 的状态。接下来的字节则表示输出 35 到 28 的状态,依此类推。由于 Modbus 协议是串行传输的,位从 LSB 到 MSB 依次流动,即 20 到 27,28 到 35,等等。

在最后一个数据字节中,输出 38 到 36 的状态由字节值 05(十六进制)或 0000 0101(二进制)表示。输出 38 位于该字节从左数的第六个位,而输出 36 位于字节的 LSB 位置。剩余的五个位被填充为零。


根据示例一的图展示了一个 Modbus 请求和响应的数据格式示例。

  1. 请求(Request)部分:

    • Function(功能码): 01(十六进制)。这是请求中指定的功能码,表示请求读取远程设备的线圈状态。
    • Starting Address Hi(起始地址高位): 00(十六进制)。这是起始地址的高字节部分。起始地址用于指示从哪个线圈开始读取。在 Modbus 协议中,地址是从 0 开始的。因此,00 表示第一个线圈(在本例中是第 20 个输出线圈的地址)。
    • Starting Address Lo(起始地址低位): 13(十六进制)。这是起始地址的低字节部分。00 13(十六进制)表示线圈的起始地址为 0x13,即十进制的 19。因此,从第 20 号线圈开始读取(因为地址从零开始计数,并且地址是16位)。
    • Quantity of Outputs Hi(输出数量高位): 00(十六进制)。这是请求中要读取的线圈数量的高字节部分。
    • Quantity of Outputs Lo(输出数量低位): 13(十六进制)。这是请求中要读取的线圈数量的低字节部分。00 13 表示需要读取的线圈数量为 19(0x13)。
  2. 响应(Response)部分:

    • Function(功能码): 01(十六进制)。这是响应消息的功能码,它与请求中的功能码一致,表示这是对读取线圈状态请求的响应。
    • Byte Count(字节计数): 03(十六进制)。这是响应消息中数据的字节数。03(十六进制)表示数据部分占 3 个字节。
    • Outputs status 27-20(输出状态 27-20): CD(十六进制),即二进制的 1100 1101。这表示第 27 到 20 号输出的状态。根据之前的解读,字节 CD 的二进制表示就是 27 到 20 号输出的状态,其中每个输出状态占据一个位。具体来说,输出 27 是最高位,输出 20 是最低位,依次类推。
    • Outputs status 35-28(输出状态 35-28): 6B(十六进制),即二进制的 0110 1011。这表示第 35 到 28 号输出的状态。与上面的字节一样,这些状态也通过按位的方式存储。
    • Outputs status 38-36(输出状态 38-36): 05(十六进制),即二进制的 0000 0101。这表示第 38 到 36 号输出的状态。字节中的剩余位(高位)被填充为零。

  1. 起始地址与数量:请求的起始地址为 0x13(十进制 19),表示从第 20 个输出线圈开始读取。请求的数据长度是 19 个线圈(0x13),因此需要读取多个字节来存储这些线圈的状态。

  2. 响应中的数据打包:响应中提供了 3 个字节的数据:

    • 第一个字节 (CD) 包含输出 27 到 20 的状态。
    • 第二个字节 (6B) 包含输出 35 到 28 的状态。
    • 第三个字节 (05) 包含输出 38 到 36 的状态。
  3. 状态编码:每个输出的状态由一个位表示,1 表示开启(ON),0 表示关闭(OFF)。字节中的每个位对应一个输出的状态。

  4. 按位传输:如同之前提到的,Modbus 协议中,线圈状态从最低有效位(LSB)到最高有效位(MSB)依次传输。例如,输出 20 是第一个位,输出 27 是最后一个位。

  5. 填充和字节计数:如果输出数量不是 8 的倍数,剩余位会填充为零,并且字节计数(Byte Count)用于指示响应消息中的数据字节数。在本例中,字节计数为 3,表示响应中有 3 个字节数据。


Note: 读取19个位,至少3个字节,剩余的五个位(朝向高位)补零。

如上图所示是 Modbus 协议中读取线圈(Read Coils)请求的状态流程图。它展示了从 Modbus 服务器接收到请求数据单元(mb_req_pdu)后,根据不同条件判断和处理的逻辑流。

  1. ENTRY(入口)
    流程从 Modbus 服务器接收到请求数据单元(mb_req_pdu)开始。此时,服务器收到的请求包含了一个功能码、起始地址、输出数量等信息。

  2. Function code supported(功能码支持)

    • 服务器首先会检查请求中指定的功能码(Function code)是否被支持。这里检查的是功能码是否为“读取线圈状态”(Function code 01)。
    • YES:如果功能码是支持的,流程继续。
    • NO:如果功能码不支持,服务器会设置异常码(ExceptionCode)为 01(非法功能码),并发送异常响应(mb_exception_rsp)。
  3. Quantity of Outputs Check(输出数量检查)

    • 服务器接着检查请求中指定的输出数量(Quantity of Outputs)是否在允许的范围内,要求数量在 0x0001(最小值)到 0x07D0(最大值)之间。
    • YES:如果数量符合要求,继续检查。
    • NO:如果数量不在有效范围内,服务器会设置异常码为 03(无效数据值),并发送异常响应。
  4. Starting Address Check(起始地址检查)

    • 服务器检查请求中的起始地址是否有效,并且起始地址与输出数量的总和是否超出了设备的可用地址范围。
    • YES:如果起始地址有效且范围内,继续处理请求。
    • NO:如果起始地址无效,或者起始地址和输出数量的总和超出了设备的可用地址范围,服务器会设置异常码为 02(非法地址),并发送异常响应。
  5. Request Processing(请求处理)

    • 如果以上检查都通过,服务器开始处理读取线圈状态的请求,即执行读取操作(ReadDiscreteOutputs)。
    • YES:如果读取操作成功,服务器继续处理,并最终生成响应(mb_rsp),然后发送给客户端。
    • NO:如果读取操作失败,服务器设置异常码为 04(从设备故障),并发送异常响应。
  6. Exit(退出)

    • 服务器完成处理后,退出流程。

这个流程图是 Modbus 读取线圈状态请求的一个完整流程,目的是确保每个请求在服务器端得到有效处理,并根据实际情况进行相应的异常处理:

  1. 功能码检查:首先,服务器需要验证客户端请求中的功能码是否是合法的(功能码 01 表示读取线圈状态)。如果不合法,服务器将返回异常码 01,表示非法功能码。

  2. 输出数量检查:Modbus 协议规定了每次读取的输出数量必须在一个有效的范围内。在本例中,要求数量在 12000(十六进制 0x07D0)之间。如果请求的数量不在此范围内,服务器将返回异常码 03,表示数据值无效。

  3. 起始地址检查:服务器接着会检查请求中指定的起始地址是否在有效范围内。如果请求的起始地址超出设备的地址空间,或者起始地址与输出数量的总和超出了设备的地址空间,服务器将返回异常码 02,表示非法地址。

  4. 读取操作:如果所有的验证都通过,服务器就会执行读取操作,即读取指定范围内的线圈状态。如果读取操作成功,服务器生成响应并发送给客户端。如果读取失败,则返回异常码 04,表示从设备故障。

  5. 异常处理:在任何步骤中,如果遇到问题,服务器都会设置相应的异常码并返回给客户端。这是 Modbus 协议的标准行为,用于确保客户端可以识别并处理错误。


6.6 06 (0x06) 写单个寄存器 (Write Single Register)

此功能码用于在远程设备中写入单个保持寄存器(Holding Register)。请求 PDU(协议数据单元)中指定了要写入的寄存器地址。寄存器的地址从零开始,因此寄存器编号为 1 的寄存器在请求中表示为 0。

正常的响应是请求的回显,即在寄存器内容被写入后,返回请求的数据。


功能码 06 是 Modbus 协议中用于写入单个寄存器的命令。这个命令主要用于修改设备的保持寄存器内容。保持寄存器(Holding Registers)是 Modbus 中常见的寄存器类型,用于存储数据(如设定参数或数值)。

  1. 请求格式:请求中需要提供寄存器的地址(从 0 开始寻址)和要写入的数据。举个例子,如果要写入编号为 1 的寄存器,地址在请求中会表示为 0。这样通过指定地址来告诉远程设备需要修改哪个寄存器的值。

  2. 响应格式:在写操作完成后,设备会返回一个响应,响应内容通常是请求内容的回显,即返回的 PDU 会与请求中的 PDU 完全相同。这样可以确保设备已经成功接收并执行了请求。

  3. 应用场景:此功能码广泛应用于修改设备的配置参数、写入数值等操作。例如,PLC 或传感器中的某些参数可能需要通过 Modbus 协议进行设置或调整,这时就会用到功能码 06。

注意:尽管该功能码用于写入单个寄存器,但它的执行依然是基于请求和响应机制的,确保了数据的一致性和可靠性。


如上图展示了使用 Modbus 功能码 06 (写单个寄存器)的请求和响应数据格式。具体来说,这是一个写入寄存器的示例,请求将值 0x03 写入寄存器地址 2

  1. 请求(Request)部分:

    • 功能码 (Function)

      • 十六进制值为 06,表示这是一个“写单个寄存器”操作。功能码 06 是用于写入单个寄存器的标准命令。
    • 寄存器地址高位 (Register Address Hi)

      • 十六进制值为 00。这是寄存器地址的高字节。寄存器地址 2 在 Modbus 中是从 0 开始寻址的,所以寄存器地址 2 实际上是 0x02。因此,地址 2 用两个字节表示,第一个字节是高位字节(00),第二个字节是低位字节(01)。
    • 寄存器地址低位 (Register Address Lo)

      • 十六进制值为 01。这是寄存器地址的低字节,表示地址 2 的低字节部分。0x02 对应于 00 01(高位字节和低位字节的组合)。
    • 寄存器值高位 (Register Value Hi)

      • 十六进制值为 00。这是要写入寄存器的值的高字节。在本示例中,要写入的值是 0x03,因此其高位是 00,表示没有更高的字节。
    • 寄存器值低位 (Register Value Lo)

      • 十六进制值为 03。这是要写入寄存器的值的低字节。要写入的值是 0x03,因此其低位是 03
  2. 响应(Response)部分:

    • 功能码 (Function)

      • 十六进制值为 06,表示这是对请求的正常响应,确认执行了写入操作。
    • 寄存器地址高位 (Register Address Hi)

      • 十六进制值为 00。这与请求中的寄存器地址高位一致,表示写入的是寄存器地址 2
    • 寄存器地址低位 (Register Address Lo)

      • 十六进制值为 01。同样,这与请求中的寄存器地址低位一致,确保客户端知道写入的寄存器地址是 2
    • 寄存器值高位 (Register Value Hi)

      • 十六进制值为 00。这与请求中的寄存器值高位一致,表示成功写入的寄存器值是 0x03
    • 寄存器值低位 (Register Value Lo)

      • 十六进制值为 03。这与请求中的寄存器值低位一致,确保客户端知道写入的寄存器值是 0x03

Modbus 功能码 06 的请求与响应过程中,客户端向远程设备请求写入指定寄存器的值。响应通常是请求的回显,确保请求已经正确处理。通过该图可以看出,寄存器地址是通过两个字节(高位字节和低位字节)来表示的,而写入的值也通过两个字节表示,其中高位字节通常为 00,低位字节表示实际的数值(例如,0x03)。

这确保了请求和响应数据结构的对称性,使得服务器能够确认写入操作成功执行,同时也帮助客户端确认写入数据的准确性。


  1. 入口(ENTRY)

    • 服务器首先接收来自客户端的请求数据单元(mb_req_pdu),即一个 Modbus 请求。此请求包含了功能码、寄存器地址以及要写入的寄存器值等数据。
  2. 功能码支持检查(Function code supported)

    • 服务器首先检查请求中的功能码是否被支持。如果功能码是 06(表示“写单个寄存器”),则继续执行。如果功能码不支持,服务器会返回一个异常响应,设置 ExceptionCode = 01,并退出流程。
  3. 寄存器值范围检查(0x0000 ≤ Register Value ≤ 0xFFFF)

    • 在功能码检查通过后,服务器会验证要写入的寄存器值是否在合法范围内。对于 Modbus 协议,寄存器值应该在 0x00000xFFFF 之间(即 16 位数据)。如果值超出这个范围,服务器返回 ExceptionCode = 03,表示无效数据值,并发送异常响应。
  4. 寄存器地址有效性检查(Register Address == OK)

    • 接下来,服务器会检查请求中提供的寄存器地址是否有效。寄存器地址必须在设备的有效地址范围内。如果地址无效,服务器返回 ExceptionCode = 02,表示非法地址,并发送异常响应。
  5. 请求处理(Request Processing)

    • 如果以上检查都通过,服务器开始处理请求,执行 写入单个寄存器 操作。如果此操作成功完成,服务器进入下一步;如果操作失败,服务器会返回 ExceptionCode = 04,表示从设备故障,并发送异常响应。
  6. 写操作成功(WriteSingleRegister == OK)

    • 如果写入操作成功,服务器会返回一个正常的响应,确认寄存器已经被写入。如果写操作失败,服务器返回设备故障异常。
  7. 响应发送(MB Server Sends mb_rsp)

    • 如果所有操作成功完成,服务器将发送响应数据单元(mb_rsp),告知客户端写入操作已经成功执行。响应数据包括功能码、寄存器地址和已写入的值。
  8. 退出(EXIT)

    • 完成所有操作后,流程结束,退出状态图。

该状态图概述了 Modbus 服务器在执行 "写单个寄存器" 操作时的处理逻辑:

  1. 检查功能码:确认请求中的功能码是否为 06,即写单个寄存器。
  2. 验证寄存器值范围:检查要写入的寄存器值是否在合法的范围内,确保数据不会超出规定的 16 位范围。
  3. 验证寄存器地址:检查请求中指定的寄存器地址是否有效,确保不会访问设备中不存在的寄存器。
  4. 执行写操作:如果所有检查通过,执行写入操作,并返回成功响应;如果遇到任何错误,返回相应的异常码。

通过该流程,Modbus 服务器确保在接收到写入请求时,只有合法和有效的操作才能成功执行,其他无效或错误的请求将触发异常响应。这提高了系统的可靠性和稳定性,确保数据传输的准确性。


MODBUS 异常响应

当客户端设备向服务器设备发送请求时,客户端期望收到正常的响应。根据客户端查询的不同情况,可能会发生以下四种情况:

  • 如果服务器设备收到请求且没有通信错误,并且能够正常处理查询,它将返回一个正常的响应。
  • 如果由于通信错误,服务器未能接收到请求,则不会返回任何响应。客户端程序最终会处理请求的超时条件。
  • 如果服务器收到请求,但检测到通信错误(如奇偶校验错误、LRC 错误、CRC 错误等),则不会返回任何响应。客户端程序最终会处理请求的超时条件。
  • 如果服务器接收到请求且没有通信错误,但无法处理该请求(例如,请求读取一个不存在的输出或寄存器),服务器将返回一个异常响应,告知客户端错误的具体情况。

异常响应消息与正常响应有两个显著的区别:

  1. 功能码字段:在正常响应中,服务器会将原始请求中的功能码回显到响应的功能码字段中。所有功能码的最高有效位(MSB,Most Significant Bit)为0(它们的值都低于80十六进制)。在异常响应中,服务器将功能码的最高有效位设置为1。这使得异常响应中的功能码值比正常响应的功能码值大80十六进制(即128十进制)。

    当功能码的最高有效位被设置为1时,客户端应用程序可以识别这是一个异常响应,并可以检查数据字段以获取异常代码。

  2. 数据字段:在正常响应中,服务器可能会在数据字段中返回数据或统计信息(即请求中所要求的内容)。但在异常响应中,服务器返回一个异常代码(exception code)到数据字段中。这个异常代码定义了导致异常的服务器状态或问题。


  1. 正常响应:这是最理想的情况,表示请求无误,服务器能够成功响应。
  2. 通信错误无响应:如果请求未成功发送或接收,客户端会经历超时错误,表明服务器没有收到请求或无法响应。
  3. 通信错误处理:如果请求虽然收到但出现了错误,像数据传输错误(例如 CRC 错误),则客户端也会遇到超时,这表明通信发生了问题。
  4. 异常响应:如果请求本身存在问题(如请求了不存在的寄存器),则服务器会返回异常响应,具体说明错误类型。异常响应通常包含一个功能码和错误代码,用于标识错误类型,常见的错误代码包括:
    • 0x01:非法功能
    • 0x02:非法数据地址
    • 0x03:非法数据值
    • 0x04:从设备故障

MODBUS 协议通过功能码字段和数据字段的变化来区分正常响应和异常响应。具体来说:

  • 功能码字段的变化:正常响应的功能码的最高有效位为0,而异常响应的功能码的最高有效位为1。这样,客户端应用程序可以通过检查功能码的变化来识别异常响应。正常响应和异常响应的功能码值相差80十六进制(128十进制),通过这一特性,客户端能够区分请求是成功还是出现了错误。

  • 数据字段的内容:正常响应中的数据字段包含的是客户端请求的数据或其他统计信息,而异常响应中的数据字段则包含一个异常代码,用以指示错误的类型或原因。常见的异常代码包括:

    • 0x01:非法功能(非法的功能码)
    • 0x02:非法数据地址(请求的地址不正确)
    • 0x03:非法数据值(请求的值不符合预期)
    • 0x04:从设备故障(设备故障或不可恢复的错误)

通过这两个字段,MODBUS 协议确保了客户端和服务器之间的通信能够有效识别和处理异常情况,提供了错误诊断和恢复机制。这种机制能够帮助开发者在实际应用中迅速识别问题并采取相应的解决措施。


客户端请求和服务器异常响应的示例

在此示例中,客户端将请求发送到服务器设备。功能代码(01)用于读取输出状态操作。它请求地址1185(04A1十六进制)处的输出状态。注意,仅读取一个输出,如输出数量字段(0001)所指定。如果输出地址在服务器设备中不存在,服务器将返回异常响应,并显示异常代码(02)。这指定了服务器的非法数据地址。


示例图展示了 MODBUS 请求与响应的对比,尤其是异常响应的示例。通过这张表格,您可以清晰地看到在请求和响应中的字段是如何变化的,尤其是在出现异常时。

  1. 请求部分:

    • Function (功能码): 请求中的功能码是 0x01,这是常见的功能码之一,通常用于读取离散输出(Coils)。
    • Starting Address Hi (起始地址高字节): 0x04,表示请求的起始地址的高字节。
    • Starting Address Lo (起始地址低字节): 0xA1,表示请求的起始地址的低字节,合起来表示具体的地址值 0x04A1
    • Quantity of Outputs Hi (输出数量高字节): 0x00,表示请求的输出数量的高字节。
    • Quantity of Outputs Lo (输出数量低字节): 0x01,表示请求的输出数量的低字节,表示请求读取1个输出。
  2. 响应部分:

    • Function (功能码): 在正常情况下,服务器会返回请求中相同的功能码 0x01。但在异常响应中,服务器将功能码的最高有效位(MSB)设置为1,导致响应的功能码变为 0x81(即请求功能码加上 0x80,表示发生了异常)。这使得客户端能够通过检查功能码来识别响应是否为异常响应。
    • Exception Code (异常代码): 服务器返回一个异常代码,表明导致异常的原因。在这个例子中,异常代码是 0x02,这表示 "非法数据地址"(Illegal Data Address)。这意味着客户端请求的地址(在请求中的 Starting Address Hi/Lo0x04A1)是无效的,可能超出了服务器可以访问的地址范围。

请求的正常响应

客户端向服务器发送一个读取离散输出的请求,地址 0x04A1,并请求读取1个输出。

异常响应

由于某种原因,服务器无法处理该请求,可能是因为地址 0x04A1 不存在或超出了设备的有效范围,导致服务器返回异常响应。异常响应中的功能码为 0x81,表明这是一个异常响应,而异常代码 0x02 表示 "非法数据地址",即客户端请求的地址无效。

  • 功能码的变化:在异常响应中,功能码的最高有效位(MSB)被设置为1,导致异常响应的功能码为正常响应功能码的 0x80 值增量。
  • 异常代码:数据字段中包含异常代码,指示异常的具体原因,常见的异常代码包括:
    • 0x01:非法功能
    • 0x02:非法数据地址
    • 0x03:非法数据值
    • 0x04:从设备故障