- 将设备相关文件重新组织为device/spi和comm分组 - 添加EtherCAT从站通信模块(esc_exmc_bus、esc_lan9252、ethercat_slave等) - 添加EXMC外设组件支持 - 统一文件命名规范为小写 refactor(canfd): 优化CANFD模块中断安全性和配置方式 - 替换自定义临界区函数为RT_CRITICAL_SAVE/RESTORE宏 - 使用配置常量CONFIG_IRQ_PREEMPT_CAN替代硬编码值 - 简化邮箱满时的处理逻辑,移除不必要的SysTick等待 - 移除废弃的中断保存头文件引用 refactor(timer): 更新定时器配置以使用配置常量 - 修复头文件包含路径大小写问题 - 使用CONFIG_NVIC_PRIORITY_GROUP配置项替代硬编码值
14 KiB
EtherCAT LAN9252 从站实时集成 Spec
Why
DCU_APP 后续要加入 EtherCAT 从站通信,要求周期 100us~250us、抖动 <1us(高性能运动控制级)。当前架构使用全局关中断(__disable_irq/PRIMASK)临界区、非抢占式协作调度、主循环 WFI 休眠,无法满足 <1us 抖动要求。需要先做实时架构改造,再新增 LAN9252 ESC 驱动骨架,并与现有系统集成。
What Changes
Requirement: EtherCAT 使能开关(未焊接硬件保护)
系统 SHALL 提供编译时使能宏 CONFIG_ETHERCAT_ENABLE(默认 0=禁用),控制 EtherCAT 相关代码的编译与运行时初始化。未焊接 LAN9252 硬件时,该宏保持 0,系统 SHALL:
- 编译期:
comm/全部代码不参与编译(#if CONFIG_ETHERCAT_ENABLE包裹),不增加任何 Flash/RAM 占用 - 运行时:
sys/initial.cpp不调用EthercatSlave::init()/EthercatSync::init(),EXMC/EXTI0/EXTI1/EXTI2 不被配置 - 主循环:
TASK_ETHERCAT_PROCESS/TASK_SPI_CMD_PROCESS分支不被执行 - 阶段 1 实时架构改造(NVIC 分组/BASEPRI 临界区/ISR 瘦身)不受影响,始终生效
Scenario: 未焊接 LAN9252 时系统正常运行
- WHEN
CONFIG_ETHERCAT_ENABLE为 0(默认) - THEN 编译产物与改造前功能等价,EXMC/EXTI0-2 未配置,主循环不调用 EtherCAT 任务,现有 ADC/CAN/SPI 功能正常
Scenario: 焊接 LAN9252 后启用
- WHEN 用户焊接硬件后将
CONFIG_ETHERCAT_ENABLE改为 1 并重新编译 - THEN
comm/代码参与编译,初始化序列调用 EtherCAT,EXTI0-2 配置生效,周期通信启动
阶段 1:实时架构改造(前置必做,不受使能开关影响)
- BREAKING: NVIC 优先级分组从
NVIC_PRIGROUP_PRE2_SUB2改为NVIC_PRIGROUP_PRE4_SUB0,抢占优先级从 2 位变为 4 位 - BREAKING: 全部临界区从
__disable_irq/__set_PRIMASK(PRIMASK,屏蔽所有中断)改为 BASEPRI(仅屏蔽 preempt≥1,保留 preempt=0 给 EtherCAT) - 新增
sys/critical.h:统一 BASEPRI 临界区宏RT_CRITICAL_ENTER()/RT_CRITICAL_EXIT() - ISR 瘦身:SPI CS release 中断与 CAN RX 中断只做"数据入队 + 置位标志",命令解析与处理移到主循环
- 主循环 WFI 去除:EtherCAT 启用后改忙等,通过
CONFIG_MAIN_LOOP_WFI_ENABLE开关控制 - 各中断优先级重排,为 EtherCAT 预留 preempt=0
阶段 2:LAN9252 ESC 驱动骨架(新建 comm/ 目录)
- 新增
comm/esc_lan9252.h/comm/esc_lan9252.cpp:LAN9252 寄存器定义、HBI 读写接口、自检(0x87654321) - 新增
comm/esc_exmc_bus.h/comm/esc_exmc_bus.cpp:EXMC NOR/SRAM 16-bit 模式初始化、GPIO 配置、读写时序 - 新增
comm/ethercat_slave.h/comm/ethercat_slave.cpp:从站周期处理接口、PDO 双缓冲(乒乓)、周期 ISR(放 RAM 执行段) - 新增
comm/ethercat_sync.h/comm/ethercat_sync.cpp:SYNC0 周期同步定时器(TIMER0,preempt=0)
阶段 3:系统集成(受 CONFIG_ETHERCAT_ENABLE 开关控制)
main.h任务枚举增加TASK_ETHERCAT_PROCESS/TASK_SPI_CMD_PROCESS(始终定义,不受开关影响)sys/initial.cpp初始化序列:#if CONFIG_ETHERCAT_ENABLE包裹EthercatSlave::init()/EthercatSync::init()调用sys/task_runner.cpp增加taskEthercatProcess()/taskSpiCmdProcess(),runPending()分支受CONFIG_ETHERCAT_ENABLE保护(无 EtherCAT 时分支空操作)sys/config.h增加 LAN9252 / EXMC / SYNC 相关配置项(含引脚定义占位宏),CONFIG_ETHERCAT_ENABLE默认 0- 构建配置纳入新文件(
comm/目录源文件纳入编译列表,实际编译由#if CONFIG_ETHERCAT_ENABLE控制)
Impact
- Affected specs: 无(首个 spec)
- Affected code:
main.cpp/main.h:主循环 WFI、任务枚举sys/config.h:NVIC 分组、优先级、LAN9252 配置sys/critical.h(新建):BASEPRI 临界区sys/task_manager.cpp:临界区换 BASEPRIsys/task_runner.cpp:增加 EtherCAT 任务sys/initial.cpp:增加 EtherCAT 初始化device/Timer.cpp:NVIC 分组设置device/CANFD.cpp:优先级、临界区、ISR 已基本瘦身device/app_spi_command.cpp:优先级、临界区、ISR 瘦身(CS release 处理移主循环)device/usart0.cpp:优先级、临界区comm/(新建全部):ESC 驱动、EXMC 总线、EtherCAT 从站、SYNC 同步
ADDED Requirements
Requirement: NVIC 优先级分组为 4 位抢占 0 位子优先级
系统 SHALL 使用 NVIC_PRIGROUP_PRE4_SUB0 分组,提供 4 个抢占优先级层级,为 EtherCAT 预留最高级(preempt=0)。
Scenario: EtherCAT 中断不被阻塞
- WHEN 任意非 EtherCAT 代码进入临界区
- THEN EtherCAT 中断(preempt=0)仍能立即响应,因为临界区使用 BASEPRI 仅屏蔽 preempt≥1
Requirement: BASEPRI 临界区替代 PRIMASK
系统 SHALL 使用 BASEPRI 实现临界区,阈值为 preempt≥1,确保 EtherCAT 中断(preempt=0)永不被阻塞。所有原有 __disable_irq/__set_PRIMASK 临界区 SHALL 替换为 BASEPRI 版本。
Scenario: 任务标志原子操作不阻塞 EtherCAT
- WHEN
TaskManager::takePending()或TaskManager::set()执行期间 - THEN EtherCAT 周期中断仍能抢占执行,BASEPRI 仅屏蔽 preempt≥1 的中断
Requirement: 中断优先级分层
系统 SHALL 按以下优先级表配置所有中断:
| 中断 | preempt | sub | 说明 |
|---|---|---|---|
| EtherCAT ESC 中断 / SYNC0 定时器 | 0 | 0 | 最高优先级,永不被阻塞 |
| TIMER2 100us 调度器 | 1 | 0 | 系统调度节拍 |
| CAN0/1/2 RX0 | 2 | 0/1/2 | CANFD 接收 |
| SPI0 / EXTI10_15 / USART0 | 3 | 0/1 | 非实时通信 |
Scenario: EtherCAT 中断抢占 CAN 中断
- WHEN CAN RX 中断正在处理时 EtherCAT 周期中断到达
- THEN EtherCAT 中断立即抢占 CAN 中断执行
Requirement: ISR 瘦身
SPI CS release 中断和 CAN RX 中断 SHALL 只做"数据入队 + 置位任务标志",命令解析、协议处理、硬件发送等耗时操作 SHALL 移到主循环任务中执行。
Scenario: SPI CS release 不在 ISR 内做命令处理
- WHEN SPI CS 释放中断触发
- THEN ISR 仅将接收数据入队并置位
TASK_SPI_LOG(或新增的解析任务),processCommand()在主循环中执行
Requirement: LAN9252 ESC HBI 驱动
系统 SHALL 通过 GD32E508 的 EXMC NOR/SRAM 模式提供 LAN9252 的 16-bit 并行 HBI 访问能力,包括:寄存器读、寄存器写、PDI 内存块读写、自检(读 0x87654321 校验)。
Scenario: ESC 自检通过
- WHEN 系统初始化调用
EscLan9252::selfTest() - THEN 通过 EXMC 读取 LAN9252 的 ESC ID 寄存器,返回值校验通过(0x87654321 或对应芯片 ID)
Scenario: 寄存器读写正确
- WHEN 调用
EscLan9252::writeReg(addr, value)后调用EscLan9252::readReg(addr) - THEN 读回值与写入值一致
Requirement: EtherCAT 周期数据双缓冲
系统 SHALL 使用 ping-pong 双缓冲机制实现 EtherCAT 周期数据交换:周期 ISR 使用一个缓冲区,主循环应用层使用另一个缓冲区,通过原子指针交换实现无锁访问。
Scenario: 主循环无锁读取最新 PDO
- WHEN 周期 ISR 完成一帧输入数据读取并翻转缓冲区指针
- THEN 主循环
taskEthercatProcess()通过原子指针读取到最新一帧完整数据,无需关中断
Requirement: 周期 ISR 放 RAM 执行段
EtherCAT 周期 ISR SHALL 标记为 RAM 执行段(通过链接脚本 section 属性),避免 Flash wait state 造成抖动,保证 <1us 抖动目标。
Scenario: 周期 ISR 执行时间稳定
- WHEN 周期 ISR 连续执行多次
- THEN 执行时间波动 <1us,不受 Flash wait state 影响
Requirement: SYNC0 周期同步由 ESC 硬件触发
系统 SHALL 使用 LAN9252 的 SYNC0 输出引脚(PB0/EXTI0)经 MCU EXTI0_IRQn 独立向量触发周期中断(而非 MCU 内部定时器自计时),由 ESC 硬件同步保证抖动最小。EXTI0_IRQn 配置为 preempt=0,永不被任何其他中断(包括 ESC IRQ)阻塞。TIMER0 作为 DC 未同步时的降级备选(通过 CONFIG_ETHERCAT_SYNC_MODE 开关切换)。
Scenario: SYNC0 引脚触发周期中断
- WHEN LAN9252 在 DC 同步点输出 SYNC0 脉冲
- THEN EXTI0 中断立即触发(独立向量),handler 清标志后调用
EthercatSlave::periodicIsr(),抖动 <1us
Scenario: SYNC0 抢占 ESC IRQ
- WHEN ESC IRQ 中断(EXTI1,preempt=1)正在处理时 SYNC0 周期中断到达
- THEN SYNC0(EXTI0,preempt=0)立即抢占 IRQ 中断执行
Scenario: 降级模式用 TIMER0
- WHEN
CONFIG_ETHERCAT_SYNC_MODE设为 TIMER 降级模式 - THEN 使用 TIMER0 以固定周期触发,作为 DC 未同步前的过渡
Requirement: ESC 事件中断(独立向量)
系统 SHALL 配置 LAN9252 的 IRQ 输出引脚(PB1/EXTI1)经 MCU EXTI1_IRQn 独立向量触发 ESC 事件中断(用于 AL 状态变化、EEPROM 加载完成、看门狗触发等事件),preempt=1。由于拥有独立向量,IRQ 与 SYNC0 完全分离,可被 SYNC0 抢占。IRQ handler SHALL 极简:仅清 EXTI 标志 + 置位 TASK_ETHERCAT_PROCESS。
Scenario: ESC 事件触发中断
- WHEN LAN9252 产生 ESC 事件(如 AL 状态变化)拉低 IRQ 引脚
- THEN EXTI1 中断(独立向量)触发,handler 置位
TASK_ETHERCAT_PROCESS,主循环处理事件
Scenario: SYNC1 备用中断
- WHEN LAN9252 输出 SYNC1 脉冲(PB2/EXTI2)
- THEN EXTI2 中断(独立向量,preempt=1)触发,清标志(备用,暂不处理)
Requirement: EtherCAT 应用层任务
系统 SHALL 在主循环中提供非实时应用层 PDO 映射任务 TASK_ETHERCAT_PROCESS,从双缓冲读取最新 PDO 数据供应用层使用。
Scenario: 应用层消费 PDO 数据
- WHEN 周期 ISR 置位
TASK_ETHERCAT_PROCESS标志 - THEN 主循环
taskEthercatProcess()从双缓冲读取最新 PDO 数据并交给应用层处理
MODIFIED Requirements
Requirement: 主循环空闲策略
原: 空闲时关中断后进 WFI 休眠。
改: 通过 CONFIG_MAIN_LOOP_WFI_ENABLE 开关控制;EtherCAT 启用时默认关闭 WFI 改忙等,避免唤醒延迟抖动。
Requirement: 临界区实现
原: 使用 __disable_irq()/__set_PRIMASK()(PRIMASK)全局关中断。
改: 使用 __set_BASEPRI()/__get_BASEPRI()(BASEPRI)仅屏蔽 preempt≥1,保留 preempt=0 给 EtherCAT。统一通过 sys/critical.h 的 RT_CRITICAL_ENTER()/RT_CRITICAL_EXIT() 宏访问。
Assumptions & Decisions
- ESC 型号: LAN9252(内建 PHY),16-bit 并行 HBI 模式
- MCU 接口: GD32E508 EXMC NOR/SRAM 模式,16-bit 数据宽度
- 通信周期: 100us~250us,抖动目标 <1us
- SYNC0 同步: 优先由 LAN9252 SYNC0 输出引脚(PB0/EXTI0)经 MCU EXTI0 独立向量触发(ESC 硬件同步,抖动最小);TIMER0 作为降级备选(
CONFIG_ETHERCAT_SYNC_MODE=1) 4a. ESC 中断引脚(已确定): SYNC0=PB0(EXTI0,preempt=0)、IRQ=PB1(EXTI1,preempt=1)、SYNC1=PB2(EXTI2,preempt=1),三个独立 NVIC 向量,完全分离优先级;保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0) 4b. 深度睡眠兼容: 引脚选择避开 PA0(WKUP),未来支持 STANDBY 最低功耗唤醒;Deep sleep 模式可用任意 EXTI(SYNC0/IRQ/CAN)唤醒 - 不包含: 完整 EtherCAT 协议栈(SSC),后续可挂接到
ethercat_slave.cpp接口 - 不包含: EEPROM/ESI 配置内容、从站应用对象字典
- 引脚定义: 基于 GD32E508 EXMC 标准映射 + 当前空闲引脚规划(见下表),配置项以宏定义在
config.h,用户可在原理图确认后调整 - EXMC Region: 使用 BANK0 Region1(NE1),基址 0x60000000
- FPU 上下文: 周期 ISR 不使用 FPU,避免惰性保存抖动;若协议栈后续需要 FPU,需评估
- RAM 执行段: 需要链接脚本支持(新增
.ramfuncsection),但本次不改链接脚本,先用__attribute__((section))标记,实际 RAM 放置由后续配置 - 使能开关(未焊接保护):
CONFIG_ETHERCAT_ENABLE默认 0(禁用)。阶段 1 实时架构改造始终生效(不依赖硬件);阶段 2/3 的 EtherCAT 代码全部由#if CONFIG_ETHERCAT_ENABLE包裹,未焊接时 0→不编译不初始化,系统功能与改造前等价。用户焊接硬件后改为 1 重新编译即可启用
引脚定义(GD32E508 EXMC 标准映射 + ESC 中断)
EXMC 16-bit 数据总线(全部 AF 复用,无当前占用冲突)
| 信号 | MCU 引脚 | EXMC 功能 | 当前占用 |
|---|---|---|---|
| D0 | PD14 | EXMC_D0 | 空闲 |
| D1 | PD15 | EXMC_D1 | 空闲 |
| D2 | PD0 | EXMC_D2 | 空闲 |
| D3 | PD1 | EXMC_D3 | 空闲 |
| D4 | PE7 | EXMC_D4 | 空闲 |
| D5 | PE8 | EXMC_D5 | 空闲 |
| D6 | PE9 | EXMC_D6 | 空闲 |
| D7 | PE10 | EXMC_D7 | 空闲 |
| D8 | PE11 | EXMC_D8 | 空闲 |
| D9 | PE12 | EXMC_D9 | 空闲 |
| D10 | PE13 | EXMC_D10 | 空闲 |
| D11 | PE14 | EXMC_D11 | 空闲 |
| D12 | PE15 | EXMC_D12 | 空闲 |
| D13 | PD8 | EXMC_D13 | 空闲 |
| D14 | PD9 | EXMC_D14 | 空闲 |
| D15 | PD10 | EXMC_D15 | 空闲 |
EXMC 控制线
| 信号 | MCU 引脚 | EXMC 功能 | 用途 |
|---|---|---|---|
| A16 | PD11 | EXMC_A16 | LAN9252 寄存器/数据选择(地址线) |
| NOE | PD4 | EXMC_NOE | 读使能 |
| NWE | PD5 | EXMC_NWE | 写使能 |
| NE1 | PD7 | EXMC_NE1 | 片选(LAN9252) |
ESC 中断引脚(已确定:PB0/PB1/PB2,三个独立向量,保留 PA0 给 STANDBY 唤醒)
| 信号 | MCU 引脚 | EXTI | 中断向量 | preempt | 用途 |
|---|---|---|---|---|---|
| LAN9252 SYNC0 | PB0 | EXTI0 | EXTI0_IRQn(独占) | 0 | 周期同步触发(DC sync),永不被阻塞 |
| LAN9252 SYNC1 | PB2 | EXTI2 | EXTI2_IRQn(独占) | 1 | 次周期同步(备用) |
| LAN9252 IRQ | PB1 | EXTI1 | EXTI1_IRQn(独占) | 1 | ESC 事件(AL 状态等) |
引脚选择理由(考虑深度睡眠需求):
- PB0/PB1/PB2 分别对应 EXTI0/1/2,在 GD32E508 上各自拥有独立的 NVIC 向量(EXTI0_IRQn/EXTI1_IRQn/EXTI2_IRQn),彻底避免 EXTI5-9 共享 EXTI9_5_IRQn 的风险
- SYNC0 独占 preempt=0,IRQ/SYNC1 同级 preempt=1,SYNC0 可抢占 IRQ/SYNC1,满足 <1us 抖动目标
- 关键:保留 PA0 给未来 STANDBY 模式唤醒。GD32E508 STANDBY 模式只有 PA0(WKUP_PIN0)能唤醒,若用于 SYNC0 会永久占用该唯一唤醒引脚。改用 PB0/PB1/PB2 后,未来 STANDBY 低功耗路径保留
- PB0/PB1/PB2 在当前代码中均未使用(已确认无占用冲突,仅 PB3/4/5=SPI0, PB9/12/13/14=CAN)
- 唤醒策略灵活:Deep sleep 模式可用 SYNC0/IRQ/CAN 等任意 EXTI 唤醒(恢复执行);Standby 模式用 PA0 唤醒(复位重启,适合完全断电场景)