BCU 平台 RS485 驱动适配:从 THVD1406 到 ISO3082

背景:BCU(Battery Control Unit)是新一代电池管理控制单元,基于 RK3568 核心板 + 自研底板。与上一代 FCU2601 使用的 RS485 收发器芯片不同,BCU 选用了 ISO3082 隔离型收发器,导致原有驱动无法直接复用。本文记录完整的适配过程。


1. 硬件现场

1.1 两代平台对比

FCU2601(上一代)BCU(新一代)
核心板RK3568RK3568
RS485 芯片THVD1406(TI)ISO3082(TI)
隔离特性无隔离2500Vrms 隔离
方向控制芯片自动完成需要 CPU GPIO 控制
封装8-Pin16-Pin SOIC
RS485 路数11 路4 路
使用 UARTUART3 / UART4 / UART5 / UART9

1.2 芯片关键区别

┌─────────────────────────────────────────────────────────┐
│  THVD1406(FCU2601)          ISO3082(BCU)             │
│                                                         │
│  ┌──────────┐                 ┌──────────┐              │
│  │ D  ← TX  │                 │ D  ← TX  │              │
│  │ R  → RX  │                 │ R  → RX  │              │
│  │ RE → GND │                 │ RE ──┐   │              │
│  │ SHDN→VCC│                 │ DE ──┤   │ ← GPIO       │
│  │ A  ↔ 485+│                 │ A  ↔ 485+│              │
│  │ B  ↔ 485-│                 │ B  ↔ 485-│              │
│  └──────────┘                 └──────────┘              │
│                                                         │
│  ★ 方向自动切换                ★ 需要 GPIO 主动控制       │
│    无 DE 引脚                    DE + RE 并联接 GPIO      │
└─────────────────────────────────────────────────────────┘
  • THVD1406:通过 D 引脚电平变化自动触发方向切换,t_device_autodir ≈ 8µs,应用层完全无感
  • ISO3082:DE(pin5,高有效)和 RE(pin4,低有效)必须由外部 GPIO 控制。通常将 DE 和 RE 并联,一个 GPIO 同时控制收发方向

1.3 BCU 硬件接线

RK3568 核心板                     ISO3082 芯片
┌──────────────┐                ┌──────────────┐
│ UARTn_TXD    │───────────────→│ D  (pin 6)   │
│ UARTn_RXD    │←───────────────│ R  (pin 3)   │
│ GPIO3_Cx     │──────┬────────→│ DE (pin 5)   │
│              │      └────────→│ RE (pin 4)   │  ← DE/RE 并联
│ GND          │───────────────→│ GND1         │
└──────────────┘                └──────────────┘

1.4 GPIO 引脚分配

串口设备节点UART 控制器DE 控制引脚chip/lineKernel GPIO 编号
1/dev/ttyS3UART3GPIO3_C4chip3, line20116
2/dev/ttyS4UART4GPIO3_B6chip3, line14110
3/dev/ttyS5UART5GPIO3_C1chip3, line17113
4/dev/ttyS9UART9GPIO3_B5chip3, line13109

2. 问题描述

2.1 现象

FCU2601 的 RS485 驱动代码直接搬到 BCU 后,所有 485 从设备无法通信

2.2 根因分析

原有代码 rtu.c 中:

// modbus_rtu_set_serial_mode(ctx->mb_ctx, MODBUS_RTU_RS485);  // ← 被注释掉

这行代码原本的作用是通知内核 8250 驱动通过 RTS 引脚自动控制方向。但在 FCU2601 上被注释掉了,因为 THVD1406 芯片内部自动完成方向切换,根本不需要外部控制。

到了 BCU 平台,RS485 芯片换成了 ISO3082

  • ISO3082 没有自动方向控制能力
  • BCU 硬件上 没有将 RTS 引脚连接到 ISO3082 的 DE/RE
  • 而是用了独立的 GPIO(GPIO3 组的 4 个引脚)

结果:DE 始终为低电平,ISO3082 永远处于接收模式,无法发送数据。

2.3 为什么不用内核 RS485 框架?

Linux 内核提供了标准的 RS485 支持:通过 ioctl(TIOCSRS485) 让 8250 驱动自动控制 RTS 引脚。但 BCU 硬件设计时没有把 UART 的 RTS 信号接到 ISO3082,而是用了独立的 GPIO,所以内核方案行不通,必须在应用层控制。


3. 解决方案

3.1 方案选择

