🤖 roboto_origin_03 Wiki
首页 / IMU 子模块 / 整体架构与模块划分

本文档从第一性原理出发,系统梳理 roboto_imu 的代码组织方式与模块边界。阅读本文后,你将理解:为什么代码被拆分为这四个目录、每个模块对外暴露什么接口、以及数据从硬件总线到用户 API 的完整流转路径。若你正准备为新的 IMU 厂商编写驱动,或需要排查 CAN/串口数据丢失问题,先建立对整体架构的认知将大幅减少定位成本。

设计哲学:分层隔离与接口抽象

roboto_imu 的核心设计目标是在同一抽象接口下屏蔽硬件差异。为此,项目采用严格的分层架构:上层(C++/Python SDK)只与抽象基类交互,中层(具体驱动)负责把硬件协议转译为统一数据结构,底层(通信协议层)只关心字节流的收发。任何一层都不应直接穿透到另一层的内部实现。这种隔离使得新增一个 IMU 型号时,只需在 src/drivers/ 下新增一个子目录,而 SDK 接口、CAN/串口基础设施、甚至 Python 绑定都无需改动。

Sources: imu_driver.hpp

四层架构概览

整个仓库可归纳为四层两平面:纵向四层自上而下分别为 SDK 层、抽象驱动层、具体驱动层、通信协议层;横向两平面分别为 C++ 原生平面与 Python 绑定平面。下图展示了关键类/模块的隶属关系与依赖方向。

graph TB
    subgraph SDK_Plane["SDK 平面 (C++ / Python)"]
        Py["imu_py (pybind11)"]
        CPP["IMUDriver (抽象基类)"]
    end

    subgraph Driver_Plane["驱动平面"]
        Factory["create_imu() 工厂"]
        Hipnuc["HipnucIMUDriver<br/>具体驱动"]
    end

    subgraph Protocol_Plane["通信协议平面"]
        CAN["IMUSocketCAN<br/>(SocketCAN 单例)"]
        Serial["IMUSerialPort<br/>(UART 封装)"]
    end

    subgraph HW_Plane["硬件平面"]
        CAN_IF["CAN 总线 (can0...)"]
        SER_IF["串口 (/dev/ttyUSB0...)"]
    end

    Py --> CPP
    CPP --> Factory
    Factory --> Hipnuc
    Hipnuc --> CAN
    Hipnuc --> Serial
    CAN --> CAN_IF
    Serial --> SER_IF

    style Py fill:#e1f5fe
    style CPP fill:#e8f5e9
    style Hipnuc fill:#fff3e0
    style CAN fill:#fce4ec
    style Serial fill:#fce4ec

Sources: imu_driver.cpp, pybind_module.cpp, hipnuc_imu_driver.hpp

模块职责与代码边界

下表按构建产物维度汇总各模块的职责、对外接口与代码规模,便于你在定位问题时快速锁定目标文件。

构建产物 源码位置 核心职责 关键接口/类 代码规模 (约)
imu (静态库) include/imu_driver.hpp<br>src/imu_driver.cpp 定义抽象基类与工厂入口 IMUDriver 基类、create_imu() 60 行
imu_py (Python 模块) src/pybind_module.cpp 将 C++ API 暴露给 Python PYBIND11_MODULE(imu_py, m) 24 行
hipnuc_imu (静态库) src/drivers/hipnuc/ HiPNUC 设备协议解析与驱动实现 HipnucIMUDriverhipnuc_dec_*hipnuc_j1939_* 2,300+ 行
imu_protocol (静态库) src/protocol/can/<br>src/protocol/serial/ Linux 底层通信封装与接收线程 IMUSocketCANIMUSerialPort 420 行

下面按分层顺序逐一展开各模块的设计意图。

SDK 层:统一数据语义

