Skip to content

feat(gpio)!: Add GPIO support for rmcs_board#48

Merged
qzhhhi merged 2 commits into
mainfrom
dev/rmcsboard-gpio
Apr 22, 2026
Merged

feat(gpio)!: Add GPIO support for rmcs_board#48
qzhhhi merged 2 commits into
mainfrom
dev/rmcsboard-gpio

Conversation

@qzhhhi
Copy link
Copy Markdown
Member

@qzhhhi qzhhhi commented Apr 20, 2026

feat(gpio)!: Add descriptor-based GPIO capability model

  • Extract channel field from GPIO data views (GpioDigitalDataView, GpioAnalogDataView, GpioReadConfigView) into an explicit channel_index parameter threaded through serialization, deserialization, and callbacks.
  • Introduce GpioCapability bitmask enum and GpioDescriptor in core/spec, with c_board-specific descriptors that declare per-channel capabilities (e.g. channel 5 lacks interrupt support).
  • Add GpioReadConfigView::supported() for unified host/firmware validation of read configurations against descriptor capabilities.
  • Replace hardcoded channel-6 EXTI exclusion in c_board firmware with declarative capability mask in the shared descriptor table.
  • Rename wire-protocol field GpioHeader::Channel to ChannelIndex and switch c_board channel numbering from 1-based to 0-based.
  • Update c_board host agent API to accept GpioDescriptor references instead of raw channel numbers.
  • Add core/include to firmware build targets that consume shared specs.

BREAKING CHANGE: GPIO data views no longer carry a channel field; all GPIO protocol and callback APIs now take a zero-based channel_index. c_board host agent methods require a GpioDescriptor reference instead of a raw channel number.

feat(gpio)!: Add GPIO support for rmcs_board and align input sampling semantics

  • Add board-specific GPIO descriptors and host-side typed GPIO APIs for rmcs_board lite and pro.
  • Implement rmcs_board firmware GPIO handling for digital input, digital output, interrupt-driven input reporting, and PWM output on supported pro channels.
  • Refactor c_board digital input scheduling to use deadline-based periodic sampling. This changes the sampling behavior for read configurations with period_ms > 0.

BREAKING CHANGE: Digital GPIO read configurations with period_ms > 0 now emit the first periodic sample on the next polling cycle instead of waiting for a full period to elapse.

GPIO 描述符模型重构(更新)

概览

本 PR 将 GPIO 支持从多处硬编码与 1-based 通道编号迁移为声明式的描述符驱动模型,统一使用零基 channel_index。改动横跨 core 协议、共享 spec、主机代理与固件(c_board、rmcs_board 及板级变体),引入能力位掩码、板级描述符表、协议字段/签名变更,并重构固件 GPIO 子系统以按描述符驱动行为与能力检查。

主要变更点

  1. 协议与数据视图
  • 移除 GpioDigitalDataView、GpioAnalogDataView、GpioReadConfigView 中的 channel 字段,序列化/反序列化与回调统一通过显式 uint8_t channel_index 传递。
  • GpioHeader 的位域别名由 Channel 重命名为 ChannelIndex(位宽不变)。
  • Serializer/Deserializer 的所有 GPIO 读写/结果/配置方法改为接受 channel_index,并在写入时断言位宽。
  1. 能力位与描述符
  • 新增 librmcs::spec::GpioCapability(按位枚举)及相应位运算重载,表示 digital/analog write、digital read(一次/周期/中断)、pull-up/down 等能力。
  • 新增通用 spec::GpioDescriptor(channel_index + capability_mask + supports()),并提供 consteval 的 channel_indices_match_indices 辅助用于编译期一致性校验。
  1. 板级描述符表
  • 为 c_board、rmcs_board_lite、rmcs_board_pro 等添加板级描述符类型与 kGpioDescriptors 常量数组(例如 c_board 通道 0–6,channel 5 去除中断能力以取代此前 channel6 的固件特例)。
  • 描述符类型为不可拷贝/不可移动的派生包装,提供命名别名(如 kPwm1..kPwm7)与 constexpr 访问器。
  1. 读配置校验
  • 新增 GpioReadConfigView::supported(const spec::GpioDescriptor&),主机与固件均使用它基于描述符能力校验读配置(asap、period_ms、边沿触发、pull 等)。
  1. 固件重构与功能
  • 固件统一使用 0-based 通道索引;移除 1-based 编号逻辑。
  • 引入 AnalogGpioPin、板级硬件描述符数组(kGpioHardwareDescriptors),并在板初始化中设置 IOC/复用。
  • 新增并实现 firmware::gpio::Gpio 子系统:数字输出、PWM(模拟输出)、数字输入、基于 deadline 的周期采样调度、以及中断驱动的输入上报。c_board 固件将 EXTI 特例替换为描述符能力控制与 exti->channel_index 映射。
  • poll/调度重构:数字输入的周期采样改为 deadline/下次截止时间调度;period_ms > 0 的读配置现在在下一轮轮询周期(而非等待完整周期)首次产生周期样本(破坏性语义变更)。
  • 在固件中添加 init_gpio_pins() 与 gpio_irq_handler 等挂钩;若干板 ISR 更新为调用通用 gpio_irq_handler。
  1. 主机端与 Agent API
  • CBoard / RmcsBoardLite / RmcsBoardPro 等 PacketBuilder 的 GPIO 发送接口改为接受 const GpioDescriptor&(而非原始通道号);发送前基于描述符能力校验并使用 gpio.channel_index 调用底层序列化器。
  • 读取结果回调链改为序列化层先提供 uint8_t channel_index,agent/host 层边界检查后将对应描述符转发至新的虚拟回调(descriptor + data view);保留最终可覆盖点供用户处理特定描述符。
  • host 协议 Handler 的 PacketBuilder 写入方法签名改为 (uint8_t channel_index, const View&),并在内部传入 serializer。
  1. 构建与包含路径
  • 将 core/include 添加到固件(c_board、rmcs_board bootloader/app)与固件目标的 include 路径,以便固件/主机消费共享的 spec 头文件。
  1. 其它调整
  • c_board 主机代理方法从接收原始通道号变更为接收 GpioDescriptor 引用(破坏性)。
  • DataCallback / DeserializeCallback 等回调签名已相应更新以包含 channel_index。
  • timer::kMaxDurationTicks 类型由 uint64_t 改为 uint32_t 并新增 check_reached 辅助。
  • 添加 AnalogGpioPin 与 PWM 支持工具函数与断言。

破坏性变更(需注意)

  • GPIO 数据视图不再包含 channel 字段;所有相关 API/回调现在以零基 channel_index 作为独立参数。
  • 各板主机代理与 PacketBuilder 写接口改为接受 GpioDescriptor 引用或显式 channel_index,调用方必须使用 spec::...::kGpioDescriptors 或保存描述符引用。
  • 数字读配置的初始周期采样时序变更(period_ms > 0 时首次周期样本在下一轮轮询产生,而不再等待完整周期)。
  • 需更新所有以 1-based 传递通道的用户代码为 0-based 或改为使用描述符的 channel_index。