方案描述评估
A. 内核 RS485 框架设备树加 linux,rs485-enabled-at-boot-time,内核自动控制 RTS❌ BCU 未使用 RTS 引脚
B. 应用层 GPIO 控制在 modbus 收发前后通过 sysfs 操作 GPIO选用

3.2 核心设计

modbus_slave_thread 的收发循环中,包裹 GPIO 控制:

正常状态(接收):DE = 0,ISO3082 处于接收模式
        │
        ↓ poll() 检测到 POLLIN
        │
modbus_receive()     ← 接收从机请求(DE=0)
        │
        ↓
rs485_set_tx()       ← GPIO 写 "1",DE=1,切换到发送模式
        │
modbus_reply()       ← 发送响应数据
        │
rs485_set_rx()       ← tcdrain() + GPIO 写 "0",切回接收模式
        │
handle_modbus_write()← 处理写操作
        │
        ↓ 继续 poll 等待

3.3 代码改动

3.3.1 global.h — 新增 DE GPIO 字段
typedef struct {
    // ... 原有字段 ...

    // RS485 DE GPIO 控制
    int de_gpio_num;    // GPIO 全局编号 (chip*32+line),-1=禁用
    int de_gpio_fd;     // /sys/class/gpio/gpioN/value 文件描述符

    // Modbus 相关
    modbus_t* mb_ctx;
    modbus_mapping_t* mb_mapping;
} SerialCtx;
3.3.2 rtu.c — GPIO 映射表
static const int DE_GPIO_MAP[NUM_SERIAL_PORTS] = {
    116,   // serial_port=1  /dev/ttyS3  UART3  GPIO3_C4
    110,   // serial_port=2  /dev/ttyS4  UART4  GPIO3_B6
    113,   // serial_port=3  /dev/ttyS5  UART5  GPIO3_C1
    109,   // serial_port=4  /dev/ttyS9  UART9  GPIO3_B5
    -1, -1, -1, -1, -1, -1, -1,   // 预留,-1 = 禁用
};
3.3.3 rtu.c — GPIO 控制函数
// 初始化:export GPIO → 设为输出 → 初始值 LOW(接收模式)
static int rs485_de_gpio_init(SerialCtx *ctx) { ... }

// 清理:恢复 LOW → close fd → unexport GPIO
static void rs485_de_gpio_cleanup(SerialCtx *ctx) { ... }

// 发送前:DE = HIGH
static inline void rs485_set_tx(SerialCtx *ctx) {
    if (ctx->de_gpio_fd >= 0)
        write(ctx->de_gpio_fd, "1", 1);
}

// 发送后:等待 FIFO 空 → DE = LOW
static inline void rs485_set_rx(SerialCtx *ctx, int uart_fd) {
    if (ctx->de_gpio_fd >= 0) {
        tcdrain(uart_fd);           // ★ 关键:等 UART 发完
        write(ctx->de_gpio_fd, "0", 1);
    }
}
3.3.4 rtu.c — modbus_slave_thread 改造
// 初始化阶段:连接串口后初始化 GPIO
//
//     rs485_de_gpio_init(ctx);   ← 新增

// 主循环中的收发:
//
//     if (rc > 0) {
//         rs485_set_tx(ctx);                                ← 新增
//         modbus_reply(ctx->mb_ctx, query, rc, ctx->mb_mapping);
//         rs485_set_rx(ctx, fd);                            ← 新增
//         handle_modbus_write(ctx, global_ctx, query);
//     }

// 退出时清理:
//
//     rs485_de_gpio_cleanup(ctx);  ← 新增
//     modbus_close(ctx->mb_ctx);

3.4 兼容性设计

de_gpio_num = -1 时所有 GPIO 操作自动跳过:

if (ctx->de_gpio_num < 0)
    return 0;  // 无需 DE 控制,直接返回

这意味着同一份代码可以同时兼容:

  • FCU2601DE_GPIO_MAP 全部填 -1,THVD1406 自动方向控制
  • BCUDE_GPIO_MAP 填实际 GPIO 编号,ISO3082 应用层控制

4. 测试方案

4.1 测试环境

┌──────────────────────────────────────────────────────────┐
│   BCU  ←──RS485总线──→  模拟从机设备/PC端Modbus工具        │
│                                                          │
│  4路 RS485 接口:                                         │
│  /dev/ttyS3  (UART3)  ←→  从机设备组1                     │
│  /dev/ttyS4  (UART4)  ←→  从机设备组2                     │
│  /dev/ttyS5  (UART5)  ←→  从机设备组3                     │
│  /dev/ttyS9  (UART9)  ←→  从机设备组4                     │
└──────────────────────────────────────────────────────────┘

