CanFestival 协议栈解析笔记(一)

📅 2026/7/12 8:47:09
CanFestival 协议栈解析笔记(一)
一、协议栈分层架构┌──────────────────────────────────────────────┐│ CiA 402 驱动层 (cia402_fsm / modes / servo) │ ← 应用层状态机├──────────────────────────────────────────────┤│ CANopen 协议核心 ││ ┌────────┬───────┬──────┬───────┬─────────┐ ││ │ NMT │ SDO │ PDO │ EMCY │ SYNC │ │ ← CiA 301 协议栈│ │ co_nmt │co_sdo │co_pdo│co_emcy│co_sync │ ││ └────────┴───────┴──────┴───────┴─────────┘ │├──────────────────────────────────────────────┤│ 对象字典 OD (co_od) — 二分查找 回调机制 │ ← 数据总线层├──────────────────────────────────────────────┤│ 硬件抽象层 (can_send 函数指针) │ ← 驱动接口├──────────────────────────────────────────────┤│ CAN 控制器中断 / 定时器中断 │ ← 中断层└──────────────────────────────────────────────┘关键设计特点模块核心函数被调用上下文NMTco_nmt_process_command()CAN RX 中断NMTco_nmt_tick()定时器中断 (心跳)SDOco_sdo_server_process()CAN RX 中断SDOco_sdo_server_tick()定时器中断 (超时)PDOco_pdo_on_rpdo()CAN RX 中断PDOco_pdo_on_sync()CAN RX 中断 (SYNC帧)PDOco_pdo_mark_changed()应用层/主循环PDOco_pdo_tick()/co_pdo_tick_ms()定时器中断EMCYco_emcy_trigger()任意上下文EMCYco_emcy_tick()定时器中断 (重发)二、canDispatch与TimeDispatch的中断关系在经典 CanFestival 中两者都存在且都需要在中断上下文中被调用canDispatch() ← 在 CAN RX 中断 ISR 中调用 TimeDispatch() ← 在硬件定时器 ISR如 SysTick中调用场景分析以 ARM Cortex-M 为例NVIC 优先级配置 (数字越小优先级越高): 场景 A — 同优先级推荐: CAN_RX_IRQ 优先级 4 TIMER_IRQ 优先级 4 → 不会互相抢占 (Cortex-M 对同优先级用尾链 Tail-Chaining) → canDispatch 和 TimeDispatch 是原子的不会嵌套 场景 B — CAN 优先级高于定时器: CAN_RX_IRQ 优先级 2 TIMER_IRQ 优先级 4 → canDispatch 可以抢占 TimeDispatch ⚠️ 危险 → TimeDispatch 修改 OD 数据时canDispatch 也可能读写同一 OD 条目 场景 C — 定时器优先级高于 CAN: → TimeDispatch 可以抢占 canDispatch ⚠️ 同样危险同优先级是安全看关键代码路径。以RPDO 写入 OD为例在co_pdo_on_rpdo()中// src/canopen/co_pdo.c — co_pdo_on_rpdo() od_write(pdo-od, me-index, me-subindex, buf, byte_len); 而 od_write() 内部是纯 memcpysrc/canopen/co_od.c:73 memcpy(entry-pdata, buf, size);没有任何锁保护。如果TimeDispatch定时器中断正在通过co_pdo_tick_ms()遍历 TPDO 并调用od_read()而canDispatchCAN 中断同时通过co_pdo_on_rpdo()写入同一个 OD 条目——这就是典型的数据竞争。但若两者同优先级Cortex-M 内核保证当 CPU 正在处理 TIMER_IRQ 时同优先级的 CAN_RX_IRQ 不会被响应而是等到 TIMER_IRQ 返回后才进入反之亦然结论canDispatch和TimeDispatch在设计良好的系统中不会互相打断前提是 CAN RX 中断和定时器中断配置为同一优先级。三、高优先级 ADC 中断能否抢占它们能而且这是一个需要特别注意的问题。NVIC 优先级: ADC_IRQ 优先级 1 (最高) CAN_RX_IRQ 优先级 4 TIMER_IRQ 优先级 4 当 canDispatch 或 TimeDispatch 正在执行时优先级更高的 ADC 电流采样中断会直接抢占 时间轴: ──────────────────────────────────────────────────── TIMER_IRQ 开始 → TimeDispatch() 执行到一半 ↓ ADC 触发 ADC_IRQ 抢占 ───── ADC_ISR 执行 ───── 返回 ↓ TimeDispatch() 继续执行 ────────────────────────────────────────────────────这是否危险取决于 ADC ISR 做了什么。危险场景 — ADC ISR 访问了 CANopen 数据如果 ADC ISR 中通过od_read()读取电流反馈值如0x6078实际电流而canDispatch正在通过od_write()修改同一 OD 条目// ADC ISR 中: od_read(od, 0x6078, 0, buf, size); // 读 current_actual_value // 同时 canDispatch 在 SDO 处理中: od_write(sdo-od, index, sub, write_data, data_size);由于od_read/od_write内部是裸memcpy会导致读到撕裂值 (torn read)——数据部分来自写入前、部分来自写入后。安全场景 — ADC ISR 只写自己的 buffer如果 ADC ISR 使用 DMA 双缓冲将采集数据写入独立的adc_buffer[]在主循环中再同步到 OD则抢占是安全的。仓库的实际情况查看 OD 回调机制src/canopen/co_od.c:74-80if (od-post_write_cb) { uint32_t r od-post_write_cb(index, subindex, entry-pdata, size, true); if (r ! 0) return r; }如果 ADC ISR 的高优先级抢占导致 post_write_cb 中的回调函数 可能触发电机控制参数更新执行到一半被更高优先级中断嵌套 会产生不可预期的行为因为OD 回调函数本身没有可重入保护。四、总结与建议问题结论canDispatch 与 TimeDispatch 能否互抢不应该。应配置 CAN RX 和 Timer 中断为同优先级利用 Cortex-M 尾链机制保证原子性高优先级 ADC ISR 能否抢占能。ARM NVIC 允许高优先级中断抢占低优先级抢占是否有风险有条件若 ADC ISR 只写独立 buffer 则安全若读写 OD 结构体则存在数据竞争推荐的加固措施配置 CAN RX 和 Timer 中断为同一优先级你已经提到保护 OD 关键区——在od_read/od_write周围使用__disable_irq()/__enable_irq()或 BASEPRI 屏蔽参考经典 CanFestival 的EnterMutex/LeaveMutex宏ADC ISR 使用独立的数据通道——通过 DMA 双缓冲写入不直接访问od_entry_t.pdata使用 BASEPRI 精确屏蔽——只屏蔽 CANopen 相关优先级不阻断 ADC// 进入 CANopen 关键区时 UINT32 oldbasepri __get_BASEPRI(); __set_BASEPRI(3 (8 - __NVIC_PRIO_BITS)); // 屏蔽优先级 4-15 od_write(...); // 安全操作 __set_BASEPRI(oldbasepri); // 恢复, ADC (优先级1) 仍可抢占这样 ADC 仍能实时采样而 CANopen 协议栈内部的数据一致性得到保障。以上就是完整的分析。核心要点canDispatch 和 TimeDispatch 若配置为同优先级则不会互相打断利用 Cortex-M 尾链机制但高优先级 ADC 中断确能抢占它们。是否安全取决于 ADC ISR 是否访问共享数据。如需加固BASEPRI 是兼顾实时性和数据一致性的最优方案。