# ADC 采样节拍设计说明(1 kHz 硬件定时器触发) > **状态**:**已实现(2026-10-10)**——TIM6 更新中断(1 kHz)触发已落地(`Src/main.c` 回调 + > `app_main.c` 启动 + `adc_task.c` 退化为纯消费者),`@build` 0 错 0 警;**台架 `@test` 验收(§7)待做**。 > **上位需求**:`docs/算法/继电保护功能复现规划.md` §3.2 L1-1(周期 CONVST 重触发 1000 Hz, > 与保护节拍同源)。**驱动契约**:`Drivers/BSP/TPAFE5160/README.md`。 > **背景**:B1(2026-10-10)已把 `adc_task` 由空桩改为真实消费者,但采样节拍是 > **任务周期软触发**(`osDelayUntil`)——CONVST 边沿 = 任务被调度到的时刻,抖动大、 > 且与 FreeRTOS tick 同源。本项把节拍源换成**硬件定时器**。 --- ## 1. 目标与现状 | 项 | 改造前(B1 软触发) | 改造后(TIM6 硬件定时器,2026-10-10 已落地) | |---|---|---| | CONVST 边沿时刻 | 任务被调度时刻(抖动 = 调度延迟,可达数十 µs~ms) | TIM 更新事件 + ISR 入口延迟(亚 µs~数 µs) | | 周期精度 | 依赖 FreeRTOS tick(TIM7) | 定时器硬件分频,**晶振级** | | 丢拍 | 任务被长时抢占会漏拍 | 硬件自动重载,不漏拍 | | 等周波调频 | 需改 tick 周期(不可行) | 运行期写 ARR 即可(§8) | **判据(L1-1)**:周期 CONVST 重触发 1000 Hz;示波器见 U3 `BUSY` 连续脉冲; `tpafe5160_frame_seq()` 持续递增(每秒 +1000)。 --- ## 2. 方案选型 ### 2.1 方案 A(**推荐**):TIM6 更新中断 → ISR 内脉冲 CONVST - TIM6 配成 1 kHz 更新中断;ISR 内调 `tpafe5160_start_conv_irq()`(现成的 GPIO 脉冲)。 - **零引脚改动**:PC9 保持 `GPIO_Output`,驱动与 `tpafe5160_init()` 首帧自检**完全不动**。 - BUSY(PC7)EXTI 读帧路径**完全不动**。 - 周期精度 = 定时器硬件(晶振级);CONVST 边沿抖动 = TIM6 ISR 入口延迟(见 §5)。 - 运行期改 ARR 即改变采样率 → 天然支持等周波调频。 ### 2.2 方案 B(备选,暂不采用):TIM3_CH4/TIM8_CH4 硬件 PWM 直驱 PC9 - PC9 复用为定时器通道,硬件直接产生 CONVST 方波,**零软件抖动**。 - **代价**:① 需把 PC9 由 `GPIO_Output` 改为 AF(`.ioc` 引脚改动); ② 驱动的 `TPAFE5160_CONVST_*` 宏(GPIO 写)全部失效 → `tpafe5160_init()` 的 "确保 CONVST 低"与首帧自检(E-44)都要重写;③ 上电初值/空闲电平语义变化。 - **结论**:收益(把抖动从"数 µs"降到"0")不足以抵消改动面与回归风险。 **仅当 §5 实测抖动超过算法容限时再考虑**。 > 下文只展开**方案 A**。 --- ## 3. CubeMX 配置规格(照此配置) **前提**:SYSCLK = 168 MHz / HCLK = 168 MHz / APB1 = 42 MHz(÷4)→ **APB1 定时器时钟 = 84 MHz**; HAL 时基 = TIM7(已占用);**TIM1~TIM6、TIM8~TIM14 当前全部空闲**。 | 步骤 | 位置 | 值 | 说明 | |---|---|---|---| | 1 | Timers → **TIM6** → *Activated* | ✅ 勾选 | 基础定时器,**无输出引脚**(不占用任何 IO) | | 2 | Prescaler (PSC) | **83** | 84 MHz ÷ (83+1) = **1 MHz** 计数时钟(1 µs 分辨率) | | 3 | Counter Period (ARR) | **999** | 1 MHz ÷ (999+1) = **1.000 kHz**(= 1000 µs 周期) | | 4 | Counter Mode | Up | 向上计数 | | 5 | **auto-reload preload** | **Enable** | 运行期写 ARR 在下一个更新事件生效,不产生毛刺(§8 等周波调频要用) | | 6 | Clock Division | No Division | 与采样无关 | | 7 | Trigger Event Selection | Reset(默认) | 不用 TRGO | | 8 | NVIC Settings → **TIM6 global interrupt** | ✅ 勾选;**Preemption Priority = 6** | 与 BUSY EXTI(`EXTI9_5_IRQn` = 6)**同级**:两者互不抢占,避免"读 RD 时序被 CONVST 脉冲打断"(§5);且 6 ≥ `configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`(=5),将来 ISR 里发任务通知也安全 | **生成后**:CubeMX 会新建 `Src/tim.c` + `Inc/tim.h`(含 `htim6` 与 `MX_TIM6_Init()`)、 在 `Src/stm32f4xx_it.c` 生成 `TIM6_IRQHandler()`、在 `Src/main.c` 的 `main()` 里插入 `MX_TIM6_Init()`。 ⚠ **`MX_TIM6_Init()` 只做配置,不启动计数**——启动由用户代码负责(§4 第 4 条)。 > **为何不用 TIM7 那种"1 MHz 计数 + ARR=999"以外的心跳**:本配置与 HAL 时基 TIM7 > 的构造完全一致(同为 84 MHz / 1 MHz / 1000 计数),便于对照排查;PSC 取 83 使 ARR 与 > "微秒"一一对应,等周波调频时可直接 `ARR = 周期µs - 1`。 --- ## 4. 代码改造清单 > 约定:**生成区不改**(CubeMX 重生成会覆盖);所有手工代码落 `USER CODE` 保护区(L2)。 ### 4.1 `Src/main.c`(CubeMX 生成;改 `USER CODE` 区) - `USER CODE BEGIN Includes` 增加 `#include "tpafe5160.h"`。 - `HAL_TIM_PeriodElapsedCallback()`(**注意:该回调被 TIM7 时基共用**)增加 TIM6 分支: ```c void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM7) { HAL_IncTick(); } /* 既有:HAL 时基 */ /* USER CODE BEGIN Callback 0 / 1 —— 加在既有 TIM7 分支之后 */ if (htim->Instance == TIM6) { tpafe5160_start_conv_irq(); /* 1 kHz 采样节拍:ISR 内脉冲 CONVST(仅 GPIO 写,亚 µs) */ } } ``` ⚠ **不得**破坏既有 `HAL_IncTick()` / `task_wd_tick_probe()` 两条 TIM7 分支。 ### 4.2 `Src/tim.c` / `Inc/tim.h` / `Src/stm32f4xx_it.c` - **CubeMX 自动生成,勿手改**(`TIM6_IRQHandler` → `HAL_TIM_IRQHandler(&htim6)`)。 ### 4.3 `App/app/app_main.c` - 增加 `#include "tim.h"`。 - 第 213 行的**单次** `tpafe5160_start_conv_irq();` 替换为**启动周期触发**: ```c /* 6) 启动 1 kHz 采样节拍(TIM6 更新中断 → ISR 内触发 CONVST) */ (void)HAL_TIM_Base_Start_IT(&htim6); ``` - ⚠ **必须在 `tpafe5160_init()` 成功之后**再启动 TIM6,否则 ISR 会在驱动自检 (首帧有效性探针,E-44)期间乱发 CONVST。 ### 4.4 `App/task/adc_task.c` - **删除**主循环里的 `tpafe5160_start_conv_irq();`(触发已交给 TIM6)。 - 本任务退化为**纯消费者**:`tpafe5160_snapshot()` + 帧统计 + 10 s 心跳 + 看门狗心跳。 1 ms 节拍(`osDelayUntil`)保留,仅用于"消费节奏"。 - 更新文件头与函数注释(说明节拍由 TIM6 提供,本任务不再触发)。 - **看门狗不变**:`TASK_WD_ADC` 心跳仍在循环内每拍调用。 ### 4.5 文档同步(2026-10-10 已执行) - `docs/算法/继电保护功能复现规划.md`:§3.2 L1-1 由 🔶 改为 ✅(TIM6 硬件触发)、§1.2 采样软件行 + 变更记录同步。 - `docs/待处理内容.md` §4:TPAFE 自动扫描模式待确认项标注"已不影响"(TIM6 方案不依赖它)。 - `AGENTS.md` 架构要点 `adcTask` 条 + `app_main_init` 序列:改为"TIM6 硬件触发 + 纯消费者"。 - `Drivers/BSP/TPAFE5160/README.md`:周期重触发改为"由 TIM6 更新中断提供"。 --- ## 5. 时序与优先级分析 **单周期时序(1 kHz,周期 1000 µs)**: ``` t=0 TIM6 更新事件 → ISR → CONVST 脉冲(LOW→HIGH 上升沿) t≈0+ε TPAFE5160 开始转换(BUSY 拉高) t≈23 µs BUSY 拉低 → PC7 EXTI(优先级 6) → 读 8 通道(≈0.5 µs)→ seq++ t=1000 µs 下一个 TIM6 更新事件 ``` - **读窗口与下一次 CONVST 相隔 ~977 µs**(转换仅 ~23 µs),互不重叠。 - **TIM6 与 BUSY EXTI 同级(6)→ 互不抢占**:读 RD 时序(8 次 RD 翻转)不会被打断, 避免"读到一半被新转换刷新数据总线"。这是选 6 而非 5 的原因。 - **触发抖动**:TIM6 是最高优先级之一(5 为 USART,7 为 SPI/DMA);被更高优先级 ISR (USART,~数 µs)挡住的概率与时长都有限 → 最坏抖动约数 µs,占 1000 µs 的 <0.5%。 - **ISR 时长**:`tpafe5160_start_conv_irq()` 只做两次 `HAL_GPIO_WritePin`(寄存器写) → 亚 µs,不拖累其它中断。 --- ## 6. 风险与注意事项 1. **共用回调**:`HAL_TIM_PeriodElapsedCallback` 同时服务 TIM7(HAL 时基)与 TIM6 → 分支必须并列、不得互相干扰;改动后必须确认开机日志时间戳正常(时基未坏)。 2. **启动顺序**:TIM6 必须在 `tpafe5160_init()` 之后启动(§4.3)。 3. **不新增任务/栈**:本项**零新任务、零新栈、零动态分配** → 不动 `stack_report.py` 预算。 4. **CubeMX 生存**:新增生成文件 `Src/tim.c` / `Inc/tim.h`;改后跑一次 `python scripts/check_cubemx_survival.py`,若有新增生成文件被登记,按提示重建基线。 5. **Keil 工程**:从 CubeMX 重生成工程(含 MDK-ARM)以把 `tim.c` 纳入编译;随后 `@build` 必须 0 错 0 警。 6. **`mem-numbers`**:本项改 Flash/RAM 极小但非零 → 若 `check_mem_numbers.py` FAIL, 跑 `python scripts/check_mem_numbers.py --write` 刷新快照。 7. **消费端与生产端同频**:TIM6(84 MHz 派生)与 FreeRTOS tick TIM7(同源)都恰好 1 kHz, 相位固定 → 消费端每拍恰好取到 1 帧。**一旦进入等周波调频(采样率≠1 kHz), 1 ms 轮询会与生产端失配 → 消费端须改为"帧就绪事件驱动"**(见 §8)。 --- ## 7. 验收判据 | # | 判据 | 手段 | |---|---|---| | 1 | `@build` 0 错 0 警,且镜像无源文件新于 hex | `MDK-ARM/build.bat` + `find -newermt` | | 2 | PC9 `CONVST` 周期 = 1.000 kHz(±0.1%) | 示波器 / 逻辑分析仪 | | 3 | PC7 `BUSY` 每周期一个脉冲,与 CONVST 相位固定 | 示波器双通道 | | 4 | `frame_seq()` 每秒 +1000(每 10 s +10000),严格递增 | `@test` 串口 `[ADC_TASK] frames=... seq=...` | | 5 | `frst_err=0`、`idle` 基本为 0 | 同上 | | 6 | 开机日志时间戳正常(TIM7 时基未坏) | 串口日志 | | 7 | 无 HardFault / `[ERR]` / 看门狗复位 | `@test` Forbidden 判定 | --- ## 8. 后续:等周波调频(不在本项范围) 参考系统按 **20 点/周波**采样:电网频率漂移时需让采样率跟随(50.0 Hz → 1000 Hz; 49.5 Hz → 990 Hz),以保持傅氏全波窗对齐一个工频周期。 - 本配置已为此预留:**1 µs 分辨率** + **ARR 预装载使能** → 运行期 `__HAL_TIM_SET_AUTORELOAD(&htim6, 周期µs - 1)` 即可无毛刺改频。 - 需要配套:① 测频源(过零/DFT 相位);② 把当前采样率同步给保护算法(相量/定值); ③ **消费端改事件驱动**(§6.7)——否则 1 ms 轮询在非 1 kHz 采样率下会丢帧。 --- ## 9. 变更记录 | 日期 | 变更 | 说明 | |---|---|---| | 2026-10-10 | 建立本文档 | B1(ADC 采集链路)遗留项"精确 1 kHz 硬件定时器触发"的规划:方案选型(A/B)、CubeMX 配置规格、代码改造清单、时序与优先级分析、风险与验收判据、等周波调频预留 | | 2026-10-10 | **实现(方案 A)** | CubeMX 加 TIM6(PSC=83 / ARR=999 / ARR 预装载 / NVIC 抢占优先级 6);`Src/main.c` 回调加 TIM6 分支触发 CONVST;`app_main.c` 用 `HAL_TIM_Base_Start_IT(&htim6)` 启动节拍;`adc_task.c` 退化为纯消费者。`@build` 0 错 0 警;台架 `@test` 验收待做 |