IMUDriver 作为抽象基类,是所有 IMU 设备在代码中的最大公约数。它规定了四元数、角速度、线加速度、温度的统一数据格式与单位:四元数为 [w, x, y, z],角速度为 rad/s,线加速度为 m/s²。无论你底层接的是 CAN 还是串口、解析的是 J1939 还是私有协议,最终都必须通过这个接口向上层交付标准化数据。该层无状态、无线程,只负责定义契约。

Sources: imu_driver.hpp

抽象驱动层:工厂与多态

src/imu_driver.cpp 仅包含一个工厂方法 IMUDriver::create_imu(),它根据 imu_type 字符串创建对应的具体驱动实例。当前仅支持 "HIPNUC",返回 std::shared_ptr<HipnucIMUDriver>。工厂模式的意义在于:SDK 层代码在编译期无需知道任何具体驱动类的存在,从而彻底解耦。这一设计为后续引入新厂商驱动预留了零侵入的扩展点。

Sources: imu_driver.cpp

具体驱动层:协议转译与线程安全

HipnucIMUDriver 是抽象基类的唯一实现,位于 src/drivers/hipnuc/。该模块的核心工作可概括为**“字节流 → 结构化数据 → 标准化单位”**的三段式处理。以 CAN 接口为例:它向 IMUSocketCAN 注册一个回调 can_rx_cbk(),每当总线收到匹配的 CAN 帧时,回调内部调用 hipnuc_j1939_parse_frame() 解析 PGN,随后将加速度从 mG 转为 m/s²、陀螺仪从 °/s 转为 rad/s,最后写入受 shared_mutex 保护的 sensor_data_ 结构。串口路径类似,但由 hipnuc_input() 逐字节解析私有协议包 0x91。所有的线程安全逻辑都收敛在这一层,上层调用 get_quat() 等只读方法时通过 shared_lock 获取快照,避免阻塞实时接收线程。

Sources: hipnuc_imu_driver.cpp, hipnuc_imu_driver.hpp

通信协议层:硬件无关的字节流封装

该层位于 src/protocol/,包含两个完全独立的子模块,各自负责一种物理接口的生命周期管理实时接收线程

CAN 子模块 (IMUSocketCAN) 采用单例模式按接口名(如 "can0")管理实例,确保同一 CAN 接口在进程内只有一个 SocketCAN 文件描述符和一个接收线程。它内部运行一个 SCHED_FIFO 实时调度线程,通过 select() 轮询非阻塞套接字,收到帧后依据用户设置的 key_extractor 将帧分发到对应的回调函数。这一设计允许多个 HipnucIMUDriver 实例(对应不同 IMU ID)共享同一条 CAN 总线,而不会创建重复的线程或套接字。

Sources: socket_can.hpp, socket_can.cpp

串口子模块 (IMUSerialPort) 采用工厂方法(非单例),每个串口设备独立拥有一个文件描述符和一个 SCHED_FIFO 接收线程。它封装了 termios 配置逻辑,支持 9600 至 921600 的常见波特率,同样通过 select() 实现超时读取,并将收到的原始字节数组直接投递给上层回调。与 CAN 不同,串口模块天然一对一,因此无需复杂的回调路由机制。

Sources: serial_port.hpp, serial_port.cpp

协议解析层:纯 C 的嵌入式友好实现

src/drivers/hipnuc/ 中,除了 C++ 的驱动类之外,还包含一组以 .c 结尾的协议解析器。它们被设计为无堆分配、无依赖、可裸机运行的纯 C 库,包括:

C++ 驱动类通过 extern "C" 包含这些头文件,从而在不改写历史 C 代码的前提下完成协议对接。这种混合语言策略既保留了 C 代码的可移植性,又借助 C++ 的类与 RAII 实现了资源管理。

Sources: hipnuc_dec.h, hipnuc_can_common.h, hipnuc_j1939_parser.h

构建系统与产物映射

项目的构建逻辑通过三层 CMakeLists.txt 将源码编译为四个主要目标,依赖关系如下:

