【个人经验分享】简易Modbus RTU主机库 - 代码及技术分享

简易Modbus RTU 主机库

1. Modbus 协议介绍

  • • Modbus 是一种串行通信协议,主要用于工业化自动设备(PLC、传感器、仪器仪表)之间的数据交换。Modbus 采样主从架构(主机问询,从机回答)。并且支持多种物理层(RS485、RS232、UART、TCP/IP)等。

  • • 本篇文章主要介绍Modbus 的通信过程,下方代码库也比较简易(但是易用,比较好移植。也在实际的生产环境部署),后续会分享完整的 Modbus RTU 主机和从机库。

  • • 如果对Modbus 协议比较熟悉,建议直接观看第二章节。

1.1 Modbus 主要的协议类型

  • • Modbus 主要有以下几种通信类型,使用于不同的通信方式
类型 传输方式 典型应用场景 特点
Modbus RTU 串行(RS-485/RS-232) 工业现场设备(PLC、传感器) 二进制编码,效率高
Modbus ASCII 串行(文本格式) 用的比较少 文本格式(人类易读)
Modbus TCP 以太网(TCP/IP) 工业物联网(IIoT)、SCADA 基于以太网,无CRC校验(由TCP/IP层处理)
Modbus Plus 专有网络(高速) Modicon 专用设备

1.2 Modbus 通信模型

  1. 1. 主从架构

    • 主站(Master):发起请求(如PLC、单片机等)。

    • 从站(Slave):响应请求(如传感器、执行器),每个从站有唯一地址(1~247)。

  2. 2. 数据模型

    数据类型 功能码 读写权限 典型用途 特点
    线圈(Coils) 0x01(读)/0x05(写)/0x0F(写多个线圈) 读/写 控制继电器、LED灯 按位读取(0/1)
    离散输入(Discrete Inputs) 0x02 只读 读取开关状态 按位读取(0/1)
    保持寄存器(Holding Registers) 0x03(读)/0x06(写单个)/0x10(写多个) 读/写 存储设备参数(如温度设定值) 16位(0~65535),用于数值或者浮点数或者
    输入寄存器(Input Registers) 0x04 只读 读取传感器数据(如温度值) 16位(0~65535),用于数值或者浮点数或者
  1. Modbus 报文格式

    • Modbus RTU报文

      • • Modbus RTU 由 设备地址功能码数据CRC校验组成

        字段 字节数 说明
        设备地址 1 从站地址(1~247)
        功能码 1 操作类型(01~10, 0F, 10)
        数据域 N 请求/响应的数据
        CRC校验 2 CRC16-Modbus校验,校验范围[设备地址][功能码][数据域](低字节在前)
  • 报文示例

    1. 1. 0x01 - 读取线圈状态

      读取多个线圈(开关量)的 ON/OFF 状态。
      [设备地址][01][起始地址高字节][起始地址低字节][线圈数量高字节][线圈数量低字节][CRC]
      
      请求:01 01 00 00 00 0A 3D C6
      
      01:从站地址 1
      01:功能码 0x01(读线圈)
      00 00:起始地址 0
      00 0A:读取 10 个线圈
      3D C6:CRC 校验
      
      响应:01 01 02 CD 01 90 08
      01:从站地址 1
      01:功能码 0x01
      02:返回字节数(2 字节)
      CD 01:线圈状态(二进制 11001101 00000001)
      线圈 0~7:ON, ON, OFF, OFF, ON, ON, OFF, ON
      线圈 8~9:ON, OFF
      90 08:CRC 校验
      
      
    2. 2. 0x02 - 读取离散输入

      读取多个离散输入(只读开关量)的状态
      [设备地址][02][起始地址高字节][起始地址低字节][输入数量高字节][输入数量低字节][CRC]
      
      请求:01 02 00 00 00 08 79 C6
      01:从站地址 1
      02:功能码 0x02(读离散输入)
      00 00:起始地址 0
      00 08:读取 8 个输入
      79 C6:CRC 校验
      
      响应:01 02 01 55 91 8B
      01:从站地址 1
      02:功能码 0x02
      01:返回字节数(1 字节)
      55:输入状态(二进制 01010101)
      输入 0~7:OFF, ON, OFF, ON, OFF, ON, OFF, ON
      91 8B:CRC 校验
      
      
    3. 3. 0x03 - 读取保持寄存器

      读取多个保持寄存器的值(16位整数)。
      [设备地址][03][起始地址高字节][起始地址低字节][寄存器数量高字节][寄存器数量低字节][CRC]
      
      01 03 00 6B 00 03 76 87
      01:从站地址 1
      03:功能码 0x03(读保持寄存器)
      00 6B:起始地址 107(0x006B)
      00 03:读取 3 个寄存器
      76 87:CRC 校验
      
      01 03 06 00 0A 01 2C 00 64 45 6F
      01:从站地址 1
      03:功能码 0x03
      06:返回字节数(6 字节)
      00 0A:寄存器 107 = 10(0x000A)
      01 2C:寄存器 108 = 300(0x012C)
      00 64:寄存器 109 = 100(0x0064)
      45 6F:CRC 校验
      
    4. 4. 0x04 - 读取输入寄存器

      读取多个输入寄存器(只读模拟量)的值
      
      [设备地址][04][起始地址高字节][起始地址低字节][寄存器数量高字节][寄存器数量低字节][CRC]
      
      01 04 00 00 00 02 F1 C9
      
      01:从站地址 1
      04:功能码 0x04(读输入寄存器)
      00 00:起始地址 0
      00 02:读取 2 个寄存器
      F1 C9:CRC 校验
      
      01 04 04 00 64 FF FF B8 3A
      
      01:从站地址 1
      04:功能码 0x04
      04:返回字节数(4 字节)
      00 64:寄存器 0 = 100(0x0064)
      FF FF:寄存器 1 = 65535(0xFFFF)
      B8 3A:CRC 校验
      
      
    5. 5. 0x05 - 写单个线圈

      强制单个线圈 ON/OFF
      [设备地址][05][线圈地址高字节][线圈地址低字节][强制值高字节][强制值低字节][CRC]
      
      01 05 00 13 FF 00 7E 7C
      
      01:从站地址 1
      05:功能码 0x05(写单个线圈)
      00 13:线圈地址 19(0x0013)
      FF 00:强制 ON(0xFF00=ON,0x0000=OFF)
      7E 7C:CRC 校验
      
      01 05 00 13 FF 00 7E 7C(回显相同数据)
      
      
    6. 6. 0x06 - 写单个寄存器

      修改单个保持寄存器的值
      [设备地址][06][寄存器地址高字节][寄存器地址低字节][数据高字节][数据低字节][CRC]
      
      01 06 00 6B 01 23 76 87
      
      01:从站地址 1
      06:功能码 0x06(写单个寄存器)
      00 6B:寄存器地址 107(0x006B)
      01 23:写入值 291(0x0123)
      76 87:CRC 校验
      
      01 06 00 6B 01 23 76 87(回显相同数据)
      
    7. 7. 0x0F - 写多个线圈

      批量写入多个线圈状态
      [设备地址][0F][起始地址高字节][起始地址低字节][线圈数量高字节][线圈数量低字节][字节数][数据字节...][CRC]
      
      01 0F 00 00 00 0A 02 CD 01 90 08
      
      01:从站地址 1
      0F:功能码 0x0F(写多个线圈)
      00 00:起始地址 0
      00 0A:写入 10 个线圈
      02:数据字节数(2 字节)
      CD 01:线圈状态(11001101 00000001)
      线圈 0~7:ON, ON, OFF, OFF, ON, ON, OFF, ON
      线圈 8~9:ON, OFF
      90 08:CRC 校验
      
      01 0F 00 00 00 0A 12 98
      
      01:从站地址 1
      0F:功能码 0x0F
      00 00:起始地址 0
      00 0A:写入 10 个线圈
      12 98:CRC 校验
      
    8. 8. 0x10 - 写多个寄存器(Write Multiple Registers)

      批量写入多个保持寄存器的值
      
      [设备地址][10][起始地址高字节][起始地址低字节][寄存器数量高字节][寄存器数量低字节][字节数][数据字节...][CRC]
      
      01 10 00 00 00 03 06 00 0A 01 2C 00 64 45 6F
      
      01:从站地址 1
      10:功能码 0x10(写多个寄存器)
      00 00:起始地址 0
      00 03:写入 3 个寄存器
      06:数据字节数(6 字节)
      00 0A:寄存器 0 = 10(0x000A)
      01 2C:寄存器 1 = 300(0x012C)
      00 64:寄存器 2 = 100(0x0064)
      45 6F:CRC 校验
      
      01 10 00 00 00 03 12 98
      
      01:从站地址 1
      10:功能码 0x10
      00 00:起始地址 0
      00 03:写入 3 个寄存器
      12 98:CRC 校验
      
  • Modbus TCP 报文(TCP 舍弃了CRC校验数据)

    • Modbus TCP 由 事务ID 、协议ID 、长度 、设备地址、 功能码和数据 组成

      字段 字节数 说明 示例值
      事务ID 2 主站生成的唯一标识 0x0001
      协议ID 2 固定0x0000(Modbus TCP) 0x0000
      长度 2 后续字段的总字节数(设备地址+功能码+数据) 0x0006
      设备地址 1 从站地址(原RTU地址,TCP中可能由IP替代) 0x01
      功能码 1 操作类型(如0x03读寄存器) 0x03
      数据 N 请求/响应的具体数据 可变
    • • 常见报文示例

      1. 1. 读取保持寄存器(功能码0x03)

        [0x0001][0x0000][0x0006][0x01][0x03][0x00][0x6B][0x00][0x03]
        
        事务ID: 0x0001(主站首次请求)
        协议ID: 0x0000(标准Modbus TCP)
        长度: 0x0006(设备地址1字节 + 功能码1字节 + 数据4字节)
        设备地址: 0x01(从站1)
        功能码: 0x03(读保持寄存器)
        数据: [0x00][0x6B](起始地址107),[0x00][0x03](读取3个寄存器)
        
        [0x0001][0x0000][0x0009][0x01][0x03][0x06][0x02][0x2B][0x00][0x64]
        
        事务ID: 0x0001(与请求匹配)
        数据: [0x02][0x2B](寄存器107值:555),[0x00][0x64](寄存器108值:100)
        
        
  • Modbus ASCII 报文

    • • Modbus ASCII 报文由 起始符、地址、功能码、数据、LRC 校验、结束符 组成

      字段 字符数 说明 示例(ASCII 字符)
      起始符 1 固定 :(冒号) :
      地址 2 从站地址(2 个十六进制字符) 01
      功能码 2 操作类型(2 个十六进制字符) 03
      数据 N 可变长度(十六进制字符对) 006B0003
      LRC 校验 2 纵向冗余校验(2 个十六进制字符) 76
      结束符 2 固定 \\r\\n(回车 + 换行) \\r\\n
  • 常用的报文示例

    1. 1. 读取线圈 (0x01)

      :010100130003A5\r\n
      
      01 → 从站地址 1
      01 → 功能码 1(读线圈)
      00130003 → 起始地址 19(0x0013),读取 3 个线圈
      A5 → LRC 校验
      
      :01010102DF\r\n
      01 → 从站地址 1
      01 → 功能码 1
      01 → 返回字节数(1 字节)
      02 → 线圈状态(二进制 00000010,表示线圈 20=ON,19=OFF)
      DF → LRC 校验
      
    2. 2. 写入单个寄存器(功能码 0x06)

      :0106006B0123A1\r\n
      
      01 → 从站地址 1
      06 → 功能码 6(写单个寄存器)
      006B → 寄存器地址 107
      0123 → 写入值 291(0x0123)
      A1 → LRC 校验
      
      :0106006B0123A1\r\n 与回复相同
      
  1. 4. Modbus 错误码

    • • Modbus 协议在通信过程中可能会返回 异常响应,用于指示请求失败的原因。异常响应的格式如下

      [设备地址][功能码 + 0x80][错误码][CRC]
      
      功能码 + 0x80:表示异常响应(如 0x03 → 0x83)。
      
      错误码:1 字节,表示具体的错误类型。
      
    • • 常用的错误码

      错误码(Hex) 名称 说明
      01 Illegal Function 从站不支持请求的功能码(如设备不支持 0x05 写线圈)。
      02 Illegal Data Address 请求的寄存器/线圈地址无效(如超出设备支持的范围)。
      03 Illegal Data Value 请求的数据值非法(如写入的寄存器值超出允许范围)。
      04 Slave Device Failure 从站设备执行请求时发生内部错误(如硬件故障)。
      05 Acknowledge 从站已接收请求,但需要较长时间处理(通常用于编程命令)。
      06 Slave Device Busy 从站正忙,无法处理当前请求(需重试)。
      08 Memory Parity Error 从站检测到存储器奇偶校验错误(罕见,通常需硬件修复)。
      0A Gateway Path Unavailable 网关路径不可用(仅适用于 Modbus 网关设备)。
      0B Gateway Target Failed 网关目标设备无响应(仅适用于 Modbus 网关设备)。

