Skip to content

Comments

feat(firmware/c_board): Migrate c_board firmware from rmcs_slave to librmcs v3 (WIP)#19

Merged
qzhhhi merged 5 commits intomainfrom
dev/cboard
Feb 17, 2026
Merged

feat(firmware/c_board): Migrate c_board firmware from rmcs_slave to librmcs v3 (WIP)#19
qzhhhi merged 5 commits intomainfrom
dev/cboard

Conversation

@qzhhhi
Copy link
Member

@qzhhhi qzhhhi commented Feb 16, 2026

c_board 固件项目初始化(更新)

概述

本 PR 为 c_board 固件引入完整工程骨架、构建/CI 配置与丰富固件子系统实现,包含构建预设、静态分析配置、外设驱动、通信链路与若干实用工具,影响范围较大,需全面审查。

关键变更

构建与 CI

  • 新增 firmware/c_board/CMakeLists.txt 与 CMakePresets.json(base/debug/debug-outside/release 预设)。
  • 为 firmware/c_board 添加 .clang-tidy 与 .clangd 配置。
  • 在 .github/workflows/lint.yml 中新增 "Configure firmware/c_board" 步骤(cmake --preset debug -S firmware/c_board)。
  • 在 .scripts/lint-targets.yml 中注册 lint 目标 c_board(指定 firmware/c_board 的源/include 路径)。

应用框架

  • 新增 App 单例(firmware/c_board/src/app.hpp/.cpp),提供 extern "C" AppEntry() 入口。构造时初始化外设(LED、USB、CAN、UART、BMI088 等),并实现 [[noreturn]] App::run() 主循环,定期调用各外设的 try_transmit/处理逻辑。

外设与通信驱动

  • CAN(firmware/c_board/src/can/)
    • 新增 Can 类:滤波配置、TX 环形缓冲、收/发解析与 HAL 集成;新增 HAL_CAN_RxFifo0MsgPendingCallback 回调。
  • UART(firmware/c_board/src/uart/)
    • 新增 Uart 编排类与实例(uart1/uart2/uart_dbus),支持双缓冲异步发送、接收到空闲触发;新增 HAL_UARTEx_RxEventCallback 回调。
  • SPI(firmware/c_board/src/spi/)
    • 新增 SpiModule 抽象与 Spi 管理类(同步/异步传输、原子锁、回调);新增 HAL_SPI_TxRxCpltCallback。
  • USB CDC(firmware/c_board/src/usb/)
    • 新增 Vendor 类与实现(Deserializer/Serializer、批次传输管理)、InterruptSafeBuffer(8 批次)用于中断安全传输、WCID 支持(wcid.cpp),并导出 USBD 接口函数绑定。
  • GPIO(gpio.cpp):处理 BMI088 INT 引脚并触发相应设备数据就绪回调。
  • TIM/延时(timer/):基于 DWT 的高精度 delay 实现,并重定向 HAL_Delay/HAL_IncTick(在滴答中调度 LED 更新)。

传感器驱动(BMI088)

  • 新增 BMI088 基类(重试、异步读)、加速度计 accel.hpp 与陀螺仪 gyro.hpp 驱动;在构造时完成设备初始化并通过中断触发异步读取,上行使用序列化器发送数据。

LED、日志与断言

  • LED(led.hpp):使用 TIM PWM 驱动 RGB,提供呼吸与闪烁指示,使用原子计数管理“缓冲满”指示。
  • Logger(logger.hpp):基于 SEGGER RTT 的轻量 printf 接口与 Lazy 懒加载实例。
  • 断言实现(utility/assert.cpp):记录断言位置(文件/行/函数)并触发陷阱以便调试。

实用工具

  • Lazy(utility/lazy.hpp):中断锁保护的延迟初始化模板(懒初始化,RAII 临界区)。
  • InterruptLock(utility/interrupt_lock.hpp):全局中断锁与 RAII 守护类。
  • RingBuffer(utility/ring_buffer.hpp):无锁单生产者/单消费者环形缓冲模板,支持批量操作与清理语义。
  • InterruptSafeBuffer(firmware/c_board/src/usb):多批次、面向中断的传输缓冲(用于 USB 发送批次管理)。