graph BT
    imu_py["imu_py (Python .so)"]
    imu["imu (静态库)"]
    hipnuc_imu["hipnuc_imu (静态库)"]
    imu_protocol["imu_protocol (静态库)"]

    imu_py --> imu
    imu --> hipnuc_imu
    imu --> imu_protocol
    hipnuc_imu --> imu_protocol

顶层 CMakeLists.txt 使用 ament_cmake 做 ROS 2 集成探测,同时为非 ROS 消费者安装 roboto_imuConfig.cmake,支持 Debian 打包场景。pybind11_add_modulesrc/pybind_module.cpp 编译为 imu_py 模块,安装路径按当前 Python 版本动态生成。所有核心库均使用 -O3-march=native 优化,并开启 PIC 以支持动态链接。

Sources: CMakeLists.txt, src/CMakeLists.txt, src/drivers/hipnuc/CMakeLists.txt, src/protocol/CMakeLists.txt

线程模型与数据流

理解数据从硬件到用户代码的流动路径,是排查丢包、延迟或线程竞争的前提。下图以 CAN 路径为例展示时序关系:

sequenceDiagram
    participant HW as CAN 总线
    participant RX as IMUSocketCAN<br/>实时接收线程
    participant CB as HipnucIMUDriver<br/>回调 (can_rx_cbk)
    participant LOCK as shared_mutex<br/>(unique_lock)
    participant USER as 用户线程<br/>(get_quat)

    RX->>HW: select() + read()
    HW-->>RX: can_frame
    RX->>RX: key_extractor(frame.can_id)
    RX->>CB: 匹配回调
    CB->>CB: hipnuc_j1939_parse_frame()
    CB->>LOCK: 获取写锁
    CB->>CB: 更新 sensor_data_<br/>(单位转换)
    CB-->>LOCK: 释放写锁
    USER->>LOCK: 获取读锁 (shared_lock)
    USER->>USER: 复制四元数/加速度
    USER-->>LOCK: 释放读锁

关键设计要点:接收线程以 SCHED_FIFO、优先级 80 运行,确保数据及时出队;回调内部持有 unique_lock 的时间被严格限制在内存写入与单位转换之间,避免阻塞接收循环;用户线程通过 shared_lock 并发读取,实现一写多读的无饥饿模型。

Sources: socket_can.cpp, hipnuc_imu_driver.cpp

扩展路径:如何增加新厂商驱动

若需支持 IMU 厂商 X,按当前架构只需执行以下三步,无需修改 SDK 或通信层:

  1. src/drivers/ 下新建 vendor_x/ 目录。
  2. 实现 VendorXIMUDriver : public IMUDriver,内部可选择复用 IMUSocketCAN/IMUSerialPort,或新增通信方式。
  3. src/imu_driver.cppcreate_imu() 中增加 "VENDOR_X" 分支。

这种扩展性正是分层架构与工厂模式带来的直接收益。关于工厂实现的细节,可继续阅读 工厂模式与抽象驱动设计

推荐阅读顺序

整体架构建立认知后,建议按以下路径深入各模块的实现细节:

  1. 抽象与多态机制工厂模式与抽象驱动设计 — 理解 IMUDriver 基类设计、create_imu() 工厂方法与 shared_ptr 生命周期管理。
  2. 线程安全实现传感器数据访问与线程安全 — 深入 shared_mutex 的读写锁策略、can_sensor_data_t 的原子性快照与实时性权衡。
  3. 通信层源码SocketCAN 单例与实时接收线程串口通信与 UART 接收线程 — 分别剖析 select() 轮询、SCHED_FIFO 配置、回调路由与错误处理。
  4. 协议解析细节J1939 协议解析与 PGN 映射超核私有协议解码CANopen TPDO 解析 — 按需阅读对应硬件协议。
  5. 多语言与部署pybind11 Python 绑定机制CMake 构建系统与依赖管理 — 若需扩展 Python API 或修改构建逻辑。