找回密码
 立即注册

微信扫码登录

搜索
查看: 215|回复: 1

[经验分享] esp 对 485 数据的读写分享

[复制链接]

0

主题

8

回帖

89

积分

注册会员

积分
89
金钱
81
HASS币
0
发表于 2025-10-23 12:31:10 | 显示全部楼层 |阅读模式
本帖最后由 18005246366 于 2025-10-23 21:49 编辑

最近折腾 485 读写逆变器和保护板的数据,碰到不少坑,记录分享下

机器部分

首先得确保你要采集的机器 485 通信口正常,以我这次要采集的晟格锐逆变器和极空 bms 为例

逆变器说明书上标明有两个 rs485 通信口,但是只有 2 可用,1 只能连接厂家上位机,无法直接使用。2 口又用于逆变器跟bms的直接通信,所以想要自己采集必须接 can 口,然后依旧使用 485 通信协议(iotrix 这种远程采集就无法使用了)

bms 带 3 个 485 口,1 口用来与逆变器直接通信,2 口用来采集,3 口用来并联,但是需要注意必须拨码指定 bms 从机地址才可以成功采集。以最简单的从机 1 为例,拨码开关第一个拨到上,其他拨到下即可




                               
登录/注册后可看大图


硬件部分

解决完上面机器的问题,用什么硬件采集反而很简单。我用 esp8266 和 esp32 都尝试过,连接基本一致,板子加一个 485 转 ttl 的模块即可

485 ttl 转换模块有好几种,都可以走通,但是建议买 3.3v 的,直接插板子 vcc 针脚供电

rx tx 的连接建议使用软串口,也就是在 esphome 的 uart 配置 tx_pin 和 rx_pin,因为 8266 的串口数量有限,debug 时可能存在串口占用的情况,统一用软串口较好




                               
登录/注册后可看大图




                               
登录/注册后可看大图



                               
登录/注册后可看大图



连接的原理就是板子的 rx tx 跟模块的 rx tx 反接,模块的 ab 跟机器的 ab 正接,具体示意图如下(max 485模块以及自动流控的485不太一样,但是类似)




                               
登录/注册后可看大图


ps: 通信不成功可以尝试不要让模块的共地跟机器连接,极空 bms 连接后无法通信

modbus 协议部分

逆变器和 bms 从网上都可以搜到协议的具体文档,如果你要采集的机器搜不到具体文档建议联系厂家获取

发送和接受的指令都是 16 进制的 hex 字符,比如 01:03:10:00:00:46:C0:F801 代表从机地址,03 代表 modbus 功能码(读),10:00:00:46 代表要读取的寄存器地址,最后两位是 CRC 校验码

如果你用 arduino 写代码的话,就需要手动拼接上面的字符串,所以建议还是用 esphome 来配置采集逻辑,相对来说简单很多




                               
登录/注册后可看大图


采集部分

来到重点了,这里坑尤其多,不过很多功能都有人封装过,比如 esp-jk-bms,但是预期未来有很多类似的采集需求,所以自己折腾下

esphome 的使用

强烈建议使用 esphome 来配置采集逻辑,一个是因为可以 ota,不需要坐在机器边上坐牢,另一个是因为跟 ha 的 api 无缝对接,不需要再转一道 mqtt,很方便

第一次需要先物理连接安装下,先在 ha 的 esphome builder 里创建个空配置,把 wifi 的名称和密码配上,然后点安装选择手动安装

esphome:
  name: jk-bms-esp32
  friendly_name: 极空保护板

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: "mlXE354s0IcV6nfgvH+SadorLlX1WP9YjuROcIyT7V0="

ota:
  - platform: esphome
    password: "b42e4788201912783e40bfd2bc37eb1e"

wifi:
  ssid: !secret wifi_ssid # -> 这个是在右上角的 secrets.yaml 中配置
  password: !secret wifi_password

构建成功下载 bin 文件后,打开 esphome web 站点,usb 连接板子选择 install,刷入刚刚的 bin 文件(驱动问题可以看下 b 站太极创客的视频,基本就是安装下 ch340 的驱动即可)




                               
登录/注册后可看大图



传感器的配置

接下来就是脏活累活了,根据 modbus 协议,挨个配置要读写的传感器,比如

比较常用的就是 sensor、binary_sensor、switch、number 这四种组件,用来控制数据的读写,但是直接配在 ha 前台使用体验会很差,后文我会补下一些技巧

uart:
  - id: uart_id
    baud_rate: 115200
    data_bits: 8
    stop_bits: 1
    parity: NONE
    tx_pin: GPIO17
    rx_pin: GPIO16
    debug: 
      direction: BOTH

modbus:
  id: modbus_id
  uart_id: uart_id

modbus_controller:
  - id: modbus_controller_id
    modbus_id: modbus_id
    address: 1
    update_interval: never

sensor:
  - platform: modbus_controller
    name: "第 1 串电压_A_4608"
    address: 4608 # -> 这个就是寄存器的地址,16进制或者10进制
    unit_of_measurement: "V"
    device_class: voltage
    state_class: measurement
    filters:
      - multiply: 0.001

采集的优化和技巧

按照协议文档挨个配置好 90 多个传感器之后,工作确实正常了,但是碰到了严重的性能问题

虽然 esphome 官方文档说连续的寄存器会合并成一个 uart 指令,但是实测不会

