From d59c2198ebc288e3ac770ba27fcaa8547668c3dd Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sat, 10 Oct 2026 16:06:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(adc):=20L1=20=E9=87=87=E9=9B=86=E5=B1=82?= =?UTF-8?q?=E8=90=BD=E5=9C=B0=E2=80=94=E2=80=94STM32=20ADC1=20=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=20+=20=E8=AF=AD=E4=B9=89=E9=80=9A=E9=81=93=E6=98=A0?= =?UTF-8?q?=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 App/algo/adc_input.c/h(L1 采集与预处理层,落实 L1-3/L1-4): - L1-4 语义通道映射表 k_adc_map[]:UA/UN/IA → TPAFE buf[6]/[5]/[2]; UB/UC/IB/IC/IN/TEMP → STM32 ADC1 CH14/3/6/1/2/5。消费侧一律经 adc_input_raw() 按语义取数,不写死下标(V0.2 改版只改表内容)。 - L1-3 STM32 ADC1 9 通道软件触发顺序扫描:HAL_ADC_Start + 逐通道 PollForConversion/GetValue(EOC_SINGLE_CONV + 无 DMA,可逐通道轮询)。 - adc_input_poll() 统一"TPAFE 原子快照 + STM32 扫描"; adc_input_init() 做映射自检(STM32 映射通道必落在扫描序列内,否则 返回 -1),挡住"映射到不扫描的通道 → 恒读陈旧值"的静默错。 集成: - adc_task.c 取数改走 L1 层(首帧日志补全 8 路 + 温度;心跳增 adc1_rc/adc1_err 观测 STM32 侧健康度)。 - app_main.c 在 tpafe5160_init() 之后调 adc_input_init(),失败记 SYSLOG_SELFTEST("ADC map fault")。 - Keil 工程挂 adc_input.c 并加 ../App/algo 包含路径。 - 文档同步:继电保护规划 §1.2/§3.1(L1-3/L1-4 改 ✅)、ADC通道映射说明 §7/§8/§9、产品需求 §5.9/§9、架构总览 §7、AGENTS.md、App/README.md、 App/task/README.md、新增 App/algo/README.md、boot_log_data 清单。 验证: - @build 0 错 0 警;镜像无源文件新于 hex。 - 台架 @test PASS:ADC input layer init OK、adc1_rc=0/adc1_err=0 (30 s ≈27 万次转换零超时)、seq 仍 1000.0 Hz、adc 栈峰值 528→584 B(余 71%)、回读一致、零 [ERR]/HardFault。 证据 docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt - 内存快照已刷新(Flash 269,404→270,712 B)。 --- AGENTS.md | 4 +- App/README.md | 1 + App/algo/README.md | 27 ++ App/algo/adc_input.c | 237 ++++++++++++++++++ App/algo/adc_input.h | 127 ++++++++++ App/app/app_main.c | 17 +- App/task/README.md | 2 +- App/task/adc_task.c | 63 +++-- MDK-ARM/STM32F407-Demo.uvprojx | 7 +- docs/产品需求/产品需求设计文档.md | 4 +- docs/内存管理/RAM使用分析与优化方案.md | 6 +- docs/架构设计/项目架构总览.md | 1 + .../20261010_prod_adc_l1_boot_log.txt | 73 ++++++ docs/测试说明/boot_log_data/README.md | 1 + docs/硬件平台/ADC通道映射说明.md | 11 +- docs/算法/继电保护功能复现规划.md | 7 +- 16 files changed, 550 insertions(+), 38 deletions(-) create mode 100644 App/algo/README.md create mode 100644 App/algo/adc_input.c create mode 100644 App/algo/adc_input.h create mode 100644 docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt diff --git a/AGENTS.md b/AGENTS.md index e18fd58c..30b9a5fb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,11 +76,11 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v `Src/main.c`(CubeMX 初始化)→ `app_main_init` → `osKernelStart` → 7 任务(`osKernelStart` 之前全是内核启动前代码:禁调任何 FreeRTOS/CMSIS-RTOS API,见「平台约束」) -- `app_main_init`(时间子系统优先,使后续日志带时间戳):`sd2506_init` → `sys_clock_init`(墙钟;RTC 故障时降级默认时间基准并返回 -1 触发"时钟芯片异常"自检;`sys_clock_get` 每 30 天惰性再基准化防 49.7 天 tick 回绕)→ `rs485_init` → `gd5f2gq5ue_init` → `tpafe5160_init` → SD2506 诊断 → **启动 TIM6 1 kHz 采样节拍**(`HAL_TIM_Base_Start_IT(&htim6)`,2026-10-10;ISR 内触发 CONVST,见下) +- `app_main_init`(时间子系统优先,使后续日志带时间戳):`sd2506_init` → `sys_clock_init`(墙钟;RTC 故障时降级默认时间基准并返回 -1 触发"时钟芯片异常"自检;`sys_clock_get` 每 30 天惰性再基准化防 49.7 天 tick 回绕)→ `rs485_init` → `gd5f2gq5ue_init` → `tpafe5160_init` → **`adc_input_init`(L1 采集层)** → SD2506 诊断 → **启动 TIM6 1 kHz 采样节拍**(`HAL_TIM_Base_Start_IT(&htim6)`,2026-10-10;ISR 内触发 CONVST,见下) - **就绪屏障**(`App/app/sys_events.h` Event Flags,取代魔法延时):`SYS_EVT_NET_READY`(netTask 置位)/ `STORAGE_READY`(defaultTask:卷挂载 + 网络配置加载完成)/ `FTP_READY` / `TIMESYNC_READY`;消费者用 `osEventFlagsWait` 阻塞等待 - netTask:`net_stack_bring_up`(配置已由 defaultTask 在屏障前加载,本任务只读 RAM 副本 → `net_init` + PHY 强制 100M 全双工 → 递增网络代次号)→ 置 NET_READY → 循环 `net_poll` + `net_process_messages`;消费 `SYS_EVT_NET_RECONFIG` → 立即重建网络(新 IP 免重启生效,各 TCP 服务据代次号重建监听) - defaultTask:`f_mount` forced(无卷自动 `f_mkfs` FAT32;挂载后卷健康探针:小写两击确认,FAT 区陈旧/擦除时自动重格式化自愈,`/config.ini` 回落工厂默认——2026-09-20 /log 写失败修复)→ 网络配置加载(`net_cfg_load`;旧档迁移已于 2026-10-10 删除,见「模块约定」网口配置条)→ STORAGE_READY → LED 闪烁;同时是 syslog(`/log/run.log`·`selftest.log`,定长 64B 记录)的**唯一写者**与**配置引擎属主**(`cfg_file` 静态态单任务收敛于此:UI 保存请求执行 `net_cfg_save_run` + 2s `cfg_file_poll`,经 `SYS_EVT_CFG_SAVE` 事件唤醒;netTask/uiTask 不得直接触碰配置引擎,见 P1-3) -- adcTask(**B1,2026-10-10**):**纯消费者**——`tpafe5160_snapshot()` 临界区原子取最新一帧(8 通道)→ 帧统计 + 每 10 s 心跳日志;只取数、不做保护运算(V0.1 仅 TPAFE 三路 `UA`/`UN`/`IA` 测量有效,见 N-18)。**采样节拍(1 kHz)由 TIM6 更新中断硬件触发**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 TIM6 分支内调 `tpafe5160_start_conv_irq()`,2026-10-10);本任务 1 ms 轮询仅作消费节奏。手写于 `Src/freertos.c` 的 `USER CODE` 区(同 timeSyncTask,512 words / Normal),纳入 IWDG `required_mask`(`TASK_WD_ADC`)。详见 `docs/子系统设计/ADC采样节拍设计说明.md` +- adcTask(**B1,2026-10-10;L1 层 v2.3,2026-10-10**):**纯消费者**——每 1 ms 调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 临界区原子快照(8 通道)+ **STM32 ADC1 9 通道软件触发顺序扫描**(L1-3)→ 帧统计 + 每 10 s 心跳日志;取数一律经 `adc_input_raw()` **按语义**(L1-4,不写死下标);只取数、不做保护运算(V0.1 仅 TPAFE 三路 `UA`/`UN`/`IA` 测量有效,见 N-18)。**采样节拍(1 kHz)由 TIM6 更新中断硬件触发**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 TIM6 分支内调 `tpafe5160_start_conv_irq()`,2026-10-10);本任务 1 ms 轮询仅作消费节奏。手写于 `Src/freertos.c` 的 `USER CODE` 区(同 timeSyncTask,512 words / Normal),纳入 IWDG `required_mask`(`TASK_WD_ADC`)。详见 `docs/子系统设计/ADC采样节拍设计说明.md`、`App/algo/README.md` - 全景 `docs/架构设计/项目架构总览.md`(六层分层 / 启动时序 / 存储·网络·UI 栈 / 目录组织) ## 硬件引脚 diff --git a/App/README.md b/App/README.md index 9c925acc..e2b14f6f 100644 --- a/App/README.md +++ b/App/README.md @@ -16,6 +16,7 @@ FreeRTOS/CMSIS-RTOS API**(见 `AGENTS.md`「平台约束」)。 | `app/` | **应用入口与全局**:`app_main.c/h`(初始化入口)、`app_cfg.h`(编译开关)、`sys_events.h`(就绪事件) | [`app/README.md`](app/README.md) | | `cfg/` | **配置引擎**:`/config.ini` 解析 + 原子持久化(旧二进制档已废弃,2026-10-10) | [`cfg/README.md`](cfg/README.md) | | `sync/` | **时间同步任务**:`time_sync.c/h` | [`sync/README.md`](sync/README.md) | +| `algo/` | **采集与算法层(L1/L2)**:`adc_input.c/h`(语义通道映射 + STM32 ADC1 扫描) | [`algo/README.md`](algo/README.md) | | `log/` | **日志子系统**:syslog(NAND)+ 控制台单写者日志 | [`log/README.md`](log/README.md) | | `task/` | **FreeRTOS 任务实现**(6 个任务) | [`task/README.md`](task/README.md) | | `util/` | **通用工具**:CRC / 环形缓冲 / 墙钟时间 | [`util/README.md`](util/README.md) | diff --git a/App/algo/README.md b/App/algo/README.md new file mode 100644 index 00000000..54375b2b --- /dev/null +++ b/App/algo/README.md @@ -0,0 +1,27 @@ +# App/algo/ — 采集与算法层(L1 / L2) + +> **自研代码**,属 `docs/算法/继电保护功能复现规划.md` §2.1 分层中的 **L1(采集与预处理)** +> 与 **L2(相量计算,拟)**。原则:**算法与 I/O 分离**——本层不碰寄存器、不碰任务调度, +> 只做"数"(码值 → 工程量)与"相量"(工程量 → 复相量),从而可成为主机侧单元测试 +> (L0.5)的天然适格对象(落实登记簿 **A-4**)。 + +## 现有模块 + +| 文件 | 层 | 职责 | +|---|---|---| +| `adc_input.c/h` | L1 | **ADC 输入层**:① **语义通道映射**(L1-4)——`UA/UB/UC/UN/IA/IB/IC/IN/温度` → 物理通道,消费侧**按语义取数、不写死下标**;② **STM32 ADC1 9 通道软件触发顺序扫描**(L1-3);③ TPAFE5160 原子快照消费 | + +## 关键约定 + +- **语义映射的唯一权威口径**是 `docs/硬件平台/ADC通道映射说明.md`(V0.1 有 13/17 路被置换, + 网名编号 ≠ 通道号)。V0.1 上 8 路电气量分属两片转换器(硬件缺陷 **N-18**):`UA/UN/IA` + 在 TPAFE5160、`UB/UC/IB/IC/IN` 在 STM32 ADC1。**V0.2 改版只改映射表内容**,本层 API + 与上层语义不动。 +- **取数一律经 `adc_input_raw()`**(按语义),禁止在消费侧直接索引驱动缓冲或写死下标。 +- 本层**不做**跨转换器的相位 / 功率 / 功率因数 / 矢量类运算(V0.1 硬件缺陷 N-18 限制)。 + +## 相关文档 + +- 通道映射权威口径:`docs/硬件平台/ADC通道映射说明.md` +- 分层与路线图:`docs/算法/继电保护功能复现规划.md`(§2.1 分层、§3.1 L1 清单) +- 采样节拍:`docs/子系统设计/ADC采样节拍设计说明.md` diff --git a/App/algo/adc_input.c b/App/algo/adc_input.c new file mode 100644 index 00000000..6496c6b6 --- /dev/null +++ b/App/algo/adc_input.c @@ -0,0 +1,237 @@ +/* + * 模块名称:ADC 输入层(L1 采集与预处理) + * 模块功能:语义通道映射(L1-4)+ STM32 ADC1 顺序扫描(L1-3)+ TPAFE 快照消费。 + * 消费侧经 adc_input_raw() 按语义取数,不写死下标。 + * 适用平台:STM32F407ZGTx + TPAFE5160 + * 作者:王建锋 + * 创建日期:2026-10-10 + * 修改记录: + * v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射) + */ + +#include "adc_input.h" + +#include + +#include "adc.h" /* hadc1:MX_ADC1_Init() 已配 9 通道 / 144 cycles / 软件触发 */ +#include "tpafe5160.h" /* tpafe5160_snapshot():临界区原子快照(AGENTS 铁律) */ + +/* STM32 ADC1 单次转换轮询超时(ms)。单通道 ≈ 7.4 µs(144+12 cycles @ 21 MHz), + * 2 ms 余量充裕;超时即判本次扫描失败,绝不长时间阻塞消费节拍 */ +#define ADC_INPUT_POLL_TIMEOUT_MS 2U +/* STM32 ADC1 通道号上限(F4 上 ADC_CHANNEL_0..15 的宏值即通道号 0..15) */ +#define ADC_INPUT_STM32_CH_MAX 16U + +/* ===== 语义映射表(L1-4)===== + * 权威口径:docs/硬件平台/ADC通道映射说明.md(2026-10-09 硬件确认的语义分配)。 + * index 含义:TPAFE 源 = 驱动 buf 下标 0..7;STM32 源 = ADC1 通道号 0..15。 + * ⚠ 消费侧一律经本表按语义取数,**不得写死下标**(V0.1 有 13/17 路被置换, + * 网名编号与通道号不等价);V0.2 改版(N-18)只改本表内容。 */ +static const adc_channel_desc_t k_adc_map[ADC_SEM_COUNT] = { + [ADC_SEM_UA] = { ADC_SRC_TPAFE, 6U }, /* TPAFE buf[6](ch7) */ + [ADC_SEM_UB] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_14 }, /* PC4 */ + [ADC_SEM_UC] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_3 }, /* PA3 */ + [ADC_SEM_UN] = { ADC_SRC_TPAFE, 5U }, /* TPAFE buf[5](ch6) */ + [ADC_SEM_IA] = { ADC_SRC_TPAFE, 2U }, /* TPAFE buf[2](ch3) */ + [ADC_SEM_IB] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_6 }, /* PA6 */ + [ADC_SEM_IC] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_1 }, /* PA1 */ + [ADC_SEM_IN] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_2 }, /* PA2 */ + [ADC_SEM_TEMP] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_5 }, /* PA5(V_ZL2) */ +}; + +/* STM32 ADC1 顺序扫描顺序(Rank 1..9),须与 MX_ADC1_Init() 的 Rank 配置严格一致: + * Rank1..8 = CH0..CH7(PA0..PA7),Rank9 = CH14(PC4)。 */ +static const uint8_t k_stm32_rank_ch[ADC_INPUT_STM32_CH_NUM] = { + (uint8_t)ADC_CHANNEL_0, (uint8_t)ADC_CHANNEL_1, (uint8_t)ADC_CHANNEL_2, + (uint8_t)ADC_CHANNEL_3, (uint8_t)ADC_CHANNEL_4, (uint8_t)ADC_CHANNEL_5, + (uint8_t)ADC_CHANNEL_6, (uint8_t)ADC_CHANNEL_7, (uint8_t)ADC_CHANNEL_14, +}; + +/* STM32 ADC1 最新一帧原始码值,按 ADC1 通道号索引(0..15) */ +static uint16_t s_stm32_raw[ADC_INPUT_STM32_CH_MAX]; +/* TPAFE5160 最新一帧原始码值(16 位二进制补码),按驱动 buf 下标索引 */ +static int16_t s_tpafe_raw[ADC_INPUT_TPAFE_CH_NUM]; +static uint8_t s_tpafe_valid; /* TPAFE 侧是否已有有效帧 */ +static uint8_t s_stm32_valid; /* STM32 侧是否已有有效扫描 */ +static int s_stm32_rc; /* STM32 最近一次扫描状态码 */ +static uint32_t s_stm32_errs; /* STM32 扫描累计失败次数 */ + +/* + * 函数功能:STM32 ADC1 顺序扫描一次(L1-3) + * 入口参数:无 + * 返 回 值:0 - 成功;-1 - 启动失败;-2 - 转换轮询超时 + * 限定条件:仅任务上下文调用;MX_ADC1_Init() 已完成 + * 函数说明:软件触发一次 9 通道扫描(ScanConvMode=ENABLE、ContinuousConvMode= + * DISABLE),按 Rank 顺序逐通道轮询 EOC 并读 DR,结果按 ADC1 通道号 + * 存入 s_stm32_raw[]。EOCSelection=SINGLE_CONV 且未用 DMA → 每个通道 + * 产生一次 EOC,可逐通道轮询(见 HAL_ADC_PollForConversion 说明)。 + */ +static int stm32_scan(void) { + uint8_t i; + + if (HAL_ADC_Start(&hadc1) != HAL_OK) { + s_stm32_rc = ADC_INPUT_RC_START; + s_stm32_errs++; + return ADC_INPUT_RC_START; + } + + for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) { + if (HAL_ADC_PollForConversion(&hadc1, ADC_INPUT_POLL_TIMEOUT_MS) != HAL_OK) { + (void)HAL_ADC_Stop(&hadc1); + s_stm32_rc = ADC_INPUT_RC_TIMEOUT; + s_stm32_errs++; + return ADC_INPUT_RC_TIMEOUT; + } + s_stm32_raw[k_stm32_rank_ch[i]] = (uint16_t)HAL_ADC_GetValue(&hadc1); + } + + (void)HAL_ADC_Stop(&hadc1); + s_stm32_valid = 1u; + s_stm32_rc = ADC_INPUT_RC_OK; + return ADC_INPUT_RC_OK; +} + +/* + * 函数功能:语义映射表自洽性自检 + * 入口参数:无 + * 返 回 值:0 - 自洽;-1 - 存在不自洽条目 + * 限定条件:无 + * 函数说明:逐条校验映射表与扫描配置是否一致——① TPAFE 源下标须落在 + * buf[0..7];② STM32 源的通道号必须出现在 k_stm32_rank_ch[] 中, + * 否则该通道永远不会被扫描(读到的恒为陈旧值)。这是"通道宏写错 → + * 读到另一只脚/读不到"这一 §5 警告的代码级兜底(无信号源时无法做 + * 引脚级注入验证,见 docs/硬件平台/ADC通道映射说明.md §8)。 + */ +static int map_check(void) { + uint8_t s; + int rc = 0; + + for (s = 0u; s < (uint8_t)ADC_SEM_COUNT; s++) { + if (k_adc_map[s].source == ADC_SRC_TPAFE) { + if (k_adc_map[s].index >= ADC_INPUT_TPAFE_CH_NUM) { + rc = -1; + } + } else { + uint8_t i; + uint8_t found = 0u; + + for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) { + if (k_stm32_rank_ch[i] == k_adc_map[s].index) { + found = 1u; + } + } + if (found == 0u) { + rc = -1; + } + } + } + return rc; +} + +/* + * 函数功能:ADC 输入层初始化 + * 入口参数:无 + * 返 回 值:0 - 成功;-1 - 语义映射表自检失败 + * 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好 + * 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置 + * 外设,也不启动任何转换(STM32 侧为软件触发,按拍扫描)。 + */ +int adc_input_init(void) { + uint8_t i; + + for (i = 0u; i < ADC_INPUT_STM32_CH_MAX; i++) { + s_stm32_raw[i] = 0u; + } + for (i = 0u; i < ADC_INPUT_TPAFE_CH_NUM; i++) { + s_tpafe_raw[i] = 0; + } + + s_tpafe_valid = 0u; + s_stm32_valid = 0u; + s_stm32_rc = ADC_INPUT_RC_NOT_RUN; + s_stm32_errs = 0u; + return map_check(); +} + +/* + * 函数功能:采集一拍(TPAFE 原子快照 + STM32 ADC1 顺序扫描) + * 入口参数:无 + * 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错) + * 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区) + * 函数说明:先取 TPAFE 一帧,再做 STM32 ADC1 扫描;两者相互独立,STM32 扫描 + * 失败不影响 TPAFE 快照与返回值语义(STM32 侧状态另由 + * adc_input_stm32_rc() 暴露)。 + */ +int adc_input_poll(void) { + int r = tpafe5160_snapshot(s_tpafe_raw, (uint8_t)ADC_INPUT_TPAFE_CH_NUM); + + if (r == 1) { + s_tpafe_valid = 1u; + } + + (void)stm32_scan(); + return r; +} + +/* + * 函数功能:取语义通道的物理描述 + * 入口参数:ch - 语义通道 + * 返 回 值:指向静态描述表的指针;ch 越界返回 NULL + * 限定条件:无 + * 函数说明:返回的是静态存储地址,调用方不得修改。 + */ +const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) { + if ((unsigned int)ch >= (unsigned int)ADC_SEM_COUNT) { + return NULL; + } + return &k_adc_map[ch]; +} + +/* + * 函数功能:读取语义通道的原始码值(未标定) + * 入口参数:ch - 语义通道 + * out - 输出原始码值(TPAFE 侧为 16 位补码;STM32 侧为 0..4095) + * 返 回 值:0 - 成功;-1 - 参数错;-2 - 该来源当前无有效数据 + * 限定条件:无 + * 函数说明:消费侧取数的唯一入口(按语义,不写死下标)。 + */ +int adc_input_raw(adc_semantic_t ch, int32_t *out) { + if ((out == NULL) || ((unsigned int)ch >= (unsigned int)ADC_SEM_COUNT)) { + return -1; + } + + if (k_adc_map[ch].source == ADC_SRC_TPAFE) { + if (s_tpafe_valid == 0u) { + return -2; + } + *out = (int32_t)s_tpafe_raw[k_adc_map[ch].index]; + } else { + if (s_stm32_valid == 0u) { + return -2; + } + *out = (int32_t)s_stm32_raw[k_adc_map[ch].index]; + } + return 0; +} + +/* + * 函数功能:读取 STM32 ADC1 最近一次扫描状态码 + * 入口参数:无 + * 返 回 值:ADC_INPUT_RC_xxx + * 限定条件:无 + * 函数说明:供心跳日志 / 自检观测 STM32 侧健康度。 + */ +int adc_input_stm32_rc(void) { + return s_stm32_rc; +} + +/* + * 函数功能:读取 STM32 ADC1 扫描累计失败次数 + * 入口参数:无 + * 返 回 值:累计失败次数(启动失败或转换超时均计入) + * 限定条件:无 + * 函数说明:单调递增,供心跳日志观测。 + */ +uint32_t adc_input_stm32_errs(void) { + return s_stm32_errs; +} diff --git a/App/algo/adc_input.h b/App/algo/adc_input.h new file mode 100644 index 00000000..13bde0ac --- /dev/null +++ b/App/algo/adc_input.h @@ -0,0 +1,127 @@ +/* + * 模块名称:ADC 输入层(L1 采集与预处理) + * 模块功能:把两片转换器的原始码值按**语义**(UA/UB/UC/UN/IA/IB/IC/IN/温度)统一 + * 暴露,使上层(UI / 自检 / 保护算法)**按语义取数,不写死下标**: + * ① 语义通道映射表(L1-4)——权威口径 docs/硬件平台/ADC通道映射说明.md; + * ② STM32 ADC1 9 通道软件触发顺序扫描(L1-3)。 + * V0.1 上 8 路电气量分属两片转换器(硬件缺陷 N-18):UA/UN/IA 走 + * TPAFE5160(16 位 / ±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC1 + * (12 位 / 0~3.3 V 单极性)。V0.2 改版(8 路电气量全部接 TPAFE) + * 只改映射表内容,本层 API 与上层语义不动。 + * 适用平台:STM32F407ZGTx + TPAFE5160 + * 作者:王建锋 + * 创建日期:2026-10-10 + * 修改记录: + * v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射) + */ + +#ifndef ADC_INPUT_H +#define ADC_INPUT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 语义通道(V0.1:8 路电气量 + 温度)。枚举值即语义编号,顺序不得随意变更; + * 备用通道(RESERVED)不接入语义,故不在此枚举内。 */ +typedef enum { + ADC_SEM_UA = 0, /* A 相电压(TPAFE) */ + ADC_SEM_UB, /* B 相电压(STM32) */ + ADC_SEM_UC, /* C 相电压(STM32) */ + ADC_SEM_UN, /* 零线电压(TPAFE) */ + ADC_SEM_IA, /* A 相电流(TPAFE) */ + ADC_SEM_IB, /* B 相电流(STM32) */ + ADC_SEM_IC, /* C 相电流(STM32) */ + ADC_SEM_IN, /* 零线电流(STM32) */ + ADC_SEM_TEMP, /* 温度(STM32,V_ZL2) */ + ADC_SEM_COUNT /* 语义通道数(哨兵) */ +} adc_semantic_t; + +/* 数据来源:V0.1 的 8 路电气量分属两片转换器(N-18) */ +typedef enum { + ADC_SRC_TPAFE = 0, /* TPAFE5160:16 位、±5 V 双极性、8 通道同步 */ + ADC_SRC_STM32 /* STM32 ADC1:12 位、0~3.3 V 单极性、9 通道顺序扫描 */ +} adc_source_t; + +/* 语义通道 → 物理通道描述 */ +typedef struct { + adc_source_t source; /* 数据来源 */ + uint8_t index; /* TPAFE 源:驱动 buf 下标 0..7;STM32 源:ADC1 通道号 0..15 */ +} adc_channel_desc_t; + +/* TPAFE5160 一帧通道数(= 驱动 buf 长度) */ +#define ADC_INPUT_TPAFE_CH_NUM 8U +/* STM32 ADC1 顺序扫描通道数(Rank 1..9) */ +#define ADC_INPUT_STM32_CH_NUM 9U + +/* STM32 ADC1 最近一次扫描状态码 */ +#define ADC_INPUT_RC_OK (0) /* 扫描成功 */ +#define ADC_INPUT_RC_START (-1) /* HAL_ADC_Start 失败 */ +#define ADC_INPUT_RC_TIMEOUT (-2) /* 转换轮询超时 */ +#define ADC_INPUT_RC_NOT_RUN (-3) /* 尚未扫描过 */ + +/* + * 函数功能:ADC 输入层初始化 + * 入口参数:无 + * 返 回 值:0 - 成功;-1 - 语义映射表自检失败(映射指向了不会被扫描的通道) + * 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好 + * 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置外设。 + */ +int adc_input_init(void); + +/* + * 函数功能:采集一拍(TPAFE 原子快照 + STM32 ADC1 顺序扫描) + * 入口参数:无 + * 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错);STM32 侧状态另见 + * adc_input_stm32_rc() + * 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区) + * 函数说明:本函数只搬运原始码值,不做标定/运算。STM32 侧扫描失败不阻断 TPAFE + * 快照,也不影响返回值语义。 + */ +int adc_input_poll(void); + +/* + * 函数功能:取语义通道的物理描述 + * 入口参数:ch - 语义通道 + * 返 回 值:指向静态描述表的指针;ch 越界返回 NULL + * 限定条件:无 + * 函数说明:供自检注入 / UI 通道序等需要"语义 → 物理通道"映射的场景使用。 + */ +const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch); + +/* + * 函数功能:读取语义通道的原始码值(未标定) + * 入口参数:ch - 语义通道 + * out - 输出原始码值(TPAFE 侧为 16 位二进制补码;STM32 侧为 0..4095) + * 返 回 值:0 - 成功;-1 - 参数错;-2 - 该来源当前无有效数据 + * 限定条件:无 + * 函数说明:消费侧取数的唯一入口(按语义,不写死下标)。取数前须先成功 + * adc_input_poll() 一次。 + */ +int adc_input_raw(adc_semantic_t ch, int32_t *out); + +/* + * 函数功能:读取 STM32 ADC1 最近一次扫描状态码 + * 入口参数:无 + * 返 回 值:ADC_INPUT_RC_xxx + * 限定条件:无 + * 函数说明:供心跳日志 / 自检观测 STM32 侧健康度。 + */ +int adc_input_stm32_rc(void); + +/* + * 函数功能:读取 STM32 ADC1 扫描累计失败次数 + * 入口参数:无 + * 返 回 值:累计失败次数(启动失败或转换超时均计入) + * 限定条件:无 + * 函数说明:单调递增,供心跳日志观测。 + */ +uint32_t adc_input_stm32_errs(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ADC_INPUT_H */ diff --git a/App/app/app_main.c b/App/app/app_main.c index 399e25d7..b7cbc48c 100644 --- a/App/app/app_main.c +++ b/App/app/app_main.c @@ -23,6 +23,7 @@ #include "sys_clock.h" #include "gd5f2gq5ue.h" #include "tpafe5160.h" +#include "adc_input.h" /* L1 采集层:adc_input_init()(语义映射 + STM32 ADC1 扫描) */ #include "sys_log.h" #include "task_watchdog.h" /* task_wd_seg_report():round 8 复位不丢"进段打点"上报 */ @@ -88,9 +89,9 @@ static void reset_cause_str(uint32_t csr, char *buf, unsigned int len) { * 1) 时间子系统(SD2506 RTC → 墙钟基准) * 2) RS-485 * 3) GD5F NAND - * 4) TPAFE5160 ADC + * 4) TPAFE5160 ADC → ADC 输入层(L1,语义映射 + STM32 ADC1) * 5) SD2506 诊断信息 - * 6) 触发首次 ADC 转换 + * 6) 启动 TIM6 1 kHz 采样节拍(ISR 内触发 CONVST) */ int app_main_init(void) { /* 0) 使能 MemManage / BusFault / UsageFault(复位后默认只有 HardFault 使能)。 @@ -181,6 +182,18 @@ int app_main_init(void) { syslog_post(SYSLOG_SELFTEST, "ADC fault"); } + /* 4b) ADC 输入层(L1)初始化:清原始码值缓冲与统计 + **语义映射表自洽性自检** + * (映射指向了不会被扫描的通道 → 返回 -1,避免"读到恒为陈旧值"的静默错)。 + * STM32 ADC1 外设已由 MX_ADC1_Init()(main.c)配好 9 通道 / 144 cycles / + * 软件触发,本层不重复配置、也不启动转换(软件触发按拍扫描)。 + * 须在启动采样节拍(步骤 6)之前。 */ + if (adc_input_init() != 0) { + DBG_ERROR("ADC input layer: map self-check FAILED"); + syslog_post(SYSLOG_SELFTEST, "ADC map fault"); + } else { + DBG_INFO("ADC input layer init: OK"); + } + /* 5) SD2506 RTC 诊断信息 */ { /* 读取 ID */ diff --git a/App/task/README.md b/App/task/README.md index 23ebc527..2cd61068 100644 --- a/App/task/README.md +++ b/App/task/README.md @@ -10,7 +10,7 @@ | `default_task.c/h` | defaultTask | Normal | FTL + FatFS 挂载、配置引擎属主、syslog 唯一写者、LED 指示 | | `net_task.c/h` | netTask | Normal | `net_stack_bring_up` → 置 NET_READY → 循环 `net_poll` + `net_process_messages` | | `ui_task.c/h` | uiTask | BelowNormal | LVGL 界面(菜单 / 内容页 / 待机)+ 推流 | -| `adc_task.c/h` | (ADC 任务) | — | 高精度 ADC(TPAFE5160)数据采集 | +| `adc_task.c/h` | (ADC 任务) | — | ADC 采集消费:TPAFE5160 快照 + STM32 ADC1 顺序扫描,经 [`../algo/adc_input.c`](../algo/README.md) **按语义**取数(采样节拍由 TIM6 硬件触发) | | `rs485_task.c/h` | (RS-485 任务) | — | 半双工 RS-485 协议通信 | | `../sync/time_sync.c` | timeSyncTask | Normal | 墙钟时间同步(实现不在本目录,见 [`../sync/README.md`](../sync/README.md)) | diff --git a/App/task/adc_task.c b/App/task/adc_task.c index a15b8740..d96f7a40 100644 --- a/App/task/adc_task.c +++ b/App/task/adc_task.c @@ -1,6 +1,7 @@ /* * 模块名称:ADC Sampling Task - * 模块功能:TPAFE5160 8 通道同步采样——帧消费 + 帧统计(节拍由 TIM6 硬件定时器提供) + * 模块功能:ADC 采集消费——TPAFE5160 快照 + STM32 ADC1 顺序扫描(经 L1 层 + * App/algo/adc_input.c 按语义取数)+ 帧统计(节拍由 TIM6 硬件定时器提供) * 状态说明:**B1 已落地(2026-10-10)**——本任务从空桩转为真实消费者: * 用 tpafe5160_snapshot() 原子取数(AGENTS 铁律),统计帧数/无新帧次数, * 并纳入看门狗 required_mask(TASK_WD_ADC)。 @@ -9,11 +10,14 @@ * HAL_TIM_PeriodElapsedCallback);本任务**不再自行触发**,退化为**纯消费者**, * 1ms 轮询(osDelayUntil)仅作消费节奏。详见 * docs/子系统设计/ADC采样节拍设计说明.md。 + * **L1 采集层(v2.3,2026-10-10)**:取数改走 App/algo/adc_input.c—— + * 本任务每拍调 adc_input_poll()(TPAFE 快照 + STM32 ADC1 9 通道扫描), + * 经 adc_input_raw() **按语义**读通道(L1-3/L1-4),不再直接触碰驱动缓冲。 * V0.1 上只有 TPAFE 三路(UA/UN/IA)测量有效;本任务只取数,不做保护运算。 - * 接入前必读:取数只用 tpafe5160_snapshot()(临界区原子快照,仅任务上下文; - * 旧的 get_buf/data_ready/clear_ready 已删除);接消费者前的强制门禁 - * 见根 AGENTS.md「接 ADC / RS-485 消费者前的强制门禁」+ 建任务前后各跑 - * scripts/stack_report.py 做栈预算。 + * 接入前必读:取数只用 adc_input_raw() / tpafe5160_snapshot()(临界区原子快照, + * 仅任务上下文;旧的 get_buf/data_ready/clear_ready 已删除);接消费者前的 + * 强制门禁见根 AGENTS.md「接 ADC / RS-485 消费者前的强制门禁」+ 建任务前后 + * 各跑 scripts/stack_report.py 做栈预算。 * 适用平台:STM32F407ZGTx + TPAFE5160 * 作者:王建锋 * 创建日期:2026-07-19 @@ -28,10 +32,14 @@ * 就绪前挂起上报,就绪后补报最近一帧。 * v2.2 2026-10-10 王建锋 采样节拍改由 TIM6 更新中断(1 kHz)硬件触发;本任务 * 删除自行触发,退化为纯消费者(见文件头"采样节拍")。 + * v2.3 2026-10-10 王建锋 L1-3/L1-4:取数改走 App/algo/adc_input.c(语义映射 + + * STM32 ADC1 9 通道扫描);首帧日志补全 8 路电气量 + 温度, + * 心跳日志增 adc1_rc / adc1_err 观测 STM32 侧健康度。 */ #include "adc_task.h" #include "cmsis_os.h" +#include "adc_input.h" /* L1 采集层:adc_input_poll()/adc_input_raw()(按语义取数) */ #include "tpafe5160.h" #include "task_watchdog.h" #include "console_log.h" @@ -44,9 +52,8 @@ /* 心跳日志周期(ms):周期性上报帧统计,作为采集链路存活的观测面 */ #define ADC_TASK_REPORT_MS 10000U -/* 最新一帧原始码值(8 通道;语义映射见 docs/硬件平台/ADC通道映射说明.md) */ -static int16_t s_frame[TPAFE5160_CH_NUM]; -/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量) */ +/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量)。 + * 原始码值缓冲由 L1 层 App/algo/adc_input.c 持有(消费侧按语义取数,不写死下标) */ static uint32_t s_frames_consumed; static uint32_t s_frames_idle; @@ -69,10 +76,10 @@ int adc_task_init(void) { * 入口参数:arg - FreeRTOS 传入参数(未使用) * 返 回 值:无 * 限定条件:adc_task_init() 已调用;内核已启动 - * 函数说明:每 1ms 原子快照取最新一帧并统计。采样节拍由 TIM6 硬件定时器提供 - * (ISR 内触发 CONVST),本任务不再触发。稳态下 consumed 每拍 +1、 - * idle 不再增长;idle 持续增长 = EXTI 未在读帧或 TIM6 未启动(异常), - * 由周期心跳日志暴露。 + * 函数说明:每 1ms 采集一拍(adc_input_poll():TPAFE 原子快照 + STM32 ADC1 9 通道 + * 顺序扫描)并统计。采样节拍由 TIM6 硬件定时器提供(ISR 内触发 CONVST), + * 本任务不再触发。稳态下 consumed 每拍 +1、idle 不再增长;idle 持续增长 = + * EXTI 未在读帧或 TIM6 未启动(异常),由周期心跳日志暴露。 */ void adc_task_func(void *arg) { uint32_t next = osKernelGetTickCount(); @@ -84,9 +91,10 @@ void adc_task_func(void *arg) { for (;;) { int r; - /* 1) 原子快照消费最新一帧(仅任务上下文;1=有新帧 0=无新帧 -1=参数错)。 + /* 1) 采集一拍:L1 层 adc_input_poll() = TPAFE 原子快照 + STM32 ADC1 顺序扫描 + * (仅任务上下文;TPAFE 侧 1=有新帧 0=无新帧 -1=参数错)。 * 采样节拍由 TIM6 更新中断提供(ISR 内触发 CONVST),本任务不触发。 */ - r = tpafe5160_snapshot(s_frame, (uint8_t)TPAFE5160_CH_NUM); + r = adc_input_poll(); if (r == 1) { s_frames_consumed++; } else if (r == 0) { @@ -98,12 +106,27 @@ void adc_task_func(void *arg) { /* 2) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走 * 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错 * (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。 - * 只报 TPAFE 三路(UA=buf[6] UN=buf[5] IA=buf[2]),验证取数契约 */ + * 8 路电气量 + 温度全部经 adc_input_raw() **按语义**取数(L1-4), + * 用于验证"语义 → 物理通道"映射与两片转换器取数契约 */ if ((first_logged == 0u) && (s_frames_consumed != 0u) && (dbg_console_writer_ready() != 0u)) { + int32_t ua = 0, ub = 0, uc = 0, un = 0; + int32_t ia = 0, ib = 0, ic = 0, in = 0, temp = 0; + + (void)adc_input_raw(ADC_SEM_UA, &ua); + (void)adc_input_raw(ADC_SEM_UB, &ub); + (void)adc_input_raw(ADC_SEM_UC, &uc); + (void)adc_input_raw(ADC_SEM_UN, &un); + (void)adc_input_raw(ADC_SEM_IA, &ia); + (void)adc_input_raw(ADC_SEM_IB, &ib); + (void)adc_input_raw(ADC_SEM_IC, &ic); + (void)adc_input_raw(ADC_SEM_IN, &in); + (void)adc_input_raw(ADC_SEM_TEMP, &temp); + first_logged = 1u; - DBG_INFO("first frame: UA=%d UN=%d IA=%d", - s_frame[6], s_frame[5], s_frame[2]); + DBG_INFO("first: UA=%ld UB=%ld UC=%ld UN=%ld IA=%ld IB=%ld IC=%ld IN=%ld T=%ld", + (long)ua, (long)ub, (long)uc, (long)un, + (long)ia, (long)ib, (long)ic, (long)in, (long)temp); } /* 3) 看门狗心跳(本任务已纳入 required_mask) */ @@ -116,11 +139,13 @@ void adc_task_func(void *arg) { if ((int32_t)(now - report_at) >= 0) { if (dbg_console_writer_ready() != 0u) { - DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u", + DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u adc1_rc=%d adc1_err=%lu", (unsigned long)s_frames_consumed, (unsigned long)s_frames_idle, (unsigned long)tpafe5160_frame_seq(), - (unsigned)tpafe5160_frstdata_err()); + (unsigned)tpafe5160_frstdata_err(), + adc_input_stm32_rc(), + (unsigned long)adc_input_stm32_errs()); } report_at = now + ADC_TASK_REPORT_MS; } diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 712357b9..3d78c155 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -337,7 +337,7 @@ --diag_suppress=1,68,111,188,191,546,1293 USE_HAL_DRIVER,STM32F407xx,NDEBUG - ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib + ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/algo;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib @@ -937,6 +937,11 @@ 1 ..\App\task\adc_task.c + + adc_input.c + 1 + ..\App\algo\adc_input.c + diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index 469564e0..8956b3e8 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -446,7 +446,7 @@ sequenceDiagram ### 5.9 ADC 采集(FR-ADC)🔶 采集链路已实现(B1);保护/遥测算法与上行待实现 - **需求(B0 已确认,2026-10-10)**:**持续采集** 8 路电气量(Ua/Ub/Uc/U0、Ia/Ib/Ic/I0,**1000 Hz / 20 点/周波**),经保护/遥测算法后**上送 IEC 60870-5-104**(以太网 TCP 2404;遥测上送短浮点、采集上传系数 = 1)。 -- **现状(采集链路 B1 已实现,2026-10-10)**:启动序列仍调用一次 `tpafe5160_init()` + `tpafe5160_start_conv_irq()`;**运行期周期 CONVST 重触发已由 `adcTask` 接管**(每 1 ms)+ `tpafe5160_snapshot()` 原子消费(8 通道)+ 帧统计,台架 `@test` 确认 `frame_seq` 严格 1000 Hz 递增、`idle=0`、`frst_err=0`;STM32 侧 ADC1 已生成但**全仓库无 `HAL_ADC_Start`**(按 N-18 仅逐通道独立监视,消费者为后续)。 +- **现状(采集链路 B1 + L1 已实现,2026-10-10)**:启动序列调用一次 `tpafe5160_init()`;**运行期周期 CONVST 重触发已由 TIM6 更新中断(1 kHz)接管**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback`,2026-10-10)+ `adcTask` 经 L1 层 `adc_input_poll()` 消费(`tpafe5160_snapshot()` 8 通道 + STM32 ADC1 9 通道扫描)+ 帧统计,台架 `@test` 确认 `frame_seq` 严格 1000 Hz 递增、`frst_err=0`、`adc1_rc=0` / `adc1_err=0`;STM32 侧 ADC1 **消费者已落地**(`App/algo/adc_input.c` 的 `stm32_scan()`,L1-3/L1-4,2026-10-10),按 N-18 仅逐通道独立监视。 - **后果**:上电后仅采集 1 个样本,此后数据永久陈旧。 - **B0 结论**(2026-10-10,详见 `docs/算法/继电保护功能复现规划.md` §1.5):① 持续采集 ② 1000 Hz(20 点/周波),通道 = 4U+4I(点表 `4011~4018`)③ 上行 **IEC 60870-5-104**(2404,非 ModBus RTU 上行)④ `APP_ADC_ENABLE` 语义 = 门控 ADC 采集链路(ADC 为遥测/保护必需来源;是否接线或删除占位宏待定)。 - **实现约束**(见 §9):周期 CONVST 重触发 + `adc_task` 消费 `tpafe5160_snapshot()`;**原子快照 API 已就位**(2026-10-10 修 E-44 / E-45 / E-58,旧的 `get_buf` / `data_ready` / `clear_ready` 已删 → 取数不再撕裂、重触发不再丢帧、芯片不驱动总线会被 `init()` 首帧自检抓到);栈预算核算;V0.1 通道映射已固化 → `docs/硬件平台/ADC通道映射说明.md`(13/17 路置换;语义 UA/UB/UC/UN、IA/IB/IC/IN、V_ZL2=温度,其余备用;网名 ADC17 = PC4 用 `ADC_CHANNEL_14`,**不是** `ADC_CHANNEL_12`)。⚠ **8 路电气量分属 TPAFE / STM32 两片转换器属 V0.1 硬件缺陷**(跨转换器无同一时刻性、精度与量程不一致,登记簿 N-18,V0.2 改版要求全部走 TPAFE)→ 在 V0.1 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。 @@ -770,7 +770,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 缺口 | 现状 | 影响 | |---|---|---| -| **ADC 采集链路** | ✅ **采集链路已实现(B1,2026-10-10)**:`adcTask` 每 1 ms 触发 CONVST + `tpafe5160_snapshot()` 原子消费,台架 `@test` 闭环(1000 Hz);STM32 侧仍无 `HAL_ADC_Start`(N-18,逐通道独立监视,后续) | 数据路径已通;剩余为上层保护/遥测算法与 104 上行 | +| **ADC 采集链路** | ✅ **采集链路已实现(B1 + L1,2026-10-10)**:采样节拍由 TIM6 更新中断(1 kHz)硬件触发;`adcTask` 经 L1 层 `adc_input_poll()` 消费(TPAFE 8 通道快照 + **STM32 ADC1 9 通道扫描**),台架 `@test` 闭环(1000 Hz、`adc1_rc=0`);取数按语义(L1-3/L1-4,N-18 逐通道独立监视) | 数据路径已通;剩余为上层保护/遥测算法与 104 上行 | | **RS-485 采集链路** | `rs485_task.c` 空桩;`App/protocol/` 仅 README | 无 RS-485 数据收发(B0 定:**对下 ModBus RTU 采集**) | | **DTU 核心转发职责** | **B0 已定(2026-10-10)**:上行 **IEC 60870-5-104**(以太网 TCP 2404,遥测短浮点系数 1);RS-485 对下 ModBus RTU 采集 | 转发目标已明确,待实现 | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index d6d876fd..94963a88 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 268,644 B (262.35 KB) | 1,048,576 B (1024 KB) | 25.6% | -| RW_IRAM1 | 89,272 B (87.18 KB) | 131,072 B (128 KB) | 68.1% | +| ER_IROM1 | 269,940 B (263.61 KB) | 1,048,576 B (1024 KB) | 25.7% | +| RW_IRAM1 | 89,320 B (87.23 KB) | 131,072 B (128 KB) | 68.1% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **269,404 B (263.1 KB)** | 1,048,576 B (1024 KB) | **25.7%** | +| **Flash (ROM 合计)** | **270,712 B (264.4 KB)** | 1,048,576 B (1024 KB) | **25.8%** | 各区的容量与常量来源: diff --git a/docs/架构设计/项目架构总览.md b/docs/架构设计/项目架构总览.md index c65d8b26..65d48d92 100644 --- a/docs/架构设计/项目架构总览.md +++ b/docs/架构设计/项目架构总览.md @@ -253,6 +253,7 @@ STM32F4-Base/ │ └── main.c 入口 → app_main_init → osKernelStart ├── App/ 应用层(业务编排,非 CubeMX) │ ├── app/ 启动编排与全局契约:app_main.c(时间子系统优先)、app_cfg.h、sys_events.h +│ ├── algo/ 采集与算法层(L1/L2):adc_input(语义通道映射 + STM32 ADC1 扫描) │ ├── task/ default_task / net_task / ui_task / adc_task* / rs485_task* │ ├── log/ sys_log 记录引擎(定长 64B;MCU 后端落盘 /log/*.log) │ ├── cfg/ 配置引擎:cfg_file(静态态单任务收敛于 defaultTask)、store(MCU=FreeRTOS 堆) diff --git a/docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt b/docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt new file mode 100644 index 00000000..5aa86843 --- /dev/null +++ b/docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt @@ -0,0 +1,73 @@ +[+0ms] [----/--/-- --:--:--][APP] boot: syslog init OK +[+11ms] [----/--/-- --:--:--][APP] fw build: Oct 10 2026 16:04:11 +[+11ms] [----/--/-- --:--:--][APP] reset cause: software (NVIC_SystemReset) (CSR=0x14000000) +[+21ms] PREVRUN seg=2 seq=1910184324 t=203360 +[+21ms] TICKRUN uw=203406 n=203406 duw=46 +[+32ms] [----/--/-- --:--:--][RTC] sd2506_init: OK +[+32ms] [----/--/-- --:--:--][APP] SD2506 init: OK (ret=0) +[+42ms] [2026-10-10 08:04:30][CLK] sys_clock_init: RTC initial time 2026-10-10 08:04:30 +[+42ms] [2026-10-10 08:04:30][CLK] sys_clock_init: wall clock base = unix 1791619470, base_tick=137 +[+53ms] [2026-10-10 08:04:30][APP] RS485 init: OK (UART4, DE=PA15) +[+77ms] [2026-10-10 08:04:30][NAND] Resetting NAND... +[+89ms] [2026-10-10 08:04:30][NAND] Reading NAND ID... +[+89ms] [2026-10-10 08:04:30][NAND] NAND ID: MID=0xC8, DID=0x52 +[+101ms] [2026-10-10 08:04:30][NAND] Scanning bad blocks... +[+232ms] [2026-10-10 08:04:30][NAND] BBT scan: 0 bad blocks found +[+232ms] [2026-10-10 08:04:30][NAND] Enabling ECC... +[+232ms] [2026-10-10 08:04:30][NAND] Unlocking block protection... +[+243ms] [2026-10-10 08:04:30][NAND] Locating BBT pool... +[+243ms] [2026-10-10 08:04:30][NAND] Loading persisted BBT... +[+254ms] [2026-10-10 08:04:30][NAND] NAND init OK +[+254ms] [2026-10-10 08:04:30][APP] NAND init: OK +[+265ms] [2026-10-10 08:04:30][ADC] Init OK +[+265ms] [2026-10-10 08:04:30][APP] ADC init: OK +[+265ms] [2026-10-10 08:04:30][APP] ADC input layer init: OK +[+276ms] [2026-10-10 08:04:30][APP] SD2506 ID: 2512153425482405 +[+276ms] [2026-10-10 08:04:30][APP] SD2506 time: 2026-10-10 08:04:30 (week 2) +[+286ms] [2026-10-10 08:04:30][APP] SD2506 temp: 29 C +[+286ms] [2026-10-10 08:04:30][APP] SD2506 battery: 3.18 V +[+297ms] [2026-10-10 08:04:30][APP] App init OK +[+297ms] [2026-10-10 08:04:30][NAND_FTL] Initializing NAND FTL... +[+330ms] [2026-10-10 08:04:30][NAND_FTL] Map resumed: 14094/97504 sectors used +[+341ms] [2026-10-10 08:04:30][NAND_FTL] Map head=108976 tail=86373 epoch=1 +[+341ms] [2026-10-10 08:04:30][NAND_FTL] NAND FTL ready (capacity: 97504 pages = 190 MB) +[+424ms] [2026-10-10 08:04:30] [FS] FATFS mounted +[+475ms] [2026-10-10 08:04:30][SYSLOG] runlog boot entry: reboot +[+475ms] [2026-10-10 08:04:30][CFG] register: section 'network' (4 keys) +[+486ms] [2026-10-10 08:04:30][CFG] load: OK (523B, heap free 15240B) +[+486ms] [2026-10-10 08:04:30][NET_CFG] load: OK ip=192.168.1.100 +[+528ms] [2026-10-10 08:04:30][NET_TASK] === Network Init === +[+539ms] [2026-10-10 08:04:30][ADC_TASK] first: UA=11429 UB=1018 UC=1044 UN=11454 IA=11452 IB=943 IC=1069 IN=1052 T=850 +[+549ms] [2026-10-10 08:04:30][NET_TASK] net init: OK +[+549ms] [2026-10-10 08:04:30][NET_TASK] MAC: 3C:AB:72:53:2B:12 +[+559ms] [2026-10-10 08:04:30][NET_TASK] IP_INF: IP=192.168.1.100 GW=192.168.1.1 MASK=255.255.255.0 +[+570ms] MISS start snap=0x00000067 req=0x00000077 t=665 +[+570ms] [2026-10-10 08:04:30][WD] [WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms +[+581ms] [2026-10-10 08:04:30][UI_TASK] UI task: started, wait net ready +[+2547ms] [2026-10-10 08:04:32][NET_TASK] PHY: 100M FULL +[+2547ms] [2026-10-10 08:04:32][NET_TASK] netTask: started (single-socket mode) +[+2589ms] [2026-10-10 08:04:32] [FTP] waiting for connection... +[+2620ms] [2026-10-10 08:04:32][TIME_SYNC] time_sync: TCP listen on :8888 (sock=1) +[+2631ms] [2026-10-10 08:04:32][UI_TASK] UI task: net ready, init port +[+2641ms] [2026-10-10 08:04:32][UI_TASK] UI task: ui_port_init enter +[+2641ms] [2026-10-10 08:04:32][UI_PORT] theme: mono dark_bg=true applied +[+2652ms] [2026-10-10 08:04:32][UI_TASK] UI port init: OK +[+2652ms] [2026-10-10 08:04:32][UI_TASK] UI task: ui_menu_init enter +[+2662ms] [2026-10-10 08:04:32][UI_MENU] menu build start: total=39408 free=36572 big=36572 +[+2673ms] [2026-10-10 08:04:32][UI_MENU_VIEW] view init: root frame w=66 h=107 +[+2683ms] [2026-10-10 08:04:32][UI_MENU] menu build done: total=39292 free=35708 big=35696 used=3584 +[+2683ms] [2026-10-10 08:04:32][UI_TASK] UI menu init: OK +[+2693ms] [2026-10-10 08:04:32][UI_STANDBY] standby: ready (idle=60000ms) +[+2693ms] MISS start snap=0x00000067 req=0x00000077 t=2795 +[+2704ms] [2026-10-10 08:04:32][UI_TASK] UI standby: ready (idle=60s) +[+2745ms] [2026-10-10 08:04:32][UI_TASK] UI net sink: started (port 7003) +[+5304ms] [2026-10-10 08:04:35] [STACK] default peak_used=2164/4096 free_min=1932 (47%) +[+5314ms] [2026-10-10 08:04:35] [STACK] net peak_used=920/3840 free_min=2920 (76%) +[+5314ms] [2026-10-10 08:04:35] [STACK] ftp peak_used=740/4096 free_min=3356 (81%) +[+5325ms] [2026-10-10 08:04:35] [STACK] ui peak_used=4016/16384 free_min=12368 (75%) +[+5335ms] [2026-10-10 08:04:35] [STACK] timesync peak_used=616/2048 free_min=1432 (69%) +[+5335ms] [2026-10-10 08:04:35] [STACK] adc peak_used=584/2048 free_min=1464 (71%) +[+5346ms] [2026-10-10 08:04:35] [STACK] heap free=15088 min_ever=7152 +[+10301ms] [2026-10-10 08:04:40][ADC_TASK] frames=9960 idle=41 seq=10005 frst_err=0 adc1_rc=0 adc1_err=0 +[+20307ms] [2026-10-10 08:04:50][ADC_TASK] frames=19955 idle=46 seq=20005 frst_err=0 adc1_rc=0 adc1_err=0 +[+30304ms] [2026-10-10 08:05:00][ADC_TASK] frames=29950 idle=51 seq=30005 frst_err=0 adc1_rc=0 adc1_err=0 diff --git a/docs/测试说明/boot_log_data/README.md b/docs/测试说明/boot_log_data/README.md index c54ca6ae..874be6c0 100644 --- a/docs/测试说明/boot_log_data/README.md +++ b/docs/测试说明/boot_log_data/README.md @@ -26,6 +26,7 @@ | 日期 | 构建 / 负载 | 关键 `[STACK]` 水位 | 文件 | |---|---|---|---| | 2026-10-10 | `prod_adc_tim6` / 开机 + 30 s 空载 | `adc` 528/2048(74%);`default` 2164/4096(47%);heap free=15088 min_ever=7152 | `20261010_prod_adc_tim6_boot_log.txt` | +| 2026-10-10 | `prod_adc_l1` / 开机 + 30 s 空载 | `adc` **584/2048(71%)**(+56 B,L1 层 adc_input_poll/raw 上链);`default` 2164/4096(47%);heap free=15088 min_ever=7152 | `20261010_prod_adc_l1_boot_log.txt` | ## 4. 关联 diff --git a/docs/硬件平台/ADC通道映射说明.md b/docs/硬件平台/ADC通道映射说明.md index 0f5db556..166b385b 100644 --- a/docs/硬件平台/ADC通道映射说明.md +++ b/docs/硬件平台/ADC通道映射说明.md @@ -124,15 +124,15 @@ STM32(U17)为 LQFP144 脚号。 --- -## 7. 实现注意事项(当前固件现状,2026-10-09) +## 7. 实现注意事项(当前固件现状,2026-10-10) | 项 | 现状 | 影响 | |---|---|---| | **通道归属(硬件缺陷)** | 8 路电气量分属两片转换器:UA/UN/IA → TPAFE,UB/UC/IB/IC/IN → STM32 ADC | **V0.1 硬件缺陷**(非有意分配,理由见 §1)→ V0.1 上禁止跨转换器做相位/功率/功率因数/矢量类运算;**V0.2 改版要求 8 路电气量全部走 TPAFE**(登记簿 N-18) | -| STM32 ADC 外设 | **已生成(2026-10-09,CubeMX 6.17 + FW_F4)**:`Src/adc.c`/`Inc/adc.h` 已入库并挂进 `MDK-ARM/*.uvprojx`;`Inc/stm32f4xx_hal_conf.h` 的 `HAL_ADC_MODULE_ENABLED` 已生效;HAL `stm32f4xx_hal_adc.c/_ex.c` + `stm32f4xx_ll_adc.h` 已补入仓库(HAL 原本是裁剪子集);`Src/main.c` 里 `MX_ADC1_Init()` 排在 `MX_IWDG_Init()` 之后。生成配置核对无误:9 通道 / Rank 1~9 / 全部 144 cycles / `ScanConvMode=ENABLE` / `ContinuousConvMode=DISABLE` / `ExternalTrigConv=ADC_SOFTWARE_START` + `EDGE_NONE` / `EOCSelection=ADC_EOC_SINGLE_CONV` / `DataAlign=RIGHT` / 12 位;`HAL_ADC_MspInit()` 已把 PA0~PA7+PC4 配成 `GPIO_MODE_ANALOG` | 硬件通路已打通(`hadc1` 可用);**仍无消费者**——见下面两行 | +| STM32 ADC 外设 | **已生成(2026-10-09,CubeMX 6.17 + FW_F4)**:`Src/adc.c`/`Inc/adc.h` 已入库并挂进 `MDK-ARM/*.uvprojx`;`Inc/stm32f4xx_hal_conf.h` 的 `HAL_ADC_MODULE_ENABLED` 已生效;HAL `stm32f4xx_hal_adc.c/_ex.c` + `stm32f4xx_ll_adc.h` 已补入仓库(HAL 原本是裁剪子集);`Src/main.c` 里 `MX_ADC1_Init()` 排在 `MX_IWDG_Init()` 之后。生成配置核对无误:9 通道 / Rank 1~9 / 全部 144 cycles / `ScanConvMode=ENABLE` / `ContinuousConvMode=DISABLE` / `ExternalTrigConv=ADC_SOFTWARE_START` + `EDGE_NONE` / `EOCSelection=ADC_EOC_SINGLE_CONV` / `DataAlign=RIGHT` / 12 位;`HAL_ADC_MspInit()` 已把 PA0~PA7+PC4 配成 `GPIO_MODE_ANALOG` | 硬件通路已打通(`hadc1` 可用);**消费者已落地**——`App/algo/adc_input.c` 的 `stm32_scan()`(L1-3,2026-10-10):`HAL_ADC_Start` + 逐通道 `PollForConversion`/`GetValue`,9 通道一轮 ≈ 67 µs | | ADC 时钟 | CubeMX 的 F4 ADC 面板**有** Clock Prescaler 一项(此前"面板没有该项、固定 DIV2"的判断有误),已选 **PCLK2/4 → ADCCLK = 21 MHz** ✓。面板里 **DIV2 是灰的、选不到**:DB 里 `FadcConstraint Max=36000000`,APB2 = 84 MHz → DIV2 = 42 MHz 超限,该候选值被 `Action="DISABLE"` 禁掉,超规值进不来 | **不设额外防护(2026-10-09 决定)**:约束已由 CubeMX 把关;`.ioc` 虽不保存 `ADC1.ClockPrescaler` 键,重生成也只会回落到约束允许的值。按 144+12 cycles 算 ≈ 7.4 µs/通道、9 通道一轮 ≈ 67 µs | -| `adc_task` | **已实现(B1,2026-10-10)**:`App/task/adc_task.c` 每 1 ms 触发 CONVST + `tpafe5160_snapshot()` 原子取数(8 通道)+ 帧统计;任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / Normal) | 数据路径已通(台架 `@test`:`frame_seq` 严格 1000 Hz 递增) | -| TPAFE 采样触发 | `tpafe5160_start_conv_irq()` 仅在 `app_main_init` 调一次,**无周期 CONVST 重触发** | 只采到一帧,无连续波形 | +| `adc_task` | **已实现(B1,2026-10-10;L1 层 v2.3,2026-10-10)**:`App/task/adc_task.c` 每 1 ms 调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 原子快照(8 通道)+ STM32 ADC1 顺序扫描,**经 `adc_input_raw()` 按语义取数**(不再直接索引驱动缓冲);任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / Normal) | 数据路径已通(台架 `@test`:`frame_seq` 严格 1000 Hz 递增;`adc1_rc=0` / `adc1_err=0`) | +| TPAFE 采样触发 | **周期 CONVST 已实现(2026-10-10)**:`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 **TIM6 更新中断(1 kHz)** 内调 `tpafe5160_start_conv_irq()`;`app_main_init()` 用 `HAL_TIM_Base_Start_IT(&htim6)` 启动节拍(在 `tpafe5160_init()` 之后) | 连续波形(1 kHz);详见 `子系统设计/ADC采样节拍设计说明.md` | | 前端偏置 | 每路 1k 串联 + 1k 到 `*_GND` + 滤波 C,**无中点偏置** | STM32 侧只能测 **0~3.3 V 单极性**,交流量负半周被削顶;主板侧只做监视可接受,若要算 RMS/功率需硬件补偏置或明确算法口径 | | TPAFE 量程 | 默认 ±5 V(RANGE 接 GND),16 位二进制补码,LSB ≈ 152.59 µV | 用 `tpafe5160_to_voltage()` 换算 | | 取数契约(**2026-10-10 已修**,E-44 / E-45 / E-58 闭环) | 旧的 `get_buf` / `data_ready` / `clear_ready` **已删除**(删除前确认全仓库零调用者):`get_buf` 只给指针、任务再拷 → 被 BUSY EXTI 抢占就是**两帧混合**;`s_ready` 电平标志被 `start_conv_irq` 触发前清 0 → **重触发即丢未消费帧**;`read_all` 从不看 FRSTDATA(宏有定义、代码零引用)→ 芯片死亡时返回 OK + 常量垃圾。现口径:**`tpafe5160_snapshot(dst, count)`**(临界区原子拷贝,返回 1/0/-1,**仅任务上下文**)+ **`tpafe5160_frame_seq()`**(帧序号只增不清)+ **`tpafe5160_frstdata_err()`**(无跳变帧计数);`tpafe5160_init()` 增**首帧自检**——一帧读下来 FRSTDATA 无跳变(芯片不驱动并行总线;板上 R24 4.7k 上拉会把它钉死为高)→ 返回 `-3` + `[ERR]` | 消费者**只能**用 `snapshot()` 取数(`AGENTS.md` 已立为铁律);真机实测开机 `[ADC] Init OK` 现只在自检帧通过后才打印 → 判据在本板成立、非空转 | @@ -142,7 +142,7 @@ STM32(U17)为 LQFP144 脚号。 ## 8. 逐通道验证方法(上板自检口径) 1. **TPAFE 侧**:对目标 `AIN` 注入 1 V DC → 观察对应 `buf[i]` 变化(UA→`buf[6]`、UN→`buf[5]`、IA→`buf[2]`),同时 U3.14 `BUSY` 有脉冲;**语义与下标不符即映射错**。 -2. **STM32 侧**:对目标引脚(如 PC4=UB)注入 0.5 V → 读 `ADC_CHANNEL_14` 应变化;若不变而相邻通道变化 → 通道宏写错(见 §5 警告)。 +2. **STM32 侧**:对目标引脚(如 PC4=UB)注入 0.5 V → 读 `ADC_CHANNEL_14` 应变化;若不变而相邻通道变化 → 通道宏写错(见 §5 警告)。⚠ **本步尚未执行**(2026-10-10 L1-3 台架只验证了"9 通道全部转换成功 + 零超时 + 互异非饱和 + 跨次开机读数不同",台架无信号源);映射的**代码侧**一致性有两道保证:① `App/algo/adc_input.c` 的 `k_adc_map[]` 与 CubeMX 标签(`ADC17_UB`=PC4 等)逐条对齐;② 开机自检 `adc_input_init()` 校验每个 STM32 语义通道都落在 `k_stm32_rank_ch[]` 扫描序列内(挡"映射到不扫描的通道 → 恒读陈旧值")。 3. **备用通道**:RESERVED 路不接入任何语义,UI 与上报里不得出现;若实测有读数漂移,只记录不消费。 --- @@ -154,3 +154,4 @@ STM32(U17)为 LQFP144 脚号。 | 2026-10-09 | 建档:固化 V0.1 语义分配(UA/UB/UC/UN、IA/IB/IC/IN、V_ZL2=温度、其余备用)与 17 路端到端映射;确立"主板只做监视/辅助测量、计量在子板"口径;勘误 PC4 通道号(IN12→IN14)与 PB0 定性(=IN8) | | 2026-10-09 | **CubeMX 生成 ADC1(9 通道)落地**:新增 `Src/adc.c`/`Inc/adc.h` + HAL adc 三文件并挂进 uvprojx、`HAL_ADC_MODULE_ENABLED` 生效、`MX_ADC1_Init()` 入 `main.c`;分频 PCLK2/4 = 21 MHz——面板里 DIV2 已被 `FadcConstraint`(36 MHz)约束禁掉,**故不加 `.ioc` 键 / 代码守卫 / 审计项等额外防护**(曾加过,按决定撤除,理由见 §7"ADC 时钟"行);修正 PA0 标签笔误 `AD9_RES`→`ADC9_RES`。⚠ 本次重生成打回了 5 处定制(IWDG 延迟启动锚点、HardFault + 3 个 Fault 手工 Handler、`FreeRTOSConfig.h` 加固区、`freertos.c` 三处栈核算注释),已全部复原;存活审计新增 1 项 `adc1-hal-enabled`(共 19 项),现 19 通过 / 1 警告(timeSyncTask 手工定义)/ 0 失败,`@build` 0 错 0 警 | | 2026-10-10 | **口径更正**:8 路电气量分属 TPAFE / STM32 两片转换器**定性为 V0.1 硬件缺陷**,撤销 2026-10-09 建档时"有意分配、不要修正"的说法;补记缺陷实质(跨转换器无同一时刻性、16 位双极性 vs 12 位单极性、语义与归属交叉)与 **V0.2 改版要求**(8 路电气量全部接 TPAFE,与 5 路备用路对调串联电阻目的地,BOM 不变);§7 新增"通道归属"行,明确 V0.1 上禁止跨转换器做相位/功率类运算。同步更正 `V0.1_网表详细分析报告.md` §1.4(新增 V10)/ §6-3,登记簿新增 **N-18** | +| 2026-10-10 | **STM32 ADC1 消费者落地(L1-3/L1-4)**:新增 `App/algo/adc_input.c/h`——语义映射表 `k_adc_map[]` 按本文件 §3/§5 逐条落地(`UA/UN/IA`→TPAFE `buf[6]/[5]/[2]`,`UB/UC/IB/IC/IN/TEMP`→`CH14/3/6/1/2/5`),并提供 `adc_input_raw()` **按语义取数**;`stm32_scan()` 实现 9 通道软件触发顺序扫描;`adc_input_init()` 增**映射自检**(STM32 映射通道必在扫描序列内)。§7 的"STM32 ADC 外设 / `adc_task` / TPAFE 采样触发"三行同步更新(后两者原为陈旧描述:TIM6 周期触发、L1 层取数)。§8 步骤 2 标注**引脚级注入验证尚未执行**。台架 `@test` PASS(`adc1_rc=0` / `adc1_err=0`、`ADC input layer init: OK`;首帧 `UA=11429 UN=11454 IA=11452`、`UB=1018 UC=1044 IB=943 IC=1069 IN=1052 T=850`——互异非饱和,且两次开机读数不同 → 实时转换而非常量),证据 `测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt` | diff --git a/docs/算法/继电保护功能复现规划.md b/docs/算法/继电保护功能复现规划.md index 3733fc2c..f0c425dd 100644 --- a/docs/算法/继电保护功能复现规划.md +++ b/docs/算法/继电保护功能复现规划.md @@ -37,7 +37,7 @@ | 操作系统 | FreeRTOS V10.3.1(CMSIS-RTOS v2) | | 已实现子系统 | 网络(CH395F)、存储(GD5F2GQ5UE SPI NAND + dhara FTL + FatFs)、时间(SD2506 RTC + `sys_clock`)、配置(cfg_file)、日志、UI(远端显示)、看门狗(IWDG)、FTP | | 采样硬件 | TPAFE5160(16 位、8 通道**同步**、±5 V 双极性、并行 `GPIOG[15:0]`)+ STM32 内部 ADC1(12 位、9 通道**顺序**扫描、0~3.3 V 单极性) | -| 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM6 更新中断(1 kHz)硬件触发**(2026-10-10);STM32 侧仍无 `HAL_ADC_Start` | +| 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM6 更新中断(1 kHz)硬件触发**(2026-10-10);STM32 侧 **`App/algo/adc_input.c` 已实现语义映射 + ADC1 9 通道扫描(L1-3/L1-4,2026-10-10)**,`HAL_ADC_Start` 已出现 | | 保护算法层 | **完全缺失**(无傅氏、无保护元件、无出口、无定值模型) | | 已知硬件缺陷 | **N-18**:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 | @@ -240,8 +240,8 @@ | --- | --- | --- | --- | --- | | L1-1 | 周期 CONVST 重触发 | 1000 Hz(与保护节拍同源) | 示波器见 U3 `BUSY` 连续脉冲;`tpafe5160_frame_seq()` 持续递增 | ✅ 2026-10-10:**TIM6 更新中断(1 kHz)硬件触发**(ISR 内 `tpafe5160_start_conv_irq()`;方案 A,零引脚/零驱动改动);台架 `@test` PASS——`seq` 每 10 s 精确 +10000 = 1000.0 Hz、`frst_err=0`(证据 `boot_log_data/20261010_prod_adc_tim6_boot_log.txt`) | | L1-2 | TPAFE 快照消费 | 8 通道 / 帧 | `adc_task` 每拍调 `tpafe5160_snapshot()`,返回 1/0/-1 有处理 | ✅ B1 已实现并 `@test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) | -| L1-3 | STM32 ADC1 启动与扫描 | 9 通道、144 cycles、软件触发 | 全库出现 `HAL_ADC_Start`;9 通道码值随注入变化 | ⬜ | -| L1-4 | 通道语义映射 | 见 `docs/硬件平台/ADC通道映射说明.md`(**按语义取数,不写死下标**) | `UA→buf[6]`、`UN→buf[5]`、`IA→buf[2]`;STM32 侧 `UB→ADC_CHANNEL_14` | ⬜ | +| L1-3 | STM32 ADC1 启动与扫描 | 9 通道、144 cycles、软件触发 | 全库出现 `HAL_ADC_Start`;9 通道码值随注入变化 | ✅ 2026-10-10:`App/algo/adc_input.c` 的 `stm32_scan()`——`HAL_ADC_Start` + 逐通道 `HAL_ADC_PollForConversion`/`HAL_ADC_GetValue`(EOC_SINGLE_CONV + 无 DMA,可逐通道轮询);台架 `@test` PASS——9 通道全部转换成功、**零超时**(30 s 内 `adc1_rc=0` / `adc1_err=0`,≈27 万次转换)。⚠ 判据"随注入变化"以"逐通道独立读数 + 零超时 + 跨次开机读数不同"替代(台架无信号源,同 §7.1 无示波器口径);**引脚级注入验证(`ADC通道映射说明.md` §8 步骤 2)仍为待做项** | +| L1-4 | 通道语义映射 | 见 `docs/硬件平台/ADC通道映射说明.md`(**按语义取数,不写死下标**) | `UA→buf[6]`、`UN→buf[5]`、`IA→buf[2]`;STM32 侧 `UB→ADC_CHANNEL_14` | ✅ 2026-10-10:`App/algo/adc_input.c` 的 `k_adc_map[]` 语义表(`UA/UN/IA`→TPAFE `buf[6]/[5]/[2]`,`UB/UC/IB/IC/IN/TEMP`→ADC1 `CH14/3/6/1/2/5`)+ `adc_input_raw()` 按语义取数;开机**映射自检**(`adc_input_init()` 校验 STM32 映射通道必落在扫描序列内 → 挡住"映射到不扫描的通道"静默错)通过(`ADC input layer init: OK`)。台架首帧日志 8 路 + 温度全部读出:`UA=11429 UN=11454 IA=11452`(与 TIM6 轮次一致)、`UB=1018 UC=1044 IB=943 IC=1069 IN=1052 T=850`(互异、非饱和;**两次开机读数不同** → 确为实时转换而非常量) | | L1-5 | 通道标定(一次系数 + 零漂) | 每通道 `k`、`b`;系数定点 `2^10` | 注入标准源,读数误差 ≤ 目标精度 | ⬜ | | L1-6 | 采样有效性校验 | FRSTDATA 跳变、帧序号单调、丢帧计数 | 芯片静默/丢帧被检出并置告警 | ⬜ | | L1-7 | 等周波采样(软件测频 + 调频闭环) | 20 点/周波;测频范围建议 **45~55 Hz**(参考 40.5~60.5) | 频率偏移 ±2 Hz 时幅值误差 ≤ 1% | ⬜ | @@ -594,3 +594,4 @@ B0 产品定义 ✅(2026-10-10 已确认,见 §1.5) | 2026-10-10 | 恢复被外部工具回写的旧稿 | 文件曾被外部格式化工具回写为旧稿:**§1.5/§1.6/§5 丢失、§10 混入 66 组下划线(u)标签、表格被填充对齐**。本次按权威来源重建 §1.5/§1.6、补齐 **§5 定值参数模型**,清除全部下划线标签并把表格压回仓库紧凑风格;内容与 §1.3/§3.3/§6.4/§7/§9 的定稿一致 | | 2026-10-10 | **B1 落地:ADC 采集链路实现** | `App/task/adc_task.c` 由空桩转为真实消费者——每 1 ms 触发 `tpafe5160_start_conv_irq()` + `tpafe5160_snapshot()` 原子取数 + 帧统计 + 每 10 s 心跳日志;`Src/freertos.c` 手工创建 `adcTask`(512 words / Normal,落 `USER CODE` 保护区)+ 纳入 IWDG 必需掩码(`TASK_WD_ADC`,`0x37`→`0x77`)。§1.4 采样软件行、§1.4 依赖表 B1 行、§3.2 任务表(L1-1/L1-2)、§4 栈预算行、§7.1 P0 行同步更新。⚠ 本阶段为**任务周期软触发**(`osDelayUntil`);硬件定时器 1 kHz 触发为后续。同日 `@test` 实机闭环 PASS(`frame_seq` 严格 1000 Hz 递增、`idle=0`、`frst_err=0`、栈峰值 496/2048) | | 2026-10-10 | **1 kHz 硬件定时器触发(TIM6)** | 采样节拍由任务周期软触发改为 **TIM6 更新中断(1 kHz)**:`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 加 TIM6 分支触发 `tpafe5160_start_conv_irq()`(`USER CODE` 区);`app_main.c` 用 `HAL_TIM_Base_Start_IT(&htim6)` 替换单次触发(在 `tpafe5160_init()` 之后);`adc_task.c` 退化为**纯消费者**(1 ms 轮询仅作消费节奏)。§1.2 采样软件行、§3.2 L1-1 同步。详见 `docs/子系统设计/ADC采样节拍设计说明.md` | +| 2026-10-10 | **L1-3/L1-4 落地:STM32 ADC1 扫描 + 语义映射** | 新增 `App/algo/adc_input.c/h`(L1 采集层):① `k_adc_map[]` **语义通道表**(`UA/UN/IA`→TPAFE `buf[6]/[5]/[2]`,`UB/UC/IB/IC/IN/TEMP`→ADC1 `CH14/3/6/1/2/5`)+ `adc_input_desc()`/`adc_input_raw()` **按语义取数**;② `stm32_scan()` STM32 ADC1 9 通道软件触发顺序扫描(`HAL_ADC_Start` + 逐通道 `PollForConversion`/`GetValue`);③ `adc_input_poll()` 统一"TPAFE 快照 + STM32 扫描"。`adc_task.c` 取数改走本层(首帧日志补全 8 路 + 温度、心跳增 `adc1_rc`/`adc1_err`);`app_main.c` 在 `tpafe5160_init()` 后调 `adc_input_init()`(含**映射自检**:STM32 映射通道必落在扫描序列内,失败记 `SYSLOG_SELFTEST`);Keil 工程挂 `adc_input.c` 并加 `../App/algo` 包含路径。§1.2 采样软件行、§3.1 L1-3/L1-4 同步。`@build` 0 错 0 警;台架 `@test` PASS(`ADC input layer init: OK`、`adc1_rc=0`/`adc1_err=0`、`seq` 仍 1000.0 Hz、`adc` 栈峰值 528→584 B、回读一致、零 `[ERR]`/HardFault),证据 `测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt` |