2. Modbus RTU 主机编程思路

  • • 根据上面的 Modbus RTU主机的请求响应,我们可以写一个简单的 发送请求和读取响应的代码。

    1. 1. 首先我需要一个 CRC16-Modbus 的校验代码

      static const uint8_t s_CRCHi[] = {
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
          0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
          0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
          0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
          0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
          0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
          0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
          0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
          0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
          0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
          0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
          0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
          0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
          0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
          0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
      } ;
      // CRC 低位字节值表
      const uint8_t s_CRCLo[] = {
          0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
          0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
          0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
          0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
          0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
          0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
          0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
          0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
          0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
          0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
          0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
          0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
          0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
          0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
          0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
          0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
          0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
          0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
          0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
          0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
          0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
          0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
          0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
          0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
          0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
          0x43, 0x83, 0x41, 0x81, 0x80, 0x40
      };
      
      /**
       * @brief                   计算CRC16_Modbus
       * 
       * @param _pBuf             数据
       * @param _usLen            数据长度
       * @return * uint16_t       CRC16_Modbus
       */
      uint16_t CRC16_Modbus(uint8_t *_pBuf, uint16_t _usLen)
      {
          uint8_t ucCRCHi = 0xFF; /* 高CRC字节初始化 */
          uint8_t ucCRCLo = 0xFF; /* 低CRC 字节初始化 */
          uint16_t usIndex;  /* CRC循环中的索引 */
      
          while (_usLen--)
          {
              usIndex = ucCRCHi ^ *_pBuf++; /* 计算CRC */
              ucCRCHi = ucCRCLo ^ s_CRCHi[usIndex];
              ucCRCLo = s_CRCLo[usIndex];
          }
          return ((uint16_t)ucCRCHi << 8 | ucCRCLo);
      }
      
    2. 2. 全局变量

      /* Modbus 功能码 */
      #define MODBUS_FC_READ_COILS               0x01
      #define MODBUS_FC_READ_DISCRETE_INPUTS     0x02
      #define MODBUS_FC_READ_INPUT_REGISTERS     0x04
      #define MODBUS_FC_READ_HOLDING_REGISTERS   0x03
      #define MODBUS_FC_WRITE_SINGLE_COIL        0x05
      #define MODBUS_FC_WRITE_SINGLE_REGISTER    0x06
      #define MODBUS_FC_WRITE_MULTIPLE_COILS     0x0F
      #define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10
      
      /* 用来切换485接受和发送模式 */
      #define MODBUS_TX_MODE()  HAL_GPIO_WritePin(U1_EN_GPIO_PORT, U1_EN_GPIO_PIN, GPIO_PIN_SET) 
      #define MODBUS_RX_MODE()  HAL_GPIO_WritePin(U1_EN_GPIO_PORT, U1_EN_GPIO_PIN, GPIO_PIN_RESET) 
      
      uint8_t modbus_tx_buffer[256];
      uint8_t modbus_rx_buffer[256];
      
      uint8_t g_modbus_slave_address = 0x01;      /* MODBUS 从站地址 */
      
      
    3. 3. 构造一个请求数据,以 0x03 读取保持寄存器为例子

      
      // 清空缓冲区
      memset(modbus_tx_buffer, 0, sizeof(modbus_tx_buffer));
      memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
      // 构造 Modbus 请求报文
      modbus_tx_buffer[0] = g_modbus_slave_address;       // 从机地址
      modbus_tx_buffer[1] = MODBUS_FC_READ_HOLDING_REGISTERS; // 功能码:读保持寄存器
      modbus_tx_buffer[2] = (uint8_t)(addr >> 8);    // 寄存器地址高字节
      modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);  // 寄存器地址低字节
      modbus_tx_buffer[4] = 0x00;                    // 寄存器数量高字节
      modbus_tx_buffer[5] = num;                     // 寄存器数量低字节
      
      // 计算 CRC
      crc = CRC16_Modbus(modbus_tx_buffer, 6);
      modbus_tx_buffer[6] = (uint8_t)(crc >> 8);     // CRC高字节
      modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);   // CRC低字节
      
      // 预计算响应长度: 从机地址(1) + 功能码(1) + 数据长度(1) + 数据(num*2) + CRC(2)
      expected_response_len = 5 + (num * 2);
      
      MODBUS_TX_MODE();  // 切换为发送模式
      
      HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY); // 发送数据
      
    4. 4. 再将请求数据发送完成之后,就需要马上接受数据。

      MODBUS_RX_MODE();  // 切换为接收模式
      
      if (HAL_UART_Receive(&huart1, modbus_rx_buffer, expected_response_len, MODBUS_TIMEOUT) != HAL_OK) {
          // printf("Timeout receiving data\n");
          return -1;  // 接收超时
      }
      
      // 检查响应格式
      if (modbus_rx_buffer[0] != g_modbus_slave_address || 
          modbus_rx_buffer[1] != MODBUS_FC_READ_HOLDING_REGISTERS) {
          printf("Invalid response format\n");
          printf("Received: %02X %02X %02X %02X %02X %02X %02X %02X\n", 
                 modbus_rx_buffer[0], modbus_rx_buffer[1], modbus_rx_buffer[2], 
                 modbus_rx_buffer[3], modbus_rx_buffer[4], modbus_rx_buffer[5], 
                 modbus_rx_buffer[6], modbus_rx_buffer[7]);
          NVIC_SystemReset();
          return -1;  // 接收到的响应格式不正确
      }
      
      // 获取数据长度
      rx_len = modbus_rx_buffer[2];
      
      // 确认数据长度是否符合预期
      if (rx_len != num * 2) {
          printf("Unexpected data length\n");
          return -1;  // 数据长度不符合预期
      }
      
      // 验证CRC
      crc = CRC16_Modbus(modbus_rx_buffer, rx_len + 3);
      if (((uint8_t)(crc >> 8) != modbus_rx_buffer[rx_len + 3]) || 
          ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[rx_len + 4])) {
          printf("CRC error\n");
          return -1;  // CRC错误
      }
      
      return rx_len;  // 返回数据长度
      
    5. 5. 使用示例

      /**
       * @brief 读取应急开关时间
       * @return uint16_t 应急开关时间
       */
      uint16_t Battery_ReadEmergencySwitchTime(void)
      {
          uint16_t addr = 0x12D4;  // 应急开关时间地址
          uint16_t value = 0;
          
          // 读取寄存器
          int result = Modbus_ReadHoldingRegisters(addr,1);
          if (result <= 0) {
              return 0;  // 读取失败返回0
          }
          
          // 解析读取到的值(高低字节顺序:高字节在前,低字节在后)
          value = modbus_rx_buffer[4];  // 高字节
          value |= modbus_rx_buffer[3] << 8;  // 低字节
          
          return value;  // 返回应急开关时间
      }
      

