CanFestival 协议栈解析笔记(一)

📅 2026/7/12 9:07:28 👁️ 阅读次数 📝 编程学习
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()定时器中断 (重发)

二、canDispatchTimeDispatch的中断关系

在经典 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() 内部是纯 memcpy(src/canopen/co_od.c:73): memcpy(entry->pdata, buf, size);

没有任何锁保护。如果TimeDispatch(定时器中断)正在通过co_pdo_tick_ms()遍历 TPDO 并调用od_read(),而canDispatch(CAN 中断)同时通过co_pdo_on_rpdo()写入同一个 OD 条目——这就是典型的数据竞争

但若两者同优先级,Cortex-M 内核保证:

  • 当 CPU 正在处理 TIMER_IRQ 时,同优先级的 CAN_RX_IRQ 不会被响应,而是等到 TIMER_IRQ 返回后才进入
  • 反之亦然

结论:canDispatchTimeDispatch在设计良好的系统中不会互相打断,前提是 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-80):

if (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 结构体则存在数据竞争

推荐的加固措施:

  1. 配置 CAN RX 和 Timer 中断为同一优先级(你已经提到)
  2. 保护 OD 关键区——在od_read/od_write周围使用__disable_irq()/__enable_irq()或 BASEPRI 屏蔽(参考经典 CanFestival 的EnterMutex/LeaveMutex宏)
  3. ADC ISR 使用独立的数据通道——通过 DMA 双缓冲写入,不直接访问od_entry_t.pdata
  4. 使用 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 是兼顾实时性和数据一致性的最优方案。