4.2 测试步骤

步骤操作验证点
1上电,启动 com_run 服务观察启动日志
2检查 GPIO 初始化日志RS485 DE GPIOxxx (chipx,linexx) OK
3用 PC Modbus 工具发送读请求是否能收到正确响应

4.3 关键观察点

  • GPIO 时序rs485_set_tx → 数据发送 → tcdrainrs485_set_rx 的间隔是否合理
  • 总线冲突:GPIO 是否在数据完全发完后才切回接收模式(tcdrain 是关键)
  • 异常恢复:通信超时后 GPIO 是否正确恢复为接收模式

5. 测试结果

5.1 启动日志

Jun 10 13:47:14 ok3568 com_run[25008]: ✓ Modbus 从机启动: /dev/ttyS3 (从机ID=16)
Jun 10 13:47:14 ok3568 com_run[25008]: ✓ Modbus 从机启动: /dev/ttyS4 (从机ID=16)
Jun 10 13:47:14 ok3568 com_run[25008]: ✓ Modbus 从机启动: /dev/ttyS5 (从机ID=16)
Jun 10 13:47:14 ok3568 com_run[25008]: ✓ Modbus 从机启动: /dev/ttyS9 (从机ID=16)
Jun 10 13:47:14 ok3568 com_run[25008]: 串口5~11: 未使能,跳过
Jun 10 13:47:14 ok3568 com_run[25008]: === 启动完成,共 4 个 Modbus 从机运行 ===

5.2 GPIO 初始化成功

[/dev/ttyS9] RS485 DE GPIO109 (chip3,line13) OK
[/dev/ttyS5] RS485 DE GPIO113 (chip3,line17) OK
[/dev/ttyS3] RS485 DE GPIO116 (chip3,line20) OK
[/dev/ttyS4] RS485 DE GPIO110 (chip3,line14) OK

4 路 GPIO 全部 export、设置为输出、初始化为 LOW 一次性成功。

5.3 联调结果

测试项结果
GPIO 初始化✅ 4/4 通过
Modbus 从机启动✅ 4/4 通过
从机读写响应✅ 正常
长时间运行稳定性✅ 无异常
GPIO 波形(示波器)✅ 发送时拉高,发送后恢复低电平

5.4 GPIO sysfs 验证

板端可随时确认 GPIO 状态:

# 查看 GPIO 已正确导出
ls /sys/class/gpio/
# export  gpio109  gpio110  gpio113  gpio116  ...

# 查看 GPIO 当前值(0=接收模式,1=发送模式)
cat /sys/class/gpio/gpio109/value   # 空闲时应为 0

6. 经验总结

6.1 设计决策

  1. 为什么不放内核层?
    BCU 硬件未使用 UART 的 RTS 引脚,而是独立 GPIO 控制 DE/RE。内核 RS485 框架依赖 RTS,无法适配。应用层控制更灵活。

  2. 为什么用 sysfs 而不是 libgpiod?
    选择 sysfs 的原因:

    • 保持文件描述符打开,write(fd, "0"/"1", 1) 一次系统调用完成切换,性能足够
    • OK3568 的 Linux 4.19 内核完全支持 sysfs GPIO
    • 无需额外链接 libgpiod,依赖更少
  3. tcdrain() 为什么不能省略?
    UART 有硬件 FIFO(通常 64 字节),write() 返回只代表数据写入内核缓冲区,不代表硬件发送完成。不做 tcdrain() 会导致 GPIO 提前拉低,截断正在发送的数据帧,造成总线冲突。

6.2 踩过的坑

  • 编码问题:项目源文件是 GBK 编码,用文本工具直接编辑中文注释会损坏文件。最终用 PowerShell 字节级操作完成修改。
  • 备份先行:每次修改前 .orig 文件是救命稻草。

6.3 后续优化建议

  1. 设备树配置化:可将 DE GPIO 信息写入设备树,应用层通过 /proc/device-tree 读取,避免硬编码
  2. 数据库配置化:在 bcu_cfg.db 的"串口配置"表中增加 de_gpio 字段,实现完全可配置
  3. 性能优化:当前 write() 走 sysfs,可改为 libgpiod 的 chardev ioctl 接口,减少 sysfs 开销
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