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

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

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


1. 硬件现场

1.1 两代平台对比

FCU2601(上一代) BCU(新一代)
核心板 RK3568 RK3568
RS485 芯片 THVD1406(TI) ISO3082(TI)
隔离特性 无隔离 2500Vrms 隔离
方向控制 芯片自动完成 需要 CPU GPIO 控制
封装 8-Pin 16-Pin SOIC
RS485 路数 11 路 4 路
使用 UART --- UART3 / 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/line Kernel GPIO 编号
1 /dev/ttyS3 UART3 GPIO3_C4 chip3, line20 116
2 /dev/ttyS4 UART4 GPIO3_B6 chip3, line14 110
3 /dev/ttyS5 UART5 GPIO3_C1 chip3, line17 113
4 /dev/ttyS9 UART9 GPIO3_B5 chip3, line13 109

2. 问题描述

2.1 现象

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

2.2 根因分析

原有代码 rtu.c 中:

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 字段
c 复制代码
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 映射表
c 复制代码
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 控制函数
c 复制代码
// 初始化: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 改造
c 复制代码
// 初始化阶段:连接串口后初始化 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 操作自动跳过:

c 复制代码
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 启动日志

log 复制代码
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 初始化成功

log 复制代码
[/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 状态:

bash 复制代码
# 查看 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 开销
相关推荐
谢平康4 小时前
解决用 rm 报bash: /usr/bin/rm: Argument list too long错
linux·运维·运维开发
hj2862516 小时前
Linux 网络服务综合笔记(概念 + 命令 + 实操案例)2
linux·运维·网络
what_20186 小时前
Linux 磁盘 (查看、划分、inode)
linux·运维·服务器
2739920296 小时前
GDB调试(Linux)
linux
凡人叶枫6 小时前
Effective C++ 条款23:宁以 non-member、non-friend 替换 member 函数
linux·开发语言·c++·嵌入式开发
不会C语言的男孩7 小时前
Linux 系统编程 · 第 4 章:文件属性与元数据
linux·c语言·开发语言
小生不才yz7 小时前
Shell脚本精读 · S02-03 | 词拆分、通配符与未加引号的变量
linux
2601_961845427 小时前
法考真题及答案解析|历年真题|资料已整理
linux·windows·ubuntu·macos·centos·gnu
A_humble_scholar7 小时前
Linux(七)调度器:从硬件矛盾到进程切换的底层逻辑
linux·服务器·网络