3. 完整的 Modbus RTU 主机代码

  • Modbus_rtu.h

    #ifndef __MODBUS_RTU_H
    #define __MODBUS_RTU_H
    #ifdef __cplusplus
    extern "C" {
    #endif
    #include "main.h"
    
    /* 使能UART1 - MODBUS 接收模式 */
    // #define MODBUS_RX_MODE()  HAL_GPIO_WritePin(U1_EN_GPIO_PORT, U1_EN_GPIO_PIN, GPIO_PIN_RESET) 
    #define MODBUS_RX_MODE() 
    #define MODBUS_TX_MODE()  
    /* 使能UART1 - MODBUS 发送模式 */
    // #define MODBUS_TX_MODE()  HAL_GPIO_WritePin(U1_EN_GPIO_PORT, U1_EN_GPIO_PIN, GPIO_PIN_SET) 
    
    /* MODBUS 功能码 */
    #define MODBUS_FC_READ_COILS               0x01
    #define MODBUS_FC_READ_DISCRETE_INPUTS     0x02
    #define MODBUS_FC_READ_INPUT_REGISTERS     0x04
    #define MODBUS_FC_READ_HOLDING_REGISTERS   0x03
    #define MODBUS_FC_WRITE_SINGLE_COIL        0x05
    #define MODBUS_FC_WRITE_SINGLE_REGISTER    0x06
    #define MODBUS_FC_WRITE_MULTIPLE_COILS     0x0F
    #define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10
    
    #define MODBUS_TIMEOUT     300      /* MODBUS 超时时间 */
    
    uint16_t CRC16_Modbus(uint8_t *_pBuf, uint16_t _usLen);
    int Modbus_ReadHoldingRegisters(uint16_t addr, uint8_t num);
    int Modbus_WriteSingleRegister(uint16_t addr, uint16_t value);
    int Modbus_WriteMultipleRegisters(uint16_t addr, uint16_t *values, uint8_t num);
    int Modbus_ReadCoils(uint16_t addr, uint16_t quantity);
    int Modbus_ReadDiscreteInputs(uint16_t addr, uint16_t quantity);
    int Modbus_ReadInputRegisters(uint16_t addr, uint16_t quantity);
    int Modbus_WriteSingleCoil(uint16_t addr, uint8_t on);
    int Modbus_WriteMultipleCoils(uint16_t addr, const uint8_t* bits, uint16_t quantity);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* __MODBUS_RTU_H */
    
    
  • modbus_rtu.c

    #include "modbus_rtu.h"
    #include "uart.h"
    #include "string.h"
    
    uint8_t g_modbus_slave_address = 0x01;      /* MODBUS 从站地址 */
    
    // 发送缓冲区和接收缓冲区
    uint8_t modbus_tx_buffer[256];
    uint8_t modbus_rx_buffer[256];
    
    /**
     * @brief 构造并发送 Modbus 读取保持寄存器报文 0x03
     * @param addr: 寄存器起始地址
     * @param num: 要读取的寄存器数量
     * @return 接收到的数据长度,-1表示错误
     */
    int Modbus_ReadHoldingRegisters(uint16_t addr, uint8_t num)
    {
        uint16_t crc;
        int rx_len;
        uint16_t expected_response_len;
        memset(modbus_tx_buffer, 0, sizeof(modbus_tx_buffer));
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        // 构造 Modbus 请求报文
        modbus_tx_buffer[0] = g_modbus_slave_address;       // 从机地址
        modbus_tx_buffer[1] = MODBUS_FC_READ_HOLDING_REGISTERS; // 功能码:读保持寄存器
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);    // 寄存器地址高字节
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);  // 寄存器地址低字节
        modbus_tx_buffer[4] = 0x00;                    // 寄存器数量高字节
        modbus_tx_buffer[5] = num;                     // 寄存器数量低字节
        
        // 计算 CRC
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);     // CRC高字节
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);   // CRC低字节
        
        // 预计算响应长度: 从机地址(1) + 功能码(1) + 数据长度(1) + 数据(num*2) + CRC(2)
        expected_response_len = 5 + (num * 2);
        
        // 清空接收缓冲区
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
    
        // 发送数据
        MODBUS_TX_MODE();  // 切换为发送模式
        
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();  // 切换为接收模式
    
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, expected_response_len, MODBUS_TIMEOUT) != HAL_OK) {
            // printf("Timeout receiving data\n");
            return -1;  // 接收超时
        }
    
        // 检查响应格式
        if (modbus_rx_buffer[0] != g_modbus_slave_address || 
            modbus_rx_buffer[1] != MODBUS_FC_READ_HOLDING_REGISTERS) {
            printf("Invalid response format\n");
            printf("Received: %02X %02X %02X %02X %02X %02X %02X %02X\n", 
                   modbus_rx_buffer[0], modbus_rx_buffer[1], modbus_rx_buffer[2], 
                   modbus_rx_buffer[3], modbus_rx_buffer[4], modbus_rx_buffer[5], 
                   modbus_rx_buffer[6], modbus_rx_buffer[7]
            return -1;  // 接收到的响应格式不正确
        }
    
        // 获取数据长度
        rx_len = modbus_rx_buffer[2];
    
        // 确认数据长度是否符合预期
        if (rx_len != num * 2) {
            printf("Unexpected data length\n");
            return -1;  // 数据长度不符合预期
        }
        
        // 验证CRC
        crc = CRC16_Modbus(modbus_rx_buffer, rx_len + 3);
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[rx_len + 3]) || 
            ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[rx_len + 4])) {
            printf("CRC error\n");
            return -1;  // CRC错误
        }
        
        return rx_len;  // 返回数据长度
    }
    
    /**
     * @brief 构造并发送 Modbus 写单个寄存器报文 0x06
     * @param addr: 寄存器地址
     * @param value: 要写入的值
     * @return 0:成功,-1:失败
     */
    int Modbus_WriteSingleRegister(uint16_t addr, uint16_t value)
    {
        uint16_t crc;
        
        // 构造 Modbus 请求报文
        modbus_tx_buffer[0] = g_modbus_slave_address;       // 从机地址
        modbus_tx_buffer[1] = MODBUS_FC_WRITE_SINGLE_REGISTER; // 功能码:写单个寄存器
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);    // 寄存器地址高字节
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);  // 寄存器地址低字节
        modbus_tx_buffer[4] = (uint8_t)(value >> 8);   // 寄存器值高字节
        modbus_tx_buffer[5] = (uint8_t)(value & 0xFF); // 寄存器值低字节
        
        // 计算 CRC (尝试交换字节顺序)
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);     // CRC高字节
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);   // CRC低字节
        
        // 发送数据
        MODBUS_TX_MODE();  // 切换为发送模式
        // HAL_Delay(10);    // 等待发送完成
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();  // 切换为接收模式
        // 接收响应
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, 8, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;  // 接收超时
        }
        
        // 检查响应
        if (modbus_rx_buffer[0] != g_modbus_slave_address || 
            modbus_rx_buffer[1] != MODBUS_FC_WRITE_SINGLE_REGISTER ||
            modbus_rx_buffer[2] != modbus_tx_buffer[2] ||
            modbus_rx_buffer[3] != modbus_tx_buffer[3] ||
            modbus_rx_buffer[4] != modbus_tx_buffer[4] ||
            modbus_rx_buffer[5] != modbus_tx_buffer[5]) {
            return -1;  // 响应数据错误
        }
        
        // 验证CRC
        crc = CRC16_Modbus(modbus_rx_buffer, 6);
        if (((uint8_t)(crc & 0xFF) != modbus_rx_buffer[7]) || 
            ((uint8_t)(crc >> 8) != modbus_rx_buffer[6])) {
                printf("CRC error in write response\n");
            return -1;  // CRC错误
        }
        
        return 0;  // 成功
    }
    
    /**
     * @brief 构造并发送 Modbus 写多个寄存器报文 0x10
     * @param addr: 寄存器起始地址
     * @param values: 要写入的值数组
     * @param num: 要写入的寄存器数量
     * @return 0:成功,-1:失败
     */
    int Modbus_WriteMultipleRegisters(uint16_t addr, uint16_t *values, uint8_t num)
    {
        uint16_t crc;
        uint8_t i;
        uint8_t tx_len;
        
        // 构造 Modbus 请求报文
        modbus_tx_buffer[0] = g_modbus_slave_address;       // 从机地址
        modbus_tx_buffer[1] = MODBUS_FC_WRITE_MULTIPLE_REGISTERS; // 功能码:写多个寄存器
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);    // 寄存器地址高字节
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);  // 寄存器地址低字节
        modbus_tx_buffer[4] = 0x00;                    // 寄存器数量高字节
        modbus_tx_buffer[5] = num;                     // 寄存器数量低字节
        modbus_tx_buffer[6] = (uint8_t)(num * 2);      // 后续字节数
        
        // 填充数据
        for (i = 0; i < num; i++) {
            modbus_tx_buffer[7 + i * 2] = (uint8_t)(values[i] >> 8);   // 寄存器值高字节
            modbus_tx_buffer[8 + i * 2] = (uint8_t)(values[i] & 0xFF); // 寄存器值低字节
        }
        
        // 计算总长度:从机地址(1) + 功能码(1) + 寄存器地址(2) + 寄存器数量(2) + 字节数(1) + 数据(num*2)
        tx_len = 7 + (num * 2);
        
        // 计算 CRC
        crc = CRC16_Modbus(modbus_tx_buffer, tx_len);
        modbus_tx_buffer[tx_len] = (uint8_t)(crc >> 8);     // CRC高字节
        modbus_tx_buffer[tx_len + 1] = (uint8_t)(crc & 0xFF);   // CRC低字节
        
        // 发送数据
        MODBUS_TX_MODE();  // 切换为发送模式
        // HAL_Delay(10);    // 等待发送完成
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, tx_len + 2, HAL_MAX_DELAY);
        MODBUS_RX_MODE();  // 切换为接收模式
        
        // 接收响应 - 预期响应长度为8字节
        // 响应格式:从机地址(1) + 功能码(1) + 寄存器地址(2) + 寄存器数量(2) + CRC(2)
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, 8, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;  // 接收超时
        }
        
        // 检查响应
        if (modbus_rx_buffer[0] != g_modbus_slave_address || 
            modbus_rx_buffer[1] != MODBUS_FC_WRITE_MULTIPLE_REGISTERS ||
            modbus_rx_buffer[2] != modbus_tx_buffer[2] ||
            modbus_rx_buffer[3] != modbus_tx_buffer[3] ||
            modbus_rx_buffer[4] != modbus_tx_buffer[4] ||
            modbus_rx_buffer[5] != modbus_tx_buffer[5]) {
            return -1;  // 响应数据错误
        }
        
        // 验证CRC
        crc = CRC16_Modbus(modbus_rx_buffer, 6);
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[6]) || 
            ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[7])) {
            return -1;  // CRC错误
        }
        
        return 0;  // 成功
    }
    
    /**
     * @brief 构造并发送 Modbus 读线圈状态报文 0x01
     * @param addr: 线圈起始地址
     * @param quantity: 要读取的线圈数量
     * @return 0:成功,-1:失败
     */
    int Modbus_ReadCoils(uint16_t addr, uint16_t quantity)
    {
        uint16_t crc;
        uint16_t expected_response_len;
        uint8_t byte_count = (uint8_t)((quantity + 7u) / 8u);
        memset(modbus_tx_buffer, 0, sizeof(modbus_tx_buffer));
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        modbus_tx_buffer[0] = g_modbus_slave_address;
        modbus_tx_buffer[1] = MODBUS_FC_READ_COILS;
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);
        modbus_tx_buffer[4] = (uint8_t)(quantity >> 8);
        modbus_tx_buffer[5] = (uint8_t)(quantity & 0xFF);
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);
        expected_response_len = 5 + byte_count;
        MODBUS_TX_MODE();
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, expected_response_len, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;
        }
        if (modbus_rx_buffer[0] != g_modbus_slave_address || modbus_rx_buffer[1] != MODBUS_FC_READ_COILS) {
            return -1;
        }
        if (modbus_rx_buffer[2] != byte_count) {
            return -1;
        }
        crc = CRC16_Modbus(modbus_rx_buffer, (uint16_t)(3 + byte_count));
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[3 + byte_count]) || ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[4 + byte_count])) {
            return -1;
        }
        return byte_count;
    }
    
    /**
     * @brief 构造并发送 Modbus 读离散输入状态报文 0x02
     * @param addr: 离散输入起始地址
     * @param quantity: 要读取的离散输入数量
     * @return 0:成功,-1:失败
     */
    int Modbus_ReadDiscreteInputs(uint16_t addr, uint16_t quantity)
    {
        uint16_t crc;
        uint16_t expected_response_len;
        uint8_t byte_count = (uint8_t)((quantity + 7u) / 8u);
        memset(modbus_tx_buffer, 0, sizeof(modbus_tx_buffer));
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        modbus_tx_buffer[0] = g_modbus_slave_address;
        modbus_tx_buffer[1] = MODBUS_FC_READ_DISCRETE_INPUTS;
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);
        modbus_tx_buffer[4] = (uint8_t)(quantity >> 8);
        modbus_tx_buffer[5] = (uint8_t)(quantity & 0xFF);
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);
        expected_response_len = 5 + byte_count;
        MODBUS_TX_MODE();
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, expected_response_len, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;
        }
        if (modbus_rx_buffer[0] != g_modbus_slave_address || modbus_rx_buffer[1] != MODBUS_FC_READ_DISCRETE_INPUTS) {
            return -1;
        }
        if (modbus_rx_buffer[2] != byte_count) {
            return -1;
        }
        crc = CRC16_Modbus(modbus_rx_buffer, (uint16_t)(3 + byte_count));
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[3 + byte_count]) || ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[4 + byte_count])) {
            return -1;
        }
        return byte_count;
    }
    
    /**
     * @brief 构造并发送 Modbus 读输入寄存器状态报文 0x04
     * @param addr: 输入寄存器起始地址
     * @param quantity: 要读取的输入寄存器数量
     * @return 0:成功,-1:失败
     */
    int Modbus_ReadInputRegisters(uint16_t addr, uint16_t quantity)
    {
        uint16_t crc;
        uint16_t expected_response_len;
        uint8_t num = (uint8_t)quantity;
        memset(modbus_tx_buffer, 0, sizeof(modbus_tx_buffer));
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        modbus_tx_buffer[0] = g_modbus_slave_address;
        modbus_tx_buffer[1] = MODBUS_FC_READ_INPUT_REGISTERS;
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);
        modbus_tx_buffer[4] = (uint8_t)(quantity >> 8);
        modbus_tx_buffer[5] = (uint8_t)(quantity & 0xFF);
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);
        expected_response_len = 5 + (num * 2);
        MODBUS_TX_MODE();
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, expected_response_len, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;
        }
        if (modbus_rx_buffer[0] != g_modbus_slave_address || modbus_rx_buffer[1] != MODBUS_FC_READ_INPUT_REGISTERS) {
            return -1;
        }
        if (modbus_rx_buffer[2] != (uint8_t)(num * 2)) {
            return -1;
        }
        crc = CRC16_Modbus(modbus_rx_buffer, (uint16_t)(3 + num * 2));
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[3 + num * 2]) || ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[4 + num * 2])) {
            return -1;
        }
        return (int)(num * 2);
    }
    
    /**
     * @brief 构造并发送 Modbus 写单个线圈状态报文 0x05
     * @param addr: 线圈地址
     * @param on: 要设置的线圈状态(1:ON,0:OFF)
     * @return 0:成功,-1:失败
     */
    int Modbus_WriteSingleCoil(uint16_t addr, uint8_t on)
    {
        uint16_t crc;
        uint16_t value = on ? 0xFF00u : 0x0000u;
        modbus_tx_buffer[0] = g_modbus_slave_address;
        modbus_tx_buffer[1] = MODBUS_FC_WRITE_SINGLE_COIL;
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);
        modbus_tx_buffer[4] = (uint8_t)(value >> 8);
        modbus_tx_buffer[5] = (uint8_t)(value & 0xFF);
        crc = CRC16_Modbus(modbus_tx_buffer, 6);
        modbus_tx_buffer[6] = (uint8_t)(crc >> 8);
        modbus_tx_buffer[7] = (uint8_t)(crc & 0xFF);
        MODBUS_TX_MODE();
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, 8, HAL_MAX_DELAY);
        MODBUS_RX_MODE();
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, 8, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;
        }
        if (modbus_rx_buffer[0] != g_modbus_slave_address || modbus_rx_buffer[1] != MODBUS_FC_WRITE_SINGLE_COIL) {
            return -1;
        }
        if (modbus_rx_buffer[2] != modbus_tx_buffer[2] || modbus_rx_buffer[3] != modbus_tx_buffer[3] || modbus_rx_buffer[4] != modbus_tx_buffer[4] || modbus_rx_buffer[5] != modbus_tx_buffer[5]) {
            return -1;
        }
        crc = CRC16_Modbus(modbus_rx_buffer, 6);
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[6]) || ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[7])) {
            return -1;
        }
        return 0;
    }
    
    /**
     * @brief 构造并发送 Modbus 写多个线圈状态报文 0x0F
     * @param addr: 线圈起始地址
     * @param bits: 要设置的线圈状态数组(每个元素为 1 字节,每个位对应一个线圈)
     * @param quantity: 要设置的线圈数量
     * @return 0:成功,-1:失败
     */
    int Modbus_WriteMultipleCoils(uint16_t addr, const uint8_t* bits, uint16_t quantity)
    {
        uint16_t crc;
        uint16_t i;
        uint8_t byte_count = (uint8_t)((quantity + 7u) / 8u);
        modbus_tx_buffer[0] = g_modbus_slave_address;
        modbus_tx_buffer[1] = MODBUS_FC_WRITE_MULTIPLE_COILS;
        modbus_tx_buffer[2] = (uint8_t)(addr >> 8);
        modbus_tx_buffer[3] = (uint8_t)(addr & 0xFF);
        modbus_tx_buffer[4] = (uint8_t)(quantity >> 8);
        modbus_tx_buffer[5] = (uint8_t)(quantity & 0xFF);
        modbus_tx_buffer[6] = byte_count;
        for (i = 0; i < quantity; ++i) {
            uint8_t b = bits ? bits[i] : 0;
            uint8_t bi = (uint8_t)(i / 8u);
            uint8_t bp = (uint8_t)(i % 8u);
            if (b) modbus_tx_buffer[7 + bi] |= (uint8_t)(1u << bp);
        }
        crc = CRC16_Modbus(modbus_tx_buffer, (uint16_t)(7 + byte_count));
        modbus_tx_buffer[7 + byte_count] = (uint8_t)(crc >> 8);
        modbus_tx_buffer[7 + byte_count + 1] = (uint8_t)(crc & 0xFF);
        MODBUS_TX_MODE();
        HAL_UART_Transmit(&huart1, modbus_tx_buffer, (uint16_t)(7 + byte_count + 2), HAL_MAX_DELAY);
        MODBUS_RX_MODE();
        memset(modbus_rx_buffer, 0, sizeof(modbus_rx_buffer));
        if (HAL_UART_Receive(&huart1, modbus_rx_buffer, 8, MODBUS_TIMEOUT) != HAL_OK) {
            return -1;
        }
        if (modbus_rx_buffer[0] != g_modbus_slave_address || modbus_rx_buffer[1] != MODBUS_FC_WRITE_MULTIPLE_COILS) {
            return -1;
        }
        if (modbus_rx_buffer[2] != modbus_tx_buffer[2] || modbus_rx_buffer[3] != modbus_tx_buffer[3] || modbus_rx_buffer[4] != modbus_tx_buffer[4] || modbus_rx_buffer[5] != modbus_tx_buffer[5]) {
            return -1;
        }
        crc = CRC16_Modbus(modbus_rx_buffer, 6);
        if (((uint8_t)(crc >> 8) != modbus_rx_buffer[6]) || ((uint8_t)(crc & 0xFF) != modbus_rx_buffer[7])) {
            return -1;
        }
        return 0;
    }
    
  • uart.c 不需要使能中断,只需要使能发送和接受即可

    /**
     * @brief  配置UART1 GPIO引脚
     * @retval None
     */
    static void UART1_GPIO_Config(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        __HAL_RCC_GPIOF_CLK_ENABLE();
    
        GPIO_InitStruct.Pin = UART1_TX_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = UART1_TX_AF;
        HAL_GPIO_Init(UART1_TX_GPIO_PORT, &GPIO_InitStruct);
    
        GPIO_InitStruct.Pin = UART1_RX_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = UART1_RX_AF;
        HAL_GPIO_Init(UART1_RX_GPIO_PORT, &GPIO_InitStruct);
    }
    
    /**
     * @brief  初始化UART1
     * @retval None
     */
    HAL_StatusTypeDef UART1_Init(void)
    {
        HAL_StatusTypeDef status = HAL_OK;
    
        __HAL_RCC_USART1_CLK_ENABLE();
    
        UART1_GPIO_Config();
        
        huart1.Instance = UART1_INSTANCE;
        huart1.Init.BaudRate = UART1_BAUDRATE;
        huart1.Init.WordLength = UART_WORDLENGTH_8B;
        huart1.Init.StopBits = UART_STOPBITS_1;
        huart1.Init.Parity = UART_PARITY_NONE;
        huart1.Init.Mode = UART_MODE_TX_RX;
        huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
        huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    
        if (HAL_UART_Init(&huart1) != HAL_OK)
        {
            status = HAL_ERROR;
        }
    
        return status;
    }
    
  • • 读取 Modbus 的 BMS示例代码

    /**
     * @brief 控制电池充电
     * @param enable 1:打开充电 0:关闭充电
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ControlCharging(uint8_t enable)
    {
        uint16_t values[2];
        uint16_t addr = 0x1070;  // 0x1000 + 0x0070
        
        // 根据enable参数设置值
        values[0] = 0x0000;  // 高16位始终为0
        values[1] = enable ? 0x0001 : 0x0000;  // 低16位:1表示打开充电,0表示关闭充电
        
        // 写入寄存器
        int result = Modbus_WriteMultipleRegisters(addr, values, 2);
        HAL_Delay(1);
        return (result > 0) ? 0 : -1;  // 成功返回0,失败返回-1
    }
    
    /**
     * @brief 控制电池放电
     * @param enable 1:打开放电 0:关闭放电
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ControlDischarging(uint8_t enable)
    {
        uint16_t values[2];
        uint16_t addr = 0x1074;  // 0x1000 + 0x0074
        
        // 根据enable参数设置值
        values[0] = 0x0000;  
        values[1] = enable ? 0x0001 : 0x0000;  
        
        // 写入寄存器
        int result = Modbus_WriteMultipleRegisters(addr, values, 2);
        HAL_Delay(1);
        return (result > 0) ? 0 : -1;  // 成功返回0,失败返回-1
    }
    
    /**
     * @brief 控制电池均衡
     * @param enable 1:打开均衡 0:关闭均衡
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ControlBalancing(uint8_t enable)
    {
        uint16_t values[2];
        uint16_t addr = 0x1078;  // 0x1000 + 0x0078
        
        // 根据enable参数设置值
        values[0] = 0x0000;  // 高16位始终为0
        values[1] = enable ? 0x0001 : 0x0000;  // 低16位:1表示打开均衡,0表示关闭均衡
        
        // 写入寄存器
        int result = Modbus_WriteMultipleRegisters(addr, values, 2);
        HAL_Delay(1);
        return (result > 0) ? 0 : -1;  // 成功返回0,失败返回-1
    }
    
    /**
     * @brief 控制保护板关机
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ShutdownProtectionBoard(void)
    {
        int result;
        uint16_t addr = 0x1604;  // 保护板关机地址
        uint16_t value = 0x0001; // 1表示关闭保护板
        
        // 写入寄存器
        result = Modbus_WriteMultipleRegisters(addr, &value, 1);
        if (result <= 0) {
            return -1;  // 写入失败
        }
        
        return 0;  // 成功
    }
    
    /**
     * @brief 读取应急开关时间
     * @return uint16_t 应急开关时间
     */
    uint16_t Battery_ReadEmergencySwitchTime(void)
    {
        uint16_t addr = 0x12D4;  // 应急开关时间地址
        uint16_t value = 0;
        
        // 读取寄存器
        int result = Modbus_ReadHoldingRegisters(addr,1);
        if (result <= 0) {
            return 0;  // 读取失败返回0
        }
        
        // 解析读取到的值(高低字节顺序:高字节在前,低字节在后)
        value = modbus_rx_buffer[4];  // 高字节
        value |= modbus_rx_buffer[3] << 8;  // 低字节
        
        return value;  // 返回应急开关时间
    }
    
    /**
     * @brief 检查应急开关时间并在低于100S时启动应急功能
     * @return int 成功返回0,失败返回-1,不需要启动返回1
     */
    int Battery_CheckAndActivateEmergency(void)
    {
        uint16_t emergencySwitchTime = Battery_ReadEmergencySwitchTime();
        
        // 如果时间低于100S,则启动应急功能
        if (emergencySwitchTime < 100) {
            // 连续发送两次启动应急开关命令
            int result1 = Battery_ActivateEmergencySwitch();
            HAL_Delay(10);  // 短暂延时
            int result2 = Battery_ActivateEmergencySwitch();
            
            // 只要有一次成功就认为成功
            if (result1 == 0 && result2 == 0) {
                return 0;  // 成功启动应急功能
            } else {
                return -1;  // 两次都失败
            }
        }
        
        return 1;  // 不需要启动应急功能
    }
    
    /**
     * @brief 启动应急开关
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ActivateEmergencySwitch(void)
    {
        int result;
        uint16_t addr = 0x1610;  // 应急开关地址
        uint16_t value = 0x0001; // 1表示启动应急开关
        
        // 写入寄存器
        result = Modbus_WriteMultipleRegisters(addr, &value, 1);
        if (result <= 0) {
            return -1;  // 写入失败
        }
        
        return 0;  // 成功
    }
    
    /**
     * @brief 读取设备信息
     * @param deviceInfo 指向DeviceInfo_TypeDef结构体的指针,用于存储读取到的设备信息
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ReadDeviceInfo(DeviceInfo_TypeDef *deviceInfo)
    {
        int result;
        uint16_t info_start_addr = 0x1400;  // 设备信息起始地址
        uint8_t i;
        uint8_t total_registers = 40;  // 总共读取20个寄存器 (0x1400-0x1427)
        
        // 初始化结构体
        memset(deviceInfo, 0, sizeof(DeviceInfo_TypeDef));
        deviceInfo->valid = 0;
        
        // 一次性连续读取所有设备信息 (0x1400-0x1427)
        // 包括厂商型号、硬件版本号、软件版本号、累计运行时间和上电次数
        result = Modbus_ReadHoldingRegisters(info_start_addr, total_registers);
        if (result <= 0) {
            return -1;  // 读取失败
        }
        
        // 解析厂商型号 (0x1400-0x140F) 16个字节
        for (i = 0; i < 16; i++) {
            // 每个寄存器包含两个字节
            if (i % 2 == 0) {
                deviceInfo->manufacturer_model[i] = modbus_rx_buffer[3 + i + 1];  // 低字节
            } else {
                deviceInfo->manufacturer_model[i] = modbus_rx_buffer[3 + i - 1];  // 高字节
            }
        }
        deviceInfo->manufacturer_model[16] = '\0';  // 添加字符串结束符
        
        // 解析硬件版本号 (0x1410-0x1417) 8个字节
        for (i = 0; i < 8; i++) {
            // 每个寄存器包含两个字节
            if (i % 2 == 0) {
                deviceInfo->hardware_version[i] = modbus_rx_buffer[3 + 16 + i + 1];  // 低字节
            } else {
                deviceInfo->hardware_version[i] = modbus_rx_buffer[3 + 16 + i - 1];  // 高字节
            }
        }
        deviceInfo->hardware_version[8] = '\0';  // 添加字符串结束符
        
        // 解析软件版本号 (0x1418-0x141F) 8个字节
        for (i = 0; i < 8; i++) {
            // 每个寄存器包含两个字节
            if (i % 2 == 0) {
                deviceInfo->software_version[i] = modbus_rx_buffer[3 + 24 + i + 1];  // 低字节
            } else {
                deviceInfo->software_version[i] = modbus_rx_buffer[3 + 24 + i - 1];  // 高字节
            }
        }
        deviceInfo->software_version[8] = '\0';  // 添加字符串结束符
        
        // 解析累计运行时间 (0x1420-0x1423) 4个字节
        deviceInfo->total_runtime = (uint32_t)((modbus_rx_buffer[3 + 32] << 24) | 
                                              (modbus_rx_buffer[3 + 33] << 16) | 
                                              (modbus_rx_buffer[3 + 34] << 8) | 
                                               modbus_rx_buffer[3 + 35]);
        
        // 解析上电次数 (0x1424-0x1427) 4个字节
        deviceInfo->power_on_count = (uint32_t)((modbus_rx_buffer[3 + 36] << 24) | 
                                               (modbus_rx_buffer[3 + 37] << 16) | 
                                               (modbus_rx_buffer[3 + 38] << 8) | 
                                                modbus_rx_buffer[3 + 39]);
        
        // 标记数据有效
        deviceInfo->valid = 1;
        
        return 0;  // 成功
    }
    
    /**
     * @brief 读取所有电芯电压和电池状态数据
     * @param batteryData 指向BatteryData_TypeDef结构体的指针,用于存储读取到的电芯电压和电池状态数据
     * @return int 成功返回0,失败返回-1
     */
    int Battery_ReadInfo(BatteryData_TypeDef *batteryData)
    {
        int result;
        uint8_t i;
        uint16_t cell_start_addr = 0x1200;  // 电芯电压起始地址
        uint8_t num_cells = 24;             // 电芯数量
        uint16_t status_start_addr = 0x40;  // 电池状态起始地址
        uint8_t num_status_regs = 5;        // 电池状态寄存器数量 (0x40-0x48)
    
        // 初始化结构体
        // memset(batteryData, 0, sizeof(BatteryData_TypeDef));
        batteryData->valid = 0;
        
        // 读取电芯电压
        result = Modbus_ReadHoldingRegisters(cell_start_addr, num_cells);
        // 检查读取结果
        if (result <= 0) {
            return -1;  // 读取失败
        }
    
        for (i = 0; i < num_cells; i++) {
            // 从接收缓冲区提取数据
            // 数据格式:从机地址(1) + 功能码(1) + 数据长度(1) + 数据(num_cells*2) + CRC(2)
            // 数据起始位置为3,每个寄存器占用2个字节
            batteryData->cell_voltage[i] = (uint16_t)((modbus_rx_buffer[3 + i * 2] << 8) | 
                                                        modbus_rx_buffer[4 + i * 2]);
        }
        HAL_Delay(30);
        // 读取电池状态数据
        result = Modbus_ReadHoldingRegisters(cell_start_addr + status_start_addr, num_status_regs);
        if (result <= 0) {
            return -1;  // 读取失败
        }
        
        // 解析电池状态数据
        // 电池状态 (0x40-0x43) - 接收缓冲区索引3-6 (四个字节)
        batteryData->battery_status = (uint32_t)((modbus_rx_buffer[3] << 24) | 
                                                (modbus_rx_buffer[4] << 16) | 
                                                (modbus_rx_buffer[5] << 8) | 
                                                 modbus_rx_buffer[6]);
        
        // 电池平均电压 (0x44-0x45) - 接收缓冲区索引7-8 (两个字节)
        batteryData->average_voltage = (uint16_t)((modbus_rx_buffer[7] << 8) | modbus_rx_buffer[8]);
        
        // 最大压差 (0x46-0x47) - 接收缓冲区索引9-10 (两个字节)
        batteryData->max_voltage_diff = (uint16_t)((modbus_rx_buffer[9] << 8) | modbus_rx_buffer[10]);
        
        // 最大单体编号和最小单体编号 (0x48) - 接收缓冲区索引11-12
        batteryData->max_cell_number = modbus_rx_buffer[11];  // 高字节为最大单体编号
        batteryData->min_cell_number = modbus_rx_buffer[12];  // 低字节为最小单体编号
        
        // 连续读取从0x8A到0xC0的所有数据
        uint16_t extended_data_start_addr = 0x8A;
        uint8_t extended_data_length = 28;  // 从0x8A到0xC0共28个寄存器
        HAL_Delay(40);
        result = Modbus_ReadHoldingRegisters(cell_start_addr + extended_data_start_addr, extended_data_length);
        if (result <= 0) {
            return -1;  // 读取失败
        }
        
        // 解析功率板温度 (0x8A)
        batteryData->power_board_temp = (int16_t)((modbus_rx_buffer[3] << 8) | modbus_rx_buffer[4]);
        
        // 解析均衡线电阻状态 (0x8C)
        batteryData->balance_line_status = (uint32_t)((modbus_rx_buffer[5] << 24) | 
                                                     (modbus_rx_buffer[6] << 16) | 
                                                     (modbus_rx_buffer[7] << 8) | 
                                                      modbus_rx_buffer[8]);
        
        // 解析电池总电压 (0x90)
        batteryData->total_voltage = (uint32_t)((modbus_rx_buffer[9] << 24) | 
                                               (modbus_rx_buffer[10] << 16) | 
                                               (modbus_rx_buffer[11] << 8) | 
                                                modbus_rx_buffer[12]);
        // printf("batteryData->total_voltage = %d\r\n", batteryData->total_voltage);
        
        // 解析电池功率 (0x94)
        batteryData->battery_power = (uint32_t)((modbus_rx_buffer[13] << 24) | 
                                               (modbus_rx_buffer[14] << 16) | 
                                               (modbus_rx_buffer[15] << 8) | 
                                                modbus_rx_buffer[16]);
        
        // 解析电池电流 (0x98)
        batteryData->battery_current = (int32_t)((modbus_rx_buffer[17] << 24) | 
                                                 (modbus_rx_buffer[18] << 16) | 
                                                 (modbus_rx_buffer[19] << 8) | 
                                                  modbus_rx_buffer[20]);
        
        // 解析电池温度1 (0x9C)
        batteryData->battery_temp1 = (int16_t)((modbus_rx_buffer[21] << 8) | modbus_rx_buffer[22]);
        
        // 解析电池温度2 (0x9E)
        batteryData->battery_temp2 = (int16_t)((modbus_rx_buffer[23] << 8) | modbus_rx_buffer[24]);
        
        // 解析报警状态 (0xA0)
        batteryData->alarm_status = (uint32_t)((modbus_rx_buffer[25] << 24) | 
                                              (modbus_rx_buffer[26] << 16) | 
                                              (modbus_rx_buffer[27] << 8) | 
                                               modbus_rx_buffer[28]);
        
        // 解析均衡电流 (0xA4)
        batteryData->balance_current = (int16_t)((modbus_rx_buffer[29] << 8) | modbus_rx_buffer[30]);
        
        // 解析均衡状态和剩余电量百分比 (0xA6)
        batteryData->balance_status = modbus_rx_buffer[31];  // 高8位为均衡状态
        batteryData->remaining_capacity_percent = modbus_rx_buffer[32];  // 低8位为剩余电量百分比
        
        // 解析剩余容量 (0xA8)
        batteryData->remaining_capacity = (int32_t)((modbus_rx_buffer[33] << 24) | 
                                                   (modbus_rx_buffer[34] << 16) | 
                                                   (modbus_rx_buffer[35] << 8) | 
                                                    modbus_rx_buffer[36]);
        
        // 解析实际容量 (0xAC)
        batteryData->actual_capacity = (uint32_t)((modbus_rx_buffer[37] << 24) | 
                                                 (modbus_rx_buffer[38] << 16) | 
                                                 (modbus_rx_buffer[39] << 8) | 
                                                  modbus_rx_buffer[40]);
        
        // 解析循环次数 (0xB0)
        batteryData->cycle_count = (uint32_t)((modbus_rx_buffer[41] << 24) | 
                                             (modbus_rx_buffer[42] << 16) | 
                                             (modbus_rx_buffer[43] << 8) | 
                                              modbus_rx_buffer[44]);
        
        // 解析循环总容量 (0xB4)
        batteryData->total_cycle_capacity = (uint32_t)((modbus_rx_buffer[45] << 24) | 
                                                      (modbus_rx_buffer[46] << 16) | 
                                                      (modbus_rx_buffer[47] << 8) | 
                                                       modbus_rx_buffer[48]);
        
        // 解析SOH估计和预充状态 (0xB8)
        batteryData->soh_estimate = modbus_rx_buffer[49];  // 高8位为SOH估计
        batteryData->precharge_status = modbus_rx_buffer[50];  // 低8位为预充状态
        
        // 解析用户层报警 (0xBA)
        batteryData->user_alarm = (uint16_t)((modbus_rx_buffer[51] << 8) | modbus_rx_buffer[52]);
        
        // 解析运行时间 (0xBC)
        batteryData->running_time = (uint32_t)((modbus_rx_buffer[53] << 24) | 
                                              (modbus_rx_buffer[54] << 16) | 
                                              (modbus_rx_buffer[55] << 8) | 
                                               modbus_rx_buffer[56]);
        
        // 解析充电状态和放电状态 (0xC0)
        batteryData->charge_status = modbus_rx_buffer[57];  // 高8位为充电状态
        batteryData->discharge_status = modbus_rx_buffer[58];  // 低8位为放电状态
        
        // 标记数据有效
        batteryData->valid = 1;
        
        return 0;  // 成功
    }
    
    // 电池数据结构体
    BatteryData_TypeDef batteryData;                // 电池数据结构体
    DeviceInfo_TypeDef deviceInfo;                  // 设备信息结构体
    
    // 电池监控任务相关变量
    static uint8_t s_isInitialized = 0;             // 初始化标志位
    static uint8_t s_emergencyActivated = 0;        // 应急开关状态标志位
    
    // 电流监测相关变量
    static uint32_t s_highCurrentCounter = 0;       // 高电流持续计数
    static uint32_t s_lowCurrentCounter = 0;        // 低电流持续计数
    
    // 电池通信相关变量
    uint8_t g_modbusErrorCount = 0;          // Modbus通信错误计数
    
    /**
     * @brief 电池监控任务函数,每2秒调用一次
     * @param param 任务参数
     */
    void Battery_MonitorTask(void *param)
    {
        // 初始化时读取设备信息
        if (!s_isInitialized) {
            // 尝试读取设备信息,确保成功
            while (Battery_ReadDeviceInfo(&deviceInfo) != 0) {
                // 错误计数 
                g_modbusErrorCount++;
                return;
            }
            s_isInitialized = 1;
        }
        // printf("battery_current: %d\n", batteryData.battery_current);
        // Battery_ControlDischarging(0);
        // 每次调用读取电池信息
        if (Battery_ReadInfo(&batteryData) == 0) {
            g_modbusErrorCount = 0;     // 重置错误计数
            // 电池电流检测逻辑
            if (batteryData.valid) {
                // 检测高电流情况 (>15A)
                if (batteryData.battery_current > 15000) {  // 15A = 15000mA
                    s_highCurrentCounter++;
                    
                    // 如果持续30秒高电流,触发应急开关
                    // 每2秒调用一次,需要计数15次
                    if (s_highCurrentCounter >= 15) {
                        if (!s_emergencyActivated) {
                            if (Battery_CheckAndActivateEmergency() == 0) {
                                s_emergencyActivated = 1;
                            }
                        }
                    }
                } else {
                    // 不是高电流,重置计数器
                    s_highCurrentCounter = 0;
                }
                
                // 检测低电流情况 (<5A)
                if (batteryData.battery_current < 5000) {  // 5A = 5000mA
                    s_lowCurrentCounter++;
                    
                    // 如果持续30秒低电流,关闭应急开关
                    // 每2秒调用一次,需要计数15次
                    if (s_lowCurrentCounter >= 15 && s_emergencyActivated) {
                        Battery_ActivateEmergencySwitch();
                        s_emergencyActivated = 0;
                    }
                } else {
                    // 不是低电流,重置计数器
                    s_lowCurrentCounter = 0;
                }
            }else{
                
            }
        }else{
            g_modbusErrorCount++;
            return;
        }
    }