所以 5s 一次的采集频率,会顺序发送 90 多条指令,这会让传感器数据的更新不同步,而且占用串口通信通道,导致修改不生效

所以传感器的采集可以手动发送指令,一次指令采集多个寄存器的数据,拿到数据后手动 publish 到指定的传感器

优化之后,一次采集发送指令的数量从 90 到 5,没有任何压力,而且 ha 的前台数据同时更新,没有挨个刷新的情况,写入数据也立即生效,不会存在排队导致写入失败的情况

uart:
  - id: uart_id
    baud_rate: 115200
    data_bits: 8
    stop_bits: 1
    parity: NONE
    tx_pin: GPIO17
    rx_pin: GPIO16
    debug: 
      direction: BOTH
      sequence:
        - lambda: |-
            if (bytes.empty() || bytes.size() < 5 || bytes[0] != 0x01 || bytes[1] != 0x03) return;

            auto get_int32 = [&](int offset) -> int32_t {
              if (offset + 3 >= bytes.size()) return 0;
              return (int32_t(bytes[offset]) << 24) | (int32_t(bytes[offset+1]) << 16) | 
                     (int32_t(bytes[offset+2]) << 8) | int32_t(bytes[offset+3]);
            };

            auto get_uint16 = [&](int offset) -> uint16_t {
              if (offset + 1 >= bytes.size()) return 0;
              return (uint16_t(bytes[offset]) << 8) | uint16_t(bytes[offset+1]);
            };

            // 在 uart 的 debug 里监听数据的返回
            // 判断返回数据的第三位来确定到底是哪个数据(第三位就是返回的字节数)
            switch (bytes[2]) {
              case 0x8c:

                // 从第 3 个字节开始,解析出 4 个字节的 int32 数据,push 到 A_4096 传感器
                id(A_4096).publish_state(get_uint32(3) / 1000.0);

                break;

              case 0x80:
                // 从第 3 个字节开始,解析出 2 个字节的 uint16 数据,push 到 A_4608 传感器
                id(A_4608).publish_state(get_uint16(3) / 1000.0);

                break;
            }

modbus:
  id: modbus_id
  uart_id: uart_id

modbus_controller:
  - id: modbus_controller_id
    modbus_id: modbus_id
    address: 1
    update_interval: never

# 重点,使用 interval 控制发送频率
interval:
  - interval: 5s
    then:
      # 用 lambda 语句手动发送指令
      - lambda: |-
          auto modbus = id(modbus_id);

          // 从 0x1000 开始读取 70 个寄存器的数据
          modbus->send(1, 0x03, 0x1000, 70, 0, nullptr);
      - delay: 100ms # -> 延迟 100ms 继续读下一个

      - lambda: |-
          auto modbus = id(modbus_id);

          // 从 0x1200 开始读取 64 个寄存器的数据
          modbus->send(1, 0x03, 0x1200, 64, 0, nullptr);
      - delay: 100ms

      - lambda: |-
          auto modbus = id(modbus_id);

          // 从 0x128a 开始读取 43 个寄存器的数据
          modbus->send(1, 0x03, 0x128a, 43, 0, nullptr);
      - delay: 100ms

# number 示意
number:
  - platform: template # -> 类型就直接使用 template,受控,由 uart 写入
    name: "进入休眠电压_A_4096"
    id: A_4096
    unit_of_measurement: "V"
    device_class: voltage
    step: 0.001
    min_value: 2.5
    max_value: 3.6
    set_action: 
      then:
        - lambda: |- # -> 这部分控制写入逻辑,同样是手动构造结果,发送写入命令
            uint32_t value = uint32_t(x * 1000);
            uint8_t data[4] = {
              uint8_t((value >> 24) & 0xFF),
              uint8_t((value >> 16) & 0xFF),
              uint8_t((value >> 8) & 0xFF),
              uint8_t(value & 0xFF)
            };

            auto modbus = id(modbus_id);
            modbus->send(1, 0x10, 4096, 2, 4, data);

# sensor 示意
sensor:
  - platform: template
    name: "第 1 串电压_A_4608"
    id: A_4608
    unit_of_measurement: "V"
    device_class: voltage
    state_class: measurement

# switch 示意
switch:
  - platform: template
    name: "充电开关_S_4208"
    id: A_4208
    restore_mode: ALWAYS_ON
    turn_on_action: # -> 同样的似乎,ha 前台控制之后,构造指令,发送写入命令
      then:
        - lambda: |- 
            auto modbus = id(modbus_id);
            uint8_t values[4] = {0, 0, 0, 1};
            modbus->send(1, 0x10, 4208, 2, 4, values);

    turn_off_action: 
      then:
        - lambda: |- 
            auto modbus = id(modbus_id);
            uint8_t values[4] = {0, 0, 0, 0};
            modbus->send(1, 0x10, 4208, 2, 4, values);

# ... 剩下的挨个配置即可




回复

使用道具 举报

0

主题

8

回帖

89

积分

注册会员

积分
89
金钱
81
HASS币
0
 楼主| 发表于 2025-10-23 17:48:18 | 显示全部楼层
好像论坛的 markdown 有点问题啊, > 符号被转义成了 >
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian ( 晋ICP备17001384号-1 )

GMT+8, 2025-12-1 02:28 , Processed in 0.053718 second(s), 6 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表