API 与导出实体(摘要)

  • 新增多种类与全局懒初始化实例,例如:librmcs::firmware::can::Can、librmcs::firmware::uart::Uart、librmcs::firmware::spi::Spi、librmcs::firmware::usb::Vendor、led::Led、logger::Logger、spi::bmi088::Accelerometer/Gyroscope 等。
  • 新增多处 extern "C" 回调以配合 STM32 HAL:HAL_CAN_RxFifo0MsgPendingCallback、HAL_UARTEx_RxEventCallback、HAL_SPI_TxRxCpltCallback、HAL_GPIO_EXTI_Callback、USBD 接口回调、HAL_Delay、HAL_IncTick 等。

技术要点与设计取舍

  • 并发与中断安全通过原子与中断锁实现(Lazy、RingBuffer、InterruptSafeBuffer、InterruptLock)。
  • 异步传输与回调模型贯穿 SPI/USB/UART,减少阻塞、提高实时性。
  • 驱动层封装 HAL 细节,暴露上行/下行接口以便协议层(Serializer/Deserializer)交互。
  • 增加大量模板与低级并发代码,需重点审查内存序、原子操作与中断临界区正确性。

审查建议(优先级)

  1. 并发/内存序检查:RingBuffer、Lazy、InterruptSafeBuffer 的原子内存序和锁自由假设是否安全。
  2. 中断上下文安全性:HAL 回调中调用的函数是否均为 IRQ-safe(尤其是内存分配、阻塞或非重入 API)。
  3. 缓冲边界与生命周期:USB 批次管理、UART 双缓冲、CAN TX 环形缓冲的溢出/重入/释放路径。
  4. 驱动初始化顺序与外设依赖:CMake/CMakePresets 与源码中假定的外部符号(如 hspi1/hcan1/huartX)是否一致。
  5. 静态分析配置一致性:.clang-tidy/.clangd 与仓库其它布局是否匹配。

变更规模与影响

  • 新增大量文件(固件模块、驱动、工具、CI 配置等),多处大文件(若干超百行),整体审查工作量为中到高。建议分模块逐步评审并在实机/仿真上进行集成测试。

Add class-scoped identifier-naming rules in .clang-tidy:
- ClassMemberCase/ClassMemberSuffix
- ClassConstantCase/ClassConstantPrefix

This fixes clang-tidy suggesting plain lower_case names for class static members (e.g. `variable`) and enforces the project style (`variable_`).
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Walkthrough

新增完整的 c_board 固件目标与实现:包含构建配置、Clang 工具配置、主程序入口、外设驱动(SPI/BMI088、CAN、UART、USB)、LED/GPIO/定时、并发/缓冲/工具库与相应 HAL 回调。

Changes

