feat(i2c): Add c_board I2C2 transport support#47
Conversation
总体概览该PR为机器人控制系统添加了完整的I2C(Inter-Integrated Circuit)通信支持,涵盖跨越核心协议层、固件硬件驱动和主机代理的集成实现。包括新增数据结构、序列化/反序列化流程、STM32硬件初始化和固件驱动等内容。 变更清单
代码审查难度评估🎯 4 (复杂) | ⏱️ ~60 分钟 说明:本 PR 涵盖多个分层次的变更(协议、硬件驱动、固件、主机集成),涉及复杂的异步I/O流程、中断处理、DMA管理、错误恢复逻辑和跨边界的回调机制。序列化/反序列化逻辑、固件驱动的请求队列和超时管理、以及主机端的模板/继承链都需要仔细审查以确保正确性。 可能相关的PR
诗
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
core/include/librmcs/protocol/i2c.hpp (1)
8-10: 建议加一条防御性 static_assert
kI2cMaxDataLength通过static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U)计算,当前kI2cDataLengthBits = 9时结果为 511,正确。但若将来有人将位宽改为 > 16,强制转换会静默截断导致协议常量错误。建议增加一个编译期断言以防止此类回归:♻️ 建议的小幅加固
inline constexpr std::size_t kI2cDataLengthBits = 9; +static_assert(kI2cDataLengthBits < 16, + "kI2cDataLengthBits must fit in uint16_t without truncation"); inline constexpr std::uint16_t kI2cMaxDataLength = static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/include/librmcs/protocol/i2c.hpp` around lines 8 - 10, Add a compile-time guard to prevent silent truncation of kI2cMaxDataLength if kI2cDataLengthBits is increased: insert a static_assert that kI2cDataLengthBits is <= std::numeric_limits<std::uint16_t>::digits (and optionally > 0) before the kI2cMaxDataLength definition so the expression static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U) cannot overflow/truncate; reference the constants kI2cDataLengthBits and kI2cMaxDataLength when adding the assertion.firmware/c_board/app/src/i2c/i2c.hpp (2)
175-183:PendingUplink对错误条目保留了整块kMaxDataLengthpayload,内存浪费可观
pending_uplinks_为kPendingUplinkQueueSize = 32条固定大小的PendingUplink,每条内含alignas(uint32_t) std::array<std::byte, kMaxDataLength> payload{}。当kMaxDataLength等于协议允许的最大帧长度(看起来是 512 字节),即使一条队列中全部只是错误上报(不携带 payload),也要占用约 16 KB RAM。对 F4 级 MCU 这是相当大的一块。可选改进(按影响由小到大):
- 将
kReadResult与kError拆成两条独立队列,错误队列只存元数据;- 或把读结果的 payload 单独放进一个小池(如 4~8 个
kMaxDataLength槽),条目用 chunk 索引引用,类似现在的payload_chunks_设计;- 或降低
kPendingUplinkQueueSize(32 条对错误/读结果积压而言偏多)。此为可选优化,不影响功能正确性。
Also applies to: 682-682
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 175 - 183, PendingUplink currently embeds a full kMaxDataLength payload causing large RAM use for error-only entries; split uplink types into a small metadata-only ErrorUplink and a ReadUplink that references payload storage: create a new lightweight struct (e.g. ErrorUplink) containing type/slave_address/data_length/has_register/reg_address/is_read without the large payload, change pending_uplinks_ usage to maintain two queues (one for ErrorUplink, one for ReadUplink or keep pending_uplinks_ only for reads), and implement a small fixed-size payload pool (reuse existing payload_chunks_ design or add a payload_chunks_ array and have ReadUplink store a chunk index and actual length) so actual kMaxDataLength buffers are only allocated in the pool; update enqueue/dequeue logic around functions that push/pop PendingUplink to use the new structs and pool.
511-531: 在InterruptLockGuard作用域内执行可达kMaxDataLength字节的 memcpy第 525 行在关中断状态下把整段 payload 拷进 pending 槽。当
kMaxDataLength取最大值时,这段拷贝会把整个系统的中断响应拖后数个微秒。建议把 payload 拷贝挪出锁外:先在锁内分配/保留一个条目槽(占位并推进 head/count),记录目标槽指针,锁外再 memcpy payload,再在锁内将条目置为"已就绪"供
try_flush_uplink消费。或者使用单生产者-单消费者的 ring,head 由 ISR 推进、tail 由主循环推进,拷贝本身不需关中断。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 511 - 531, enqueue_read_result_uplink holds InterruptLockGuard while doing a potentially large memcpy of up to kMaxDataLength bytes into pending_uplinks_, which extends interrupt-disabled time; fix by reserving a slot inside the lock (use pending_uplink_head_, pending_uplink_count_ and advance_pending_uplink_index to increment head/count and obtain the target entry index or a pointer), release the lock, perform std::memcpy to that target entry's payload outside the InterruptLockGuard, then re-enter the lock and mark the entry fields (type, slave_address, data_length, has_register, reg_address or a ready flag) so try_flush_uplink can consume it; alternatively implement single-producer-single-consumer ring semantics where ISR advances head and main loop advances tail so the copy does not need interrupts disabled.
🤖 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/src/protocol/deserializer.hpp`:
- Around line 44-52: Add no-op overrides for the four missing I2C callbacks in
the Vendor class so it fulfills the DeserializeCallback interface: implement
i2c_write_deserialized_callback(FieldId id, const data::I2cDataView& data)
override, i2c_read_config_deserialized_callback(FieldId id, const
data::I2cReadConfigView& data) override,
i2c_read_result_deserialized_callback(FieldId id, const data::I2cDataView& data)
override, and i2c_error_deserialized_callback(FieldId id, const
data::I2cErrorView& data) override in
firmware/rmcs_board/app/src/usb/vendor.hpp; each should have an empty body
(no-op), similar to existing rmcs_board GPIO/accelerometer callbacks, to satisfy
the pure-virtual methods in DeserializeCallback and unblock compilation.
In `@firmware/c_board/app/src/i2c/i2c.hpp`:
- Around line 412-419: recover_active_request_locked currently performs heavy
HAL operations (abort_dma_if_enabled, HAL_I2C_DeInit/HAL_I2C_Init) while holding
InterruptLockGuard, which blocks other ISRs; change it to only perform quick
locked updates: set recovery_pending_ = true, set internal state to Idle,
clear_stale_irq_sources_locked(), and call finish_active_request_locked() — do
not call abort_dma_if_enabled or HAL_I2C_DeInit/Init inside the lock. Move the
actual DMA abort and HAL reinit into the non-ISR path: have update() perform
abort_dma_if_enabled and HAL_I2C_DeInit/HAL_I2C_Init when recovery_pending_ is
true (mirroring error_callback's deferred recovery pattern at lines ~124-128).
Also modify callers (recover_active_request, recover_timed_out_transfer,
consume_deferred_recovery and the try_start_next_request failure branch) to set
recovery_pending_ instead of performing immediate HAL reinit so all heavy HAL
work occurs unlocked in update().
---
Nitpick comments:
In `@core/include/librmcs/protocol/i2c.hpp`:
- Around line 8-10: Add a compile-time guard to prevent silent truncation of
kI2cMaxDataLength if kI2cDataLengthBits is increased: insert a static_assert
that kI2cDataLengthBits is <= std::numeric_limits<std::uint16_t>::digits (and
optionally > 0) before the kI2cMaxDataLength definition so the expression
static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U) cannot
overflow/truncate; reference the constants kI2cDataLengthBits and
kI2cMaxDataLength when adding the assertion.
In `@firmware/c_board/app/src/i2c/i2c.hpp`:
- Around line 175-183: PendingUplink currently embeds a full kMaxDataLength
payload causing large RAM use for error-only entries; split uplink types into a
small metadata-only ErrorUplink and a ReadUplink that references payload
storage: create a new lightweight struct (e.g. ErrorUplink) containing
type/slave_address/data_length/has_register/reg_address/is_read without the
large payload, change pending_uplinks_ usage to maintain two queues (one for
ErrorUplink, one for ReadUplink or keep pending_uplinks_ only for reads), and
implement a small fixed-size payload pool (reuse existing payload_chunks_ design
or add a payload_chunks_ array and have ReadUplink store a chunk index and
actual length) so actual kMaxDataLength buffers are only allocated in the pool;
update enqueue/dequeue logic around functions that push/pop PendingUplink to use
the new structs and pool.
- Around line 511-531: enqueue_read_result_uplink holds InterruptLockGuard while
doing a potentially large memcpy of up to kMaxDataLength bytes into
pending_uplinks_, which extends interrupt-disabled time; fix by reserving a slot
inside the lock (use pending_uplink_head_, pending_uplink_count_ and
advance_pending_uplink_index to increment head/count and obtain the target entry
index or a pointer), release the lock, perform std::memcpy to that target
entry's payload outside the InterruptLockGuard, then re-enter the lock and mark
the entry fields (type, slave_address, data_length, has_register, reg_address or
a ready flag) so try_flush_uplink can consume it; alternatively implement
single-producer-single-consumer ring semantics where ISR advances head and main
loop advances tail so the copy does not need interrupts disabled.
🪄 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: 72a1c41d-62bf-49f3-a7d9-58760f13cde5
📒 Files selected for processing (25)
core/include/librmcs/data/datas.hppcore/include/librmcs/protocol/i2c.hppcore/src/protocol/deserializer.cppcore/src/protocol/deserializer.hppcore/src/protocol/protocol.hppcore/src/protocol/serializer.hppfirmware/c_board/app/src/app.cppfirmware/c_board/app/src/i2c/i2c.cppfirmware/c_board/app/src/i2c/i2c.hppfirmware/c_board/app/src/usb/interrupt_safe_buffer.hppfirmware/c_board/app/src/usb/vendor.hppfirmware/c_board/bsp/cubemx/Core/Inc/i2c.hfirmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_hal_conf.hfirmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_it.hfirmware/c_board/bsp/cubemx/Core/Src/dma.cfirmware/c_board/bsp/cubemx/Core/Src/gpio.cfirmware/c_board/bsp/cubemx/Core/Src/i2c.cfirmware/c_board/bsp/cubemx/Core/Src/main.cfirmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.cfirmware/c_board/bsp/cubemx/cmake/stm32cubemx/CMakeLists.txtfirmware/c_board/bsp/cubemx/rmcs_slave.iochost/include/librmcs/agent/c_board.hpphost/include/librmcs/agent/detail/i2c0_common.hpphost/include/librmcs/protocol/handler.hpphost/src/protocol/handler.cpp
| virtual void i2c_write_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0; | ||
|
|
||
| virtual void | ||
| i2c_read_config_deserialized_callback(FieldId id, const data::I2cReadConfigView& data) = 0; | ||
|
|
||
| virtual void | ||
| i2c_read_result_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0; | ||
|
|
||
| virtual void i2c_error_deserialized_callback(FieldId id, const data::I2cErrorView& data) = 0; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查两个 Vendor 类是否 override 了新加的 4 个 i2c_*_deserialized_callback 方法。
fd -a 'vendor.hpp' firmware | while read -r f; do
echo "=== $f ==="
rg -n 'i2c_(write|read_config|read_result|error)_deserialized_callback' "$f" || echo " (未实现)"
doneRepository: Alliance-Algorithm/librmcs
Length of output: 410
🏁 Script executed:
#!/bin/bash
# 检查 rmcs_board vendor.hpp 的内容和继承关系
echo "=== rmcs_board vendor.hpp 内容 ==="
cat -n firmware/rmcs_board/app/src/usb/vendor.hpp | head -100
echo -e "\n=== rmcs_board Vendor 类定义 ==="
rg -A 5 'class Vendor' firmware/rmcs_board/app/src/usb/vendor.hpp
echo -e "\n=== c_board 四个 callback 实现的内容(示例) ==="
rg -A 8 'i2c_write_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp | head -20Repository: Alliance-Algorithm/librmcs
Length of output: 4810
🏁 Script executed:
#!/bin/bash
# 检查是否存在其他继承 DeserializeCallback 的类
echo "=== 所有继承 DeserializeCallback 的类 ==="
rg 'class \w+.*:\s*.*DeserializeCallback' firmware --type hpp --type cppRepository: Alliance-Algorithm/librmcs
Length of output: 150
🏁 Script executed:
#!/bin/bash
# 读取 rmcs_board vendor.hpp 的全部内容,确认是否有 i2c 回调实现
wc -l firmware/rmcs_board/app/src/usb/vendor.hpp
echo "=== 完整文件内容 ==="
cat -n firmware/rmcs_board/app/src/usb/vendor.hpp
echo -e "\n=== 检查是否含有 i2c_write_deserialized_callback ==="
grep -n 'i2c_write_deserialized_callback' firmware/rmcs_board/app/src/usb/vendor.hpp || echo "未找到"Repository: Alliance-Algorithm/librmcs
Length of output: 6532
🏁 Script executed:
#!/bin/bash
# 正确的方式:用 --type-list 查看支持的类型,或直接用 rg 的默认设置
echo "=== 所有继承 DeserializeCallback 的类 ==="
rg 'class \w+.*:\s*.*DeserializeCallback' firmwareRepository: Alliance-Algorithm/librmcs
Length of output: 105
🏁 Script executed:
#!/bin/bash
# 查看 DeserializeCallback 中新增的 i2c 方法签名
echo "=== DeserializeCallback 中的 i2c 方法 ==="
rg -A 2 'i2c_(write|read_config|read_result|error)_deserialized_callback' core/src/protocol/deserializer.hpp
# 检查 rmcs_board 是否有 I2C 支持
echo -e "\n=== rmcs_board 是否有 I2C 支持 ==="
find firmware/rmcs_board -name '*i2c*' -type f 2>/dev/null || echo "(未找到 I2C 文件)"Repository: Alliance-Algorithm/librmcs
Length of output: 604
🏁 Script executed:
#!/bin/bash
# 检查 c_board vendor.hpp 中 i2c 回调的完整实现
echo "=== c_board i2c_write_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_write_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp
echo -e "\n=== c_board i2c_read_result_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_read_result_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp
echo -e "\n=== c_board i2c_error_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_error_deserialized_callback' firmware/c_board/app/src/usb/vendor.hppRepository: Alliance-Algorithm/librmcs
Length of output: 1048
firmware/rmcs_board/app/src/usb/vendor.hpp 中缺少四个 I2C 回调实现,导致构建失败。
Vendor 类继承自 DeserializeCallback(私有继承),必须实现所有纯虚方法。firmware/c_board/app/src/usb/vendor.hpp 中的实现已齐全(downlink 写操作路由到 i2c0,读结果和错误帧作为仅上行数据类型被忽略)。但 firmware/rmcs_board/app/src/usb/vendor.hpp 缺失下述四个 override:
i2c_write_deserialized_callbacki2c_read_config_deserialized_callbacki2c_read_result_deserialized_callbacki2c_error_deserialized_callback
rmcs_board 没有 I2C 硬件,这些方法应为 no-op(可参考 rmcs_board 现有的 GPIO 或加速度计回调实现),以便解除编译阻塞。
🧰 Tools
🪛 GitHub Actions: Lint
[error] 44-52: Pure virtual functions in DeserializeCallback: i2c_write_deserialized_callback, i2c_read_config_deserialized_callback, i2c_read_result_deserialized_callback, i2c_error_deserialized_callback (unimplemented by Vendor).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/src/protocol/deserializer.hpp` around lines 44 - 52, Add no-op overrides
for the four missing I2C callbacks in the Vendor class so it fulfills the
DeserializeCallback interface: implement i2c_write_deserialized_callback(FieldId
id, const data::I2cDataView& data) override,
i2c_read_config_deserialized_callback(FieldId id, const data::I2cReadConfigView&
data) override, i2c_read_result_deserialized_callback(FieldId id, const
data::I2cDataView& data) override, and i2c_error_deserialized_callback(FieldId
id, const data::I2cErrorView& data) override in
firmware/rmcs_board/app/src/usb/vendor.hpp; each should have an empty body
(no-op), similar to existing rmcs_board GPIO/accelerometer callbacks, to satisfy
the pure-virtual methods in DeserializeCallback and unblock compilation.
| void recover_active_request_locked() { | ||
| abort_dma_if_enabled(hal_i2c_handle_->hdmarx); | ||
| abort_dma_if_enabled(hal_i2c_handle_->hdmatx); | ||
| core::utility::assert_always(HAL_I2C_DeInit(hal_i2c_handle_) == HAL_OK); | ||
| core::utility::assert_always(HAL_I2C_Init(hal_i2c_handle_) == HAL_OK); | ||
| clear_stale_irq_sources_locked(); | ||
| finish_active_request_locked(); | ||
| } |
There was a problem hiding this comment.
recover_active_request_locked() 在关中断期间执行 HAL_I2C_DeInit/Init,会放大中断延迟
该函数通过 recover_active_request()、recover_timed_out_transfer()、consume_deferred_recovery() 被调用时,都处于 InterruptLockGuard 作用域内。HAL_I2C_DeInit + HAL_I2C_Init 涉及多次寄存器写、可能等待 BUSY 位清零,关中断期间执行会阻塞所有其它 ISR(CAN/UART/USB/定时器)。
考虑把真正的 DMA abort + HAL 重初始化移到锁外:锁内只快速"标记为需要恢复 + 状态置 Idle",锁外再做 HAL 重建。error_callback() 已经采用了这种延迟恢复的思路(行 124-128),可以把 try_start_next_request 启动失败分支和超时恢复也统一改走 recovery_pending_ 路径,让所有重型 HAL 操作都在 update() 主循环中(且锁外)执行。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 412 - 419,
recover_active_request_locked currently performs heavy HAL operations
(abort_dma_if_enabled, HAL_I2C_DeInit/HAL_I2C_Init) while holding
InterruptLockGuard, which blocks other ISRs; change it to only perform quick
locked updates: set recovery_pending_ = true, set internal state to Idle,
clear_stale_irq_sources_locked(), and call finish_active_request_locked() — do
not call abort_dma_if_enabled or HAL_I2C_DeInit/Init inside the lock. Move the
actual DMA abort and HAL reinit into the non-ISR path: have update() perform
abort_dma_if_enabled and HAL_I2C_DeInit/HAL_I2C_Init when recovery_pending_ is
true (mirroring error_callback's deferred recovery pattern at lines ~124-128).
Also modify callers (recover_active_request, recover_timed_out_transfer,
consume_deferred_recovery and the try_start_next_request failure branch) to set
recovery_pending_ instead of performing immediate HAL reinit so all heavy HAL
work occurs unlocked in update().
Summary
This PR adds end-to-end I2C transport support for
c_board.It introduces the I2C wire format in the shared protocol layer, exposes host-side I2C APIs for
CBoard, and implements the firmware-side transport onc_boardby mapping the protocol-facing logicalI2C0channel to STM32
I2C2with DMA-backed transfers.What Changed
1. Shared protocol layer
core/include/librmcs/protocol/i2c.hppI2cDataViewI2cReadConfigViewI2cErrorViewDataCallbackto report structured I2C errors instead of only a slave addressI2cHeaderprotocol layout with:2. Host-side support
host::protocol::Handler::PacketBuilderwith I2C write/read/read-result helpersagent/detail/i2c0_common.hppagent::CBoard3.
c_boardfirmware transportfirmware/c_board/app/src/i2c/module4. USB downlink/uplink integration
5. CubeMX / BSP integration
I2C2peripheral filesI2C2rmcs_slave.ioc) accordinglyDesign Notes
I2C0in shared/host APIs.c_board, that logical channel is implemented by STM32I2C2.Scope
This PR is focused on
c_boardI2C transport support and the shared protocol/host pieces required for it.It does not introduce a new public multi-I2C board model yet; it adds the first concrete
c_boardI2C transport path on top of the shared protocol.I2C2传输支持
本PR为c_board添加了端到端的I2C传输支持,跨越共享协议、主机端、固件和USB/CubeMX集成四个领域。
协议层
新增I2C协议定义 (
core/include/librmcs/protocol/i2c.hpp)数据接口扩展 (
core/include/librmcs/data/datas.hpp)DataId::kI2c0 = 16标识符I2cDataView(从地址、数据载荷、可选寄存器)、I2cReadConfigView(从地址、读长度、可选寄存器)、I2cErrorView(从地址、数据长度、可选寄存器、读写标志)DataCallback接口新增两个虚函数:i2c_receive_callback()和i2c_error_callback()协议编码 (
core/src/protocol/protocol.hpp,serializer.hpp,deserializer.cpp)I2cHeader位域结构体,包含载荷类型选择器、寄存器标志、错误标志、7比特从地址和9比特数据长度kWrite)、读请求(kReadRequest)、读结果(kReadResult)、错误(kError)write_i2c_write()、write_i2c_read_config()、write_i2c_read_result()、write_i2c_error()(两个重载)process_i2c_field()处理I2C帧解析与验证,包括从地址范围检查、长度验证、标志组合验证等主机端
代理接口 (
host/include/librmcs/agent/c_board.hpp)CBoard现继承自detail::SingleI2c0DataCallback替代直接继承DataCallbackPacketBuilder继承自detail::I2c0PacketBuilderMixin<PacketBuilder>I2C0工具库 (
host/include/librmcs/agent/detail/i2c0_common.hpp)SingleI2c0DataCallback实现,针对DataId::kI2c0路由I2C数据和错误回调I2c0PacketBuilderMixin<Derived>提供i2c0_write()和i2c0_read()方法,含参数验证(从地址≤0x7F,数据长度验证)协议处理器 (
host/include/librmcs/protocol/handler.hpp,src/protocol/handler.cpp)PacketBuilder新增三个公共方法:write_i2c()、write_i2c_read_config()、write_i2c_read_result()process_result_strict辅助函数改进错误处理(kBadAlloc现视为失败)c_board固件
I2C驱动 (
firmware/c_board/app/src/i2c/i2c.hpp和i2c.cpp)I2c类实现DMA驱动的排队写/读传输,单例i2c0绑定到STM32 I2C2handle_downlink_write()、handle_downlink_read_config())tx_complete_callback()、rx_complete_callback())、错误处理和恢复HAL_I2C_MasterTxCpltCallback等)应用集成 (
firmware/c_board/app/src/app.cpp)USB处理 (
firmware/c_board/app/src/usb/vendor.hpp,interrupt_safe_buffer.hpp)i2c::i2c0try_unlock_and_clear()更名为try_unlock(),移除清除行为以防丢弃队列中的上链消息硬件和BSP配置
CubeMX配置 (
firmware/c_board/bsp/cubemx/)HAL_I2C_MODULE_ENABLED)MX_I2C2_Init())、MSP初始化/反初始化GPIO和DMA
设计说明:协议层I2C通道标识为I2C0,在c_board硬件上映射到STM32 I2C2外设。