迁移建议

  • 将所有调用改为传递零基 channel_index,或改为传入板级 kGpioDescriptors 中的 GpioDescriptor 引用并依赖库在内部使用 gpio.channel_index。
  • 主机侧在调用 PacketBuilder 前应基于 GpioDescriptor 做能力校验,或依赖 PacketBuilder 已做的校验。
  • 固件板级初始化需调用新增 init_gpio_pins() 并确保 kGpioHardwareDescriptors 与 spec 描述符数组长度一致(已有 static_assert 校验)。

审查重点

  • 描述符表与硬件描述符的索引/顺序一致性(channel_indices_match_indices 与 static_assert)。
  • GpioCapability 定义及组合语义(中断/周期/一次性读等能力互斥与兼容性)。
  • Serializer/Deserializer 对 GpioHeader 位宽与 channel_index 断言的一致性。
  • 固件 PWM 初始化与多通道共享资源(reload/clock)实现的正确性与鲁棒性。
  • 主机端 API 迁移路径与异常/错误处理逻辑。

总体而言,本 PR 用描述符与能力模型替换了多处板级特例并统一了通道索引语义,提升可扩展性与表达能力,但伴随较多接口变更与迁移工作。

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

本PR将GPIO通道从数据视图字段移除为显式的 uint8_t channel_index 参数,新增通用GPIO能力位与板级描述符(GpioDescriptor / kGpioDescriptors),并同步修改序列化/反序列化、主机与固件侧的路由与能力校验(签名、调用点、硬件描述数组更新)。

Changes