Cohort / File(s) Summary
CI / lint 配置
​.github/workflows/lint.yml, .scripts/lint-targets.yml
在 lint 工作流与 lint 目标中添加对 firmware/c_board 的 CMake 预设配置与目标 c_board
构建 & IDE 配置
firmware/c_board/CMakeLists.txt, firmware/c_board/CMakePresets.json, firmware/c_board/.clang-tidy, firmware/c_board/.clangd
新增项目 CMakeLists 与预设,设置编译/映射/工具链与 clang-tidy/clangd 配置。
应用入口
firmware/c_board/src/app.hpp, firmware/c_board/src/app.cpp
新增 App 类与 AppEntry,构造时初始化外设并在主循环周期性触发各外设 try_transmit()
CAN 子系统
firmware/c_board/src/can/can.hpp, firmware/c_board/src/can/can.cpp
新增 Can 类(缓冲、Tx/Rx 解析、HAL 过滤配置)及 HAL_CAN_RxFifo0MsgPendingCallback 回调,提供 can1/can2 全局实例。
UART 子系统
firmware/c_board/src/uart/uart.hpp, firmware/c_board/src/uart/uart.cpp
新增 Uart 类(双缓冲发送、接收到空闲事件、uplink/downlink 处理)及 HAL_UARTEx_RxEventCallback,并声明三个 UART 实例。
SPI 子系统
firmware/c_board/src/spi/spi.hpp, firmware/c_board/src/spi/spi.cpp
新增 SpiModule/Spi(CS 管理、同步/异步传输、回调分发)及 HAL_SPI_TxRxCpltCallback,并提供 spi1 实例。
BMI088 驱动
firmware/c_board/src/spi/bmi088/base.hpp, .../accel.hpp, .../gyro.hpp
新增通用 Bmi088Base 与加速计/陀螺驱动,含读写确认、异步读取、数据就绪回调与 uplink 序列化。
USB / Vendor 与缓冲
firmware/c_board/src/usb/vendor.hpp, .../vendor.cpp, .../interrupt_safe_buffer.hpp, .../helper.hpp, .../wcid.cpp
新增 Vendor 类(Deserializer/Serializer、批次传输)、中断安全批次缓冲、WCID 响应与 USB CDC HAL 接口实现。
LED / GPIO / 定时
firmware/c_board/src/led/led.hpp, .../gpio/gpio.cpp, .../timer/delay.hpp, .../timer/delay.cpp
新增 LED PWM 控制逻辑、GPIO EXTI 回调、基于 DWT 的精确 delay 实现及 HAL 钩子(HAL_IncTick/HAL_Delay)。
日志
firmware/c_board/src/logger/logger.hpp
新增基于 SEGGER RTT 的轻量 Logger(printf 接口)与懒初始化访问器。
并发 / 工具 库
firmware/c_board/src/utility/assert.cpp, .../interrupt_lock.hpp, .../lazy.hpp, .../ring_buffer.hpp
新增断言记录实现、中断互斥 RAII、线程安全 Lazy 模板与单生产者单消费者无锁 RingBuffer
RMCS 现有调整
firmware/rmcs_board/src/usb/interrupt_safe_buffer.hpp, firmware/rmcs_board/src/utility/assert.cpp
为旧实现引入或调整 kMask 常量与指针 cv-限定顺序(语义等价的小改动)。

Sequence Diagram(s)

sequenceDiagram
    participant App as App
    participant LED as LED
    participant IMU as BMI088
    participant SPI as SPI1
    participant USB as USB Vendor
    participant Buf as InterruptSafeBuffer
    participant CAN as CAN1/CAN2
    participant UART as UARTs

    App->>LED: init()
    App->>SPI: init()
    App->>IMU: init()
    App->>USB: init()
    App->>CAN: init()
    App->>UART: init()

    loop main loop
        App->>USB: try_transmit()
        USB->>Buf: pop_batch()/serialize()
        Buf-->>USB: batch data

        App->>CAN: try_transmit()
        CAN->>CAN: dequeue & write TX mailbox

        App->>UART: try_transmit()
        UART->>UART: start HAL transmit IT

        IMU->>SPI: read_async()
        SPI->>IMU: TxRx Complete (HAL interrupt)
        IMU->>USB: handle_uplink(serializer)
        USB->>Buf: allocate() / queue batch

        App->>LED: update(tick)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

诗篇

🐰 新板启程心欢欣,
CAN 与 UART 并肩亲;
SPI 轻唤 IMU 念,
USB 缓冲稳传信,
懒兔守护夜未尽。

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into dev/cboard-middle
Title check ✅ Passed 标题清晰且与主要变更相关。PR标题'feat(firmware/c_board): Migrate c_board firmware from rmcs_slave to librmcs v3 (WIP)'准确概括了主要变更:将c_board固件从rmcs_slave迁移到librmcs v3。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev/cboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qzhhhi
Copy link
Member Author

