Refactor and clean up codebase

- Removed unused constants and type aliases in app_spi_command_defs.h and temp_sensor.h.
- Simplified ISpiCommandHandler destructor to default.
- Eliminated redundant methods and comments in temp_sensor and usart0 files.
- Consolidated writeU32Le function to app_spi_command_defs.h for reuse.
- Updated comments for clarity and accuracy in task_manager.h and task_runner.cpp.
- Removed hardfault.h as it contained no declarations.
- Cleaned up CANFD and watchdog implementations by removing dead code and unnecessary comments.
- Updated version numbers in config.h to reflect recent changes.
- Enhanced error handling and diagnostics in hardfault handling.
This commit is contained in:
cbb
2026-07-15 02:49:37 +08:00
parent a52d94687b
commit 76da7a3dd6
37 changed files with 1401 additions and 1955 deletions
@@ -0,0 +1,256 @@
# 清理剩余低风险可优化项 Plan
## Summary
在上一轮 `purge-legacy-dead-code` spec(已清除 23 项 A 类死代码 + 11 项 B/C/D 类改进)基础上,对 `GD32E508_DCU_APP/` 进行二次扫描,识别并处理 **13 项剩余低风险可优化项**(不改变运行期行为,构建可验证)。
覆盖三类:
- **A 类残留死代码**(6 项):死别名、写而不读成员/静态、空头文件、冗余类型别名
- **过期/缺失注释**(3 项):PRIMASK 过期说明、taskHeartbeat 过期注释、复位前 PRIMASK 缺解释
- **重复代码提取**(4 项):spinDelay 延时循环、armRingPending 赋值模式、writeU32Le 跨文件去重、adc.cpp namespace 合并
## Current State Analysis
### 已完成(上一轮 spec)
- A1-A14:23 项死代码清除(CAN 兼容轮询链/SPI v1 状态机/EtherCAT 死 API/ADC/USART0/Watchdog/TempSensor 死接口等)
- B1-B6:6 项前瞻性桩加 TODO 标记
- C1-C4:4 项条件编译优化
- D1:1 项历史注释清理
- 构建验证通过(43/43,0 错误,Code=47462)
### 本轮新发现(经 Grep 交叉验证)
二次扫描发现 13 项上一轮未覆盖的低风险优化项,均不改变运行期行为。
## Proposed Changes
### 第一组:A 类残留死代码清除(6 项)
#### 1.1 删除死类型别名 `canfd_diag_t`
- **文件**:`GD32E508_DCU_APP/device/canfd.h:211`
- **现状**:`using canfd_diag_t = CanFd::Diag;` — Grep 全项目仅命中声明自身,无任何使用方
- **改法**:删除第 211 行及相邻空行
- **风险**:低(纯类型别名删除)
#### 1.2 删除写而永不读成员 `m_ringTxLen`
- **文件**:
- `GD32E508_DCU_APP/device/spi/app_spi_command.h:119-120`(声明 + 注释)
- `GD32E508_DCU_APP/device/spi/app_spi_command.cpp:407, 712, 745, 859, 867`(5 处写入)
- **现状**:`volatile uint16_t m_ringTxLen = 0U;` 头注释称"供 onCsAssert 装入 DMA",但 `onCsAssert` 实际使用的是 `m_txLen`。在 `requestEsp32Read`(745-746 行)中 `m_ringTxLen` 与 `m_txLen` 被赋同值,`m_ringTxLen` 是冗余写者。Grep 确认 5 处写入、0 处读取。
- **改法**:
1. 删除 `app_spi_command.h:119-120`(注释 + 成员声明)
2. 删除 `app_spi_command.cpp` 中 5 处 `m_ringTxLen = ...;` 赋值语句
- **风险**:低(仅删除写操作,`m_txLen` 与 `m_ringTxLen` 始终同值,读取路径不变)
- **注意**:745 行 `m_ringTxLen = (uint16_t)built_len;` 的下一行 `m_txLen = (uint16_t)built_len;` 必须保留
#### 1.3 删除写而永不读静态 `s_pushCount`
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp:257, 282`
- **现状**:`static uint32_t s_pushCount = 0U;` 仅被自增(282 行 `++s_pushCount;`),从未被读取、打印或暴露给诊断接口
- **改法**:删除 257 行声明与 282 行自增语句(含其注释 `/* 仅保留计数,不打印心跳日志 */`)
- **风险**:低(纯死状态)
#### 1.4 删除 4 个未使用本地别名
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_command.cpp:49, 51, 54, 55`
- **现状**:本地别名块(48-56 行)共 9 个别名,其中 `kTypeCanSend`/`kTypeAppPing`/`kRspAck`/`kRspWait` 4 个在文件内从未被引用(Grep 仅命中声明行)。其余 5 个(`kTypeBootReq`/`kTypeOtaReq`/`kTypeOtaData`/`kTypeOtaCancel`/`kRspError`)仍被使用。
- **改法**:删除第 49、51、54、55 行
- **风险**:低(删除 `static constexpr` 未使用符号)
#### 1.5 删除空头文件 `sys/hardfault.h`
- **文件**:`GD32E508_DCU_APP/sys/hardfault.h`(整文件,仅 3 行 include guard)
- **现状**:A13 清理后该头文件已无任何声明,仅剩 `#ifndef HARDFAULT_H / #define HARDFAULT_H / #endif`。`sys/hardfault.cpp` 不包含此头,全项目无任何 `#include "sys/hardfault.h"` 或 `#include "hardfault.h"`(Grep 确认 0 匹配)。
- **改法**:删除整个文件
- **风险**:低(无引用方)
- **注意**:需确认构建脚本(CMakeLists.txt)中是否显式列出该头文件作为安装目标;若仅有 .cpp 参与编译则无影响
#### 1.6 删除 TempSensor 冗余类型别名(方案 A)
- **文件**:
- `GD32E508_DCU_APP/device/temp_sensor.h:12`(`using Data = Adc::Sample;`)
- `GD32E508_DCU_APP/device/temp_sensor.h:24`(`using temp_sensor_data_t = TempSensor::Data;`)
- `GD32E508_DCU_APP/sys/task_runner.cpp:62`(消费点 `temp_sensor_data_t sample{};`)
- **现状**:两层 `using` 别名链(`temp_sensor_data_t` → `TempSensor::Data` → `Adc::Sample`),仅 1 处消费(task_runner.cpp:62)。`TempSensor` 类本身保留(`init()` 仍被 `initial.cpp:45` 调用,作为语义门面有价值)。
- **改法**:
1. 删除 `temp_sensor.h:12` 的 `using Data = Adc::Sample;`
2. 删除 `temp_sensor.h:24` 的 `using temp_sensor_data_t = TempSensor::Data;`
3. 修改 `task_runner.cpp:62` 将 `temp_sensor_data_t sample{};` 改为 `Adc::Sample sample{};`
4. 确认 `task_runner.cpp` 已 `#include "device/adc.h"`(若未包含则需添加,但 task_runner.cpp 大概率已包含)
- **风险**:低(仅类型别名替换,不改语义)
- **注意**:`TempSensor` 类的 `init()` 声明与 doxygen 注释保留不动
---
### 第二组:过期/缺失注释修正(3 项)
#### 2.1 修正 `task_manager.h` PRIMASK 过期注释
- **文件**:`GD32E508_DCU_APP/sys/task_manager.h:45`
- **现状**:注释 `内部通过保持当前 PRIMASK 状态的临界区保证可由主循环与 ISR 共享调用`,但实现已迁移至 BASEPRI(`RT_CRITICAL_SAVE/RESTORE`)
- **改法**:改为 `内部通过 BASEPRI 临界区(RT_CRITICAL_SAVE/RESTORE)保证可由主循环与 ISR 共享调用`
- **风险**:低(纯注释)
#### 2.2 修正 `task_runner.cpp` taskHeartbeat 过期注释
- **文件**:`GD32E508_DCU_APP/sys/task_runner.cpp:225`
- **现状**:`taskHeartbeat` 的 doxygen 注释 `@note 当前仅保留任务入口。` 已过期失真——函数体实际已有实现(227 行:`OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); canFeedbackStartRound();`)。相邻的 `taskStatusLed`(216 行)确实空体,其注释准确。
- **改法**:将 225 行注释改为 `@note 当前任务:推进 OTA Boot 请求、刷新 CAN 反馈快照、启动反馈推送轮次。`
- **风险**:低(纯注释)
- **注意**:`taskStatusLed` 的 216 行注释保持不动(空体桩是 B 类前瞻性桩)
#### 2.3 补充 `ota_boot_request.cpp` PRIMASK 解释注释
- **文件**:`GD32E508_DCU_APP/sys/ota_boot_request.cpp:220`
- **现状**:`__disable_irq(); NVIC_SystemReset();` 是全项目唯一使用 PRIMASK 而非 BASEPRI 的位置。这是**合法用法**(系统复位前最终屏蔽全部中断,防止复位过程中断嵌套),但缺乏解释,后续维护者可能误判为违反项目硬约束。
- **改法**:在 220 行前加注释:`/* 复位前最终关中断:必须用 PRIMASK 全屏蔽(非 BASEPRI),确保 NVIC_SystemReset 过程中无任何中断嵌套 */`
- **风险**:低(纯注释)
---
### 第三组:重复代码提取(4 项)
#### 3.1 提取 `spinDelay` 延时循环(CANFD.cpp)
- **文件**:`GD32E508_DCU_APP/device/CANFD.cpp`
- **现状**:9 处 `for (volatile uint32_t d = 0; d < NNNNN; ++d) {}` 纯延时循环散落在 `hardResetCan`、`runSelfTestFrame`、`init` 退出 init mode 兜底等多处。魔数 `{40000, 50000, 100000, 200000}` 无命名常量。
- **改法**:
1. 在文件顶部匿名 namespace 内新增:
```cpp
/* 纯软件延时循环(不依赖 SysTick,可在任何中断状态下使用) */
static inline void spinDelay(uint32_t loops) {
for (volatile uint32_t d = 0; d < loops; ++d) { }
}
```
2. 把以下**纯延时循环**(无 break/条件)替换为 `spinDelay(N);`:
- 94 行:`for (volatile uint32_t dly = 0; dly < 40000U; ++dly) { }` → `spinDelay(40000U);`
- 120 行:`for (volatile uint32_t d = 0; d < 100000U; ++d) {}` → `spinDelay(100000U);`
- 125 行:`for (volatile uint32_t d = 0; d < 50000U; ++d) {}` → `spinDelay(50000U);`
- 130 行:`for (volatile uint32_t d = 0; d < 200000U; ++d) {}` → `spinDelay(200000U);`
- 142 行:需确认是否纯延时(读取后判断)
- 382 行:需确认是否纯延时
- 384 行:需确认是否纯延时
3. **不替换**带有条件 break 的轮询等待循环(如 355 行 `for (...) { if ((CAN_STAT(...) & CAN_STAT_IWS) == 0U) break; }`)——这些不是纯延时,保持原样
- **风险**:低(纯结构提取,延时循环行为不变)
- **注意**:执行时需逐一 Read 确认 134/142/382/384 行的实际内容,仅替换纯延时循环
#### 3.2 提取 `armRingPending` 赋值模式(app_spi_command.cpp)
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_command.cpp`
- **现状**:7 处重复的"按铃挂起"赋值模式:
```
m_ringPending = true;
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
```
出现在 516-517, 556-557, 580-581, 604-605, 630-631, 699-700, 748-749 行。
- **改法**:
1. 在 `app_spi_command.h` 的 `AppSpiCommand` private 段新增声明:`void armRingPending();`
2. 在 `app_spi_command.cpp` 新增实现:
```cpp
void AppSpiCommand::armRingPending() {
m_ringPending = true;
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
}
```
3. 把 7 处两行赋值替换为 `armRingPending();`
- **风险**:低(语义等价提取,两个独立赋值无依赖关系,顺序无关)
- **注意**:第 7 处(748-749)原顺序为先 `m_ringDeadline` 后 `m_ringPending`,提取后顺序统一为方法内顺序,不影响语义
#### 3.3 `writeU32Le` 跨文件去重
- **文件**:
- `GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp:53-59`(`static void writeU32Le`)
- `GD32E508_DCU_APP/device/spi/app_spi_cmd_id_snapshot.cpp:33-39`(`inline void writeU32Le`)
- **现状**:两个 SPI 命令处理器的匿名 namespace 各自定义了字节序相同的 `writeU32Le(uint8_t *dst, uint32_t val)`,实现完全一致(仅参数名 value/val 不同)。
- **改法**:
1. 在 `GD32E508_DCU_APP/device/spi/app_spi_command_defs.h` 末尾(`SpiDefs` namespace 外或内,视现有结构而定)新增:
```cpp
inline void writeU32Le(uint8_t *dst, uint32_t val) {
dst[0] = static_cast<uint8_t>(val & 0xFFU);
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
dst[2] = static_cast<uint8_t>((val >> 16) & 0xFFU);
dst[3] = static_cast<uint8_t>((val >> 24) & 0xFFU);
}
```
2. 删除 `app_spi_cmd_can_feedback.cpp:53-59` 的本地 `writeU32Le` 定义
3. 删除 `app_spi_cmd_id_snapshot.cpp:33-39` 的本地 `writeU32Le` 定义
4. 确认两个 .cpp 已 `#include "device/spi/app_spi_command_defs.h"`(大概率已包含,因它们使用 `SpiDefs::kType*` 常量)
- **风险**:低(纯函数提取,实现完全一致)
#### 3.4 合并 `adc.cpp` 双重匿名 namespace
- **文件**:`GD32E508_DCU_APP/device/adc.cpp:12-27, 29-35`
- **现状**:同一文件有两个独立 `namespace { ... }` 块:
- 第一个(12-27):含 `kMinValidVdda` 等校验常量与 `isValidAdcRaw`/`isFiniteFloat`
- 第二个(29-35):含 `kDmaPeriph`/`kDmaChannel`/`s_dmaBuffer` 等 DMA 配置
- 语义上都属于文件内静态,无必要分两块
- **改法**:合并为单一 `namespace { ... }` 块,用空行 + 注释分隔逻辑分组:
```cpp
namespace {
/* ---- 校验常量与辅助函数 ---- */
constexpr float kMinValidVdda = 2.7f;
... (原第一块内容)
/* ---- DMA 配置 ---- */
constexpr uint32_t kDmaPeriph = DMA0;
... (原第二块内容)
} // namespace
```
- **风险**:低(纯结构调整)
---
## Assumptions & Decisions
### 假设
1. 上一轮 `purge-legacy-dead-code` spec 的所有修改已合并且构建通过(已确认:Code=47462)
2. `task_runner.cpp` 已 `#include "device/adc.h"`(因 `Adc::collectSample` 在 319 行被调用)
3. 两个 SPI 命令处理器 .cpp 已 `#include "device/spi/app_spi_command_defs.h"`(因使用 `SpiDefs::kType*` 常量)
4. CMake 构建脚本未显式安装 `sys/hardfault.h`(头文件通常不被列为编译目标)
### 决策
1. **3.1 spinDelay 仅提取纯延时循环**:带条件 break 的轮询等待循环(如 355 行)保持原样,避免改变语义
2. **1.6 TempSensor 保留类本身**:仅删冗余别名,`TempSensor::init()` 作为语义门面保留(`initial.cpp:45` 仍调用)
3. **不处理 3 项中风险项**:CANFD.cpp 文件名大小写、init() 重构、volatile 审计——需独立 spec
4. **不主动清理 #include**:即使某头文件因移除类型而变冗余,也不删除 #include(避免过度改动,构建不会因此报错)
## Verification Steps
### 1. Grep 验证(清除项零残留)
- `canfd_diag_t` → 0 匹配
- `m_ringTxLen` → 0 匹配
- `s_pushCount` → 0 匹配
- `kTypeCanSend|kTypeAppPing|kRspAck|kRspWait`(在 app_spi_command.cpp 内)→ 0 匹配
- `#include.*hardfault.h` → 0 匹配
- `temp_sensor_data_t` → 0 匹配
- `writeU32Le`(在 app_spi_cmd_can_feedback.cpp 和 app_spi_cmd_id_snapshot.cpp 内)→ 0 匹配(仅 app_spi_command_defs.h 中有定义)
### 2. Grep 验证(新增项存在)
- `spinDelay` 在 CANFD.cpp 中出现 ≥1 次(定义)+ N 次(调用)
- `armRingPending` 在 app_spi_command.h 中出现 1 次(声明)+ app_spi_command.cpp 中出现 1 次(定义)+ 7 次(调用)
- `writeU32Le` 在 app_spi_command_defs.h 中出现 1 次(定义)
- `BASEPRI 临界区` 在 task_manager.h:45 出现 1 次
- `推进 OTA Boot 请求` 在 task_runner.cpp 出现 1 次
- `复位前最终关中断` 在 ota_boot_request.cpp 出现 1 次
### 3. 构建验证
- `cmake --build g:\Robot_v2\DCU\DCU_APP\tmp\1 --clean-first`
- 0 编译错误
- 0 新增警告(`#warning` 仅来自上一轮 C1 的 EXMC,不计入)
- Program Size: Code 应略小于 47462(因删除了死代码)
### 4. 活跃代码完整性核查
- `Adc::collectSample` 仍被 task_runner.cpp 引用
- `TempSensor::init` 仍被 initial.cpp:45 引用
- `m_txLen`/`m_ringPending`/`m_ringDeadline` 成员完整保留
- `SpiDefs::kTypeCanSend` 等常量在 app_spi_command_defs.h 中完整保留(仅本地别名删除)
- `HardFault_Handler`/`HardFault_Handler_C` 在 hardfault.cpp 中完整保留
## 任务分组与执行顺序
### 可并行执行的三组(文件集不交叠)
**组 A:device/spi/ 目录(1.2 + 1.3 + 1.4 + 3.2 + 3.3)**
- 文件:`app_spi_command.h/.cpp`、`app_spi_cmd_can_feedback.cpp`、`app_spi_cmd_id_snapshot.cpp`、`app_spi_command_defs.h`
- 注意:3.2(armRingPending)和 1.2(m_ringTxLen)都改 `app_spi_command.h/.cpp`,需顺序执行
**组 B:device/ 目录(1.1 + 1.6 + 3.1 + 3.4)**
- 文件:`canfd.h`、`CANFD.cpp`、`temp_sensor.h`、`task_runner.cpp`、`adc.cpp`
- 注意:1.6 改 `temp_sensor.h` + `task_runner.cpp`;3.1 改 `CANFD.cpp`
**组 C:sys/ 目录(1.5 + 2.1 + 2.2 + 2.3)**
- 文件:`hardfault.h`(删除)、`task_manager.h`、`task_runner.cpp`、`ota_boot_request.cpp`
- 注意:2.2 和 1.6 都改 `task_runner.cpp`,需顺序执行或合并到同一子代理
### 建议执行顺序
1. 先并行执行组 A、组 B、组 C(三组文件集基本不交叠,仅 `task_runner.cpp` 跨组)
2. `task_runner.cpp` 的改动(1.6 改类型 + 2.2 改注释)合并到同一子代理
3. 最后执行构建验证
@@ -1,82 +0,0 @@
# 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/` 结构
@@ -1,222 +0,0 @@
# 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 唤醒(复位重启,适合完全断电场景)
@@ -1,131 +0,0 @@
# 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(构建配置在编译验证前)