Cohort / File(s) Summary
规范与描述符
core/include/librmcs/spec/gpio.hpp, core/include/librmcs/spec/c_board/gpio.hpp, core/include/librmcs/spec/rmcs_board_lite/gpio.hpp, core/include/librmcs/spec/rmcs_board_pro/gpio.hpp
新增 GpioCapability 枚举及位运算、通用 GpioDescriptor 与 compile-time 描述符容器 kGpioDescriptors,并为多板提供固定描述符数组与命名引用。
数据视图与回调签名
core/include/librmcs/data/datas.hpp
GpioDigitalDataView/GpioAnalogDataView 中移除 channel 字段;新增 GpioReadConfigView::supported(const spec::GpioDescriptor&);将相关回调改为以 (uint8_t channel_index, const ...&) 接收。
协议:头/序列化/反序列化
core/src/protocol/protocol.hpp, core/src/protocol/serializer.hpp, core/src/protocol/deserializer.hpp, core/src/protocol/deserializer.cpp
GpioHeader::Channel 重命名为 ChannelIndex;序列化/反序列化接口及回调均新增 uint8_t channel_index 参数;序列化器现在显式写入 header 的 channel_index,反序列化按 channel_index 分发。
主机协议层与代理 API
host/include/librmcs/protocol/handler.hpp, host/src/protocol/handler.cpp, host/include/librmcs/agent/c_board.hpp, host/include/librmcs/agent/rmcs_board_*.hpp
PacketBuilder 和高级 API 改为接受 channel_index 或以 GpioDescriptor 指定通道(使用 gpio.channel_index);反序列化回调新增 channel_index,边界检查后基于描述符转发至描述符重载。
固件(C Board / RMCS Board)
firmware/c_board/app/src/gpio/gpio.hpp, firmware/c_board/app/src/usb/vendor.hpp, firmware/rmcs_board/app/src/gpio/*, firmware/rmcs_board/boards/*/app/*, firmware/rmcs_board/app/src/gpio/gpio.cpp, firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp
引入并使用板级硬件描述符数组进行引脚初始化、PWM 与中断映射;固件 GPIO 类新增/修改为以 channel_index 接收操作,周期采样、IRQ 处理与结果发布基于通道索引;新增 AnalogGpioPin 与硬件描述数组及 IRQ 绑定。
USB / 固件 Vendor 层
firmware/c_board/app/src/usb/vendor.hpp, firmware/rmcs_board/app/src/usb/vendor.hpp
USB 端回调签名包含 channel_index,加入对 kGpioDescriptors 的边界与能力检查,按能力条件调用固件 GPIO 处理或忽略无效请求。
构建系统更新
firmware/c_board/CMakeLists.txt, firmware/rmcs_board/app/CMakeLists.txt, firmware/rmcs_board/bootloader/CMakeLists.txt
向若干固件目标添加 ${LIBRMCS_PROJECT_ROOT}/core/include 到 include 路径,以便包含新增的 core headers。
Handler / Serializer 公共接口
host/include/librmcs/protocol/handler.hpp, core/src/protocol/serializer.hpp
Handler/Serializer 的 GPIO 写接口改为接受 uint8_t channel_index 作为首参,并在序列化 header 中设置该索引。
定时器小改动
firmware/c_board/app/src/timer/timer.hpp
kMaxDurationTicksuint64_t 改为 uint32_t,并新增 check_reached(TimePoint) 辅助方法。

Sequence Diagram(s)

sequenceDiagram
participant Host as Host/Agent
participant Serializer as Host Serializer
participant Transport as USB/Transport
participant Deserializer as Firmware Deserializer
participant GPIO as Firmware GPIO Handler
participant SerializerFW as Firmware Serializer
participant HostHandler as Host Handler

Host->>Serializer: 构建 GPIO 报文(含 channel_index)
Serializer->>Transport: 发送报文字节
Transport->>Deserializer: 传递报文
Deserializer->>GPIO: gpio_*_deserialized(channel_index, data_view)
GPIO->>SerializerFW: write_gpio_*_read_result(channel_index, data_view)
SerializerFW->>Transport: 发送结果报文
Transport->>HostHandler: 传递结果报文
HostHandler->>Host: 回调 gpio_*_read_result_callback(channel_index, data_view)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 我是小兔队列长,索引跳跃来报到,
通道拆出不再藏,描述符排队把关闹,
能力位上亮灯号,读写回调携手跑,
数据往返带索引,硬件花园更整齐 🌿🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地总结了本次更改的主要内容:为 rmcs_board 添加 GPIO 支持,并通过感叹号标记表示了这是一个破坏性更改。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/rmcsboard-gpio

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.

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (4)
core/include/librmcs/spec/c_board/gpio.hpp (1)

46-48: 建议在 operator[] 中加入越界断言。

当前 operator[] 未做边界检查,若上游忘记 channel_index < size() 校验即直接索引将触发 UB。可参考 firmware/c_board/app/src/gpio/gpio.hppchannel_state()/channel_hardware() 的做法,加一个 assert_debug 提高防御性。虽然现有调用点(vendor.hpp)已做了前置校验,但作为公开可见的容器访问接口,断言能避免未来新增调用方忘记检查。

♻️ 建议改动
-    static constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept {
-        return kArray[channel_index];
-    }
+    static constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept {
+        // NOTE: 需先引入 assert 头;非 constexpr 上下文的越界会触发断言。
+        // 对 constexpr 求值,越界访问会自动编译失败。
+        return kArray[channel_index];
+    }

(若希望保持 constexpr,可考虑改用 std::array 并在非 constexpr 求值路径上通过 utility::assert_debug 校验,或直接保留现状并在文档注释中明确 API 前置条件。)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/include/librmcs/spec/c_board/gpio.hpp` around lines 46 - 48, The
operator[] accessor for GpioDescriptor (static constexpr const GpioDescriptor&
operator[](std::size_t channel_index) noexcept) lacks a bounds check and can
trigger undefined behavior; add a defensive assert_debug that verifies
channel_index < size() (or kArray.size()) at the start of operator[] before
returning kArray[channel_index]; keep the noexcept/constexpr intent in mind (you
can keep the assert_guard disabled in constexpr evaluation or follow the
existing pattern used in channel_state()/channel_hardware() in
firmware/c_board/app/src/gpio/gpio.hpp).
core/src/protocol/serializer.hpp (1)

123-291: 建议在 6 个 write_gpio_* 入口统一校验 channel_index 是否超出 6 bit 范围。

GpioHeader::ChannelIndexBitfieldMember<8, 6>(6 bit,最大 63)。目前 6 个 write_gpio_* 方法都没有校验 channel_index,若上游传入 >= 64 的值,位域写入会静默截断高位,产生错误的线协议编码而不返回 kInvalidArgument。现阶段调用方都基于 kGpioDescriptors(size=7)做了前置约束,但 serializer 作为通用协议原语,加一行防御性校验更稳妥,也与本文件中 period_ms <= (1U << 13) - 1U 的校验风格一致。

♻️ 建议改动(以 `write_gpio_digital_data` 为例;其余 5 个方法同样处理)
     SerializeResult write_gpio_digital_data(
         uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept {
+        LIBRMCS_VERIFY_LIKELY(channel_index < (1U << 6), SerializeResult::kInvalidArgument);
         const auto payload_type = view.high ? GpioHeader::PayloadEnum::kDigitalWriteHigh
                                             : GpioHeader::PayloadEnum::kDigitalWriteLow;

可考虑抽成一个私有常量,例如 static constexpr std::uint8_t kMaxChannelIndex = (1U << 6) - 1U;,在全部 6 个 GPIO 写入方法中复用。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/serializer.hpp` around lines 123 - 291, Add a defensive
bounds check for channel_index against the 6-bit limit (max 63) before
allocating/writing in all GPIO serializers: write_gpio_digital_data,
write_gpio_digital_read_config, write_gpio_analog_data,
write_gpio_analog_read_config, write_gpio_digital_read_result, and
write_gpio_analog_read_result; define a private constant like kMaxChannelIndex =
(1U << 6) - 1U and verify channel_index <= kMaxChannelIndex (since
GpioHeader::ChannelIndex is BitfieldMember<8,6>) and return
SerializeResult::kInvalidArgument when out of range, mirroring the existing
period_ms validation style.
core/include/librmcs/data/datas.hpp (1)

68-75: 建议给 supported() 补上 noexcept[[nodiscard]]

该函数的所有子调用(gpio.supports(...))均为 noexcept,且返回值是能力校验结果,丢弃结果基本都属于误用;加上 [[nodiscard]] 可由编译器阻止忽略返回值的情况,noexcept 则便于在 noexcept 路径(如 vendor.hpp 的反序列化回调)中无开销调用。

♻️ 建议改动
-    bool supported(const spec::GpioDescriptor& gpio) const {
+    [[nodiscard]] bool supported(const spec::GpioDescriptor& gpio) const noexcept {
         return (!asap || gpio.supports(spec::GpioCapability::kDigitalReadOnce))
             && (!period_ms || gpio.supports(spec::GpioCapability::kDigitalReadPeriodic))
             && ((!rising_edge && !falling_edge)
                 || gpio.supports(spec::GpioCapability::kDigitalReadInterrupt))
             && (pull != GpioPull::kUp || gpio.supports(spec::GpioCapability::kPullUp))
             && (pull != GpioPull::kDown || gpio.supports(spec::GpioCapability::kPullDown));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/include/librmcs/data/datas.hpp` around lines 68 - 75, The
supported(const spec::GpioDescriptor& gpio) const method should be marked
[[nodiscard]] and noexcept; update the method signature in its
declaration/definition (function supported) to read as a [[nodiscard]] bool
supported(...) const noexcept so callers are warned if they ignore the result
and the function can be used in noexcept contexts (also update any out-of-line
definition or forward declaration for supported to match this signature).
host/include/librmcs/agent/c_board.hpp (1)

10-12: 项目头文件引用风格不一致

同一文件内,第 10 行使用 <librmcs/spec/gpio.hpp>(尖括号),而第 12 行对同样属于本仓库的 librmcs/spec/c_board/gpio.hpp 使用了 "..."(双引号)。本文件中其它项目头文件(librmcs/agent/common.hpplibrmcs/data/datas.hpplibrmcs/protocol/handler.hpp)均使用尖括号,建议统一:

🔧 建议修改
 `#include` <librmcs/spec/gpio.hpp>
-
-#include "librmcs/spec/c_board/gpio.hpp"
+#include <librmcs/spec/c_board/gpio.hpp>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@host/include/librmcs/agent/c_board.hpp` around lines 10 - 12, The include
style is inconsistent: <librmcs/spec/gpio.hpp> uses angle brackets while
"librmcs/spec/c_board/gpio.hpp" uses quotes; change the latter to use angle
brackets to match the project's other includes (e.g.,
<librmcs/agent/common.hpp>, <librmcs/data/datas.hpp>,
<librmcs/protocol/handler.hpp>), so replace "librmcs/spec/c_board/gpio.hpp" with
<librmcs/spec/c_board/gpio.hpp> in the c_board.hpp header to unify include
style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/include/librmcs/spec/gpio.hpp`:
- Around line 36-45: Remove the dead constant kNoExtiLine from the
GpioDescriptor struct: locate the struct GpioDescriptor and delete the static
constexpr std::uint8_t kNoExtiLine = 0xFFU declaration, leaving only the used
members (channel_index, capability_mask and supports()); ensure no other code
references GpioDescriptor::kNoExtiLine before deletion.

---

Nitpick comments:
In `@core/include/librmcs/data/datas.hpp`:
- Around line 68-75: The supported(const spec::GpioDescriptor& gpio) const
method should be marked [[nodiscard]] and noexcept; update the method signature
in its declaration/definition (function supported) to read as a [[nodiscard]]
bool supported(...) const noexcept so callers are warned if they ignore the
result and the function can be used in noexcept contexts (also update any
out-of-line definition or forward declaration for supported to match this
signature).

In `@core/include/librmcs/spec/c_board/gpio.hpp`:
- Around line 46-48: The operator[] accessor for GpioDescriptor (static
constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept)
lacks a bounds check and can trigger undefined behavior; add a defensive
assert_debug that verifies channel_index < size() (or kArray.size()) at the
start of operator[] before returning kArray[channel_index]; keep the
noexcept/constexpr intent in mind (you can keep the assert_guard disabled in
constexpr evaluation or follow the existing pattern used in
channel_state()/channel_hardware() in firmware/c_board/app/src/gpio/gpio.hpp).

In `@core/src/protocol/serializer.hpp`:
- Around line 123-291: Add a defensive bounds check for channel_index against
the 6-bit limit (max 63) before allocating/writing in all GPIO serializers:
write_gpio_digital_data, write_gpio_digital_read_config, write_gpio_analog_data,
write_gpio_analog_read_config, write_gpio_digital_read_result, and
write_gpio_analog_read_result; define a private constant like kMaxChannelIndex =
(1U << 6) - 1U and verify channel_index <= kMaxChannelIndex (since
GpioHeader::ChannelIndex is BitfieldMember<8,6>) and return
SerializeResult::kInvalidArgument when out of range, mirroring the existing
period_ms validation style.

In `@host/include/librmcs/agent/c_board.hpp`:
- Around line 10-12: The include style is inconsistent: <librmcs/spec/gpio.hpp>
uses angle brackets while "librmcs/spec/c_board/gpio.hpp" uses quotes; change
the latter to use angle brackets to match the project's other includes (e.g.,
<librmcs/agent/common.hpp>, <librmcs/data/datas.hpp>,
<librmcs/protocol/handler.hpp>), so replace "librmcs/spec/c_board/gpio.hpp" with
<librmcs/spec/c_board/gpio.hpp> in the c_board.hpp header to unify include
style.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7d4d34b8-b70e-491a-a1d3-ee3df3627467

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1bda6 and c992634.

📒 Files selected for processing (18)
  • core/include/librmcs/data/datas.hpp
  • core/include/librmcs/spec/c_board/gpio.hpp
  • core/include/librmcs/spec/gpio.hpp
  • core/src/protocol/deserializer.cpp
  • core/src/protocol/deserializer.hpp
  • core/src/protocol/protocol.hpp
  • core/src/protocol/serializer.hpp
  • firmware/c_board/CMakeLists.txt
  • firmware/c_board/app/src/gpio/gpio.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/app/CMakeLists.txt
  • firmware/rmcs_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/bootloader/CMakeLists.txt
  • host/include/librmcs/agent/c_board.hpp
  • host/include/librmcs/agent/rmcs_board_lite.hpp
  • host/include/librmcs/agent/rmcs_board_pro.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp

Comment thread core/include/librmcs/spec/gpio.hpp Outdated
Copy link
Copy Markdown

@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

🧹 Nitpick comments (3)
firmware/c_board/app/src/timer/timer.hpp (1)

70-75: 建议补充 check_reached 的半周期前提。

这个无状态回绕比较只在 deadline 没有被安排到超过半个 32-bit tick 周期的未来时可靠;否则过远的未来时间点会被误判为“已到达”。当前 GPIO 调用路径看起来由 to_duration_checked() 约束,但这个方法是 public,建议把前提写在接口旁边,避免后续误用。

可选注释补充
+    // Precondition: `deadline` must not be scheduled more than `kMaxDurationTicks`
+    // ticks in the future; farther deadlines are ambiguous under modular time.
     [[nodiscard]] bool check_reached(TimePoint deadline) const {
         const uint32_t deadline_ticks = deadline.time_since_epoch().count();
         const uint32_t now_ticks = timepoint().time_since_epoch().count();
         const uint32_t elapsed_ticks = now_ticks - deadline_ticks;
         return elapsed_ticks < kMaxDurationTicks;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/timer/timer.hpp` around lines 70 - 75, Add a clear
precondition to check_reached: document (and optionally assert) that the
supplied deadline must not be scheduled more than half of the 32‑bit tick wrap
period into the future because the unsigned wrap-around comparison using
kMaxDurationTicks is only reliable within that half‑period; reference the
check_reached function name, the kMaxDurationTicks constant, and the public
to_duration_checked() method in the comment near the API so callers know the
constraint and avoid misusing the public to_duration_checked() path.
host/include/librmcs/agent/rmcs_board_lite.hpp (1)

138-151: 建议与 rmcs_board_pro.hpp 保持一致,使用 final 替代 override

RmcsBoardPro 对应的两个路由入口(rmcs_board_pro.hpp 第 140 / 147 行)声明为 final,防止用户子类误覆盖 channel_index 版本而旁路描述符分发;lite 这里只用 override,功能上可行但与 pro 不对称,且一旦用户意外覆盖该重载会绕过边界检查与描述符路由。

♻️ 建议的修改
     void gpio_digital_read_result_callback(
-        uint8_t channel_index, const librmcs::data::GpioDigitalDataView& data) override {
+        uint8_t channel_index, const librmcs::data::GpioDigitalDataView& data) final {
         if (channel_index >= spec::rmcs_board_lite::kGpioDescriptors.size()) [[unlikely]]
             return;
         gpio_digital_read_result_callback(
             spec::rmcs_board_lite::kGpioDescriptors[channel_index], data);
     }
     void gpio_analog_read_result_callback(
-        uint8_t channel_index, const librmcs::data::GpioAnalogDataView& data) override {
+        uint8_t channel_index, const librmcs::data::GpioAnalogDataView& data) final {
         if (channel_index >= spec::rmcs_board_lite::kGpioDescriptors.size()) [[unlikely]]
             return;
         gpio_analog_read_result_callback(
             spec::rmcs_board_lite::kGpioDescriptors[channel_index], data);
     }

Based on learnings: "avoid using both final and override on the same virtual method: if a method is declared final, do not also add override since it is redundant. Use final alone for final overrides."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@host/include/librmcs/agent/rmcs_board_lite.hpp` around lines 138 - 151, The
two channel-index overloads gpio_digital_read_result_callback and
gpio_analog_read_result_callback in class RmcsBoardLite should match
RmcsBoardPro by being declared final (not override) to prevent subclasses from
bypassing the descriptor-boundary check and dispatch; find the methods in
rmcs_board_lite.hpp and replace the trailing override specifier with final for
both methods (do not use both override and final together).
firmware/rmcs_board/app/src/gpio/gpio.hpp (1)

53-85: LGTM(一个小的可选建议)。

handle_port_interrupt 遍历所有通道匹配端口并 check_clear_interrupt_flag(),对 N=17 规模没问题;poll_periodic_input_samples 的调度器正确地只在 DigitalInputperiod_ticks != 0 时采样。

一个可选的稳健性改进:当调度被长时间阻塞时,state.next_sample_tick = now + state.period_ticks; 会丢失相位(相对原计划时刻漂移)。若希望保持周期稳定,可改为 state.next_sample_tick += state.period_ticks; 并在严重滞后时快速赶齐或丢弃过期点。当前策略对大多数上位机采样场景也够用,属于可选优化。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/app/src/gpio/gpio.hpp` around lines 53 - 85, Update
poll_periodic_input_samples to advance state.next_sample_tick by adding
state.period_ticks instead of resetting to now to preserve phase: in
poll_periodic_input_samples, replace the assignment state.next_sample_tick = now
+ state.period_ticks with state.next_sample_tick += state.period_ticks and add
logic to catch up/skew handling (e.g., while now >= state.next_sample_tick {
publish_digital_input_sample(...); state.next_sample_tick += state.period_ticks;
} or drop excessive missed ticks) so the scheduler in
poll_periodic_input_samples keeps a consistent phase; touch the same
ChannelState fields and use
publish_digital_input_sample/static_cast<uint8_t>(channel_index) and now_ticks()
as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/include/librmcs/spec/c_board/gpio.hpp`:
- Line 18: The constructor parameter for the derived GpioDescriptor in c_board
(and likewise in rmcs_board_pro and rmcs_board_lite) uses uint8_t; change its
signature to use std::uint8_t to match the base class spec::GpioDescriptor and
the file's fully-qualified style — update the constructor
declarations/definitions named GpioDescriptor(uint8_t channel_index,
GpioCapability capability_mask) to GpioDescriptor(std::uint8_t channel_index,
GpioCapability capability_mask) in each derived class.

In `@core/include/librmcs/spec/rmcs_board_lite/gpio.hpp`:
- Line 18: Replace the unqualified uint8_t parameter with std::uint8_t in the
GpioDescriptor constructors (e.g., change constexpr GpioDescriptor(uint8_t
channel_index, GpioCapability capability_mask) to use std::uint8_t) in the
GpioDescriptor declarations across the three board header files; also ensure
each header includes <cstdint> (or the common header that provides std::uint8_t)
so the qualified type compiles consistently with the base class.

In `@core/include/librmcs/spec/rmcs_board_pro/gpio.hpp`:
- Line 18: 构造函数参数类型在 rmcs_board_pro 的 GpioDescriptor(spec::GpioDescriptor)中使用了裸的
uint8_t;请将构造函数签名中的参数类型改为 std::uint8_t 以与基类
spec::GpioDescriptor(及其成员)保持一致,并对其它两个类似实现(rmcs_board_lite、c_board 中的
GpioDescriptor)做相同修改;确保包含或保留 <cstdint> 可用以解析 std::uint8_t。

In `@firmware/rmcs_board/app/src/gpio/gpio.hpp`:
- Around line 208-212: The duty16_to_pwm_compare function currently inverts duty
semantics vs c_board (duty=0→full on), causing a contradiction with
GpioAnalogDataView; first confirm whether inverted (active-low) PWM is
intentional for rmcs_board, and if so add a clear comment in
duty16_to_pwm_compare and/or GpioAnalogDataView documenting the active-low
convention and rationale; if not intentional, change duty16_to_pwm_compare to
standard semantics to match c_board by special-casing duty==65535 to return
pwm_reload_+1 and using return (static_cast<uint64_t>(pwm_reload_) * duty) /
65535U for the general case so duty=0→CMP≈0 and duty=65535→CMP≈pwm_reload_.

---

Nitpick comments:
In `@firmware/c_board/app/src/timer/timer.hpp`:
- Around line 70-75: Add a clear precondition to check_reached: document (and
optionally assert) that the supplied deadline must not be scheduled more than
half of the 32‑bit tick wrap period into the future because the unsigned
wrap-around comparison using kMaxDurationTicks is only reliable within that
half‑period; reference the check_reached function name, the kMaxDurationTicks
constant, and the public to_duration_checked() method in the comment near the
API so callers know the constraint and avoid misusing the public
to_duration_checked() path.

In `@firmware/rmcs_board/app/src/gpio/gpio.hpp`:
- Around line 53-85: Update poll_periodic_input_samples to advance
state.next_sample_tick by adding state.period_ticks instead of resetting to now
to preserve phase: in poll_periodic_input_samples, replace the assignment
state.next_sample_tick = now + state.period_ticks with state.next_sample_tick +=
state.period_ticks and add logic to catch up/skew handling (e.g., while now >=
state.next_sample_tick { publish_digital_input_sample(...);
state.next_sample_tick += state.period_ticks; } or drop excessive missed ticks)
so the scheduler in poll_periodic_input_samples keeps a consistent phase; touch
the same ChannelState fields and use
publish_digital_input_sample/static_cast<uint8_t>(channel_index) and now_ticks()
as needed.

In `@host/include/librmcs/agent/rmcs_board_lite.hpp`:
- Around line 138-151: The two channel-index overloads
gpio_digital_read_result_callback and gpio_analog_read_result_callback in class
RmcsBoardLite should match RmcsBoardPro by being declared final (not override)
to prevent subclasses from bypassing the descriptor-boundary check and dispatch;
find the methods in rmcs_board_lite.hpp and replace the trailing override
specifier with final for both methods (do not use both override and final
together).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38f83cd1-524e-4726-9b47-ce8100d8a04d

📥 Commits

Reviewing files that changed from the base of the PR and between c992634 and 3683953.

📒 Files selected for processing (29)
  • core/include/librmcs/data/datas.hpp
  • core/include/librmcs/spec/c_board/gpio.hpp
  • core/include/librmcs/spec/gpio.hpp
  • core/include/librmcs/spec/rmcs_board_lite/gpio.hpp
  • core/include/librmcs/spec/rmcs_board_pro/gpio.hpp
  • core/src/protocol/deserializer.cpp
  • core/src/protocol/deserializer.hpp
  • core/src/protocol/protocol.hpp
  • core/src/protocol/serializer.hpp
  • firmware/c_board/CMakeLists.txt
  • firmware/c_board/app/src/gpio/gpio.hpp
  • firmware/c_board/app/src/timer/timer.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/app/CMakeLists.txt
  • firmware/rmcs_board/app/src/app.cpp
  • firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp
  • firmware/rmcs_board/app/src/gpio/gpio.cpp
  • firmware/rmcs_board/app/src/gpio/gpio.hpp
  • firmware/rmcs_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/boards/lite/app/board_app.cpp
  • firmware/rmcs_board/boards/lite/app/board_app.hpp
  • firmware/rmcs_board/boards/pro/app/board_app.cpp
  • firmware/rmcs_board/boards/pro/app/board_app.hpp
  • firmware/rmcs_board/bootloader/CMakeLists.txt
  • host/include/librmcs/agent/c_board.hpp
  • host/include/librmcs/agent/rmcs_board_lite.hpp
  • host/include/librmcs/agent/rmcs_board_pro.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp
✅ Files skipped from review due to trivial changes (3)
  • firmware/rmcs_board/app/CMakeLists.txt
  • host/src/protocol/handler.cpp
  • core/include/librmcs/spec/gpio.hpp
🚧 Files skipped from review as they are similar to previous changes (9)
  • firmware/rmcs_board/bootloader/CMakeLists.txt
  • firmware/c_board/CMakeLists.txt
  • core/src/protocol/protocol.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • core/include/librmcs/data/datas.hpp
  • core/src/protocol/deserializer.hpp
  • firmware/c_board/app/src/gpio/gpio.hpp
  • core/src/protocol/serializer.hpp
  • host/include/librmcs/agent/c_board.hpp

Comment thread core/include/librmcs/spec/c_board/gpio.hpp
Comment thread core/include/librmcs/spec/rmcs_board_lite/gpio.hpp
Comment thread core/include/librmcs/spec/rmcs_board_pro/gpio.hpp
Comment thread firmware/rmcs_board/app/src/gpio/gpio.hpp
@qzhhhi qzhhhi force-pushed the dev/rmcsboard-gpio branch from 3683953 to 9b23ead Compare April 21, 2026 17:47
@qzhhhi qzhhhi changed the title feat(gpio)!: Add descriptor-based GPIO capability model feat(gpio)!: Add GPIO support for rmcs_board Apr 21, 2026
Copy link
Copy Markdown

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/protocol/serializer.hpp (1)

123-288: ⚠️ Potential issue | 🟠 Major

channel_index 校验升级为运行时参数校验。

这些方法是公共序列化入口;当前只用 assert_debug 校验通道位宽,release 构建里无效通道可能被写入/截断到协议头,而不是返回 kInvalidArgument。建议像 period_ms 一样用 LIBRMCS_VERIFY_LIKELY 保护所有 GPIO 写入方法。

建议修复
     SerializeResult write_gpio_digital_data(
         uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
         const auto payload_type = view.high ? GpioHeader::PayloadEnum::kDigitalWriteHigh
                                             : GpioHeader::PayloadEnum::kDigitalWriteLow;
@@
     SerializeResult write_gpio_digital_read_config(
         uint8_t channel_index, const data::GpioReadConfigView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
@@
     SerializeResult write_gpio_analog_data(
         uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
@@
     SerializeResult write_gpio_analog_read_config(
         uint8_t channel_index, const data::GpioReadConfigView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
@@
     SerializeResult write_gpio_digital_read_result(
         uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
@@
     SerializeResult write_gpio_analog_read_result(
         uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept {
-        utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth));
+        LIBRMCS_VERIFY_LIKELY(valid_gpio_channel_index(channel_index),
+            SerializeResult::kInvalidArgument);
@@
 private:
+    static constexpr bool valid_gpio_channel_index(uint8_t channel_index) noexcept {
+        return channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth);
+    }
+
     static constexpr bool use_extended_field_header(FieldId field_id) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/serializer.hpp` around lines 123 - 288, The GPIO functions
currently use utility::assert_debug to validate channel_index which is stripped
in release builds; replace those asserts with a runtime check using
LIBRMCS_VERIFY_LIKELY(channel_index < (1U <<
GpioHeader::ChannelIndex::kBitWidth), SerializeResult::kInvalidArgument) in each
public serializer entry (write_gpio_digital_data,
write_gpio_digital_read_config, write_gpio_analog_data,
write_gpio_analog_read_config, write_gpio_digital_read_result,
write_gpio_analog_read_result) — perform this verification before computing
required size/allocating buffer so invalid channel indices return
SerializeResult::kInvalidArgument instead of being truncated in release.
🧹 Nitpick comments (4)
core/include/librmcs/data/datas.hpp (1)

68-75: 建议给 supported() 加上 [[nodiscard]]noexcept,与 GpioDescriptor::supports() 保持一致。

同文件里紧邻的 GpioDescriptor::supports() 声明为 [[nodiscard]] constexpr bool ... const noexcept。此处 supported() 没有返回值属性也没有 noexcept,但实现只做 bool 短路 + supports() 的 noexcept 调用,实际是无异常的纯函数,返回值也不应被调用方忽略(丢弃返回值就等于绕过能力校验)。

♻️ 建议修改
-    bool supported(const spec::GpioDescriptor& gpio) const {
+    [[nodiscard]] constexpr bool supported(const spec::GpioDescriptor& gpio) const noexcept {
         return (!asap || gpio.supports(spec::GpioCapability::kDigitalReadOnce))
             && (!period_ms || gpio.supports(spec::GpioCapability::kDigitalReadPeriodic))
             && ((!rising_edge && !falling_edge)
                 || gpio.supports(spec::GpioCapability::kDigitalReadInterrupt))
             && (pull != GpioPull::kUp || gpio.supports(spec::GpioCapability::kPullUp))
             && (pull != GpioPull::kDown || gpio.supports(spec::GpioCapability::kPullDown));
     }

constexpr 还有额外收益:配合 kGpioDescriptors 的 constexpr 描述符,future 代码中 static_assert(config.supported(...)) 成为可能。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/include/librmcs/data/datas.hpp` around lines 68 - 75, The supported(...)
member should be marked [[nodiscard]] and noexcept (and consider constexpr) to
match spec::GpioDescriptor::supports() and reflect that it is a pure,
non-throwing boolean check; update the signature of supported(const
spec::GpioDescriptor& gpio) to be [[nodiscard]] constexpr bool supported(...)
const noexcept and keep the existing implementation unchanged so callers cannot
accidentally ignore the result and the function may be used in compile-time
contexts.
firmware/rmcs_board/boards/pro/app/board_app.hpp (2)

16-16: core/include/... 包含路径与 spec 头自身使用的 <librmcs/spec/gpio.hpp> 形式不一致。

本文件用 "core/include/librmcs/spec/rmcs_board_pro/gpio.hpp"(相对仓库根的 quoted 路径),而该头内部却用 #include <librmcs/spec/gpio.hpp>(angle-bracket,依赖 core/include 被加入 include path)。两种风格在同一调用链上混用会让新贡献者困惑:既然 firmware 构建目标已加入 core/include(符合 PR 描述),这里也可以直接写 #include <librmcs/spec/rmcs_board_pro/gpio.hpp>,与 host/spec 侧保持一致。非阻塞。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/boards/pro/app/board_app.hpp` at line 16, 在 board_app.hpp
中的包含使用了相对仓库根的 quoted 路径
("core/include/librmcs/spec/rmcs_board_pro/gpio.hpp"),与该头内部和其他模块使用的
angle-bracket 风格 (<librmcs/spec/gpio.hpp>) 不一致;请在 board_app.hpp 中将该 include 改为
<librmcs/spec/rmcs_board_pro/gpio.hpp>(保留 IWYU pragma: export),以与其他 spec
头的包含风格一致并利用已加入的 core/include include-path;定位符:board_app.hpp 中的 `#include` 行和目标头
rmcs_board_pro/gpio.hpp。

60-79: kGpioHardwareDescriptors 与 spec 侧 kGpioDescriptors 的映射已核对。

按 channel_index:0–3 (PB00–PB03, PWM0_P_0..3) 对应 kPwm1..kPwm4;4–10 (PA9/10/11/12/13/18/19) 对应 kSpiI2cSocket1..7;11–12 (PA22/PA23) 对应 UART5 RX/TX (kUart0Rx/Tx);13–14 (PA14/PA15) 对应 UART3 RX/TX (kUart1Rx/Tx);15–16 (PA1/PA0) 对应 UART0 RX/TX (kUart2Rx/Tx)。UART 映射与 init_uart() 中的 IOC 配置完全吻合;static_assert 亦保障尺寸一致。

一点建议:该表中 channel 顺序上"跳跃"(Socket 从 A9 连续到 A13 后跳到 A18/A19/A22/A23,随后 UART2 落回 A14/A15,再跳到 A0/A1)不容易一眼看出含义,建议在每个分组上方加一行 // PWM1..4 / // SpiI2cSocket1..7 / // UART0 RX/TX 等简短注释(或按 kGpioDescriptors 中的 kUart0Rx 等命名引用同步命名),便于后续维护时和 spec 头对照。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/boards/pro/app/board_app.hpp` around lines 60 - 79, The
GPIO descriptor array kGpioHardwareDescriptors is correct but its grouping/order
is hard to read; add brief inline comments above each logical group (e.g., "//
PWM1..4" for entries 0–3, "// SpiI2cSocket1..7" for entries 4–10, "// UART0
RX/TX" etc.) to map channel_index ranges to their spec names (kPwm1..kPwm4,
kSpiI2cSocket1..7, kUart0Rx/Tx, kUart1Rx/Tx, kUart2Rx/Tx) so maintainers can
quickly cross-reference kGpioDescriptors and init_uart() IOC mappings; keep the
existing ordering and static_assert as-is.
core/include/librmcs/spec/gpio.hpp (1)

43-45: supports() 对组合 mask 的语义建议注释明确。

supports(capability) 通过 capability_mask & capability != 0 判断,在传入单个位常量(如 kDigitalWrite)时行为等价于 "contains",这也是目前所有调用点(vendor.hppdatas.hpp::supported、host agents)使用的方式。但如果将来有人写 descriptor.supports(kDigitalReadPeriodic | kDigitalReadInterrupt) 期望"两项都支持",当前实现会在仅支持其中一项时错误返回 true(any-of 语义)。

建议二选一:

  1. 在注释中明确 "capability 必须是单一位" 的使用契约(若真的只打算支持单 bit)。
  2. 把实现改为 contains 语义:(capability_mask & capability) == capability,这样单 bit / 组合 bit 都语义自然,也为 kPwmCapabilities 这类聚合位的后续使用留出空间。
-    [[nodiscard]] constexpr bool supports(GpioCapability capability) const noexcept {
-        return static_cast<std::uint8_t>(capability_mask & capability) != 0;
-    }
+    // Returns true iff `capability_mask` supports all bits set in `capability`.
+    [[nodiscard]] constexpr bool supports(GpioCapability capability) const noexcept {
+        return (capability_mask & capability) == capability;
+    }

非阻塞;请按项目意图决定保留 any-of 还是改为 all-of。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/include/librmcs/spec/gpio.hpp` around lines 43 - 45, The
supports(GpioCapability) method currently uses (capability_mask & capability) !=
0 which implements an "any-of" test; decide whether to require a single-bit
input or to accept multi-bit masks and update accordingly: either add a clear
comment on supports/GpioCapability stating the contract "capability must be a
single-bit flag (e.g. kDigitalWrite)" and keep the current expression, or change
the implementation to an all-of/contains test by replacing the expression with
(capability_mask & capability) == capability so combinations like
kDigitalReadPeriodic | kDigitalReadInterrupt and aggregates like
kPwmCapabilities behave naturally; update the comment accordingly and ensure
callers (vendor.hpp, datas.hpp::supported, host agents) conform to the chosen
contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@firmware/c_board/app/src/gpio/gpio.hpp`:
- Around line 58-63: When handle_digital_read calls configure_digital_input_mode
and then immediately publishes an ASAP sample via publish_digital_input_sample
(when data.asap is true), postpone the next periodic deadline by one sample
period to avoid an immediate duplicate from poll_periodic_input_samples: after
publishing, advance the configured next-sample timestamp by data.sample_period
(or the internal sample_period used in configure_digital_input_mode) so the
periodic scheduler won't emit another sample immediately; apply the same change
to the analogous handler elsewhere in the diff that also uses config + asap
publishing.

In `@firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp`:
- Around line 30-31: 当前代码在 gpio/analog_gpio_pin.hpp 中用 __builtin_trap() 检查
pwm_ioc_function 范围,这违反了“禁用 GNU 扩展”的约定;请用项目的断言工具替换:包含的 assert.hpp 中的
assert_debug 应用于 pwm_ioc_function 检查(例如用 assert_debug(pwm_ioc_function <= 31)
或先判断再 assert_debug(false)),将 __builtin_trap() 替换为
assert_debug(...),保持原有约束并符合项目标准。

In `@firmware/rmcs_board/app/src/gpio/gpio.hpp`:
- Around line 46-51: handle_digital_read currently calls
configure_digital_input_mode (set_digital_input_mode_state) then, if data.asap,
immediately calls publish_digital_input_sample which can be followed immediately
by the periodic sampler because set_digital_input_mode_state initialized
next_sample_tick to now; after publishing when data.asap is true and the
configured period_ms > 0, advance the mode's next_sample_tick by one period
(e.g., next_sample_tick += period_ms or set to current_tick + period_ms) so the
next periodic sample is deferred one cycle; update this logic in
handle_digital_read (or in set_digital_input_mode_state) referencing
next_sample_tick, period_ms, data.asap, and publish_digital_input_sample.

In `@firmware/rmcs_board/app/src/usb/vendor.hpp`:
- Around line 156-160: 函数 gpio_analog_read_config_deserialized_callback 目前无视
channel_index 和 data;请改为按照 gpio_digital_read_config_deserialized_callback
的防御性模式实现:对 channel_index 做边界检查(与其他 GPIO 回调相同的 channel 数量判断),构造或获取对应的 descriptor
并用 data.supported(descriptor) 验证支持位(参照 spec::GpioCapability 的位掩码逻辑),在 validation
通过时再将请求转发到现有处理路径,否则记录或安全忽略;保持行为即便当前无 kAnalogRead* 能力位也遵循同样的验证与转发模式以避免未来静默丢包。

In `@firmware/rmcs_board/boards/pro/app/board_app.cpp`:
- Around line 205-208: The init_gpio_pins() function is intentionally an empty
stub; add a clear comment inside init_gpio_pins() stating that PWM IOC channels
are configured in descriptors, user buttons/switches and interrupt pins are
initialized in their own init routines, and remaining channels are configured
on-demand via
configure_digital_output_mode/configure_pwm_output_mode/configure_digital_input_mode
to avoid future confusion—refer to symbols init_gpio_pins,
SDK_DECLARE_EXT_ISR_M/gpio_a_isr, gpio_irq_handler, and GPIO_DI_GPIOA for
context.

In `@host/include/librmcs/agent/c_board.hpp`:
- Around line 52-74: 在 gpio_digital_write、gpio_digital_read 和 gpio_analog_write
三个方法中除了现有 capability/数据校验外还要验证传入的 GpioDescriptor 的 channel_index 落在 c_board
可接受的范围内:在每个方法开始处加入对 gpio.channel_index 的范围检查(例如与
spec::c_board::kGpioDescriptors.size() 或相应的描述符计数常量比较),如果超出范围则像其他失败情况一样抛出
std::invalid_argument(带上类似 "GPIO channel index out of range for c_board"
的描述性信息);把这个范围校验放在 capability/data 写入之前以避免发送无效通道到主机。

---

Outside diff comments:
In `@core/src/protocol/serializer.hpp`:
- Around line 123-288: The GPIO functions currently use utility::assert_debug to
validate channel_index which is stripped in release builds; replace those
asserts with a runtime check using LIBRMCS_VERIFY_LIKELY(channel_index < (1U <<
GpioHeader::ChannelIndex::kBitWidth), SerializeResult::kInvalidArgument) in each
public serializer entry (write_gpio_digital_data,
write_gpio_digital_read_config, write_gpio_analog_data,
write_gpio_analog_read_config, write_gpio_digital_read_result,
write_gpio_analog_read_result) — perform this verification before computing
required size/allocating buffer so invalid channel indices return
SerializeResult::kInvalidArgument instead of being truncated in release.

---

Nitpick comments:
In `@core/include/librmcs/data/datas.hpp`:
- Around line 68-75: The supported(...) member should be marked [[nodiscard]]
and noexcept (and consider constexpr) to match spec::GpioDescriptor::supports()
and reflect that it is a pure, non-throwing boolean check; update the signature
of supported(const spec::GpioDescriptor& gpio) to be [[nodiscard]] constexpr
bool supported(...) const noexcept and keep the existing implementation
unchanged so callers cannot accidentally ignore the result and the function may
be used in compile-time contexts.

In `@core/include/librmcs/spec/gpio.hpp`:
- Around line 43-45: The supports(GpioCapability) method currently uses
(capability_mask & capability) != 0 which implements an "any-of" test; decide
whether to require a single-bit input or to accept multi-bit masks and update
accordingly: either add a clear comment on supports/GpioCapability stating the
contract "capability must be a single-bit flag (e.g. kDigitalWrite)" and keep
the current expression, or change the implementation to an all-of/contains test
by replacing the expression with (capability_mask & capability) == capability so
combinations like kDigitalReadPeriodic | kDigitalReadInterrupt and aggregates
like kPwmCapabilities behave naturally; update the comment accordingly and
ensure callers (vendor.hpp, datas.hpp::supported, host agents) conform to the
chosen contract.

In `@firmware/rmcs_board/boards/pro/app/board_app.hpp`:
- Line 16: 在 board_app.hpp 中的包含使用了相对仓库根的 quoted 路径
("core/include/librmcs/spec/rmcs_board_pro/gpio.hpp"),与该头内部和其他模块使用的
angle-bracket 风格 (<librmcs/spec/gpio.hpp>) 不一致;请在 board_app.hpp 中将该 include 改为
<librmcs/spec/rmcs_board_pro/gpio.hpp>(保留 IWYU pragma: export),以与其他 spec
头的包含风格一致并利用已加入的 core/include include-path;定位符:board_app.hpp 中的 `#include` 行和目标头
rmcs_board_pro/gpio.hpp。
- Around line 60-79: The GPIO descriptor array kGpioHardwareDescriptors is
correct but its grouping/order is hard to read; add brief inline comments above
each logical group (e.g., "// PWM1..4" for entries 0–3, "// SpiI2cSocket1..7"
for entries 4–10, "// UART0 RX/TX" etc.) to map channel_index ranges to their
spec names (kPwm1..kPwm4, kSpiI2cSocket1..7, kUart0Rx/Tx, kUart1Rx/Tx,
kUart2Rx/Tx) so maintainers can quickly cross-reference kGpioDescriptors and
init_uart() IOC mappings; keep the existing ordering and static_assert as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f3ff6b9-fa56-41f8-8129-8a1d6307b557

📥 Commits

Reviewing files that changed from the base of the PR and between 3683953 and 9b23ead.

📒 Files selected for processing (29)
  • core/include/librmcs/data/datas.hpp
  • core/include/librmcs/spec/c_board/gpio.hpp
  • core/include/librmcs/spec/gpio.hpp
  • core/include/librmcs/spec/rmcs_board_lite/gpio.hpp
  • core/include/librmcs/spec/rmcs_board_pro/gpio.hpp
  • core/src/protocol/deserializer.cpp
  • core/src/protocol/deserializer.hpp
  • core/src/protocol/protocol.hpp
  • core/src/protocol/serializer.hpp
  • firmware/c_board/CMakeLists.txt
  • firmware/c_board/app/src/gpio/gpio.hpp
  • firmware/c_board/app/src/timer/timer.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/app/CMakeLists.txt
  • firmware/rmcs_board/app/src/app.cpp
  • firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp
  • firmware/rmcs_board/app/src/gpio/gpio.cpp
  • firmware/rmcs_board/app/src/gpio/gpio.hpp
  • firmware/rmcs_board/app/src/usb/vendor.hpp
  • firmware/rmcs_board/boards/lite/app/board_app.cpp
  • firmware/rmcs_board/boards/lite/app/board_app.hpp
  • firmware/rmcs_board/boards/pro/app/board_app.cpp
  • firmware/rmcs_board/boards/pro/app/board_app.hpp
  • firmware/rmcs_board/bootloader/CMakeLists.txt
  • host/include/librmcs/agent/c_board.hpp
  • host/include/librmcs/agent/rmcs_board_lite.hpp
  • host/include/librmcs/agent/rmcs_board_pro.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp
✅ Files skipped from review due to trivial changes (4)
  • firmware/rmcs_board/bootloader/CMakeLists.txt
  • firmware/rmcs_board/app/CMakeLists.txt
  • core/src/protocol/protocol.hpp
  • firmware/rmcs_board/app/src/gpio/gpio.cpp
🚧 Files skipped from review as they are similar to previous changes (12)
  • firmware/c_board/CMakeLists.txt
  • firmware/rmcs_board/app/src/app.cpp
  • firmware/rmcs_board/boards/lite/app/board_app.cpp
  • core/src/protocol/deserializer.cpp
  • firmware/rmcs_board/boards/lite/app/board_app.hpp
  • firmware/c_board/app/src/timer/timer.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • core/src/protocol/deserializer.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp
  • host/include/librmcs/agent/rmcs_board_lite.hpp
  • host/include/librmcs/agent/rmcs_board_pro.hpp

Comment thread firmware/c_board/app/src/gpio/gpio.hpp
Comment thread firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp Outdated
Comment thread firmware/rmcs_board/app/src/gpio/gpio.hpp
Comment thread firmware/rmcs_board/app/src/usb/vendor.hpp
Comment thread firmware/rmcs_board/boards/pro/app/board_app.cpp Outdated
Comment thread host/include/librmcs/agent/c_board.hpp
@qzhhhi qzhhhi force-pushed the dev/rmcsboard-gpio branch from 9b23ead to be1161e Compare April 21, 2026 18:18
@qzhhhi
Copy link
Copy Markdown
Member Author

qzhhhi commented Apr 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 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.

@qzhhhi
Copy link
Copy Markdown
Member Author

qzhhhi commented Apr 22, 2026

@coderabbitai pause

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

✅ Actions performed

Reviews paused.

qzhhhi added 2 commits April 22, 2026 03:02
- Extract channel field from GPIO data views (GpioDigitalDataView, GpioAnalogDataView, GpioReadConfigView) into an explicit channel_index parameter threaded through serialization, deserialization, and callbacks.
- Introduce GpioCapability bitmask enum and GpioDescriptor in core/spec, with c_board-specific descriptors that declare per-channel capabilities (e.g. channel 5 lacks interrupt support).
- Add GpioReadConfigView::supported() for unified host/firmware validation of read configurations against descriptor capabilities.
- Replace hardcoded channel-6 EXTI exclusion in c_board firmware with declarative capability mask in the shared descriptor table.
- Rename wire-protocol field GpioHeader::Channel to ChannelIndex and switch c_board channel numbering from 1-based to 0-based.
- Update c_board host agent API to accept GpioDescriptor references instead of raw channel numbers.
- Add core/include to firmware build targets that consume shared specs.

BREAKING CHANGE: GPIO data views no longer carry a channel field; all GPIO protocol and callback APIs now take a zero-based channel_index. c_board host agent methods require a GpioDescriptor reference instead of a raw channel number.
… semantics (#48)

- Add board-specific GPIO descriptors and host-side typed GPIO APIs for rmcs_board lite and pro.
- Implement rmcs_board firmware GPIO handling for digital input, digital output, interrupt-driven input reporting, and PWM output on supported pro channels.
- Refactor c_board digital input scheduling to use deadline-based periodic sampling. This changes the sampling behavior for read configurations with period_ms > 0.

BREAKING CHANGE: Digital GPIO read configurations with period_ms > 0 now emit the first periodic sample on the next polling cycle instead of waiting for a full period to elapse.
@qzhhhi qzhhhi force-pushed the dev/rmcsboard-gpio branch from be1161e to e77f3d3 Compare April 22, 2026 04:08
@qzhhhi qzhhhi merged commit e77f3d3 into main Apr 22, 2026
5 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in RMCS Slave SDK Apr 22, 2026
@qzhhhi qzhhhi deleted the dev/rmcsboard-gpio branch April 22, 2026 04:13
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