qzhhhi commented Feb 16, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@firmware/c_board/src/timer/delay.cpp`:
- Around line 18-22: HAL_IncTick currently dereferences led::led which may not
be initialized when SysTick fires during HAL_Init; update HAL_IncTick to guard
the call by checking the Lazy LED object's initialization state before calling
led::led->update(tick) (e.g., use led::led.is_initialized() or a
boolean/operator on the Lazy wrapper), or alternatively ensure the interrupt
masking/InterruptMutex semantics prevent SysTick from reaching HAL_IncTick
before App::App() calls led::led.init(); reference HAL_IncTick, led::led,
led::Led::update and the Lazy init path when making the change.

In `@firmware/c_board/src/uart/uart.hpp`:
- Around line 23-25: The template parameter types are inconsistent: Lazy is
declared as utility::Lazy<Uart, UART_HandleTypeDef*, size_t> while the Uart
constructor uses uint16_t for max_receive_size; change the Uart constructor
signature and any related member/storage from uint16_t to size_t so it matches
Lazy's second template parameter (update explicit Uart(UART_HandleTypeDef*
hal_uart_handle, uint16_t max_receive_size) to use size_t and adjust any uses of
max_receive_size accordingly), ensuring Lazy and Uart use the same size type.

In `@firmware/c_board/src/usb/wcid.cpp`:
- Around line 7-8: 修复 wcid.cpp 注释中的拼写重复:在注释段落(以 "WCID allows a device to be used
by a Windows application almost as soon as it is plugged in..." 开头)将出现的短语 "the
the usual scenario" 改为 "the usual scenario",保留其余注释不变以消除重复词错误。

In `@firmware/c_board/src/utility/assert.cpp`:
- Around line 7-9: 把 volatile 从指向类型前移到指针本身:当前声明 volatile const char* assert_file
和 volatile const char* assert_function 只让被指向的字符为 volatile,而非指针本身,应改为让指针本身为
volatile(同时保持被指向内容为 const),例如 将 assert_file 和 assert_function 的声明改为 const char *
volatile assert_file = nullptr; const char * volatile assert_function =
nullptr;,保留 volatile unsigned int assert_line
不变;在函数或断言设置处使用这些同名符号以确保调试器能读到文件/函数名。
🧹 Nitpick comments (13)
firmware/c_board/src/usb/wcid.cpp (1)

87-87: 函数返回类型为 uint8_t,但返回值使用 bool 字面量。

函数声明返回 uint8_t,内部却使用 true/false。虽然在 C++ 中 bool 可隐式转换为 uint8_t,但由于此函数通过 extern "C" 导出供 C 代码调用,建议考虑直接返回 bool(C99+ 支持 _Bool/bool),或者改用 1/0 以提高语义一致性。

firmware/c_board/.clangd (1)

4-7: 工具链版本 15.2.1 在四处硬编码。

.clangd 不支持变量替换,所以这里没有好的替代方案。但建议在文件顶部添加注释,提示升级工具链时需同步更新这些路径,以降低未来维护遗漏的风险。

firmware/c_board/CMakeLists.txt (2)

65-84: HOST_DEBUGGER=OFFSOURCE_PATH_MAP 仍可生效 — 请确认是否为预期行为。

Line 76 的条件 NOT SOURCE_PATH_MAP STREQUAL "" 独立于 HOST_DEBUGGER。这意味着用户可以在 HOST_DEBUGGER=OFF 的情况下手动传入 -DSOURCE_PATH_MAP=/some/path,路径映射仍然会被应用。如果这是有意为之(允许高级用户手动覆盖),建议添加注释说明;如果不是,应将 Line 76 的条件也纳入 HOST_DEBUGGER 的判断范围内。


91-98: file(GLOB_RECURSE ...) 配合 CONFIGURE_DEPENDS 是可接受的做法,但需注意局限性。

CONFIGURE_DEPENDS 可以在部分生成器(如 Ninja)下检测新增/删除文件并触发重新配置,但 CMake 官方文档仍建议在可行时显式列出源文件。对于嵌入式项目,当前方案是常见实践,此处仅作提示。

firmware/c_board/src/led/led.hpp (2)

99-99: 缺少 constinit,与其他 Lazy 实例声明不一致。

项目中其他 Lazy 全局实例(如 logger.hpp 中的 inline constinit Logger::Lazy logger;can.hpp 中的 inline constinit Can::Lazy can1{...};)均使用了 constinit。此处应保持一致,以确保编译器强制执行常量初始化。

建议修复
-inline utility::Lazy<Led> led;
+inline constinit utility::Lazy<Led> led;

14-18: HAL_TIM_PWM_Start 返回值未检查。

can.hppconfig_can() 中,所有 HAL 调用的返回值都通过 assert_always 检查了。建议此处也保持一致,在初始化阶段捕获潜在的硬件配置错误。

建议修复
 Led() {
-    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1);
-    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_2);
-    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3);
+    constexpr auto ok = HAL_OK;
+    core::utility::assert_always(HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1) == ok);
+    core::utility::assert_always(HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_2) == ok);
+    core::utility::assert_always(HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3) == ok);
     reset();
 }
firmware/c_board/src/uart/uart.cpp (2)

1-1: 头文件包含路径不一致

其他 .cpp 文件均使用完整项目路径引入对应头文件(如 "firmware/c_board/src/can/can.hpp"),此处使用了相对路径 "uart.hpp"。建议统一为完整路径以保持一致性。

建议修改
-#include "uart.hpp"
+#include "firmware/c_board/src/uart/uart.hpp"

Based on learnings: "In the librmcs project, enforce Google C++ Include Style in all C++ source files (.cpp/.cc). The include order should be: 1) the corresponding header..."


17-28: UART 外设与逻辑通道的映射关系不直观,建议添加注释

huart1 映射到 uart2huart6 映射到 uart1——物理外设编号与逻辑通道名称交叉对应,容易引起混淆。如果这是 PCB 布线导致的正确映射,建议添加简短注释说明原因,方便后续维护。

firmware/c_board/src/timer/delay.hpp (1)

33-54: delay 模板的溢出保护存在间隙

Line 40-42 仅当 sizeof(Rep) > sizeof(uint32_t) 时进行运行时断言,但对于 sizeof(Rep) == sizeof(uint32_t)Rep 为有符号类型(如 int32_t)的情况,负值已在上方处理,不会有问题。不过,如果 Repuint64_t,断言会生效,但 assert 在 release 构建中(NDEBUG)会被移除,此时大于 UINT32_MAX 的值会被静默截断。

考虑使用项目中已有的 core::utility::assert_always 替代 assert,确保 release 构建中也能捕获溢出。

建议修改
     if constexpr (sizeof(Rep) > sizeof(uint32_t)) {
-        assert(delay.count() <= std::numeric_limits<uint32_t>::max());
+        core::utility::assert_always(delay.count() <= std::numeric_limits<uint32_t>::max());
     }
firmware/c_board/src/uart/uart.hpp (1)

37-46: written_sizememcpy 之前更新——依赖裸机 ISR 原子性。

第 44-45 行先 store 了新的 written_size,第 46 行才执行 memcpy。如果 handle_downlink 在 ISR 中被调用(USB CDC 接收回调 → 反序列化 → UART 下行),而 try_transmit 在主循环中运行,由于单核裸机环境 ISR 运行到完成,这是安全的。但建议加一行简短注释说明此顺序依赖,以防未来维护者在多线程环境中误用。

firmware/c_board/src/usb/interrupt_safe_buffer.hpp (1)

49-49: kMask 的位置与 kBatchCount 不相邻,建议放在一起以提高可读性。

kMask 当前在 allocate() 方法之后定义(第 49 行),而与其紧密相关的 kBatchCount 在第 20 行。将两者放在一起更直观。

建议移动 kMask
     static constexpr size_t kBatchCount = 8;
     static_assert(std::has_single_bit(kBatchCount), "Batch count must be a power of 2");
+    static constexpr size_t kMask = kBatchCount - 1;

     constexpr InterruptSafeBuffer() = default;

     std::span<std::byte> allocate(size_t size) noexcept override {
         // ...
     }

-    static constexpr size_t kMask = kBatchCount - 1;
firmware/c_board/src/spi/spi.hpp (2)

92-97: tx_rx_size_ 类型为 size_t,但 HAL SPI 函数期望 uint16_t

tx_rx_size_(第 125 行,size_t)被直接传递给 HAL_SPI_TransmitReceiveHAL_SPI_TransmitReceive_IT,这两个函数的 Size 参数类型为 uint16_t(第 64 行、第 77 行)。虽然 kMaxTransferSize = 32 不会溢出,但这是隐式窄化转换,可能产生编译器警告。

建议将 tx_rx_size_ 改为 uint16_t
-    size_t tx_rx_size_{0};
+    uint16_t tx_rx_size_{0};

同时调整 transmit_receivetransmit_receive_asyncsize 参数,或在赋值处显式 static_cast

-    tx_rx_size_ = size;
+    tx_rx_size_ = static_cast<uint16_t>(size);

Also applies to: 120-125


17-36: SpiModule 同时继承 Immovable 并显式 delete 拷贝/移动操作——存在冗余。

Immovable 基类已经禁止了拷贝和移动操作,第 24-27 行的显式 delete 声明是冗余的。如果 Immovable 的私有继承导致错误信息不够清晰是你保留它们的理由,可以考虑改为 public 继承 Immovable,或者直接移除冗余声明。

@qzhhhi
Copy link
Member Author

qzhhhi commented Feb 16, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (10)
firmware/c_board/src/utility/interrupt_lock.hpp (1)

32-42: Immovable 基类已删除拷贝/移动操作,第 36-39 行的显式 delete 声明冗余。

core::utility::Immovable 基类已明确删除拷贝构造、拷贝赋值、移动构造和移动赋值。由于 InterruptLockGuard 私有继承自 Immovable,这些删除在派生类中会自动继承,第 36-39 行的显式声明可以移除。保留这些声明虽无害,但增加维护成本。建议删除冗余的显式 delete 声明以简化代码。

firmware/c_board/src/spi/spi.hpp (3)

47-54: atomic_flag 全部使用 relaxed 序,在异步路径中缺乏 happens-before 保证。

try_lock / unlock 实质上充当互斥锁,保护 spi_module_tx_rx_size_ 以及 tx_buffer/rx_buffer 等非原子数据。在异步路径中,锁在线程上下文获取、在 ISR 中释放(通过 module callback 调用 unlock)。

在单核 Cortex-M 上,中断屏障使得 relaxed 在实践中是安全的。但在 C++ 抽象内存模型中,relaxed 不为非原子数据的读写建立 happens-before 关系。建议将 try_lock 改为 acquireunlock 改为 release,这在 ARM 上不会产生额外开销(dmb 已被中断隐含),但能让代码在形式上正确且更具可移植性。

♻️ 建议的修改
-    bool is_locked() const { return locking_.test(std::memory_order::relaxed); }
+    bool is_locked() const { return locking_.test(std::memory_order::acquire); }

-    bool try_lock() { return !locking_.test_and_set(std::memory_order::relaxed); }
+    bool try_lock() { return !locking_.test_and_set(std::memory_order::acquire); }

     void unlock() {
         core::utility::assert_debug_lazy([&]() noexcept { return is_locked(); });
-        locking_.clear(std::memory_order::relaxed);
+        locking_.clear(std::memory_order::release);
     }

17-36: SpiModule 已继承 Immovable,显式 delete 拷贝/移动是冗余的。

core::utility::Immovable 基类应该已经禁止了拷贝和移动操作,Lines 24-27 的显式删除是多余的。如果是出于文档化意图保留也可以接受,仅作提示。


81-84: 异步回调路径中 tx_rx_size_finish_transfer() 之后使用,逻辑上安全但略显脆弱。

finish_transfer() 清除了 spi_module_ 但没有清除 tx_rx_size_,所以 Line 83 读取的值仍然有效。但如果将来 finish_transfer() 也重置 tx_rx_size_,这里就会出 bug。建议在 finish_transfer 之前取出 tx_rx_size_ 的值,或者让 finish_transfer 同时返回 size。

♻️ 一种更防御性的写法
     void transmit_receive_async_callback() {
+        auto size = tx_rx_size_;
         if (auto* module = finish_transfer())
-            module->transmit_receive_async_callback(rx_buffer, tx_rx_size_);
+            module->transmit_receive_async_callback(rx_buffer, size);
     }
firmware/c_board/src/led/led.hpp (1)

40-41: user_controlling_ 只被设为 false,缺少设为 true 的路径。

user_controlling_reset() 中被设为 falseupdate() 中被检查,但没有任何公开方法将其设为 true。如果这是尚未实现的功能,建议添加 // TODO 注释说明;否则,update() 中的检查是死代码。

Also applies to: 96-96

firmware/c_board/src/uart/uart.hpp (1)

37-50: handle_downlinkwritten_size 先 store 后 memcpy 存在潜在风险。

第 44-45 行先将更新后的 written_size 写入 atomic,第 46 行才执行 memcpy。如果 try_transmit(主循环)在 handle_downlink(ISR 上下文)的 store 和 memcpy 之间被调度(例如在多核场景、或未来重构执行上下文时),try_transmit 在第 76 行读到的 written_size 将包含尚未拷贝的数据长度,导致发送未初始化的内容。

建议将 memcpy 移到 written_size.store() 之前,并在两者之间加一个 signal fence 保证编译器不会重排:

♻️ 建议调整顺序
-        transmit_buffer.written_size.store(
-            static_cast<uint8_t>(written_size + size_allowed), std::memory_order::relaxed);
         std::memcpy(&transmit_buffer.data[written_size], data.uart_data.data(), size_allowed);
+        std::atomic_signal_fence(std::memory_order::release);
+        transmit_buffer.written_size.store(
+            static_cast<uint8_t>(written_size + size_allowed), std::memory_order::relaxed);
firmware/c_board/src/can/can.hpp (2)

73-77: Line 76 使用了 TX 寄存器常量来解析 RX 寄存器。

CAN_TI0R_STID_Pos 是发送邮箱标识符寄存器的位偏移常量,此处读取的是接收 FIFO 邮箱的 RIR 寄存器,应使用 CAN_RI0R_STID_Pos。虽然在 STM32F4 上二者数值相同(均为 21),不影响功能,但语义不一致会误导读者。

建议修正为 RX 寄存器对应的常量
         if (data.is_extended_can_id) {
             data.can_id = ((CAN_RI0R_EXID | CAN_RI0R_STID) & rir) >> CAN_RI0R_EXID_Pos;
         } else {
-            data.can_id = (CAN_RI0R_STID & rir) >> CAN_TI0R_STID_Pos;
+            data.can_id = (CAN_RI0R_STID & rir) >> CAN_RI0R_STID_Pos;
         }

34-55: handle_downlink 中对 can_data.size() 仅使用 assert_debug 校验。

Line 47 的 assert_debug 在 Release 构建中会被移除。如果上游意外传入超过 8 字节的数据,std::memcpy 会越界写入 mailbox.data(仅 8 字节)。考虑到这是固件代码且 CAN 协议层面保证 ≤ 8,风险较低,但如果 downlink 数据来自外部(USB),建议提升为 assert_always 或添加 std::min 防护。

建议加固数据长度校验
-            core::utility::assert_debug(data.can_data.size() <= 8);
-            mailbox.data_length_and_timestamp = data.can_data.size();
-            if (!data.can_data.empty())
-                std::memcpy(mailbox.data, data.can_data.data(), data.can_data.size());
+            auto len = std::min(data.can_data.size(), size_t{8});
+            mailbox.data_length_and_timestamp = len;
+            if (len > 0)
+                std::memcpy(mailbox.data, data.can_data.data(), len);
firmware/c_board/src/usb/interrupt_safe_buffer.hpp (2)

27-49: allocate() 设计合理,符合裸机 ISR 安全模型。

out_ 在循环外加载一次(与已知设计意图一致),-1 间距保护了已弹出但未释放的批次不被 ISR 覆写。compare_exchange_weak 在单 ISR 场景下本质上总是成功,但作为防御性编码是合理的。

有一处值得留意的细节:writeable = kBatchCount - readable - 1 中的 -1 是保证 pop_batch() 返回后、release_batch() 调用前,正在被主线程读取的批次不会被 ISR 重用的关键。建议在此处添加一行注释说明这个 -1 的安全语义,方便后续维护者理解。


79-94: pop_batch() 逻辑正确,signal_fence 用法适合裸机场景。

atomic_signal_fence(release) 在单核裸机上防止编译器将 out_.store 重排到 batch.empty() 检查之前,配合 Cortex-M 中断进出的隐式屏障,确保了 ISR 与主线程之间的可见性。

需要注意的是:pop_batch() 在返回批次指针之前就已更新了 out_(第 91 行),这意味着调用方在消费完数据并调用 release_batch() 之前,ISR 已经"看到"了新的 out_ 值。安全性完全依赖于 allocate()writeable = kBatchCount - readable - 1-1 间距保护。当前 kBatchCount = 8 时余量充足,但如果未来缩小 kBatchCount(如改为 2),安全边际会非常紧,建议在 kBatchCountstatic_assert 中加一个最小值约束(例如 >= 4)。

🛡️ 建议添加最小批次数约束
     static constexpr size_t kBatchCount = 8;
-    static_assert(std::has_single_bit(kBatchCount), "Batch count must be a power of 2");
+    static_assert(std::has_single_bit(kBatchCount), "Batch count must be a power of 2");
+    static_assert(kBatchCount >= 4, "Batch count must be >= 4 to maintain safety margin");

@qzhhhi
Copy link
Member Author

qzhhhi commented Feb 16, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
firmware/c_board/src/uart/uart.hpp (3)

53-79: trigger_hal_receive() 返回值在 try_transmit 中被静默忽略。

Line 59 调用 trigger_hal_receive() 但未检查返回值,而构造函数(Line 29)和 handle_uplink(Line 98)均通过 assert_always 对返回值进行断言。虽然注释说明这是一种轮询补偿机制,但如果接收恢复持续失败,当前逻辑不会有任何提示,可能导致 UART 永久停止接收而无感知。

建议至少在失败时通过 LED 信号提示(类似 downlink_buffer_full 的做法),或者添加注释说明为何此处故意忽略失败。


32-51: 双缓冲写入逻辑:隐含单写者假设。

handle_downlink 的 load-compute-memcpy-store 序列不是原子的。如果两个不同优先级的中断(或主循环与中断)同时调用 handle_downlink,会出现经典的 lost-update 竞争——两者读到相同的 written_size,写入同一偏移,最终只有一个 store 生效。

如果当前设计确实保证 handle_downlink 只由单一上下文调用,建议添加简短注释说明这一前提假设,便于后续维护。


127-129: 逻辑名称与 HAL 句柄映射关系不直观。

uart1 绑定 huart6uart2 绑定 huart1——逻辑编号与 USART 外设编号不对应。虽然这可能是硬件设计决定的,但建议添加简短注释说明映射关系(例如引脚/连接器对应),减少后续维护者的困惑。

@qzhhhi qzhhhi changed the title c_board feat(firmware/c_board): Migrate c_board firmware from rmcs_slave to librmcs v3 (WIP) Feb 17, 2026
@qzhhhi qzhhhi changed the base branch from dev/cboard-middle to main February 17, 2026 04:47
@qzhhhi qzhhhi merged commit 0272892 into main Feb 17, 2026
5 checks passed
@github-project-automation github-project-automation bot moved this from Todo to Done in RMCS Slave SDK Feb 17, 2026
@qzhhhi qzhhhi deleted the dev/cboard branch February 17, 2026 04:49
qzhhhi added a commit that referenced this pull request Feb 17, 2026
…ibrmcs v3 (WIP) (#19)

Migration source
- This migration is based on the c_board firmware from the legacy rmcs_slave repository (baseline commit: a6ca5c3).

Completed in this stage
- Migrated the build system from xmake to CMake, added presets/toolchain support, and integrated with the existing lint/CI workflow.
- Restructured the firmware layout from app/utility to src and aligned it with librmcs naming and project conventions.
- Migrated the communication layer from the legacy HAL CDC field protocol to core::protocol (serializer/deserializer), reconnecting CAN/UART/IMU uplink/downlink paths.
- Refactored and modernized key modules, including SPI/UART/CAN/USB.
- Updated and synchronized CubeMX/HAL project files.

Not yet completed
- Windows auto-recognition is not supported at this stage (WCID path not wired in yet).
- USB EP0 OUT continuation-length handling still needs to be unified and fixed in follow-up migration work.
- SPI/UART have not been migrated to DMA-based transfers.
- No hardware bring-up or on-device regression testing has been performed yet.

Next steps
- Continue closing migration gaps and run behavioral regression to align with legacy firmware behavior.
- Follow the rmcs_board path to migrate to TinyUSB and phase out HAL CDC.
- Restore and validate Windows compatibility after TinyUSB migration is converged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant