From 1612e326ee02efc5db853306250d62e3b31eda49 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Fri, 26 Jun 2026 00:57:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(cproject):=20=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=96=87=E4=BB=B6=E7=BB=93=E6=9E=84=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0EtherCAT=E9=80=9A=E4=BF=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将设备相关文件重新组织为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配置项替代硬编码值 --- .../checklist.md | 82 +++++++ .../add-ethercat-lan9252-realtime/spec.md | 222 ++++++++++++++++++ .../add-ethercat-lan9252-realtime/tasks.md | 131 +++++++++++ .../GD32E508_DCU_APP.cproject.yml | 45 ++-- GD32E508_DCU_APP/comm/esc_exmc_bus.cpp | 99 ++++++++ GD32E508_DCU_APP/comm/esc_exmc_bus.h | 53 +++++ GD32E508_DCU_APP/comm/esc_lan9252.cpp | 82 +++++++ GD32E508_DCU_APP/comm/esc_lan9252.h | 58 +++++ GD32E508_DCU_APP/comm/ethercat_slave.cpp | 70 ++++++ GD32E508_DCU_APP/comm/ethercat_slave.h | 67 ++++++ GD32E508_DCU_APP/comm/ethercat_sync.cpp | 122 ++++++++++ GD32E508_DCU_APP/comm/ethercat_sync.h | 26 ++ GD32E508_DCU_APP/device/ADC.cpp | 2 +- GD32E508_DCU_APP/device/CANFD.cpp | 63 +---- GD32E508_DCU_APP/device/Timer.cpp | 6 +- .../{ => spi}/app_spi_cmd_boot_request.cpp | 6 +- .../{ => spi}/app_spi_cmd_can_bridge.cpp | 20 +- .../device/{ => spi}/app_spi_cmd_ota_wait.cpp | 6 +- .../device/{ => spi}/app_spi_cmd_ping.cpp | 6 +- .../device/{ => spi}/app_spi_command.cpp | 46 +++- .../device/{ => spi}/app_spi_command.h | 8 +- .../device/{ => spi}/app_spi_command_defs.h | 0 .../device/{ => spi}/spi_command_handler.h | 0 GD32E508_DCU_APP/device/usart0.cpp | 9 +- GD32E508_DCU_APP/main.cpp | 11 +- GD32E508_DCU_APP/main.h | 4 +- GD32E508_DCU_APP/sys/config.h | 124 +++++++++- GD32E508_DCU_APP/sys/critical.h | 62 +++++ GD32E508_DCU_APP/sys/initial.cpp | 17 +- GD32E508_DCU_APP/sys/task_manager.cpp | 37 +-- GD32E508_DCU_APP/sys/task_runner.cpp | 37 ++- GD32E508_DCU_APP/sys/task_runner.h | 4 + 32 files changed, 1378 insertions(+), 147 deletions(-) create mode 100644 .trae/specs/add-ethercat-lan9252-realtime/checklist.md create mode 100644 .trae/specs/add-ethercat-lan9252-realtime/spec.md create mode 100644 .trae/specs/add-ethercat-lan9252-realtime/tasks.md create mode 100644 GD32E508_DCU_APP/comm/esc_exmc_bus.cpp create mode 100644 GD32E508_DCU_APP/comm/esc_exmc_bus.h create mode 100644 GD32E508_DCU_APP/comm/esc_lan9252.cpp create mode 100644 GD32E508_DCU_APP/comm/esc_lan9252.h create mode 100644 GD32E508_DCU_APP/comm/ethercat_slave.cpp create mode 100644 GD32E508_DCU_APP/comm/ethercat_slave.h create mode 100644 GD32E508_DCU_APP/comm/ethercat_sync.cpp create mode 100644 GD32E508_DCU_APP/comm/ethercat_sync.h rename GD32E508_DCU_APP/device/{ => spi}/app_spi_cmd_boot_request.cpp (88%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_cmd_can_bridge.cpp (89%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_cmd_ota_wait.cpp (86%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_cmd_ping.cpp (93%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_command.cpp (95%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_command.h (90%) rename GD32E508_DCU_APP/device/{ => spi}/app_spi_command_defs.h (100%) rename GD32E508_DCU_APP/device/{ => spi}/spi_command_handler.h (100%) create mode 100644 GD32E508_DCU_APP/sys/critical.h diff --git a/.trae/specs/add-ethercat-lan9252-realtime/checklist.md b/.trae/specs/add-ethercat-lan9252-realtime/checklist.md new file mode 100644 index 0000000..f5ec79b --- /dev/null +++ b/.trae/specs/add-ethercat-lan9252-realtime/checklist.md @@ -0,0 +1,82 @@ +# Verification Checklist + +## 使能开关(未焊接保护) +- [x] `sys/config.h` 中 `CONFIG_ETHERCAT_ENABLE` 默认为 0 +- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `comm/` 全部代码不参与编译(用 `#if` 包裹) +- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `sys/initial.cpp` 不调用 `EthercatSlave::init()` / `EthercatSync::init()` +- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `sys/task_runner.cpp` 的 `TASK_ETHERCAT_PROCESS` 分支空操作 +- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 EXMC / EXTI0 / EXTI1 / EXTI2 不被配置 +- [x] `CONFIG_ETHERCAT_ENABLE=0` 时编译产物与改造前功能等价(ADC/CAN/SPI 正常) +- [x] 阶段 1 实时架构改造(NVIC/BASEPRI/ISR 瘦身)不受 `CONFIG_ETHERCAT_ENABLE` 影响,始终生效 +- [x] `TASK_SPI_CMD_PROCESS` 与 `taskSpiCmdProcess()` 不受 `CONFIG_ETHERCAT_ENABLE` 限制(阶段1 改造已需要) + +## 阶段 1:实时架构改造 +- [x] `sys/critical.h` 已创建,`RT_CRITICAL_ENTER/EXIT/SAVE/RESTORE` 宏基于 BASEPRI 实现 +- [x] `sys/config.h` 中 `CONFIG_NVIC_PRIORITY_GROUP` 设为 `NVIC_PRIGROUP_PRE4_SUB0` +- [x] `sys/config.h` 中各中断优先级宏已按分层表配置(EtherCAT=0/调度器=1/CAN=2/SPI+EXTI+USART=3) +- [x] `sys/config.h` 中 `CONFIG_MAIN_LOOP_WFI_ENABLE` 默认为 0 +- [x] `device/Timer.cpp` 中 `nvic_priority_group_set` 使用 `CONFIG_NVIC_PRIORITY_GROUP` +- [x] `device/Timer.cpp` 中调度器 NVIC 优先级为 (1, 0) +- [x] `sys/task_manager.cpp` 中 `set()` 和 `takePending()` 使用 BASEPRI 临界区 +- [x] `sys/task_manager.cpp` 中无残留 `__disable_irq/__set_PRIMASK` +- [x] `device/CANFD.cpp` 中 `hasRxFrame()` 和 `consumeRxFrame()` 使用 BASEPRI 临界区 +- [x] `device/CANFD.cpp` 中 CAN RX0 NVIC 优先级为 (2, n) +- [x] `device/CANFD.cpp` 中 `handleRxInterrupt()` 仅做入队+置位(验证未退化) +- [x] `device/app_spi_command.cpp` 中 SPI0 NVIC 优先级为 (3, 0) +- [x] `device/app_spi_command.cpp` 中 EXTI10_15 NVIC 优先级为 (3, 1) +- [x] `device/app_spi_command.cpp` 中 `poll()` 使用 BASEPRI 临界区 +- [x] `device/app_spi_command.cpp` 中 `onCsRelease()` 不在 ISR 内调用 `processCommand()`(移主循环) +- [x] `device/usart0.cpp` 中 USART0 NVIC 优先级为 (3, 0) +- [x] `device/usart0.cpp` 中 `putCharNonBlocking()` 使用 BASEPRI 临界区 +- [x] `main.h` 中 `system_task_t` 增加 `TASK_ETHERCAT_PROCESS` +- [x] `main.h` 中 `system_task_t` 增加 `TASK_SPI_CMD_PROCESS` +- [x] `device/app_spi_command.h` 中增加 `processPendingCommand()` 公有方法声明 +- [x] `device/app_spi_command.cpp` 中 `onCsRelease()` 不再调用 `processCommand()`,改置位 `m_cmdPending` + `TASK_SPI_CMD_PROCESS` +- [x] `device/app_spi_command.cpp` 中 `processPendingCommand()` 清除 `m_cmdPending` 后调用 `processCommand()` +- [x] `main.cpp` 中 WFI 代码块由 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关控制 +- [x] `main.cpp` 中 WFI 关闭时使用 `__NOP()` 忙等 +- [x] 全项目搜索确认无遗漏的 `__disable_irq` / `__set_PRIMASK`(除 `sys/critical.h` 实现内) + +## 阶段 2:LAN9252 ESC 驱动骨架 +- [x] `comm/esc_exmc_bus.h` 已创建,声明 `EscExmcBus` 类接口 +- [x] `comm/esc_exmc_bus.cpp` 已创建,EXMC NOR/SRAM 16-bit 初始化实现 +- [x] `comm/esc_exmc_bus.cpp` 中 GPIO 配置使用 `config.h` 引脚宏(基于标准映射 PD0/PD1/PD4/PD5/PD7/PD8-PD11/PD14/PD15,PE7-PE15) +- [x] `comm/esc_exmc_bus.cpp` 中 `read16/write16` 通过内存映射地址(0x60000000)访问 +- [x] `comm/esc_lan9252.h` 已创建,声明 `EscLan9252` 类接口 +- [x] `comm/esc_lan9252.cpp` 已创建,实现 `init()` / `selfTest()` / `readReg` / `writeReg` / `readPdi` / `writePdi` +- [x] `comm/esc_lan9252.cpp` 中 `selfTest()` 读取 ESC ID 寄存器并校验 +- [x] `comm/ethercat_slave.h` 已创建,声明 `EthercatSlave` 类与 PDO 双缓冲结构 +- [x] `comm/ethercat_slave.cpp` 已创建,实现周期处理与双缓冲 +- [x] `comm/ethercat_slave.cpp` 中 `periodicIsr()` 标记 `__attribute__((section(".ramfunc")))` +- [x] `comm/ethercat_slave.cpp` 中 `periodicIsr()` 执行:读输入→写输出→翻转指针→置位任务 +- [x] `comm/ethercat_slave.cpp` 中 `getLatestInputs()` 无锁原子读取 +- [x] `comm/ethercat_sync.h` / `comm/ethercat_sync.cpp` 已创建 +- [x] `comm/ethercat_sync.cpp` 中 SYNC0 EXTI(PB0/EXTI0)配置完成,下降沿触发 +- [x] `comm/ethercat_sync.cpp` 中 IRQ EXTI(PB1/EXTI1)配置完成,下降沿触发 +- [x] `comm/ethercat_sync.cpp` 中 SYNC1 EXTI(PB2/EXTI2)配置完成,下降沿触发 +- [x] `comm/ethercat_sync.cpp` 中 EXTI0_IRQn NVIC 优先级 (0, 0)(SYNC0 独占 preempt=0) +- [x] `comm/ethercat_sync.cpp` 中 EXTI1_IRQn NVIC 优先级 (1, 0)(IRQ preempt=1,可被 SYNC0 抢占) +- [x] `comm/ethercat_sync.cpp` 中 EXTI2_IRQn NVIC 优先级 (1, 0)(SYNC1 preempt=1,备用) +- [x] `comm/ethercat_sync.cpp` 中 `onSync0Exti()` 清 EXTI0 标志 → 调用 `EthercatSlave::periodicIsr()` +- [x] `comm/ethercat_sync.cpp` 中 `onEscIrqExti()` 清 EXTI1 标志 → 置位 `TASK_ETHERCAT_PROCESS`(极简) +- [x] `comm/ethercat_sync.cpp` 中 `onSync1Exti()` 清 EXTI2 标志(备用) +- [x] `comm/ethercat_sync.cpp` 中提供 `EXTI0_IRQHandler` / `EXTI1_IRQHandler` / `EXTI2_IRQHandler` C 包装 +- [x] GPIO PB0/PB1/PB2 时钟使能与输入模式配置完成 +- [x] PA0 未被占用,保留给未来 STANDBY 模式唤醒(WKUP_PIN0) +- [x] 降级模式 `CONFIG_ETHERCAT_SYNC_MODE==1` 时 TIMER0 配置可用 + +## 阶段 3:系统集成 +- [x] `sys/initial.cpp` 中 `AppInitializer::init()` 增加 `EthercatSlave::init()` 调用 +- [x] `sys/initial.cpp` 中增加 `EthercatSync::init()` 调用 +- [x] `sys/task_runner.cpp` 中增加 `taskEthercatProcess()` 方法 +- [x] `sys/task_runner.cpp` 中增加 `taskSpiCmdProcess()` 方法 +- [x] `sys/task_runner.cpp` 中 `runPending()` 增加 `TASK_ETHERCAT_PROCESS` 分支 +- [x] `sys/task_runner.cpp` 中 `runPending()` 增加 `TASK_SPI_CMD_PROCESS` 分支 +- [x] `sys/config.h` 中 NVIC 分组、优先级、EXMC 引脚、ESC 中断引脚、SYNC 模式等配置宏已定义 +- [x] 构建配置 `GD32E508_DCU_APP.cproject.yml` 包含 `comm/` 目录 + +## 编译验证 +- [x] 全量编译通过,无 error +- [x] 无新增 warning +- [x] 无遗漏的 `__disable_irq/__set_PRIMASK`(全项目搜索确认) +- [x] 新增文件均在 `comm/` 目录下,不污染现有 `device/` / `sys/` 结构 diff --git a/.trae/specs/add-ethercat-lan9252-realtime/spec.md b/.trae/specs/add-ethercat-lan9252-realtime/spec.md new file mode 100644 index 0000000..c652d6d --- /dev/null +++ b/.trae/specs/add-ethercat-lan9252-realtime/spec.md @@ -0,0 +1,222 @@ +# 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: +1. 编译期:`comm/` 全部代码不参与编译(`#if CONFIG_ETHERCAT_ENABLE` 包裹),不增加任何 Flash/RAM 占用 +2. 运行时:`sys/initial.cpp` 不调用 `EthercatSlave::init()` / `EthercatSync::init()`,EXMC/EXTI0/EXTI1/EXTI2 不被配置 +3. 主循环:`TASK_ETHERCAT_PROCESS` / `TASK_SPI_CMD_PROCESS` 分支不被执行 +4. 阶段 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`:临界区换 BASEPRI + - `sys/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 + +1. **ESC 型号**: LAN9252(内建 PHY),16-bit 并行 HBI 模式 +2. **MCU 接口**: GD32E508 EXMC NOR/SRAM 模式,16-bit 数据宽度 +3. **通信周期**: 100us~250us,抖动目标 <1us +4. **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)唤醒 +5. **不包含**: 完整 EtherCAT 协议栈(SSC),后续可挂接到 `ethercat_slave.cpp` 接口 +6. **不包含**: EEPROM/ESI 配置内容、从站应用对象字典 +7. **引脚定义**: 基于 GD32E508 EXMC 标准映射 + 当前空闲引脚规划(见下表),配置项以宏定义在 `config.h`,用户可在原理图确认后调整 +8. **EXMC Region**: 使用 BANK0 Region1(NE1),基址 0x60000000 +9. **FPU 上下文**: 周期 ISR 不使用 FPU,避免惰性保存抖动;若协议栈后续需要 FPU,需评估 +10. **RAM 执行段**: 需要链接脚本支持(新增 `.ramfunc` section),但本次不改链接脚本,先用 `__attribute__((section))` 标记,实际 RAM 放置由后续配置 +11. **使能开关(未焊接保护)**: `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 唤醒(复位重启,适合完全断电场景) diff --git a/.trae/specs/add-ethercat-lan9252-realtime/tasks.md b/.trae/specs/add-ethercat-lan9252-realtime/tasks.md new file mode 100644 index 0000000..5e86b63 --- /dev/null +++ b/.trae/specs/add-ethercat-lan9252-realtime/tasks.md @@ -0,0 +1,131 @@ +# Tasks + +- [x] Task 1: 新增 `sys/critical.h` BASEPRI 临界区基础设施 + - [x] SubTask 1.1: 定义 `RT_CRITICAL_ENTER()` / `RT_CRITICAL_EXIT()` 宏,基于 `__set_BASEPRI()` / `__get_BASEPRI()`,阈值为 preempt≥1 + - [x] SubTask 1.2: 定义 `RT_CRITICAL_SAVE()` 返回保存值,`RT_CRITICAL_RESTORE(val)` 恢复,用于嵌套场景 + - [x] SubTask 1.3: 确认 GD32E508 (Cortex-M4F/M33) 支持 BASEPRI 寄存器 + +- [x] Task 2: 修改 `sys/config.h` 增加 NVIC 分组、优先级与引脚配置 + - [x] SubTask 2.0: 增加 `CONFIG_ETHERCAT_ENABLE` 宏(默认 0,禁用),作为所有 EtherCAT 相关代码的编译使能开关 + - [x] SubTask 2.1: 增加 `CONFIG_NVIC_PRIORITY_GROUP` 宏设为 `NVIC_PRIGROUP_PRE4_SUB0` + - [x] SubTask 2.2: 增加各中断抢占优先级宏:`CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC=0` / `CONFIG_IRQ_PREEMPT_SCHED=1` / `CONFIG_IRQ_PREEMPT_CAN=2` / `CONFIG_IRQ_PREEMPT_ETHERCAT_ESC=1` / `CONFIG_IRQ_PREEMPT_SPI_USART=3` + - [x] SubTask 2.3: 增加 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关宏(默认 0,关闭 WFI) + - [x] SubTask 2.4: 增加 `CONFIG_ETHERCAT_SYNC_MODE` 宏(EXTI=0 默认 / TIMER=1 降级) + - [x] SubTask 2.5: 增加 EXMC 引脚配置宏:D0-D15 / A16 / NOE / NWE / NE1 引脚(基于标准映射 PD0/PD1/PD4/PD5/PD7/PD8-PD11/PD14/PD15,PE7-PE15) + - [x] SubTask 2.6: 增加 ESC 中断引脚宏:`CONFIG_ESC_SYNC0_PORT/PIN/EXTI/IRQn`(GPIOB/PB0/EXTI0/EXTI0_IRQn)、`CONFIG_ESC_IRQ_PORT/PIN/EXTI/IRQn`(GPIOB/PB1/EXTI1/EXTI1_IRQn)、`CONFIG_ESC_SYNC1_PORT/PIN/EXTI/IRQn`(GPIOB/PB2/EXTI2/EXTI2_IRQn),三个独立向量(保留 PA0 给 STANDBY 唤醒) + - [x] SubTask 2.7: 增加 EXMC Region 基址宏 `CONFIG_ESC_EXMC_BASE_ADDR`(0x60000000)和 `CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1` + +- [x] Task 3: 修改 `device/Timer.cpp` NVIC 分组与调度器优先级 + - [x] SubTask 3.1: `configureNvic()` 中 `nvic_priority_group_set` 改为 `CONFIG_NVIC_PRIORITY_GROUP` + - [x] SubTask 3.2: `CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO` 改为 1,`CONFIG_SCHED_TIMER_IRQ_SUB_PRIO` 保持 0 + +- [x] Task 4: 修改 `sys/task_manager.cpp` 临界区换 BASEPRI + - [x] SubTask 4.1: 移除本地 `enterCriticalSection()` / `exitCriticalSection()`(基于 PRIMASK) + - [x] SubTask 4.2: 改用 `sys/critical.h` 的 `RT_CRITICAL_SAVE()/RESTORE()` + - [x] SubTask 4.3: `set()` 和 `takePending()` 使用新临界区宏 + +- [x] Task 5: 修改 `device/CANFD.cpp` 优先级与临界区 + - [x] SubTask 5.1: 移除本地 `enterCriticalSection()` / `exitCriticalSection()`,改用 `sys/critical.h` + - [x] 5.2: `init()` 中 `nvic_irq_enable` CAN RX0 优先级从 (1, n) 改为 (2, n) + - [x] 5.3: 确认 `handleRxInterrupt()` 已只做入队+置位(当前已满足,验证即可) + +- [x] Task 6: 修改 `device/app_spi_command.cpp` 优先级、临界区与 ISR 瘦身 + - [x] SubTask 6.1: `configureSpi0()` 中 SPI0 NVIC 优先级从 (2, 0) 改为 `(CONFIG_IRQ_PREEMPT_SPI_USART, 0)` + - [x] SubTask 6.2: `configureExti()` 中 EXTI10_15 NVIC 优先级从 (1, 1) 改为 `(CONFIG_IRQ_PREEMPT_SPI_USART, 1)` + - [x] SubTask 6.3: `poll()` 中 `__disable_irq/__enable_irq` 改用 `RT_CRITICAL_SAVE/RESTORE` + - [x] SubTask 6.4: ISR 瘦身:`onCsRelease()` 中 `processCommand()` 移出 ISR,改为置位 `TASK_SPI_CMD_PROCESS` 标志由主循环执行 + - [x] SubTask 6.5: `AppSpiCommand` 类增加 `processPendingCommand()` 公有方法供主循环调用,内部调用 `processCommand()` + - [x] SubTask 6.6: 增加 `volatile bool m_cmdPending` 标志,`onCsRelease()` 置位,`processPendingCommand()` 清除 + +- [x] Task 7: 修改 `device/usart0.cpp` 优先级与临界区 + - [x] SubTask 7.1: `init()` 中 USART0 NVIC 优先级从 (2, 0) 改为 (3, 0) + - [x] SubTask 7.2: `putCharNonBlocking()` 中 `__disable_irq/__enable_irq` 改用 `RT_CRITICAL_SAVE/RESTORE` + +- [x] Task 8: 修改 `main.h` 增加任务枚举 + - [x] SubTask 8.1: `system_task_t` 增加 `TASK_ETHERCAT_PROCESS` + - [x] SubTask 8.2: `system_task_t` 增加 `TASK_SPI_CMD_PROCESS`(用于 SPI CS release 后的命令解析,移出 ISR) + +- [x] Task 9: 修改 `main.cpp` 主循环 WFI 策略 + - [x] SubTask 9.1: 用 `#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0)` 包裹 WFI 代码块 + - [x] SubTask 9.2: WFI 关闭时改为 `__NOP()` 空转忙等 + - [x] SubTask 9.3: WFI 块内的临界区改用 `RT_CRITICAL_SAVE/RESTORE` + +- [x] Task 10: 新建 `comm/esc_exmc_bus.h` EXMC 总线抽象接口 + - [x] SubTask 10.1: 声明 `EscExmcBus` 类:`init()` / `read16(addr)` / `write16(addr, data)` / `readBlock(addr, buf, len)` / `writeBlock(addr, buf, len)` + - [x] SubTask 10.2: 定义 EXMC 寄存器基址宏、Region 选择宏(待引脚确认) + +- [x] Task 11: 新建 `comm/esc_exmc_bus.cpp` EXMC 总线实现 + - [x] SubTask 11.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空) + - [x] SubTask 11.1: `init()`:配置 EXMC NOR/SRAM Region(GPIO 时钟、EXMC 时钟、时序参数) + - [x] SubTask 11.2: GPIO 配置:D0-D15 / A16 / NOE / NWE / NE1(引脚基于标准映射) + - [x] SubTask 11.3: EXMC 时序参数:按 LAN9252 HBI 时序要求配置(read/write wait state) + - [x] SubTask 11.4: `read16/write16`:通过内存映射地址(0x60000000)直接读写 + - [x] SubTask 11.5: `readBlock/writeBlock`:循环调用 read16/write16 + +- [x] Task 12: 新建 `comm/esc_lan9252.h` LAN9252 ESC 驱动接口 + - [x] SubTask 12.1: 定义 LAN9252 关键寄存器地址宏(ESC ID、PDI 控制、中断使能等) + - [x] SubTask 12.2: 声明 `EscLan9252` 类:`init()` / `selfTest()` / `readReg(addr)` / `writeReg(addr, val)` / `readPdi(offset, buf, len)` / `writePdi(offset, buf, len)` + +- [x] Task 13: 新建 `comm/esc_lan9252.cpp` LAN9252 ESC 驱动实现 + - [x] SubTask 13.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空) + - [x] SubTask 13.1: `init()`:调用 `EscExmcBus::init()`,初始化 ESC 基本寄存器 + - [x] SubTask 13.2: `selfTest()`:读 ESC ID 寄存器校验(0x87654321 或芯片 ID) + - [x] SubTask 13.3: `readReg/writeReg`:封装 EXMC 读写 + 地址映射 + - [x] SubTask 13.4: `readPdi/writePdi`:PDI 内存块读写 + +- [x] Task 14: 新建 `comm/ethercat_slave.h` 从站周期接口与双缓冲 + - [x] SubTask 14.1: 定义 PDO 输入/输出缓冲结构体(大小可配置) + - [x] SubTask 14.2: 声明 `EthercatSlave` 类:`init()` / `periodicIsr()` / `getLatestInputs()` / `setOutputs()` / `process()` + - [x] SubTask 14.3: 声明 ping-pong 双缓冲成员:两个输入缓冲 + 原子指针索引 + +- [x] Task 15: 新建 `comm/ethercat_slave.cpp` 从站周期实现 + - [x] SubTask 15.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空) + - [x] SubTask 15.1: `init()`:调用 `EscLan9252::init()` + `selfTest()` + 初始化双缓冲 + - [x] SubTask 15.2: `periodicIsr()`:标 `__attribute__((section(".ramfunc")))`,读输入 PDO → 写输出 PDO → 翻转缓冲指针 → 置位 `TASK_ETHERCAT_PROCESS` + - [x] SubTask 15.3: `getLatestInputs()`:原子读取缓冲指针返回最新输入数据指针(无锁) + - [x] SubTask 15.4: `setOutputs()`:写入输出缓冲供下次 ISR 发送 + - [x] SubTask 15.5: `process()`:应用层 PDO 映射入口(非实时,留接口) + +- [x] Task 16: 新建 `comm/ethercat_sync.h` / `comm/ethercat_sync.cpp` SYNC0 同步(EXTI 独立向量 / TIMER0 降级) + - [x] SubTask 16.0: `.cpp` 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空,EXTI0/1/2 不配置) + - [x] SubTask 16.1: 声明 `EthercatSync` 类:`init()` / `onSync0Exti()` / `onEscIrqExti()` / `onSync1Exti()` / `onTimerIrq()` + - [x] SubTask 16.2: `init()`:配置 SYNC0 EXTI(PB0/EXTI0,下降沿,NVIC EXTI0_IRQn preempt=0,sub=0) + - [x] SubTask 16.3: `init()`:配置 IRQ EXTI(PB1/EXTI1,下降沿,NVIC EXTI1_IRQn preempt=1,sub=0) + - [x] SubTask 16.4: `init()`:配置 SYNC1 EXTI(PB2/EXTI2,下降沿,NVIC EXTI2_IRQn preempt=1,sub=0) + - [x] SubTask 16.5: `init()`:GPIO PB0/PB1/PB2 时钟使能与输入模式配置(GPIOB) + - [x] SubTask 16.6: 降级模式(`CONFIG_ETHERCAT_SYNC_MODE==1`)时配置 TIMER0 周期 + NVIC preempt=0 + - [x] SubTask 16.7: `onSync0Exti()`:清 EXTI0 标志 → 调用 `EthercatSlave::periodicIsr()` + - [x] SubTask 16.8: `onEscIrqExti()`:清 EXTI1 标志 → 置位 `TASK_ETHERCAT_PROCESS` + - [x] SubTask 16.9: `onSync1Exti()`:清 EXTI2 标志(备用,暂不处理) + - [x] SubTask 16.10: 提供 `EXTI0_IRQHandler` / `EXTI1_IRQHandler` / `EXTI2_IRQHandler` C 包装(及降级模式 `TIMER0_IRQHandler`) + +- [x] Task 17: 修改 `sys/initial.cpp` 集成 EtherCAT 初始化(受使能开关保护) + - [x] SubTask 17.1: `AppInitializer::init()` 中在 `SchedulerTimer::init()` 之后,用 `#if CONFIG_ETHERCAT_ENABLE` 包裹 `#include "comm/ethercat_slave.h"` 与 `comm/ethercat_sync.h"` + - [x] SubTask 17.2: 在 `#if CONFIG_ETHERCAT_ENABLE` 块内调用 `EthercatSlave::init()` + - [x] SubTask 17.3: 在 `EthercatSlave::init()` 之后(同块内)调用 `EthercatSync::init()` + +- [x] Task 18: 修改 `sys/task_runner.cpp` 增加 EtherCAT 与 SPI 命令处理任务 + - [x] SubTask 18.1: 增加 `taskEthercatProcess()` 私有方法:用 `#if CONFIG_ETHERCAT_ENABLE` 包裹,内部调用 `EthercatSlave::getLatestInputs()` + `EthercatSlave::process()` + - [x] SubTask 18.2: 增加 `taskSpiCmdProcess()` 私有方法:调用 `AppSpiCommandBus.processPendingCommand()`(此功能独立于 EtherCAT,不受 `CONFIG_ETHERCAT_ENABLE` 限制,阶段1 ISR 瘦身已需要) + - [x] SubTask 18.3: `runPending()` 中增加 `TASK_ETHERCAT_PROCESS` 分支调用(受 `#if CONFIG_ETHERCAT_ENABLE` 保护) + - [x] SubTask 18.4: `runPending()` 中增加 `TASK_SPI_CMD_PROCESS` 分支调用(无条件,阶段1 改造已需要) + +- [x] Task 19: 构建配置纳入新文件 + - [x] SubTask 19.1: 确认 `GD32E508_DCU_APP.cproject.yml` 中 source 路径包含 `comm/` 目录 + - [x] SubTask 19.2: 确认 include 路径包含 `comm/` 目录 + +- [x] Task 20: 编译验证 + - [x] SubTask 20.1: 全量编译通过,无 error + - [x] SubTask 20.2: 检查无新增 warning + - [x] SubTask 20.3: 检查无遗漏的 `__disable_irq/__set_PRIMASK`(应全部改为 BASEPRI) + +# Task Dependencies +- Task 1 → Task 4, 5, 6, 7, 9(临界区宏是改造前提) +- Task 2 → Task 3, 5, 6, 7, 16(config 优先级与引脚宏) +- Task 8 → Task 6.4, 18(任务枚举先定义,才能在 ISR/task_runner 中引用) +- Task 10, 12 → Task 11, 13(EXMC 接口定义在实现前) +- Task 13, 14 → Task 15(ESC 驱动 + 从站接口在周期实现前) +- Task 14, 16 → Task 15(双缓冲 + 同步中断在周期实现前) +- Task 1-9(阶段1) 可与 Task 10-16(阶段2) 部分并行:阶段1 改造不依赖 comm/ 代码 +- Task 17, 18(阶段3) 依赖阶段1+阶段2 全部完成 +- Task 19 → Task 20(构建配置在编译验证前) diff --git a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml index 41e6657..0ec4518 100644 --- a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml +++ b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml @@ -1,4 +1,4 @@ -# A project translates into one executable or library. +# A project translates into one executable or library. project: groups: @@ -22,26 +22,38 @@ project: - file: ./sys/config.h - group: device files: + - file: ./device/adc.h + - file: ./device/adc.cpp + - file: ./device/canfd.h + - file: ./device/canfd.cpp + - file: ./device/timer.h + - file: ./device/timer.cpp - file: ./device/usart0.h - file: ./device/usart0.cpp - - file: ./device/adc.h - - file: ./device/ADC.cpp - - file: ./device/canfd.h - - file: ./device/CANFD.cpp - - file: ./device/app_spi_command.h - - file: ./device/app_spi_command.cpp - - file: ./device/spi_command_handler.h - - file: ./device/app_spi_command_defs.h - - file: ./device/app_spi_cmd_boot_request.cpp - - file: ./device/app_spi_cmd_ota_wait.cpp - - file: ./device/app_spi_cmd_ping.cpp - - file: ./device/app_spi_cmd_can_bridge.cpp - - file: ./device/Timer.h - - file: ./device/Timer.cpp - file: ./device/temp_sensor.h - - file: ./device/watchdog.h - file: ./device/temp_sensor.cpp + - file: ./device/watchdog.h - file: ./device/watchdog.cpp + - group: device/spi + files: + - file: ./device/spi/app_spi_command.h + - file: ./device/spi/app_spi_command.cpp + - file: ./device/spi/spi_command_handler.h + - file: ./device/spi/app_spi_command_defs.h + - file: ./device/spi/app_spi_cmd_boot_request.cpp + - file: ./device/spi/app_spi_cmd_ota_wait.cpp + - file: ./device/spi/app_spi_cmd_ping.cpp + - file: ./device/spi/app_spi_cmd_can_bridge.cpp + - group: comm + files: + - file: ./comm/esc_exmc_bus.h + - file: ./comm/esc_exmc_bus.cpp + - file: ./comm/esc_lan9252.h + - file: ./comm/esc_lan9252.cpp + - file: ./comm/ethercat_slave.h + - file: ./comm/ethercat_slave.cpp + - file: ./comm/ethercat_sync.h + - file: ./comm/ethercat_sync.cpp # List components to use for your application. # A software component is a re-usable unit that may be configurable. @@ -53,6 +65,7 @@ project: - component: Device:GD32E50x_StdPeripherals:CRC - component: Device:GD32E50x_StdPeripherals:DBG - component: Device:GD32E50x_StdPeripherals:DMA + - component: Device:GD32E50x_StdPeripherals:EXMC - component: Device:GD32E50x_StdPeripherals:EXTI - component: Device:GD32E50x_StdPeripherals:FMC - component: Device:GD32E50x_StdPeripherals:GPIO diff --git a/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp b/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp new file mode 100644 index 0000000..4124ff7 --- /dev/null +++ b/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp @@ -0,0 +1,99 @@ +/** + * @file esc_exmc_bus.cpp + * @brief LAN9252 ESC 的 EXMC 16-bit 并行总线驱动实现。 + */ +#include "comm/esc_exmc_bus.h" +#include "sys/config.h" + +#if CONFIG_ETHERCAT_ENABLE + +extern "C" { +#include "gd32e50x_rcu.h" +#include "gd32e50x_gpio.h" +#ifdef EXMC_NORSRAM_REGION1 +#include "gd32e50x_exmc.h" +#endif +} + +/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 + * 使用 volatile uint16_t* 转换避免依赖 CMSIS 的 __IO 宏。 */ +#define ESC_EXMC_REG16(offset) (*(volatile uint16_t*)(CONFIG_ESC_EXMC_BASE_ADDR + (offset))) + +void EscExmcBus::init() +{ + /* 使能 GPIO 与 EXMC 时钟 */ + rcu_periph_clock_enable(RCU_GPIOD); + rcu_periph_clock_enable(RCU_GPIOE); + rcu_periph_clock_enable(RCU_AF); +#ifdef EXMC_NORSRAM_REGION1 + rcu_periph_clock_enable(RCU_EXMC); + + /* 配置 16-bit 数据线 D0-D15 为 AF 推挽 */ + gpio_init(CONFIG_EXMC_D0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D0_PIN); + gpio_init(CONFIG_EXMC_D1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D1_PIN); + gpio_init(CONFIG_EXMC_D2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D2_PIN); + gpio_init(CONFIG_EXMC_D3_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D3_PIN); + gpio_init(CONFIG_EXMC_D4_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D4_PIN); + gpio_init(CONFIG_EXMC_D5_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D5_PIN); + gpio_init(CONFIG_EXMC_D6_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D6_PIN); + gpio_init(CONFIG_EXMC_D7_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D7_PIN); + gpio_init(CONFIG_EXMC_D8_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D8_PIN); + gpio_init(CONFIG_EXMC_D9_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D9_PIN); + gpio_init(CONFIG_EXMC_D10_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D10_PIN); + gpio_init(CONFIG_EXMC_D11_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D11_PIN); + gpio_init(CONFIG_EXMC_D12_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D12_PIN); + gpio_init(CONFIG_EXMC_D13_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D13_PIN); + gpio_init(CONFIG_EXMC_D14_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D14_PIN); + gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D15_PIN); + + /* 控制线:A16 / NOE / NWE / NE1 */ + gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_A16_PIN); + gpio_init(CONFIG_EXMC_NOE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NOE_PIN); + gpio_init(CONFIG_EXMC_NWE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NWE_PIN); + gpio_init(CONFIG_EXMC_NE1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NE1_PIN); + + /* EXMC NOR/SRAM Region1 时序配置(16-bit,非复用,异步) */ + exmc_norsram_parameter_struct norsram_init; + exmc_norsram_struct_para_init(&norsram_init); + norsram_init.norsram_region = CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1; + norsram_init.write_mode = EXMC_ASYN_WRITE; + norsram_init.read_mode = EXMC_ASYN_READ; + norsram_init.nwait_mode = EXMC_NWAIT_DISABLE; + norsram_init.asyn_wait = DISABLE; + norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW; + norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B; + /* 时序:按 LAN9252 HBI 要求配置 read/write wait state */ + norsram_init.read_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整 + norsram_init.write_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整 + exmc_norsram_init(&norsram_init); + exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1); +#else + /* EXMC 外设库未安装:仅占位,CONFIG_ETHERCAT_ENABLE=1 时需先安装 EXMC 组件。 */ +#endif +} + +uint16_t EscExmcBus::read16(uint32_t offset) +{ + return ESC_EXMC_REG16(offset); +} + +void EscExmcBus::write16(uint32_t offset, uint16_t data) +{ + ESC_EXMC_REG16(offset) = data; +} + +void EscExmcBus::readBlock(uint32_t offset, uint16_t *buf, size_t count) +{ + for (size_t i = 0U; i < count; ++i) { + buf[i] = ESC_EXMC_REG16(offset + (uint32_t)(i * 2U)); + } +} + +void EscExmcBus::writeBlock(uint32_t offset, const uint16_t *buf, size_t count) +{ + for (size_t i = 0U; i < count; ++i) { + ESC_EXMC_REG16(offset + (uint32_t)(i * 2U)) = buf[i]; + } +} + +#endif /* CONFIG_ETHERCAT_ENABLE */ diff --git a/GD32E508_DCU_APP/comm/esc_exmc_bus.h b/GD32E508_DCU_APP/comm/esc_exmc_bus.h new file mode 100644 index 0000000..bfa321e --- /dev/null +++ b/GD32E508_DCU_APP/comm/esc_exmc_bus.h @@ -0,0 +1,53 @@ +/** + * @file esc_exmc_bus.h + * @brief LAN9252 ESC 的 EXMC 16-bit 并行总线抽象接口。 + * @note 通过 EXMC NOR/SRAM Region1(NE1,基址 0x60000000)以内存映射方式访问 LAN9252。 + * 16-bit 数据总线 + A16 地址线 + NOE/NWE/NE1 控制线。 + */ +#ifndef COMM_ESC_EXMC_BUS_H +#define COMM_ESC_EXMC_BUS_H + +#include +#include + +/** + * @brief LAN9252 ESC 的 EXMC 并行总线驱动。 + * @note 提供 16-bit 读写与块读写接口,内部通过内存映射地址直接访问 ESC 寄存器空间。 + */ +class EscExmcBus { +public: + /** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。 */ + static void init(); + + /** + * @brief 从指定偏移读取 16-bit 数据。 + * @param[in] offset 地址偏移(字节)。 + * @return uint16_t 读取的 16-bit 数据。 + */ + static uint16_t read16(uint32_t offset); + + /** + * @brief 向指定偏移写入 16-bit 数据。 + * @param[in] offset 地址偏移(字节)。 + * @param[in] data 待写入的 16-bit 数据。 + */ + static void write16(uint32_t offset, uint16_t data); + + /** + * @brief 从指定偏移连续读取一块 16-bit 数据。 + * @param[in] offset 地址偏移(字节)。 + * @param[out] buf 输出缓冲(以 uint16_t 为单位)。 + * @param[in] count 读取的 16-bit 半字数量。 + */ + static void readBlock(uint32_t offset, uint16_t *buf, size_t count); + + /** + * @brief 向指定偏移连续写入一块 16-bit 数据。 + * @param[in] offset 地址偏移(字节)。 + * @param[in] buf 数据缓冲(以 uint16_t 为单位)。 + * @param[in] count 写入的 16-bit 半字数量。 + */ + static void writeBlock(uint32_t offset, const uint16_t *buf, size_t count); +}; + +#endif /* COMM_ESC_EXMC_BUS_H */ diff --git a/GD32E508_DCU_APP/comm/esc_lan9252.cpp b/GD32E508_DCU_APP/comm/esc_lan9252.cpp new file mode 100644 index 0000000..0400cc9 --- /dev/null +++ b/GD32E508_DCU_APP/comm/esc_lan9252.cpp @@ -0,0 +1,82 @@ +/** + * @file esc_lan9252.cpp + * @brief LAN9252 EtherCAT ESC 驱动实现。 + */ +#include "comm/esc_lan9252.h" +#include "comm/esc_exmc_bus.h" +#include "sys/config.h" + +#if CONFIG_ETHERCAT_ENABLE + +namespace { +/* LAN9252 ESC 关键寄存器地址(字节偏移,EXMC 访问) */ +constexpr uint16_t LAN9252_REG_CHIP_ID_L = 0x0000U; /**< 芯片 ID 低 16 位(=0x4321)。 */ +constexpr uint16_t LAN9252_REG_CHIP_ID_H = 0x0002U; /**< 芯片 ID 高 16 位(=0x8765)。 */ +constexpr uint32_t LAN9252_CHIP_ID_VALUE = 0x87654321U; /**< 期望的 32-bit 芯片 ID。 */ + +/* PDI 内存基址偏移(实际取决于 LAN9252 PDI 配置,这里为示意值) */ +constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x1000U; +} + +void EscLan9252::init() +{ + EscExmcBus::init(); + /* TODO: 配置 ESC 中断使能、AL 控制等基本寄存器(留待协议栈集成阶段实现) */ +} + +bool EscLan9252::selfTest() +{ + const uint16_t id_low = readReg(LAN9252_REG_CHIP_ID_L); + const uint16_t id_high = readReg(LAN9252_REG_CHIP_ID_H); + const uint32_t chip_id = ((uint32_t)id_high << 16) | id_low; + return (chip_id == LAN9252_CHIP_ID_VALUE); +} + +uint16_t EscLan9252::readReg(uint16_t addr) +{ + /* ESC 寄存器地址按字节寻址,EXMC 16-bit 访问需要地址左移 1 位 */ + return EscExmcBus::read16((uint32_t)addr << 1U); +} + +void EscLan9252::writeReg(uint16_t addr, uint16_t val) +{ + EscExmcBus::write16((uint32_t)addr << 1U, val); +} + +void EscLan9252::readPdi(uint16_t offset, uint8_t *buf, size_t len) +{ + if (buf == nullptr || len == 0U) { + return; + } + /* PDI 按 16-bit 读取,非对齐字节需特殊处理(此处简化为按半字读取) */ + const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U; + const size_t half_count = len / 2U; + for (size_t i = 0U; i < half_count; ++i) { + uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(i * 2U)); + buf[i * 2U] = (uint8_t)(w & 0xFFU); + buf[i * 2U + 1] = (uint8_t)((w >> 8) & 0xFFU); + } + /* 处理奇数字节 */ + if ((len & 1U) != 0U) { + uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(half_count * 2U)); + buf[half_count * 2U] = (uint8_t)(w & 0xFFU); + } +} + +void EscLan9252::writePdi(uint16_t offset, const uint8_t *buf, size_t len) +{ + if (buf == nullptr || len == 0U) { + return; + } + const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U; + const size_t half_count = len / 2U; + for (size_t i = 0U; i < half_count; ++i) { + uint16_t w = (uint16_t)buf[i * 2U] | ((uint16_t)buf[i * 2U + 1] << 8); + EscExmcBus::write16(exmc_offset + (uint32_t)(i * 2U), w); + } + if ((len & 1U) != 0U) { + EscExmcBus::write16(exmc_offset + (uint32_t)(half_count * 2U), (uint16_t)buf[half_count * 2U]); + } +} + +#endif /* CONFIG_ETHERCAT_ENABLE */ diff --git a/GD32E508_DCU_APP/comm/esc_lan9252.h b/GD32E508_DCU_APP/comm/esc_lan9252.h new file mode 100644 index 0000000..908f26f --- /dev/null +++ b/GD32E508_DCU_APP/comm/esc_lan9252.h @@ -0,0 +1,58 @@ +/** + * @file esc_lan9252.h + * @brief LAN9252 EtherCAT 从站控制器(ESC)驱动接口。 + * @note 通过 EscExmcBus 16-bit 并行总线访问 LAN9252 寄存器与 PDI 内存。 + */ +#ifndef COMM_ESC_LAN9252_H +#define COMM_ESC_LAN9252_H + +#include +#include + +/** + * @brief LAN9252 ESC 驱动。 + * @note 封装 ESC 寄存器读写与 PDI(Process Data Interface)内存块访问。 + */ +class EscLan9252 { +public: + /** @brief 初始化 EXMC 总线与 ESC 基本寄存器。 */ + static void init(); + + /** + * @brief ESC 自检:读取芯片 ID 寄存器校验。 + * @return bool true 表示 ID 匹配(0x87654321),false 表示失败。 + */ + static bool selfTest(); + + /** + * @brief 读取 ESC 寄存器(16-bit)。 + * @param[in] addr 寄存器地址(ESC 寄存器空间偏移)。 + * @return uint16_t 寄存器值。 + */ + static uint16_t readReg(uint16_t addr); + + /** + * @brief 写入 ESC 寄存器(16-bit)。 + * @param[in] addr 寄存器地址。 + * @param[in] val 待写入值。 + */ + static void writeReg(uint16_t addr, uint16_t val); + + /** + * @brief 从 PDI 内存读取一块数据。 + * @param[in] offset PDI 偏移(字节)。 + * @param[out] buf 输出缓冲。 + * @param[in] len 读取字节数。 + */ + static void readPdi(uint16_t offset, uint8_t *buf, size_t len); + + /** + * @brief 向 PDI 内存写入一块数据。 + * @param[in] offset PDI 偏移(字节)。 + * @param[in] buf 数据缓冲。 + * @param[in] len 写入字节数。 + */ + static void writePdi(uint16_t offset, const uint8_t *buf, size_t len); +}; + +#endif /* COMM_ESC_LAN9252_H */ diff --git a/GD32E508_DCU_APP/comm/ethercat_slave.cpp b/GD32E508_DCU_APP/comm/ethercat_slave.cpp new file mode 100644 index 0000000..7fb90d6 --- /dev/null +++ b/GD32E508_DCU_APP/comm/ethercat_slave.cpp @@ -0,0 +1,70 @@ +/** + * @file ethercat_slave.cpp + * @brief EtherCAT 从站周期处理实现。 + */ +#include "comm/ethercat_slave.h" +#include "comm/esc_lan9252.h" +#include "sys/config.h" +#include "sys/task_manager.h" +#include "main.h" + +#if CONFIG_ETHERCAT_ENABLE + +PdoBuffer EthercatSlave::s_inputs[2] = {}; +PdoBuffer EthercatSlave::s_outputs[2] = {}; +volatile uint8_t EthercatSlave::s_inputReadIndex = 0U; +volatile uint8_t EthercatSlave::s_outputWriteIndex = 0U; + +void EthercatSlave::init() +{ + EscLan9252::init(); + if (EscLan9252::selfTest()) { + /* ESC 自检通过:TODO 启用周期同步 */ + } + s_inputReadIndex = 0U; + s_outputWriteIndex = 0U; +} + +__attribute__((section(".ramfunc"))) +void EthercatSlave::periodicIsr() +{ + /* 1. 读取输入 PDO 到另一块缓冲(当前可读缓冲的配对) */ + const uint8_t write_idx = s_inputReadIndex ^ 1U; + EscLan9252::readPdi(0x0000U, s_inputs[write_idx].data, CONFIG_ETHERCAT_PDO_SIZE); + s_inputs[write_idx].len = CONFIG_ETHERCAT_PDO_SIZE; + + /* 2. 写入输出 PDO(从应用层未写入的那个缓冲读取) */ + const uint8_t out_read_idx = s_outputWriteIndex ^ 1U; + EscLan9252::writePdi(0x0000U, s_outputs[out_read_idx].data, s_outputs[out_read_idx].len); + + /* 3. 翻转输入缓冲指针,使新完成的缓冲成为可读缓冲 */ + s_inputReadIndex = write_idx; + + /* 4. 置位非实时任务,通知主循环处理应用层 PDO 映射 */ + TaskManager::set(1UL << TASK_ETHERCAT_PROCESS); +} + +const PdoBuffer* EthercatSlave::getLatestInputs() +{ + /* s_inputReadIndex 始终指向最新完成的输入缓冲 */ + return &s_inputs[s_inputReadIndex]; +} + +void EthercatSlave::setOutputs(const PdoBuffer *outputs) +{ + if (outputs == nullptr) { + return; + } + /* 写入应用层缓冲(ISR 当前不读取的那个) */ + const uint8_t write_idx = s_outputWriteIndex ^ 1U; + s_outputs[write_idx] = *outputs; + s_outputWriteIndex = write_idx; +} + +void EthercatSlave::process() +{ + /* 应用层 PDO 映射入口(非实时,由主循环 TASK_ETHERCAT_PROCESS 调用)。 + * TODO: 将 getLatestInputs() 数据映射到应用对象字典;将应用输出填入 setOutputs()。 */ +} + +#endif /* CONFIG_ETHERCAT_ENABLE */ diff --git a/GD32E508_DCU_APP/comm/ethercat_slave.h b/GD32E508_DCU_APP/comm/ethercat_slave.h new file mode 100644 index 0000000..fff688e --- /dev/null +++ b/GD32E508_DCU_APP/comm/ethercat_slave.h @@ -0,0 +1,67 @@ +/** + * @file ethercat_slave.h + * @brief EtherCAT 从站周期处理接口与 PDO 双缓冲(ping-pong)结构。 + * @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入; + * 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。 + */ +#ifndef COMM_ETHERCAT_SLAVE_H +#define COMM_ETHERCAT_SLAVE_H + +#include +#include + +/** @brief 默认 PDO 大小(可按需调整)。 */ +constexpr size_t CONFIG_ETHERCAT_PDO_SIZE = 32U; + +/** + * @brief PDO 数据缓冲(输入/输出共用结构)。 + * @note 周期 ISR 与应用层通过双缓冲无锁交换。 + */ +struct PdoBuffer { + uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */ + size_t len = 0U; /**< 有效数据长度。 */ +}; + +/** + * @brief EtherCAT 从站周期处理。 + * @note 周期路径(periodicIsr)必须极简:读输入→写输出→翻转指针→置位任务。 + */ +class EthercatSlave { +public: + /** @brief 初始化 ESC 与双缓冲。 */ + static void init(); + + /** + * @brief 周期中断处理(由 SYNC0 EXTI 触发)。 + * @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。 + * 流程:读输入 PDO → 写输出 PDO → 翻转输入缓冲指针 → 置位 TASK_ETHERCAT_PROCESS。 + */ + static void periodicIsr(); + + /** + * @brief 获取最新输入 PDO(主循环调用,无锁)。 + * @return const PdoBuffer* 最新输入缓冲只读指针;周期 ISR 不会修改该缓冲。 + */ + static const PdoBuffer* getLatestInputs(); + + /** + * @brief 设置下次周期要发送的输出 PDO(主循环调用)。 + * @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。 + * @note 调用方在 ISR 不持有期间调用即可,内部使用乒乓缓冲避免竞争。 + */ + static void setOutputs(const PdoBuffer *outputs); + + /** + * @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 调用)。 + * @note 留接口,具体映射逻辑由协议栈后续实现。 + */ + static void process(); + +private: + static PdoBuffer s_inputs[2]; /**< 输入双缓冲(pong-pong)。 */ + static PdoBuffer s_outputs[2]; /**< 输出双缓冲(ping-pong)。 */ + static volatile uint8_t s_inputReadIndex; /**< ISR 当前写入索引;getLatestInputs 读另一个。 */ + static volatile uint8_t s_outputWriteIndex; /**< 应用层写入索引;ISR 读另一个。 */ +}; + +#endif /* COMM_ETHERCAT_SLAVE_H */ diff --git a/GD32E508_DCU_APP/comm/ethercat_sync.cpp b/GD32E508_DCU_APP/comm/ethercat_sync.cpp new file mode 100644 index 0000000..6163017 --- /dev/null +++ b/GD32E508_DCU_APP/comm/ethercat_sync.cpp @@ -0,0 +1,122 @@ +/** + * @file ethercat_sync.cpp + * @brief EtherCAT SYNC0 周期同步实现。 + */ +#include "comm/ethercat_sync.h" +#include "comm/ethercat_slave.h" +#include "sys/config.h" +#include "sys/task_manager.h" +#include "main.h" + +#if CONFIG_ETHERCAT_ENABLE + +extern "C" { +#include "gd32e50x_rcu.h" +#include "gd32e50x_gpio.h" +#include "gd32e50x_exti.h" +#include "gd32e50x_misc.h" +#if (CONFIG_ETHERCAT_SYNC_MODE == 1) +#include "gd32e50x_timer.h" +#endif +} + +void EthercatSync::init() +{ + /* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */ + rcu_periph_clock_enable(RCU_GPIOB); + rcu_periph_clock_enable(RCU_AF); + + /* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */ + gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, + CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN); + +#if (CONFIG_ETHERCAT_SYNC_MODE == 0) + /* ---- 默认:EXTI 外部触发模式 ---- */ + /* SYNC0:PB0 -> EXTI0,下降沿,preempt=0 */ + gpio_exti_source_select(CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN); + exti_init(CONFIG_ESC_SYNC0_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING); + exti_interrupt_enable(CONFIG_ESC_SYNC0_EXTI); + exti_interrupt_flag_clear(CONFIG_ESC_SYNC0_EXTI); + nvic_irq_enable(CONFIG_ESC_SYNC0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U); + + /* ESC IRQ:PB1 -> EXTI1,下降沿,preempt=1 */ + gpio_exti_source_select(CONFIG_ESC_IRQ_EXTI_SOURCE_PORT, CONFIG_ESC_IRQ_EXTI_SOURCE_PIN); + exti_init(CONFIG_ESC_IRQ_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING); + exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI); + exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI); + nvic_irq_enable(CONFIG_ESC_IRQ_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_ESC, 0U); + + /* SYNC1:PB2 -> EXTI2,下降沿,preempt=1(备用) */ + gpio_exti_source_select(CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN); + exti_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING); + exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI); + exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI); + nvic_irq_enable(CONFIG_ESC_SYNC1_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1, 0U); +#else + /* ---- 降级模式:TIMER0 内部定时 ---- */ + rcu_periph_clock_enable(RCU_TIMER0); + timer_parameter_struct timer_initpara; + timer_struct_para_init(&timer_initpara); + /* 100us 周期:180MHz / (179+1) / (99+1) = 10kHz = 100us */ + timer_initpara.prescaler = 179U; + timer_initpara.alignedmode = TIMER_COUNTER_EDGE; + timer_initpara.counterdirection = TIMER_COUNTER_UP; + timer_initpara.period = 99U; + timer_initpara.clockdivision = TIMER_CKDIV_DIV1; + timer_init(TIMER0, &timer_initpara); + timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP); + timer_interrupt_enable(TIMER0, TIMER_INT_UP); + nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U); + timer_enable(TIMER0); +#endif +} + +void EthercatSync::onSync0Exti() +{ + if (exti_interrupt_flag_get(CONFIG_ESC_SYNC0_EXTI) != RESET) { + exti_interrupt_flag_clear(CONFIG_ESC_SYNC0_EXTI); + EthercatSlave::periodicIsr(); + } +} + +void EthercatSync::onEscIrqExti() +{ + if (exti_interrupt_flag_get(CONFIG_ESC_IRQ_EXTI) != RESET) { + exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI); + /* 极简:仅置位任务,事件处理移主循环 */ + TaskManager::set(1UL << TASK_ETHERCAT_PROCESS); + } +} + +void EthercatSync::onSync1Exti() +{ + if (exti_interrupt_flag_get(CONFIG_ESC_SYNC1_EXTI) != RESET) { + exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI); + /* SYNC1 备用,暂不处理 */ + } +} + +void EthercatSync::onTimerIrq() +{ +#if (CONFIG_ETHERCAT_SYNC_MODE == 1) + if (timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP) != RESET) { + timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP); + EthercatSlave::periodicIsr(); + } +#endif +} + +/* ---- 中断入口 C 包装 ---- */ +extern "C" { + +#if (CONFIG_ETHERCAT_SYNC_MODE == 0) +void EXTI0_IRQHandler(void) { EthercatSync::onSync0Exti(); } +void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); } +void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); } +#else +void TIMER0_IRQHandler(void) { EthercatSync::onTimerIrq(); } +#endif + +} /* extern "C" */ + +#endif /* CONFIG_ETHERCAT_ENABLE */ diff --git a/GD32E508_DCU_APP/comm/ethercat_sync.h b/GD32E508_DCU_APP/comm/ethercat_sync.h new file mode 100644 index 0000000..9daf9b6 --- /dev/null +++ b/GD32E508_DCU_APP/comm/ethercat_sync.h @@ -0,0 +1,26 @@ +/** + * @file ethercat_sync.h + * @brief EtherCAT SYNC0 周期同步接口(EXTI 独立向量 / TIMER0 降级)。 + * @note 默认由 LAN9252 SYNC0 引脚(PB0/EXTI0)硬件触发周期中断, + * preempt=0 独占向量保证抖动 <1us;CONFIG_ETHERCAT_SYNC_MODE=1 时用 TIMER0 降级。 + * ESC IRQ(PB1/EXTI1,preempt=1)与 SYNC1(PB2/EXTI2,preempt=1)独立向量。 + */ +#ifndef COMM_ETHERCAT_SYNC_H +#define COMM_ETHERCAT_SYNC_H + +class EthercatSync { +public: + /** @brief 初始化 SYNC0/IRQ/SYNC1 EXTI 引脚与 NVIC(或降级模式 TIMER0)。 */ + static void init(); + + /** @brief SYNC0 EXTI 中断处理(EXTI0)。 */ + static void onSync0Exti(); + /** @brief ESC IRQ EXTI 中断处理(EXTI1)。 */ + static void onEscIrqExti(); + /** @brief SYNC1 EXTI 中断处理(EXTI2,备用)。 */ + static void onSync1Exti(); + /** @brief TIMER0 降级模式中断处理。 */ + static void onTimerIrq(); +}; + +#endif /* COMM_ETHERCAT_SYNC_H */ diff --git a/GD32E508_DCU_APP/device/ADC.cpp b/GD32E508_DCU_APP/device/ADC.cpp index f86f39c..0def0f4 100644 --- a/GD32E508_DCU_APP/device/ADC.cpp +++ b/GD32E508_DCU_APP/device/ADC.cpp @@ -1,5 +1,5 @@ /** - * @file ADC.cpp + * @file adc.cpp * @brief ADC 采样模块实现。 */ diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index a9e9232..bfa9ea6 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -1,13 +1,13 @@ /** - * @file CANFD.cpp + * @file canfd.cpp * @brief CANFD 通信模块实现。 */ #include "main.h" #include "device/canfd.h" #include "sys/config.h" +#include "sys/critical.h" #include "sys/task_manager.h" -#include "sys/systick.h" namespace { struct TxFrame { @@ -44,30 +44,6 @@ constexpr CanChannelConfig kChannels[] = { constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]); constexpr uint8_t kTxQueueDepth = 4U; constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE); - -/** - * @brief Enter a critical section and preserve the previous interrupt mask. - * @param[in] 无。 - * @param[out] 无。 - * @retval uint32_t Previous PRIMASK value. - */ -static uint32_t enterCriticalSection() -{ - const uint32_t primask = __get_PRIMASK(); - __disable_irq(); - return primask; -} - -/** - * @brief Exit a critical section by restoring the saved interrupt mask. - * @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`. - * @param[out] 无。 - * @retval 无返回值。 - */ -static void exitCriticalSection(uint32_t primask) -{ - __set_PRIMASK(primask); -} } // namespace CanFd::Diag CanFd::s_diag{}; @@ -281,7 +257,7 @@ void CanFd::init() initFdMode(ch.canPeriph); configureRxFilter(ch.canPeriph, ch.filterNumber); can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0); - nvic_irq_enable(ch.rx0Irq, 1U, static_cast(i)); + nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast(i)); s_rxQueues[i].head = 0U; s_rxQueues[i].tail = 0U; @@ -375,14 +351,14 @@ void CanFd::sendPrepared() bool CanFd::hasRxFrame() { bool found = false; - const uint32_t primask = enterCriticalSection(); + uint32_t save = RT_CRITICAL_SAVE(); for(size_t i = 0; i < kChannelCount; ++i) { if(s_rxQueues[i].count != 0U) { found = true; break; } } - exitCriticalSection(primask); + RT_CRITICAL_RESTORE(save); return found; } @@ -401,7 +377,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t RxFrame localFrame{}; bool found = false; - const uint32_t primask = enterCriticalSection(); + uint32_t save = RT_CRITICAL_SAVE(); for(size_t n = 0; n < kChannelCount; ++n) { const uint8_t idx = static_cast((s_nextConsumeChannel + n) % kChannelCount); auto &queue = s_rxQueues[idx]; @@ -416,7 +392,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t found = true; break; } - exitCriticalSection(primask); + RT_CRITICAL_RESTORE(save); if(!found) { return false; @@ -467,29 +443,10 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, const uint32_t canPeriph = kChannels[channelIndex].canPeriph; uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg); - /* 邮箱全占时的恢复处理: - * 当目标控制器初始化时 auto_retrans = ENABLE,且总线上没有其它节点回 ACK, - * 已入邮箱的帧会无限自动重传,TME 位永不置回,导致后续帧一律 CAN_NOMAILBOX。 - * - * 改良策略(两段式,尽量不丢帧): - * 1) 先短自旋 ~2 ms 轮询邮箱状态。健康总线上 1 Mbps + 8 字节帧的实际发送 - * 仅约 130 µs,正常重传几百 µs 内即可 drain,绝大多数“偶发 NOMAILBOX” - * 场景到这一步就已经恢复,不会触发 abort,也就不会丢帧。 - * 2) 若 2 ms 后仍全部 PENDING(典型:物理层不通、TCAN1057A 没焊或 CAN_L/H - * 断线导致 ACK Error 死循环),只 abort 第一个 PENDING 邮箱腾一个位置, - * 最坏丢 1 帧而不是 3 帧,并通过 s_diag.tx_abort_count 暴露给串口 diag, - * 上层(含 SPI 桥)仍能把后续帧正常下发。 */ + /* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待 + * SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort + * 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */ if (mailbox == CAN_NOMAILBOX) { - constexpr uint8_t kDrainPolls = 2U; - for (uint8_t poll = 0U; poll < kDrainPolls && mailbox == CAN_NOMAILBOX; ++poll) { - SysTickTimer::delayMs(1U); - mailbox = can_message_transmit(canPeriph, &tx_msg); - } - } - if (mailbox == CAN_NOMAILBOX) { - /* 只 abort 一个 PENDING 邮箱:找到首个 stop 成功的就停手, - * 不再扫描后续邮箱,把丢帧上限收敛到 1。stop 失败的邮箱 - * (硬件正在物理发送)继续尝试下一个,避免一次 abort 都没成功。 */ for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) { if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) { continue; diff --git a/GD32E508_DCU_APP/device/Timer.cpp b/GD32E508_DCU_APP/device/Timer.cpp index c33c43d..2fa3b6b 100644 --- a/GD32E508_DCU_APP/device/Timer.cpp +++ b/GD32E508_DCU_APP/device/Timer.cpp @@ -1,10 +1,10 @@ /** - * @file Timer.cpp + * @file timer.cpp * @brief 用户调度定时器模块实现。 */ #include "main.h" -#include "device/Timer.h" +#include "device/timer.h" #include "sys/task_manager.h" #include "sys/config.h" @@ -44,7 +44,7 @@ void SchedulerTimer::configureTimer() */ void SchedulerTimer::configureNvic() { - nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); + nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP); nvic_irq_enable(CONFIG_SCHED_TIMER_IRQn, CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO, CONFIG_SCHED_TIMER_IRQ_SUB_PRIO); } diff --git a/GD32E508_DCU_APP/device/app_spi_cmd_boot_request.cpp b/GD32E508_DCU_APP/device/spi/app_spi_cmd_boot_request.cpp similarity index 88% rename from GD32E508_DCU_APP/device/app_spi_cmd_boot_request.cpp rename to GD32E508_DCU_APP/device/spi/app_spi_cmd_boot_request.cpp index 0ab5f37..4bc3dca 100644 --- a/GD32E508_DCU_APP/device/app_spi_cmd_boot_request.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_cmd_boot_request.cpp @@ -1,6 +1,6 @@ -#include "device/spi_command_handler.h" -#include "device/app_spi_command.h" -#include "device/app_spi_command_defs.h" +#include "device/spi/spi_command_handler.h" +#include "device/spi/app_spi_command.h" +#include "device/spi/app_spi_command_defs.h" #include "sys/ota_boot_request.h" #include "sys/config.h" #include "sys/task_manager.h" diff --git a/GD32E508_DCU_APP/device/app_spi_cmd_can_bridge.cpp b/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_bridge.cpp similarity index 89% rename from GD32E508_DCU_APP/device/app_spi_cmd_can_bridge.cpp rename to GD32E508_DCU_APP/device/spi/app_spi_cmd_can_bridge.cpp index 231029a..17748f7 100644 --- a/GD32E508_DCU_APP/device/app_spi_cmd_can_bridge.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_bridge.cpp @@ -1,17 +1,21 @@ -#include "device/spi_command_handler.h" -#include "device/app_spi_command.h" -#include "device/app_spi_command_defs.h" +#include "device/spi/spi_command_handler.h" +#include "device/spi/app_spi_command.h" +#include "device/spi/app_spi_command_defs.h" #include "device/canfd.h" +#include "sys/config.h" #include namespace { +#if (CONFIG_SPI_CAN_SEND_VERBOSE_LOG != 0) constexpr uint8_t kCanLogMaxDlc = 64U; +#endif void postCanSendLog(AppSpiCommand &bus, const char *result, uint8_t can_ch, uint32_t can_id, const uint8_t *can_data, uint8_t can_dlc) { +#if (CONFIG_SPI_CAN_SEND_VERBOSE_LOG != 0) const uint8_t logged_dlc = (can_dlc <= kCanLogMaxDlc) ? can_dlc : kCanLogMaxDlc; char data_hex[(kCanLogMaxDlc * 2U) + 1U] = {}; for (uint8_t i = 0U; i < logged_dlc; ++i) { @@ -27,6 +31,14 @@ void postCanSendLog(AppSpiCommand &bus, const char *result, (logged_dlc > 0U) ? data_hex : "-", result ? result : "UNKNOWN"); bus.postLog(log_msg); +#else + (void)bus; + (void)result; + (void)can_ch; + (void)can_id; + (void)can_data; + (void)can_dlc; +#endif } class SpiCanBridgeHandler final : public ISpiCommandHandler { @@ -39,11 +51,13 @@ public: void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override { if (ctx.payload_len < 6U) { +#if (CONFIG_SPI_APP_LOG_ENABLE != 0) char log_msg[AppSpiCommand::kLogBufSize] = {}; (void)snprintf(log_msg, sizeof(log_msg), "CAN_SEND payload too short: len=%u -> ERROR", static_cast(ctx.payload_len)); bus.postLog(log_msg); +#endif uint8_t err = 0xE0U; bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len, SpiDefs::kRspError, &err, 1U); diff --git a/GD32E508_DCU_APP/device/app_spi_cmd_ota_wait.cpp b/GD32E508_DCU_APP/device/spi/app_spi_cmd_ota_wait.cpp similarity index 86% rename from GD32E508_DCU_APP/device/app_spi_cmd_ota_wait.cpp rename to GD32E508_DCU_APP/device/spi/app_spi_cmd_ota_wait.cpp index fa8e438..e802682 100644 --- a/GD32E508_DCU_APP/device/app_spi_cmd_ota_wait.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_cmd_ota_wait.cpp @@ -1,6 +1,6 @@ -#include "device/spi_command_handler.h" -#include "device/app_spi_command.h" -#include "device/app_spi_command_defs.h" +#include "device/spi/spi_command_handler.h" +#include "device/spi/app_spi_command.h" +#include "device/spi/app_spi_command_defs.h" namespace { diff --git a/GD32E508_DCU_APP/device/app_spi_cmd_ping.cpp b/GD32E508_DCU_APP/device/spi/app_spi_cmd_ping.cpp similarity index 93% rename from GD32E508_DCU_APP/device/app_spi_cmd_ping.cpp rename to GD32E508_DCU_APP/device/spi/app_spi_cmd_ping.cpp index d6d03e4..5323686 100644 --- a/GD32E508_DCU_APP/device/app_spi_cmd_ping.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_cmd_ping.cpp @@ -1,6 +1,6 @@ -#include "device/spi_command_handler.h" -#include "device/app_spi_command.h" -#include "device/app_spi_command_defs.h" +#include "device/spi/spi_command_handler.h" +#include "device/spi/app_spi_command.h" +#include "device/spi/app_spi_command_defs.h" #include "sys/config.h" #include "main.h" #include diff --git a/GD32E508_DCU_APP/device/app_spi_command.cpp b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp similarity index 95% rename from GD32E508_DCU_APP/device/app_spi_command.cpp rename to GD32E508_DCU_APP/device/spi/app_spi_command.cpp index 79fff37..b5aeffa 100644 --- a/GD32E508_DCU_APP/device/app_spi_command.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp @@ -1,4 +1,4 @@ -/** +/** * @file app_spi_command.cpp * @brief APP 运行期 SPI 链路与命令路由实现。 * @@ -13,9 +13,9 @@ * 只需要新增命令处理器,不再继续膨胀本文件。 */ -#include "device/app_spi_command.h" -#include "device/spi_command_handler.h" -#include "device/app_spi_command_defs.h" +#include "device/spi/app_spi_command.h" +#include "device/spi/spi_command_handler.h" +#include "device/spi/app_spi_command_defs.h" extern "C" { #include "gd32e50x_exti.h" @@ -26,6 +26,7 @@ extern "C" { #include "gd32e50x_dma.h" #include "gd32e50x_spi.h" #include "sys/config.h" +#include "sys/critical.h" #include "sys/task_manager.h" #include "sys/ota_boot_request.h" #include "sys/systick.h" @@ -180,16 +181,25 @@ static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len, void AppSpiCommand::postLog(const char *msg) { +#if (CONFIG_SPI_APP_LOG_ENABLE != 0) if (!msg) return; snprintf(m_logBuf, kLogBufSize, "[SPI-APP] %s", msg); m_logPending = true; +#else + (void)msg; +#endif } void AppSpiCommand::postLogFmt(const char *fmt, uint8_t val) { +#if (CONFIG_SPI_APP_LOG_ENABLE != 0) if (!fmt) return; snprintf(m_logBuf, kLogBufSize, fmt, val); m_logPending = true; +#else + (void)fmt; + (void)val; +#endif } void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size, @@ -251,7 +261,7 @@ void AppSpiCommand::configureSpi0() spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE); spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR); spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE); - nvic_irq_enable(SPI0_IRQn, 2U, 0U); + nvic_irq_enable(SPI0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U); spi_enable(SPI0); } @@ -282,7 +292,7 @@ void AppSpiCommand::configureExti() exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH); exti_interrupt_enable(EXTI_15); exti_interrupt_flag_clear(EXTI_15); - nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U); + nvic_irq_enable(EXTI10_15_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U); } @@ -341,7 +351,7 @@ void AppSpiCommand::processCommand() uint16_t payload_len = 0U; if (spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len)) { - if (!isFlashRelatedFrame(frame_type)) { + if (CONFIG_SPI_APP_LOG_ENABLE != 0 && !isFlashRelatedFrame(frame_type)) { postLogFmt("[SPI-APP] 收到非刷写 SPI type=0x%02X", frame_type); } @@ -432,8 +442,10 @@ void AppSpiCommand::processCommand() } } - /* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */ - TaskManager::set(1UL << TASK_SPI_LOG); + if (m_logPending) { + /* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */ + TaskManager::set(1UL << TASK_SPI_LOG); + } } /* ============================================================ @@ -445,19 +457,28 @@ void AppSpiCommand::poll() char log_copy[kLogBufSize] = {}; bool has_log = false; - __disable_irq(); + uint32_t save = RT_CRITICAL_SAVE(); if (m_logPending) { memcpy(log_copy, m_logBuf, sizeof(log_copy)); m_logPending = false; has_log = true; } - __enable_irq(); + RT_CRITICAL_RESTORE(save); if (has_log) { printf("%s\r\n", log_copy); } } +void AppSpiCommand::processPendingCommand() +{ + if (!m_cmdPending) { + return; + } + m_cmdPending = false; + processCommand(); +} + /* ============================================================ * CS 边沿处理 * ============================================================ */ @@ -522,7 +543,8 @@ void AppSpiCommand::onCsRelease() m_rxLen = 0U; return; } - processCommand(); + m_cmdPending = true; + TaskManager::set(1UL << TASK_SPI_CMD_PROCESS); } /* ============================================================ diff --git a/GD32E508_DCU_APP/device/app_spi_command.h b/GD32E508_DCU_APP/device/spi/app_spi_command.h similarity index 90% rename from GD32E508_DCU_APP/device/app_spi_command.h rename to GD32E508_DCU_APP/device/spi/app_spi_command.h index d7edadd..ae6247f 100644 --- a/GD32E508_DCU_APP/device/app_spi_command.h +++ b/GD32E508_DCU_APP/device/spi/app_spi_command.h @@ -1,4 +1,4 @@ -#ifndef DEVICE_APP_SPI_COMMAND_H +#ifndef DEVICE_APP_SPI_COMMAND_H #define DEVICE_APP_SPI_COMMAND_H #include @@ -12,7 +12,7 @@ class ISpiCommandHandler; * - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI) * - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler * - * 具体命令处理逻辑不再放在这里,而是拆到 `device/app_spi_cmd_*.cpp`, + * 具体命令处理逻辑不再放在这里,而是拆到 `device/spi/app_spi_cmd_*.cpp`, * 通过 `registerHandler()` 注册进来,保持本模块可扩展。 */ class AppSpiCommand { @@ -23,6 +23,9 @@ public: /** @brief 主循环轮询:打印延迟日志。 */ void poll(); + /** @brief 主循环调用:执行 ISR 中推迟的 SPI 命令解析(从 onCsRelease 移出)。 */ + void processPendingCommand(); + /** @brief SPI0 中断处理入口。 */ void onInterrupt(); @@ -66,6 +69,7 @@ private: uint8_t m_txLen = 0U; volatile uint8_t m_txPos = 0U; volatile bool m_logPending = false; + volatile bool m_cmdPending = false; /**< ISR 置位,标记有待主循环解析的 SPI 命令。 */ uint8_t m_rxBuf[kRxBufSize] = {}; uint8_t m_txBuf[kTxBufSize] = {}; char m_logBuf[kLogBufSize] = {}; diff --git a/GD32E508_DCU_APP/device/app_spi_command_defs.h b/GD32E508_DCU_APP/device/spi/app_spi_command_defs.h similarity index 100% rename from GD32E508_DCU_APP/device/app_spi_command_defs.h rename to GD32E508_DCU_APP/device/spi/app_spi_command_defs.h diff --git a/GD32E508_DCU_APP/device/spi_command_handler.h b/GD32E508_DCU_APP/device/spi/spi_command_handler.h similarity index 100% rename from GD32E508_DCU_APP/device/spi_command_handler.h rename to GD32E508_DCU_APP/device/spi/spi_command_handler.h diff --git a/GD32E508_DCU_APP/device/usart0.cpp b/GD32E508_DCU_APP/device/usart0.cpp index 275b50b..e6b4702 100644 --- a/GD32E508_DCU_APP/device/usart0.cpp +++ b/GD32E508_DCU_APP/device/usart0.cpp @@ -6,6 +6,7 @@ #include "main.h" #include "device/usart0.h" #include "sys/config.h" +#include "sys/critical.h" uint8_t Usart0::s_txBuf[Usart0::kTxBufSize]; volatile uint16_t Usart0::s_txHead = 0U; @@ -54,10 +55,10 @@ bool Usart0::isBufferEmpty() */ bool Usart0::putCharNonBlocking(int ch) { - __disable_irq(); + uint32_t save = RT_CRITICAL_SAVE(); const uint16_t next = nextIndex(s_txHead); if(next == s_txTail) { - __enable_irq(); + RT_CRITICAL_RESTORE(save); s_dropCount++; return false; } @@ -65,7 +66,7 @@ bool Usart0::putCharNonBlocking(int ch) s_txBuf[s_txHead] = static_cast(ch); s_txHead = next; usart_interrupt_enable(USART0, USART_INT_TBE); - __enable_irq(); + RT_CRITICAL_RESTORE(save); return true; } @@ -136,7 +137,7 @@ void Usart0::init() usart_receive_config(USART0, USART_RECEIVE_ENABLE); usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); usart_enable(USART0); - nvic_irq_enable(USART0_IRQn, 2U, 0U); + nvic_irq_enable(USART0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U); } /* ----- 中断入口 / printf 重定向的 C 包装 --------------------------- */ diff --git a/GD32E508_DCU_APP/main.cpp b/GD32E508_DCU_APP/main.cpp index bf0eb76..51b0ace 100644 --- a/GD32E508_DCU_APP/main.cpp +++ b/GD32E508_DCU_APP/main.cpp @@ -8,6 +8,7 @@ #include "sys/task_manager.h" #include "sys/task_runner.h" #include "sys/config.h" +#include "sys/critical.h" /** * @brief 打印应用启动横幅与系统时钟信息。 @@ -46,13 +47,17 @@ int App::run() while(1) { TaskRunner::runPending(); - const uint32_t primask = __get_PRIMASK(); - __disable_irq(); +#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0) + uint32_t save = RT_CRITICAL_SAVE(); if(TaskManager::state().task_flags == 0U) { __DSB(); __WFI(); } - __set_PRIMASK(primask); + RT_CRITICAL_RESTORE(save); +#else + /* EtherCAT 实时模式:关闭 WFI 改用忙等,保证周期中断立即响应。 */ + __NOP(); +#endif } return 0; diff --git a/GD32E508_DCU_APP/main.h b/GD32E508_DCU_APP/main.h index 236c02d..fb9b175 100644 --- a/GD32E508_DCU_APP/main.h +++ b/GD32E508_DCU_APP/main.h @@ -1,4 +1,4 @@ -#ifndef MAIN_H +#ifndef MAIN_H #define MAIN_H #include "RTE_Components.h" @@ -23,6 +23,8 @@ enum system_task_t { TASK_HEARTBEAT, /**< 系统心跳维护任务。 */ TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */ TASK_SPI_LOG, /**< SPI 延迟日志打印任务。 */ + TASK_ETHERCAT_PROCESS, /**< EtherCAT 周期/事件处理任务(非实时应用层 PDO 映射)。 */ + TASK_SPI_CMD_PROCESS, /**< SPI CS release 后的命令解析任务(从 ISR 移出)。 */ TASK_COUNT /**< 任务总数,用于边界或统计场景。 */ }; diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index d12311c..be73b7b 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -102,9 +102,13 @@ #define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U /** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */ -#define CONFIG_UART_TEMP_PERIODIC_PRINT 1 +#define CONFIG_UART_TEMP_PERIODIC_PRINT 0 /** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */ #define CONFIG_UART_DIAG_LEVEL 1 +/** @brief SPI 命令逐帧日志开关:0=关闭,1=开启。高速 CAN 桥接时应关闭。 */ +#define CONFIG_SPI_APP_LOG_ENABLE 0 +/** @brief CAN_SEND 成功帧详细日志开关:0=关闭,1=开启。开启会显著影响高速传输。 */ +#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0 /** @brief FWDGT 独立看门狗预分频值。 */ @@ -123,11 +127,11 @@ /** @brief APP 主版本号。 */ #define APP_FW_VERSION_MAJOR 1U /** @brief APP 次版本号。 */ -#define APP_FW_VERSION_MINOR 0U +#define APP_FW_VERSION_MINOR 1U /** @brief APP 补丁版本号。 */ -#define APP_FW_VERSION_PATCH 1U +#define APP_FW_VERSION_PATCH 0U /** @brief APP 构建编号。 */ -#define APP_FW_VERSION_BUILD 2U +#define APP_FW_VERSION_BUILD 0U /* ============================================================ * BOOT 版本在 OTA 配置页中的 Flash 存储地址 @@ -142,4 +146,116 @@ /** @brief OTA 配置页总 word 数(6 个 OTA 标志 + 2 个 BOOT 版本)。 */ #define CONFIG_OTA_CONFIG_WORD_COUNT 8U +/* ============================================================ + * NVIC 优先级分组与中断优先级分层 + * (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级) + * ============================================================ */ +/** @brief NVIC 优先级分组:4 位抢占 + 0 位子优先级。 */ +#define CONFIG_NVIC_PRIORITY_GROUP NVIC_PRIGROUP_PRE4_SUB0 + +/** @brief EtherCAT SYNC0 周期中断抢占优先级(最高,永不被阻塞)。 */ +#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC 0U +/** @brief 调度器定时器抢占优先级。 */ +#define CONFIG_IRQ_PREEMPT_SCHED 1U +/** @brief CAN RX0 抢占优先级。 */ +#define CONFIG_IRQ_PREEMPT_CAN 2U +/** @brief EtherCAT ESC 事件中断抢占优先级(可被 SYNC0 抢占)。 */ +#define CONFIG_IRQ_PREEMPT_ETHERCAT_ESC 1U +/** @brief EtherCAT SYNC1 备用抢占优先级。 */ +#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 1U +/** @brief SPI / EXTI / USART 抢占优先级(最低)。 */ +#define CONFIG_IRQ_PREEMPT_SPI_USART 3U + +/* ============================================================ + * 主循环与实时策略 + * ============================================================ */ +/** @brief 主循环 WFI 开关:0=关闭(忙等,适配 EtherCAT),1=开启(省电)。 */ +#define CONFIG_MAIN_LOOP_WFI_ENABLE 0 + +/* ============================================================ + * EtherCAT LAN9252 使能与配置 + * ============================================================ */ +/** @brief EtherCAT 使能开关:0=禁用(未焊接 LAN9252),1=启用(焊接后)。 + * 禁用时 comm/ 全部代码不编译,初始化不调用 EtherCAT,EXMC/EXTI0-2 不配置。 */ +#define CONFIG_ETHERCAT_ENABLE 0 + +/** @brief SYNC0 同步模式:0=EXTI 外部触发(ESC 硬件同步,默认),1=TIMER0 降级(DC 未同步时)。 */ +#define CONFIG_ETHERCAT_SYNC_MODE 0 + +/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */ +/** @brief EXMC NOR/SRAM Region1 基址(NE1 片选)。 */ +#define CONFIG_ESC_EXMC_BASE_ADDR 0x60000000U +/** @brief EXMC BANK 选择(BANK0 Region1)。 + * @note EXMC 库未纳入编译时回退为 0U;启用 EtherCAT 时 EXMC 组件将被引入。 */ +#ifdef EXMC_BANK0_NORSRAM_REGION1 +#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1 +#else +#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 0U +#endif + +/* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */ +/** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */ +#define CONFIG_ESC_SYNC0_PORT GPIOB +#define CONFIG_ESC_SYNC0_PIN GPIO_PIN_0 +#define CONFIG_ESC_SYNC0_EXTI EXTI_0 +#define CONFIG_ESC_SYNC0_IRQn EXTI0_IRQn +#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB +#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_0 + +#define CONFIG_ESC_IRQ_PORT GPIOB +#define CONFIG_ESC_IRQ_PIN GPIO_PIN_1 +#define CONFIG_ESC_IRQ_EXTI EXTI_1 +#define CONFIG_ESC_IRQ_IRQn EXTI1_IRQn +#define CONFIG_ESC_IRQ_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB +#define CONFIG_ESC_IRQ_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_1 + +#define CONFIG_ESC_SYNC1_PORT GPIOB +#define CONFIG_ESC_SYNC1_PIN GPIO_PIN_2 +#define CONFIG_ESC_SYNC1_EXTI EXTI_2 +#define CONFIG_ESC_SYNC1_IRQn EXTI2_IRQn +#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB +#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_2 + +/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */ +#define CONFIG_EXMC_D0_PORT GPIOD +#define CONFIG_EXMC_D0_PIN GPIO_PIN_14 +#define CONFIG_EXMC_D1_PORT GPIOD +#define CONFIG_EXMC_D1_PIN GPIO_PIN_15 +#define CONFIG_EXMC_D2_PORT GPIOD +#define CONFIG_EXMC_D2_PIN GPIO_PIN_0 +#define CONFIG_EXMC_D3_PORT GPIOD +#define CONFIG_EXMC_D3_PIN GPIO_PIN_1 +#define CONFIG_EXMC_D4_PORT GPIOE +#define CONFIG_EXMC_D4_PIN GPIO_PIN_7 +#define CONFIG_EXMC_D5_PORT GPIOE +#define CONFIG_EXMC_D5_PIN GPIO_PIN_8 +#define CONFIG_EXMC_D6_PORT GPIOE +#define CONFIG_EXMC_D6_PIN GPIO_PIN_9 +#define CONFIG_EXMC_D7_PORT GPIOE +#define CONFIG_EXMC_D7_PIN GPIO_PIN_10 +#define CONFIG_EXMC_D8_PORT GPIOE +#define CONFIG_EXMC_D8_PIN GPIO_PIN_11 +#define CONFIG_EXMC_D9_PORT GPIOE +#define CONFIG_EXMC_D9_PIN GPIO_PIN_12 +#define CONFIG_EXMC_D10_PORT GPIOE +#define CONFIG_EXMC_D10_PIN GPIO_PIN_13 +#define CONFIG_EXMC_D11_PORT GPIOE +#define CONFIG_EXMC_D11_PIN GPIO_PIN_14 +#define CONFIG_EXMC_D12_PORT GPIOE +#define CONFIG_EXMC_D12_PIN GPIO_PIN_15 +#define CONFIG_EXMC_D13_PORT GPIOD +#define CONFIG_EXMC_D13_PIN GPIO_PIN_8 +#define CONFIG_EXMC_D14_PORT GPIOD +#define CONFIG_EXMC_D14_PIN GPIO_PIN_9 +#define CONFIG_EXMC_D15_PORT GPIOD +#define CONFIG_EXMC_D15_PIN GPIO_PIN_10 +#define CONFIG_EXMC_A16_PORT GPIOD +#define CONFIG_EXMC_A16_PIN GPIO_PIN_11 +#define CONFIG_EXMC_NOE_PORT GPIOD +#define CONFIG_EXMC_NOE_PIN GPIO_PIN_4 +#define CONFIG_EXMC_NWE_PORT GPIOD +#define CONFIG_EXMC_NWE_PIN GPIO_PIN_5 +#define CONFIG_EXMC_NE1_PORT GPIOD +#define CONFIG_EXMC_NE1_PIN GPIO_PIN_7 + #endif /* SYS_CONFIG_H */ diff --git a/GD32E508_DCU_APP/sys/critical.h b/GD32E508_DCU_APP/sys/critical.h new file mode 100644 index 0000000..b930c83 --- /dev/null +++ b/GD32E508_DCU_APP/sys/critical.h @@ -0,0 +1,62 @@ +/** + * @file critical.h + * @brief 实时临界区基础设施:基于 BASEPRI 寄存器的中断屏蔽。 + * @note EtherCAT 实时架构改造专用。GD32E508 为 Cortex-M4F,支持 BASEPRI 寄存器。 + * + * @details 设计说明: + * - BASEPRI 阈值屏蔽抢占优先级 >= 1 的中断,仅保留 preempt=0(EtherCAT SYNC0 + * 周期中断)可立即响应,保证 EtherCAT 周期抖动 <1us。 + * - 取代原 PRIMASK 方案(`__disable_irq` / `__set_PRIMASK`),后者会屏蔽全部中断 + * (含 preempt=0),无法满足 EtherCAT 实时性要求。 + * - NVIC 优先级分组为 `NVIC_PRIGROUP_PRE4_SUB0`(4 位抢占 + 0 位子优先级), + * 因此 `__NVIC_PRIO_BITS = 4`,阈值 `(1U << (8U - 4U)) = 0x10`,即屏蔽优先级 + * 数值 0x1x 及以上(对应 preempt 1..15),保留 preempt=0(优先级 0x0x)。 + * - 阈值定义依赖 `__NVIC_PRIO_BITS`,该宏由 CMSIS 设备头文件提供(GD32E508 = 4)。 + * - 项目使用 AC6(armclang) gnu++17,支持 GCC 语句表达式(statement expression), + * `RT_CRITICAL_SAVE()` 利用该特性返回保存的 BASEPRI 值,便于嵌套恢复。 + */ +#ifndef SYS_CRITICAL_H +#define SYS_CRITICAL_H + +#include "RTE_Components.h" +#include CMSIS_device_header + +/** @brief BASEPRI 屏蔽阈值:屏蔽 preempt>=1,保留 preempt=0(EtherCAT SYNC0)。 + * 对于 4 位抢占优先级(__NVIC_PRIO_BITS=4),该值为 0x10。 */ +#define RT_CRITICAL_BASEPRI_VALUE (1U << (8U - __NVIC_PRIO_BITS)) + +/** + * @brief 进入临界区:屏蔽抢占优先级 >= 1 的中断。 + * @note preempt=0 的 EtherCAT SYNC0 周期中断仍可立即抢占执行。 + */ +#define RT_CRITICAL_ENTER() __set_BASEPRI(RT_CRITICAL_BASEPRI_VALUE) + +/** + * @brief 退出临界区:恢复全部中断响应(BASEPRI 清零)。 + */ +#define RT_CRITICAL_EXIT() __set_BASEPRI(0U) + +/** + * @brief 保存当前 BASEPRI 并进入临界区,返回进入前的 BASEPRI 值。 + * @return 进入临界区前的 BASEPRI 寄存器值(供 RT_CRITICAL_RESTORE 恢复)。 + * @note 利用 GCC 语句表达式返回值,用于嵌套临界区场景: + * @code + * uint32_t save = RT_CRITICAL_SAVE(); + * // ... 临界区代码 ... + * RT_CRITICAL_RESTORE(save); + * @endcode + */ +#define RT_CRITICAL_SAVE() \ + ({ \ + uint32_t __basepri_save = __get_BASEPRI(); \ + __set_BASEPRI(RT_CRITICAL_BASEPRI_VALUE); \ + __basepri_save; \ + }) + +/** + * @brief 恢复 BASEPRI 到指定值(用于 RT_CRITICAL_SAVE() 配套的嵌套恢复)。 + * @param[in] val RT_CRITICAL_SAVE() 返回的 BASEPRI 值。 + */ +#define RT_CRITICAL_RESTORE(val) __set_BASEPRI(val) + +#endif /* SYS_CRITICAL_H */ diff --git a/GD32E508_DCU_APP/sys/initial.cpp b/GD32E508_DCU_APP/sys/initial.cpp index f01bf7b..34afe66 100644 --- a/GD32E508_DCU_APP/sys/initial.cpp +++ b/GD32E508_DCU_APP/sys/initial.cpp @@ -1,4 +1,4 @@ -/** +/** * @file initial.cpp * @brief 应用启动初始化实现。 */ @@ -8,13 +8,18 @@ #include "device/canfd.h" #include "device/temp_sensor.h" #include "device/usart0.h" -#include "device/Timer.h" +#include "device/timer.h" #include "device/adc.h" #include "device/watchdog.h" -#include "device/app_spi_command.h" +#include "device/spi/app_spi_command.h" #include "sys/systick.h" #include "sys/ota_boot_request.h" #include "sys/task_manager.h" +#include "sys/config.h" +#if CONFIG_ETHERCAT_ENABLE +#include "comm/ethercat_slave.h" +#include "comm/ethercat_sync.h" +#endif /** * @brief 执行应用级外设与调度初始化。 @@ -42,6 +47,12 @@ void AppInitializer::init() TaskManager::init(); printf("[APP] init: SchedulerTimer...\r\n"); SchedulerTimer::init(); +#if CONFIG_ETHERCAT_ENABLE + printf("[APP] init: EthercatSlave...\r\n"); + EthercatSlave::init(); + printf("[APP] init: EthercatSync...\r\n"); + EthercatSync::init(); +#endif printf("[APP] init: Watchdog...\r\n"); Watchdog::init(); printf("[APP] init: all done\r\n"); diff --git a/GD32E508_DCU_APP/sys/task_manager.cpp b/GD32E508_DCU_APP/sys/task_manager.cpp index f86564f..7e75625 100644 --- a/GD32E508_DCU_APP/sys/task_manager.cpp +++ b/GD32E508_DCU_APP/sys/task_manager.cpp @@ -1,4 +1,4 @@ -/** +/** * @file task_manager.cpp * @brief 任务调度管理器实现。 */ @@ -6,32 +6,7 @@ #include "main.h" #include "sys/task_manager.h" #include "sys/config.h" - -namespace { -/** - * @brief Enter a critical section and preserve the previous interrupt mask. - * @param[in] 无。 - * @param[out] 无。 - * @retval uint32_t Previous PRIMASK value. - */ -static uint32_t enterCriticalSection() -{ - const uint32_t primask = __get_PRIMASK(); - __disable_irq(); - return primask; -} - -/** - * @brief Exit a critical section by restoring the saved interrupt mask. - * @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`. - * @param[out] 无。 - * @retval 无返回值。 - */ -static void exitCriticalSection(uint32_t primask) -{ - __set_PRIMASK(primask); -} -} // namespace +#include "sys/critical.h" task_scheduler_t g_scheduler = {0}; @@ -68,9 +43,9 @@ void TaskManager::init() */ void TaskManager::set(uint32_t taskMask) { - const uint32_t primask = enterCriticalSection(); + uint32_t save = RT_CRITICAL_SAVE(); g_scheduler.task_flags |= taskMask; - exitCriticalSection(primask); + RT_CRITICAL_RESTORE(save); } /** @@ -83,10 +58,10 @@ void TaskManager::set(uint32_t taskMask) uint32_t TaskManager::takePending() { uint32_t pending; - const uint32_t primask = enterCriticalSection(); + uint32_t save = RT_CRITICAL_SAVE(); pending = g_scheduler.task_flags; g_scheduler.task_flags &= ~pending; - exitCriticalSection(primask); + RT_CRITICAL_RESTORE(save); return pending; } diff --git a/GD32E508_DCU_APP/sys/task_runner.cpp b/GD32E508_DCU_APP/sys/task_runner.cpp index 59f71ac..de5aae3 100644 --- a/GD32E508_DCU_APP/sys/task_runner.cpp +++ b/GD32E508_DCU_APP/sys/task_runner.cpp @@ -1,4 +1,4 @@ -/** +/** * @file task_runner.cpp * @brief 任务执行器实现。 */ @@ -6,13 +6,16 @@ #include "main.h" #include "device/adc.h" #include "device/watchdog.h" -#include "device/app_spi_command.h" +#include "device/spi/app_spi_command.h" #include "device/canfd.h" #include "device/temp_sensor.h" #include "sys/config.h" #include "sys/ota_boot_request.h" #include "sys/task_manager.h" #include "sys/task_runner.h" +#if CONFIG_ETHERCAT_ENABLE +#include "comm/ethercat_slave.h" +#endif namespace { constexpr uint32_t kAdcFilterWindow = CONFIG_ADC_FILTER_SIZE + 1U; @@ -265,6 +268,34 @@ void TaskRunner::taskDiagnosticPrint() #endif } +/** + * @brief 执行 SPI CS release 后推迟的命令解析(从 ISR 移出)。 + * @param[in] 无。 + * @param[out] 无。 + * @retval 无返回值。 + * @note ISR 瘦身:onCsRelease() 仅置位 TASK_SPI_CMD_PROCESS,实际解析在此执行。 + */ +void TaskRunner::taskSpiCmdProcess() +{ + AppSpiCommandBus.processPendingCommand(); +} + +/** + * @brief 执行 EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。 + * @param[in] 无。 + * @param[out] 无。 + * @retval 无返回值。 + * @note 由周期 ISR(EXTI0 SYNC0)或 ESC 事件 ISR(EXTI1 IRQ)置位 TASK_ETHERCAT_PROCESS。 + */ +void TaskRunner::taskEthercatProcess() +{ +#if CONFIG_ETHERCAT_ENABLE + EthercatSlave::process(); +#else + /* 未使能 EtherCAT 时此分支不应被置位;保留空操作以避免链接错误。 */ +#endif +} + /** * @brief 初始化任务执行器运行时上下文。 * @param[in] 无。 @@ -309,4 +340,6 @@ void TaskRunner::runPending() if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat(); if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint(); if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommandBus.poll(); + if(pending & (1UL << TASK_SPI_CMD_PROCESS)) taskSpiCmdProcess(); + if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess(); } diff --git a/GD32E508_DCU_APP/sys/task_runner.h b/GD32E508_DCU_APP/sys/task_runner.h index 5575088..559afd9 100644 --- a/GD32E508_DCU_APP/sys/task_runner.h +++ b/GD32E508_DCU_APP/sys/task_runner.h @@ -49,6 +49,10 @@ private: static void taskHeartbeat(); /** @brief 诊断打印任务。 */ static void taskDiagnosticPrint(); + /** @brief SPI CS release 后推迟的命令解析(从 ISR 移出)。 */ + static void taskSpiCmdProcess(); + /** @brief EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。 */ + static void taskEthercatProcess(); }; #endif /* SYS_TASK_RUNNER_H */