From 5c978465c97f0b60a6876f78590f24d266dfd851 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sat, 10 Oct 2026 03:46:50 +0800 Subject: [PATCH] =?UTF-8?q?TPAFE=E9=A9=B1=E5=8A=A8=E9=97=AD=E7=8E=AFE-44/E?= =?UTF-8?q?-45/E-58=E5=8F=96=E6=95=B0=E5=A5=91=E7=BA=A6=EF=BC=8C=E5=9B=BA?= =?UTF-8?q?=E5=8C=96E-59=E9=97=A8=E7=A6=81=EF=BC=8Cadc=5Ftask=E6=8C=82?= =?UTF-8?q?=E8=BF=9BKeil=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 + App/task/adc_task.c | 6 + Drivers/BSP/TPAFE5160/README.md | 12 +- Drivers/BSP/TPAFE5160/tpafe5160.c | 169 ++++++++++++++++------ Drivers/BSP/TPAFE5160/tpafe5160.h | 47 +++--- MDK-ARM/STM32F407-Demo.uvprojx | 5 + docs/产品需求/产品需求设计文档.md | 6 +- docs/内存管理/RAM使用分析与优化方案.md | 6 +- docs/子系统设计/运行与自检记录设计说明.md | 2 +- docs/待处理内容.md | 17 ++- docs/硬件平台/ADC通道映射说明.md | 4 +- docs/问题记录/错误码传播审计.md | 98 +++++++------ 12 files changed, 247 insertions(+), 127 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6f1cdae5..87d8623a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,6 +53,8 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v - **自研代码禁用 C 库堆分配**(2026-09-18,P0-1):禁止 `malloc/calloc/realloc/strdup/free`——本机工具链 MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL 且**模拟器(宿主 CRT 堆)完全正常、照不出来**。需要动态内存只用 `pvPortMalloc/vPortFree`(FreeRTOS 堆 `configTOTAL_HEAP_SIZE`),或像配置引擎那样把分配收进平台后端(`cfg_store_alloc/free/heap_free`:MCU=FreeRTOS 堆、模拟器=宿主堆,共享核心零分配符号);分配失败必须有 `FAIL` 日志 + 自检上报,暂态峰值要写编译期预算 - **FatFs 打开模式陷阱(2026-09-20 台架实测,`Lib/FatFs` 移植版)**:`FA_OPEN_APPEND`(0x30) = `FA_OPEN_ALWAYS|FA_SEEKEND`,**不含 `FA_WRITE`(0x02)**——不带写位打开的文件 `f_write` 恒返回 `FR_DENIED`(7)(/log 写失败真根因);追加写必须用 `FA_WRITE|FA_OPEN_APPEND`。该移植的 `f_write` 簇分配失败时返回 `FR_OK`+短写(无错误码),判失败必须校验写回字节数。标志位为旧式编号(`FA_READ`=0x01 / `FA_WRITE`=0x02 / `FA_CREATE_NEW`=0x04 / `FA_CREATE_ALWAYS`=0x08 / `FA_OPEN_ALWAYS`=0x10 / `FA_OPEN_APPEND`=0x30 / `FA_SEEKEND`=0x20(ff.c 内部定义)),与上游 FatFs R0.14 不同,勿照抄上游代码示例 - **FatFs 文件名约束(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)**:本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**——主名≤8 字符、扩展≤3 字符、**不得以点开头**(`.fschk` 会被 `ff.c create_short` 把点后当扩展域解析超限 → `f_open` 恒返 `FR_INVALID_NAME`(6),纯 RAM 名字判定、不碰盘)。ff.c `create_short` 全部拒绝点已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`,非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入卷损坏触发集(整卷重格式化不可逆) +- **ADC 取数只用 `tpafe5160_snapshot()`(原子快照)**(2026-10-10 修 E-44/E-45/E-58):旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已从驱动**删除**——`get_buf` 只给缓冲区指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回**两帧混合**数据(8 通道失去同一时刻性,且无错误指示);`s_ready` 电平标志有三个写者,`start_conv_irq` 触发前清 0 = **重触发本身就会抹掉一帧未消费数据**。现口径:帧序号只增不清(`tpafe5160_frame_seq()`)、取数=临界区内拷贝(`tpafe5160_snapshot(dst, count)` 返回 1/0/-1,**仅任务上下文**)、FRSTDATA 全程无跳变(芯片不驱动并行总线)由 `tpafe5160_frstdata_err()` 计数且 `tpafe5160_init()` 首帧自检直接返回 `-3` + `[ERR]` +- **接 ADC / RS-485 消费者前的强制门禁(E-59,2026-10-10 固化)**:`adc_task` / `rs485_task` 从空桩转为真实消费者**之前**必须确认——① 驱动契约已修:TPAFE **E-44 / E-45 / E-58 已修 ✅**、RS485 **E-36 / E-37 已修 ✅** 但 **E-50(单缓冲无同步 → 包撕裂)未修 ❌**、**E-49(TX 超时拉 DE)未修 ❌**;② 栈预算:两任务**尚未入 `Src/freertos.c` 栈表**,建任务前后各跑一次 `scripts/stack_report.py`。与"改栈前跑 `stack_report.py`"同级。另按 N-18:V0.1 上两片转换器之间**禁止**做相位/功率/功率因数类运算 - **`DBG_*` 每条日志约 90 B Flash + 192 B 栈**(2026-10-02 S10 实测):`dbg_log.h` 的 `DBG_INFO/ERROR/DEBUG` 在**每个调用点内联** `char __con_line[DBG_CON_LINE_MAX]`(`console_log.h` 定义 **192 B**)+ 完整 `snprintf` 调用序列 → 单条约 **90 B Flash**(字符串常量另计)。给新模块接入日志时按"条数 × 90 B"估算 Flash 代价(实测 `sd2506_init` 加 6 条 = +576 B;`app_main` 加 3 条 = +180 B);**深调用链模块另需核算栈**(每条日志在栈上多占 192 B,会进 `stack_report.py` 的最坏链)。故**日志不是免费的**——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补 diff --git a/App/task/adc_task.c b/App/task/adc_task.c index 8dddfcf3..1d07ecac 100644 --- a/App/task/adc_task.c +++ b/App/task/adc_task.c @@ -4,6 +4,12 @@ * 状态说明:**预留桩(未启用)**——采集链路尚未实现,产品定义未定 * (见 docs/待处理内容.md 阶段 B0/B1)。当前 init/func 为空实现, * 不触发 CONVST、不消费数据。 + * 2026-10-10 起本文件已挂进 MDK-ARM 工程(此前两边都不注册 = 永不编译, + * 改它不生效),但仍未在 Src/freertos.c 创建任务。 + * 接入前必读:取数只用 tpafe5160_snapshot()(临界区原子快照,仅任务上下文; + * 旧的 get_buf/data_ready/clear_ready 已删除);接消费者前的强制门禁 + * 见根 AGENTS.md「接 ADC / RS-485 消费者前的强制门禁」+ 建任务前后各跑 + * scripts/stack_report.py 做栈预算。 * 适用平台:STM32F407ZGTx + TPAFE5160 * 作者:王建锋 * 创建日期:2026-07-19 diff --git a/Drivers/BSP/TPAFE5160/README.md b/Drivers/BSP/TPAFE5160/README.md index 4fa0d445..1ac2a437 100644 --- a/Drivers/BSP/TPAFE5160/README.md +++ b/Drivers/BSP/TPAFE5160/README.md @@ -11,15 +11,19 @@ ## 硬件接口 -- **并行 16 位**:PG0–PG15 数据总线 / **PD3 RD**(读选通)/ **PD4 CONVST**(转换启动)/ **PD7 BUSY**(转换中) +- **并行 16 位**:PG0–PG15 数据总线 / **PC8 RD**(读选通)/ **PC9 CONVST**(转换启动)/ **PC7 BUSY**(转换中)/ **PC6 FRSTDATA**(首通道标记,板上 R24 4.7k 上拉)/ **PA12·PA11·PA8 = OS0·OS1·OS2**(过采样选择)——V0.1 网表口径(V0.0 曾为 PD3/PD4/PD7,已作废) - 部分引脚为 GPIO 模拟时序(非硬件 SPI/FSMC) ## 关键点 -- **首次转换在 `app_main_init` 末尾由 `tpafe5160_start_conv_irq` 启动**(中断驱动) -- 采样数据由 `App/task/adc_task.c` 消费处理 +- **首次转换在 `app_main_init` 末尾由 `tpafe5160_start_conv_irq` 启动**(中断驱动);**无周期重触发** → 现只采到一帧(登记簿 P1-1) +- **取数只用 `tpafe5160_snapshot(int16_t *dst, uint8_t count)`**(**仅任务上下文**,内部临界区原子拷贝;返回 1=新帧 / 0=无新帧 / -1=参数无效)。配套的 `tpafe5160_frame_seq()` 给帧序号、`tpafe5160_frstdata_err()` 给"FRSTDATA 无跳变"帧计数 +- ⚠️ **不要用缓冲区指针取数**:旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已于 2026-10-10 **删除**(审计 E-45 / E-58)——`get_buf` 只给指针、任务再逐通道拷,中间被 BUSY EXTI 抢占就拷回**两帧混合**数据;`s_ready` 电平标志在 `start_conv_irq` 里被清 0,等于**用重触发去丢帧**。现由**帧序号**(只增不清)+ **临界区快照**取代 +- **`tpafe5160_init()` 做首帧有效性自检**(E-44):完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `TPAFE5160_FRSTDATA_ERR`(-3) 并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片 +- 采样数据的消费者 `App/task/adc_task.c` **仍是空桩**(已挂进 `MDK-ARM/*.uvprojx`,尚未在 `Src/freertos.c` 建任务) - CONVST→BUSY→RD 三段时序对**建立/保持时间敏感**,改动需对照芯片手册 -- ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`) +- ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`);接消费者前的强制门禁见根 `AGENTS.md`「接 ADC / RS-485 消费者前的强制门禁」 +- ⚠️ **通道语义不是通道号**:`buf[i]` 对应哪个电气量必须查 `docs/硬件平台/ADC通道映射说明.md`(`buf[2]=IA`、`buf[5]=UN`、`buf[6]=UA`);V0.1 上 8 路电气量分属两片转换器属硬件缺陷(N-18)→ **禁止**跨转换器做相位/功率/功率因数类运算 ## 已知问题 diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.c b/Drivers/BSP/TPAFE5160/tpafe5160.c index 88e9064e..72ba84e4 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.c +++ b/Drivers/BSP/TPAFE5160/tpafe5160.c @@ -8,12 +8,20 @@ * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) + * 2026-10-10 王建锋 消费者接入前置修复(E-44 / E-45 / E-58),详见 tpafe5160.h */ /* 头文件包含区 */ +#include /* memcpy:原子快照 */ + #include "tpafe5160.h" #include "main.h" +/* taskENTER_CRITICAL:把"选缓冲 + 拷数据"收进一个不可分割动作(E-58)。 + * 与 App/log/console_log.c、Drivers/BSP/NET/net_socket.c 同一写法,仅任务上下文可用 */ +#include "FreeRTOS.h" +#include "task.h" + /* * 调试输出配置 */ @@ -53,18 +61,20 @@ /* ======================== 私有函数声明 ======================== */ -static void tpafe5160_read_channels(uint8_t count, int16_t *buf); +static int tpafe5160_read_channels(uint8_t count, int16_t *buf); /* ======================== 公共函数定义 ======================== */ /* - * 函数功能:初始化 TPAFE5160(设置过采样、等待就绪) + * 函数功能:初始化 TPAFE5160(设置过采样、等待就绪、首帧有效性自检) * 入口参数:无 - * 返回值:0 - 成功,-2 - BUSY 超时 + * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(芯片未驱动数据总线) * 限定条件:CubeMX 已完成 GPIO 初始化 * 函数说明:1. 设置过采样为无过采样 (000) * 2. 确保 RD 为高、CONVST 为低 * 3. 等待 BUSY 释放(转换空闲) + * 4. 采一帧并校验 FRSTDATA 有跳变(E-44:旧实现只看 BUSY,芯片死亡 + * 时照样报 Init OK,此后所有读数都是上拉/总线残值——静默死亡) */ int tpafe5160_init(void) { /* 设置默认过采样:无过采样 (OS[2:0] = 000) */ @@ -78,9 +88,24 @@ int tpafe5160_init(void) { int ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); if (ret != TPAFE5160_OK) { DBG_ERROR("Init wait BUSY timeout"); - } else { - DBG_INFO("Init OK"); + return ret; } + + /* 首帧有效性自检(E-44):一次完整转换 + 读取,确认芯片真的在响应 RD */ + int16_t probe[TPAFE5160_CH_NUM]; + tpafe5160_start_conv(); + ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); + if (ret != TPAFE5160_OK) { + DBG_ERROR("Init probe BUSY timeout"); + return ret; + } + if (tpafe5160_read_channels(TPAFE5160_CH_NUM, probe) != TPAFE5160_OK) { + ret = TPAFE5160_FRSTDATA_ERR; + DBG_ERROR("Init FRSTDATA no-toggle (chip not driving DB bus)"); + return ret; + } + + DBG_INFO("Init OK"); return ret; } @@ -142,15 +167,19 @@ uint8_t tpafe5160_is_busy(void) { /* * 函数功能:读取全部8通道转换结果 * 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL - * 返回值:0 - 成功,-2 - BUSY 超时 + * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信) * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 - * 3. 通过 FRSTDATA 验证第一通道 + * 3. 校验 FRSTDATA 在这一帧内发生跳变(E-44) */ int tpafe5160_read_all(int16_t *buf) { int ret = 0; + if (buf == NULL) { + return TPAFE5160_ERROR; + } + /* 启动转换 */ tpafe5160_start_conv(); @@ -161,8 +190,12 @@ int tpafe5160_read_all(int16_t *buf) { return ret; } - /* 连续读取8个通道 */ - tpafe5160_read_channels(TPAFE5160_CH_NUM, buf); + /* 连续读取8个通道(内含 FRSTDATA 有效性校验) */ + ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, buf); + if (ret != TPAFE5160_OK) { + DBG_ERROR("read_all FRSTDATA no-toggle (data not trustworthy)"); + return TPAFE5160_FRSTDATA_ERR; + } return TPAFE5160_OK; } @@ -171,7 +204,7 @@ int tpafe5160_read_all(int16_t *buf) { * 函数功能:读取指定通道的转换结果 * 入口参数:channel - 通道号 uint8_t 0 - 7 * value - 输出指针 int16_t* 不为 NULL - * 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时 + * 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时,-3 - FRSTDATA 无跳变 * 限定条件:GPIO 已初始化 * 函数说明:启动转换并等待完成后,连续读取至指定通道 */ @@ -220,10 +253,10 @@ uint16_t tpafe5160_read_bus(void) { /* ======================== 私有函数定义 ======================== */ /* - * 函数功能:通过 RD 脉冲连续读取多个通道数据 + * 函数功能:通过 RD 脉冲连续读取多个通道数据(含 FRSTDATA 有效性校验) * 入口参数:count - 要读取的通道数 uint8_t 1 - 8 * buf - 输出缓冲区 int16_t* 不为 NULL - * 返回值:无 + * 返回值:0 - 成功,-3 - 整帧读完 FRSTDATA 无跳变(读回值不可信) * 限定条件:转换已完成(BUSY 为低),RD 初始为高 * 函数说明:每次 RD 下降沿输出一个通道数据,按通道1~8顺序输出 * DB[15:0] 直接接 GPIOG[15:0],通过 IDR 寄存器一次读取 @@ -232,9 +265,19 @@ uint16_t tpafe5160_read_bus(void) { * GPIO 写操作 ~6ns + 端口延迟 ~18ns → RD 引脚实际翻转约 24ns 后 * t14 数据建立 ≤ 21ns → 数据在 RD 下降沿后 21ns 内有效 * 5 个 NOP (~30ns) 覆盖建立时间 + * + * E-44(数据有效性):FRSTDATA 由芯片主动驱动,板上 R24 4.7k 上拉(V0.1 网表)。 + * 手册 Parallel Data Read:FRSTDATA 随首个 RD 下降沿变高标记通道 1 可读,t26/t27 + * 给出"RD 下降沿 → FRSTDATA 高/低"两个跳变。判据刻意**不锁死具体是哪个 RD 边沿 + * 拉高/拉低**(本板未实测确认边沿次序),只要求"一帧读下来必须发生跳变": + * 芯片死亡 / 不响应 RD 时该脚被上拉钉死为高 → 全程无跳变 → 判失败。 + * 旧实现完全不看这根脚(宏 TPAFE5160_FRSTDATA_READ 有定义、代码零引用), + * 却在注释里承诺"通过 FRSTDATA 验证第一通道",芯片死亡时返回 OK + 常量垃圾。 */ -static void tpafe5160_read_channels(uint8_t count, int16_t *buf) { +static int tpafe5160_read_channels(uint8_t count, int16_t *buf) { uint8_t i; + uint8_t lvl0 = (TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u; + uint8_t toggled = 0; for (i = 0; i < count; i++) { /* RD 下降沿:ADC 输出当前通道数据到 DB[15:0] */ @@ -245,16 +288,30 @@ static void tpafe5160_read_channels(uint8_t count, int16_t *buf) { /* RD 上升沿:准备下一通道 */ TPAFE5160_RD_HIGH_DLY(); + + /* FRSTDATA 跳变观测(每次 RD 之后采一次) */ + if (((TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u) != lvl0) { + toggled = 1; + } } + + return toggled ? TPAFE5160_OK : TPAFE5160_FRSTDATA_ERR; } /* ======================== 中断模式实现 ======================== */ -/* 双缓冲区:ISR 写 s_buf_b,主循环读 s_buf_a */ +/* 双缓冲区:ISR 写非活动缓冲,消费者在临界区内拷活动缓冲 */ static int16_t s_buf_a[TPAFE5160_CH_NUM]; static int16_t s_buf_b[TPAFE5160_CH_NUM]; -static volatile uint8_t s_ready = 0; static volatile uint8_t s_buf_sel = 0; /* 0 = 写 B / 读 A, 1 = 写 A / 读 B */ +/* E-45:帧序号取代"就绪电平标志"。旧 s_ready 有三个写者,其中 start_conv_irq + * 在触发前清 0 —— 重触发这个动作本身就会抹掉一帧还没被消费的数据(高速采样 + * 系统性丢样,且无从发现)。序号只增不清,消费者比较序号即可 */ +static volatile uint32_t s_frame_seq = 0; +/* E-44:FRSTDATA 无跳变帧计数。中断里只加计数(不打日志),由消费者/自检侧上报 */ +static volatile uint16_t s_frstdata_err = 0; +/* 消费者游标:只在任务上下文读写(snapshot 内部),中断不碰 */ +static uint32_t s_consumer_seq = 0; /* * 函数功能:使能 BUSY EXTI 中断(运行时重使能用) @@ -283,44 +340,64 @@ void tpafe5160_irq_disable(void) { * 入口参数:无 * 返回值:无 * 限定条件:已调用 tpafe5160_irq_enable() - * 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据 + * 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据。 + * E-45:这里**不再清就绪标志**——旧实现"触发前置 0"等于用重触发去丢帧 */ void tpafe5160_start_conv_irq(void) { - s_ready = 0; TPAFE5160_CONVST_PULSE(); } /* - * 函数功能:检查是否有新的转换数据 + * 函数功能:查询已完成的帧序号 * 入口参数:无 - * 返回值:1 - 数据就绪,0 - 无新数据 + * 返回值:uint32_t 帧序号(EXTI 每读完一帧 +1) * 限定条件:中断模式已启用 - * 函数说明:在 EXTI 回调中置位,主循环读取后需调用 tpafe5160_clear_ready() 清除 + * 函数说明:见 tpafe5160.h 同名声明的契约说明(E-45) */ -uint8_t tpafe5160_data_ready(void) { - return s_ready; +uint32_t tpafe5160_frame_seq(void) { + return s_frame_seq; } /* - * 函数功能:清除数据就绪标志 + * 函数功能:FRSTDATA 无跳变帧的累计次数 * 入口参数:无 - * 返回值:无 + * 返回值:uint16_t 计数 * 限定条件:中断模式已启用 - * 函数说明:主循环处理完数据后调用 + * 函数说明:非 0 = 至少一帧的 8 个值不可信(E-44),消费者据此上报自检 */ -void tpafe5160_clear_ready(void) { - s_ready = 0; +uint16_t tpafe5160_frstdata_err(void) { + return s_frstdata_err; } /* - * 函数功能:获取数据缓冲区指针 - * 入口参数:无 - * 返回值:int16_t[8] 数据缓冲区的 const 指针 - * 限定条件:tpafe5160_data_ready() 返回 1 时调用 - * 函数说明:双缓冲切换,ISR 写另一个缓冲区,主循环安全读取当前缓冲区 + * 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势) + * 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL + * count - 拷贝通道数 uint8_t 1 - 8 + * 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效 + * 限定条件:**任务上下文**(内部 taskENTER_CRITICAL,中断里不可调用) + * 函数说明:E-58——把"选缓冲 + 拷数据 + 记游标"收进一个临界区。旧 get_buf() 只给 + * 指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回 + * 两帧混合的数据(8 通道失去同一时刻性)且无任何错误指示 */ -const int16_t* tpafe5160_get_buf(void) { - return (s_buf_sel == 0) ? s_buf_a : s_buf_b; +int tpafe5160_snapshot(int16_t *dst, uint8_t count) { + uint32_t seq; + + if (dst == NULL || count == 0u || count > TPAFE5160_CH_NUM) { + return TPAFE5160_ERROR; + } + + seq = s_frame_seq; + if (seq == s_consumer_seq) { + return 0; /* 无新帧 */ + } + + taskENTER_CRITICAL(); + (void)memcpy(dst, (s_buf_sel == 0u) ? s_buf_a : s_buf_b, + (size_t)count * sizeof(int16_t)); + s_consumer_seq = seq; + taskEXIT_CRITICAL(); + + return 1; } /* @@ -339,18 +416,18 @@ void tpafe5160_exti_handler(uint16_t GPIO_Pin) { return; } - /* 双缓冲切换:ISR 写与主循环读不同的缓冲区 */ - int16_t *p_wr = (s_buf_sel == 0) ? s_buf_b : s_buf_a; + /* 双缓冲切换:ISR 写与消费者读不同的缓冲区 */ + int16_t *p_wr = (s_buf_sel == 0u) ? s_buf_b : s_buf_a; - /* 连续读取8个通道,约 0.5µs @ 168MHz */ - uint8_t i; - for (i = 0; i < TPAFE5160_CH_NUM; i++) { - TPAFE5160_RD_LOW_DLY(); - p_wr[i] = (int16_t)TPAFE5160_READ_BUS(); - TPAFE5160_RD_HIGH_DLY(); + /* 连续读取8个通道(内含 FRSTDATA 有效性校验),约 0.5µs @ 168MHz */ + int ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, p_wr); + + /* 切换缓冲区并推进帧序号(E-45:序号只增不清,重触发不会抹掉未消费帧) */ + s_buf_sel ^= 1u; + s_frame_seq++; + + /* E-44:中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */ + if (ret != TPAFE5160_OK && s_frstdata_err < 0xFFFFu) { + s_frstdata_err++; } - - /* 切换缓冲区并标记就绪 */ - s_buf_sel ^= 1; - s_ready = 1; } diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.h b/Drivers/BSP/TPAFE5160/tpafe5160.h index c614d9ad..9210c5fa 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.h +++ b/Drivers/BSP/TPAFE5160/tpafe5160.h @@ -11,6 +11,9 @@ * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) + * 2026-10-10 王建锋 消费者接入前置修复(错误码传播审计 E-44 / E-45 / E-58): + * FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 + + * 原子快照 API;移除会撕裂的 get_buf/data_ready/clear_ready */ #ifdef __cplusplus @@ -69,6 +72,9 @@ typedef enum { #define TPAFE5160_OK 0 #define TPAFE5160_ERROR -1 #define TPAFE5160_BUSY_TIMEOUT -2 +/* 整帧读完 FRSTDATA 无跳变 = 芯片没在驱动数据线(死亡 / 未响应 RD), + * 此时读回的是上拉/总线残值而非转换结果(E-44,见 read_channels 注释) */ +#define TPAFE5160_FRSTDATA_ERR -3 /* 默认超时时间 (ms) */ #define TPAFE5160_CONV_TIMEOUT_MS 10 @@ -125,11 +131,11 @@ uint8_t tpafe5160_is_busy(void); /* * 函数功能:读取全部8通道转换结果 * 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL - * 返回值:0 - 成功,-2 - BUSY 超时 + * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信) * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 - * 3. 通过 FRSTDATA 验证第一通道 + * 3. 校验 FRSTDATA 在这一帧内发生过跳变(= 芯片确实在驱动数据线,E-44) */ int tpafe5160_read_all(int16_t *buf); @@ -192,31 +198,38 @@ void tpafe5160_irq_disable(void); void tpafe5160_start_conv_irq(void); /* - * 函数功能:检查是否有新的转换数据 + * 函数功能:查询已完成的帧序号(消费者判断"有没有新帧"的唯一依据) * 入口参数:无 - * 返回值:1 - 数据就绪,0 - 无新数据 + * 返回值:uint32_t 帧序号,EXTI 每读完一帧 +1 * 限定条件:中断模式已启用 - * 函数说明:在 EXTI 回调中置位,主循环读取后需调用 tpafe5160_clear_ready() 清除 + * 函数说明:取代旧的 data_ready/clear_ready **电平标志**(E-45)——电平标志有三个 + * 写者,其中 start_conv_irq 在触发前清 0,于是"重触发"本身就会抹掉一帧 + * 还没被消费的数据(高速采样系统性丢样且无从发现)。序号只增不清, + * 消费者比较 seq 即可,重触发不会丢 */ -uint8_t tpafe5160_data_ready(void); +uint32_t tpafe5160_frame_seq(void); /* - * 函数功能:清除数据就绪标志 - * 入口参数:无 - * 返回值:无 - * 限定条件:中断模式已启用 - * 函数说明:主循环处理完数据后调用 + * 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势) + * 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL + * count - 拷贝通道数 uint8_t 1 - 8 + * 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效 + * 限定条件:**任务上下文**调用(内部用 taskENTER_CRITICAL,不可在中断里调) + * 函数说明:E-58——旧 get_buf() 只返回缓冲区指针,任务再逐通道拷;两步之间被 BUSY + * EXTI 抢占并跨过一帧周期,拷回的就是**两帧混合**的 8 通道数据(同一时刻 + * 的 8 路值互相错位),且没有任何错误指示。本函数把"选缓冲 + 拷数据 + + * 记游标"收进一个临界区(只盖 16 字节拷贝),撕裂在设计上消除 */ -void tpafe5160_clear_ready(void); +int tpafe5160_snapshot(int16_t *dst, uint8_t count); /* - * 函数功能:获取数据缓冲区指针 + * 函数功能:读取到"FRSTDATA 无跳变"帧的累计次数(芯片死亡 / 总线异常的留痕) * 入口参数:无 - * 返回值:int16_t[8] 数据缓冲区的 const 指针 - * 限定条件:tpafe5160_data_ready() 返回 1 时调用 - * 函数说明:缓冲区由 EXTI 回调写入,主循环只读 + * 返回值:uint16_t 计数(中断里只加计数不打日志) + * 限定条件:中断模式已启用 + * 函数说明:E-44——非 0 表示至少有一帧的 8 个值不可信;消费者据此上报自检 */ -const int16_t* tpafe5160_get_buf(void); +uint16_t tpafe5160_frstdata_err(void); /* * 函数功能:EXTI 中断处理(由 HAL_GPIO_EXTI_Callback 调用) diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 280085c1..a101b29d 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -872,6 +872,11 @@ 1 ..\App\task\ui_task.c + + adc_task.c + 1 + ..\App\task\adc_task.c + diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index 7bd7f802..b2b1d47b 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -444,10 +444,10 @@ sequenceDiagram ### 5.9 ADC 采集(FR-ADC)⬜ 待实现 - **需求(待产品定义确认)**:周期采集 TPAFE5160 多通道模拟量并转发。 -- **现状**:`tpafe5160_init()` 与 `tpafe5160_start_conv_irq()` 在启动序列调用一次;**全仓库无周期性 CONVST 重触发**;`data_ready`/`get_buf` 无消费者(`adc_task.c` 为空桩,且未在 `freertos.c` 创建)。 +- **现状**:`tpafe5160_init()` 与 `tpafe5160_start_conv_irq()` 在启动序列调用一次;**全仓库无周期性 CONVST 重触发**;`tpafe5160_snapshot()` 无消费者(`adc_task.c` 为空桩——2026-10-10 起已挂进 `uvprojx`,仍未在 `freertos.c` 创建);STM32 侧 ADC1 已生成但**全仓库无 `HAL_ADC_Start`**。 - **后果**:上电后仅采集 1 个样本,此后数据永久陈旧。 - **待确认(阻塞项 B0)**:① 采集是否持续 ② 采样率/通道 ③ 转发目标 ④ `APP_ADC_ENABLE` 语义。 -- **实现约束**(见 §9):周期 CONVST 重触发 + `adc_task` 消费;**先修原子快照 API**(否则接入即撕裂);栈预算核算;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 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。 +- **实现约束**(见 §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 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。 ### 5.10 RS-485 采集(FR-RS485)⬜ 待实现 @@ -768,7 +768,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 缺口 | 现状 | 影响 | |---|---|---| -| **ADC 采集链路** | `adc_task.c` 空桩;无周期 CONVST 重触发;`data_ready`/`get_buf` 无消费者 | 上电后仅采 1 个样本 | +| **ADC 采集链路** | `adc_task.c` 空桩(已挂进 `uvprojx`、未建任务);无周期 CONVST 重触发;`tpafe5160_snapshot()` 无消费者;STM32 侧无 `HAL_ADC_Start` | 上电后仅采 1 个样本(驱动取数契约已于 2026-10-10 修好,接消费者不再有撕裂/丢样风险) | | **RS-485 采集链路** | `rs485_task.c` 空桩;`App/protocol/` 仅 README | 无 RS-485 数据收发 | | **DTU 核心转发职责** | 未定义 | 采集到数据后无处转发 | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 76cda73f..0aecc1a0 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 267,820 B (261.54 KB) | 1,048,576 B (1024 KB) | 25.5% | -| RW_IRAM1 | 85,032 B (83.04 KB) | 131,072 B (128 KB) | 64.9% | +| ER_IROM1 | 268,128 B (261.84 KB) | 1,048,576 B (1024 KB) | 25.6% | +| RW_IRAM1 | 85,040 B (83.05 KB) | 131,072 B (128 KB) | 64.9% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **268,560 B (262.3 KB)** | 1,048,576 B (1024 KB) | **25.6%** | +| **Flash (ROM 合计)** | **268,876 B (262.6 KB)** | 1,048,576 B (1024 KB) | **25.6%** | 各区的容量与常量来源: diff --git a/docs/子系统设计/运行与自检记录设计说明.md b/docs/子系统设计/运行与自检记录设计说明.md index e89e582e..8fd0fa03 100644 --- a/docs/子系统设计/运行与自检记录设计说明.md +++ b/docs/子系统设计/运行与自检记录设计说明.md @@ -256,7 +256,7 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。 | O1 | 断电 <10min 的"上电/重启"歧义 | §5.3 阈值折中,文档声明;如需精确可后续加掉电标志(PVD) | | O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 | | O3 | "清除记录"菜单项(node 27 #P,现为空框) | v2:UI 动作 = 删除/重建 log 文件(与满时策略同路径) | -| O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_get_buf()` | +| O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_snapshot()`(原子快照;旧 `tpafe5160_get_buf()` 已于 2026-10-10 删除,见 `AGENTS.md`) | | R-1 | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 | | R-2 | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) | diff --git a/docs/待处理内容.md b/docs/待处理内容.md index a2e7c3f1..6f97f6cb 100644 --- a/docs/待处理内容.md +++ b/docs/待处理内容.md @@ -65,16 +65,18 @@ ### 2.1 阶段 B:核心功能补齐(产品价值最高,需产品定义确认) > 目标:让 DTU 真正采到数——这是当前**最大的功能空洞**,**当前最高优先级**。 -> 一旦 B0 产品定义明确,即可与 B3(纪律门禁)并行启动。 +> **B3(纪律门禁)已于 2026-10-10 固化进 `AGENTS.md` 并销项**;同日 TPAFE 侧前置缺陷 +> **E-44 / E-45 / E-58 全部修复** → **B1 现在只欠 B0 的产品定义 + 栈预算**; +> B2 仍欠 **E-50 / E-49**(RS485 驱动契约未修,门禁仍挡着)。 | 序 | 任务 | 前置 | 说明 | |---|---|---|---| | **B0** | **产品定义确认**(阻塞项) | — | 需甲方明确:① 采集是否持续 ② 采样率(**通道语义已于 2026-10-09 确认并固化 → `docs/硬件平台/ADC通道映射说明.md`**:UA/UB/UC/UN=`AIN1~4`、IA/IB/IC/IN=`AIN8~11`、V_ZL2=温度、其余 8 路备用;主板只做监视/辅助测量,计量在子板。**注意:8 路电气量分属 TPAFE/STM32 两片转换器属 V0.1 硬件缺陷 → N-18**,主板侧不得做跨转换器的相位/功率类运算) ③ 转发目标(MODBUS RTU?上行 TCP?)④ `APP_ADC_ENABLE`/`APP_RS485_ENABLE` 语义。**B1/B2 的规模完全取决于此** | -| **B1** | **ADC 采集链路实现**(消化 P1-1、E-58) | B0 + **E-59 门禁** | 周期 CONVST 重触发 + `adc_task` 消费 `get_buf`;**先修 E-58**(原子快照 API,否则接入即撕裂);栈预算核算(两任务未入 `freertos.c` 栈表);**取数必须按 `docs/硬件平台/ADC通道映射说明.md`**(TPAFE `buf[2]=IA`/`buf[5]=UN`/`buf[6]=UA`;STM32 侧 ADC1 **已于 2026-10-09 生成**:`hadc1` + `MX_ADC1_Init()`、9 通道 Rank1~9、144 cycles、ADCCLK=21 MHz、软件触发,网名 ADC17=PC4 用 `ADC_CHANNEL_14`;剩活只剩消费者与 `adc_task` 建任务) | -| **B2** | **RS-485 采集链路实现**(消化 E-50、E-49) | B0 + **E-59 门禁** | 双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ 协议状态机(`App/protocol/` 正是为此预留) | -| **B3** | **E-59 门禁固化入 `AGENTS.md`** | — | 把"接消费者前必须先修 E-36/E-44/E-45/E-58 + 栈预算"写成强制门禁,与"改栈前跑 `stack_report.py`"同级。**这是防止 B1/B2 带缺陷上线的关键** | +| **B1** | **ADC 采集链路实现**(消化 P1-1;E-58 已闭环) | B0 + **E-59 门禁**(TPAFE 侧已满足) | 周期 CONVST 重触发 + `adc_task` 消费 **`tpafe5160_snapshot()`**(**2026-10-10 驱动契约已修**:E-44 FRSTDATA 有效性校验 + `init()` 首帧自检返 `-3`、E-45 帧序号取代就绪电平标志、E-58 临界区原子快照;旧 `get_buf`/`data_ready`/`clear_ready` **已删**,删除前零调用者);`adc_task.c` **已挂进 `uvprojx`**(此前 Keil/模拟器两边都不注册 = 永不编译;simulator 无任务层,只挂 Keil);**仍欠**:栈预算核算(两任务未入 `freertos.c` 栈表,建任务前后各跑 `stack_report.py`;先处理 S-1)+ 消费者本体 + 建任务走 **L1(`.ioc` 的 `FREERTOS.Tasks01`)**;**取数必须按 `docs/硬件平台/ADC通道映射说明.md`**(TPAFE `buf[2]=IA`/`buf[5]=UN`/`buf[6]=UA`;STM32 侧 ADC1 **已于 2026-10-09 生成**:`hadc1` + `MX_ADC1_Init()`、9 通道 Rank1~9、144 cycles、ADCCLK=21 MHz、软件触发,网名 ADC17=PC4 用 `ADC_CHANNEL_14`;⚠ 全仓库至今**无 `HAL_ADC_Start`** → STM32 侧一路未跑) | +| **B2** | **RS-485 采集链路实现**(消化 E-50、E-49) | B0 + **E-59 门禁**(**尚未满足**:E-50 / E-49 未修) | 双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ 协议状态机(`App/protocol/` 正是为此预留) | -**建议**:B3 与 B0 并行推进(前者是纪律,后者问甲方);**E-58 修复应在 B1 动消费者之前完成**。 +**建议**:驱动契约与编译接线已在 2026-10-10 铺完 → B1 的下一步是**栈预算 + 建任务(L1)**, +再谈消费者与节拍;B2 之前必须先修 E-50 / E-49。 ### 2.2 阶段 C:结构债清理(二期)+ 存储侧健壮性 @@ -114,9 +116,10 @@ #### P1-1 ADC 采样数据路径产品构建中死亡 - **位置**:`App/app/app_main.c`(`tpafe5160_start_conv_irq()` 仅启动时调用一次)+ `Drivers/BSP/TPAFE5160/tpafe5160.c` -- **证据**:启动序列调用一次 `tpafe5160_start_conv_irq()`;全仓库无周期性 CONVST 重触发;`data_ready` / `get_buf` 无任何消费者(`adc_task.c` 是空桩)。 +- **证据**:启动序列调用一次 `tpafe5160_start_conv_irq()`;全仓库无周期性 CONVST 重触发;`tpafe5160_snapshot()` 无任何消费者(`adc_task.c` 是空桩——2026-10-10 起已挂进 `uvprojx`,但仍未在 `freertos.c` 建任务);STM32 侧 ADC1 已生成却**全仓库无 `HAL_ADC_Start`**(一路彻底未跑)。 - **影响**:上电后仅采集 1 个样本,此后数据永久陈旧;且 `APP_ADC_ENABLE` 为未接线占位(已如实标注,不再冒充开关),形成"看似启用实则失效"的假象。 -- **修复建议**:周期 CONVST 重触发(定时器或任务)+ 数据消费任务;若基线版本有意不实现采集,应删除启动调用并以 `APP_ADC_ENABLE` 真实门控。 +- **进展**(2026-10-10):**驱动侧取数契约已修**(E-44 FRSTDATA 有效性校验 + `init()` 首帧自检、E-45 帧序号、E-58 原子快照,索引见 `docs/问题记录/错误码传播审计.md` §2.2)→ 接消费者不再有"撕裂 / 重触发丢样 / 芯片静默死亡"三类风险。本条剩余 = **周期重触发 + 消费者 + 建任务**。 +- **修复建议**:周期 CONVST 重触发(定时器或任务)+ `adc_task` 消费 `tpafe5160_snapshot()`;建任务走 **L1(`.ioc` 的 `FREERTOS.Tasks01`)** 并做栈预算;若基线版本有意不实现采集,应删除启动调用并以 `APP_ADC_ENABLE` 真实门控。 - **置信度**:high(代码级确证);P0/P1 定级取决于产品定义(见 §4 待确认)。 #### P1-10 测试固件静态 RAM 预算(基线已重建、测试缓冲已瘦身,剩构建实测闭环) diff --git a/docs/硬件平台/ADC通道映射说明.md b/docs/硬件平台/ADC通道映射说明.md index a671f75d..bdb30204 100644 --- a/docs/硬件平台/ADC通道映射说明.md +++ b/docs/硬件平台/ADC通道映射说明.md @@ -131,11 +131,11 @@ STM32(U17)为 LQFP144 脚号。 | **通道归属(硬件缺陷)** | 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` 可用);**仍无消费者**——见下面两行 | | 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` | **空桩**:`App/task/adc_task.c` 只 `osDelay(1000)`,且任务未在 `Src/freertos.c` 创建 | 无消费者,数据路径死(登记簿 P1-1) | +| `adc_task` | **空桩**:`App/task/adc_task.c` 只 `osDelay(1000)`;**2026-10-10 起已挂进 `MDK-ARM/*.uvprojx`**(此前 Keil/模拟器两边都不注册 = 永不编译,改它不生效;simulator 无任务层故只挂 Keil),任务仍未在 `Src/freertos.c` 创建 | 无消费者,数据路径死(登记簿 P1-1) | | TPAFE 采样触发 | `tpafe5160_start_conv_irq()` 仅在 `app_main_init` 调一次,**无周期 CONVST 重触发** | 只采到一帧,无连续波形 | | 前端偏置 | 每路 1k 串联 + 1k 到 `*_GND` + 滤波 C,**无中点偏置** | STM32 侧只能测 **0~3.3 V 单极性**,交流量负半周被削顶;主板侧只做监视可接受,若要算 RMS/功率需硬件补偏置或明确算法口径 | | TPAFE 量程 | 默认 ±5 V(RANGE 接 GND),16 位二进制补码,LSB ≈ 152.59 µV | 用 `tpafe5160_to_voltage()` 换算 | -| 双缓冲撕裂 | 审计项 **E-58**(`docs/问题记录/错误码传播审计.md`):`get_buf` 换缓冲 + 任务抢占 → 8 通道可能失去同一性 | 实现消费者时必须处理(一次帧内原子取用) | +| 取数契约(**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` 现只在自检帧通过后才打印 → 判据在本板成立、非空转 | --- diff --git a/docs/问题记录/错误码传播审计.md b/docs/问题记录/错误码传播审计.md index 942a19f3..f312bdc9 100644 --- a/docs/问题记录/错误码传播审计.md +++ b/docs/问题记录/错误码传播审计.md @@ -17,7 +17,13 @@ ## 0. 结论速览 -**当前未闭环 17 项:P1×2、P2×15。无 P0。** +**当前未闭环 16 项:P1×1、P2×15。无 P0。** + +> **2026-10-10 追记(E-59 门禁的 TPAFE 前置项全部闭环)**:**E-44 / E-45 / E-58** 已修复并经真机验证 +> (`tpafe5160` 驱动:FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 + 原子快照 API; +> 旧的 `get_buf`/`data_ready`/`clear_ready` **删除**,全仓库零调用者)。未闭环 17 → **16**(P1 2→1), +> 已闭环索引 41 → **44**(§2.2)。门禁本身已固化进 `AGENTS.md`;**E-59 仍未闭环**(消费者仍是空桩, +> 且 RS485 侧 E-50 / E-49 未修、两任务栈预算未做)。 > **2026-10-04 订正(计数同步)**:2026-10-03 的 **E-34 / E-04**(第一档)与 > **GD5F 错误通道统一(E-10 / E-42 / E-53 / E-56 / E-57,第二档 #1)** 已修复并台架验证, @@ -32,23 +38,22 @@ > 主机侧单测(S1-b)首跑抓出,已修复并通过 `@build` 0/0。**已闭环**, > 明细见 §2.1。 -首版 59 项中 **41 项已闭环**(索引见 §2),其中 **4 项 P0 全部修复并在位**;**17 项未闭环** +首版 59 项中 **44 项已闭环**(索引见 §2),其中 **4 项 P0 全部修复并在位**;**16 项未闭环** (见 §3),另 **1 项回归监督**(E-62,无需改动、仅防后人误改)。剩余问题分三类: | 类别 | 数量 | 性质 | 处置 | |---|---|---|---| -| **A. 待处理的实质缺陷** | 5 | 有明确后果,需改代码 | §3.1,第一 / 二档 | +| **A. 待处理的实质缺陷** | 4 | 有明确后果,需改代码 | §3.1,第一 / 二档 | | **B. 契约缺口(随消费者接入触发)** | 7 | 当前调用者"恰好不会踩",但契约没写死 | §3.2,接消费者前必修 | | **C. 一致性 / 可维护性** | 5 | 无功能影响或影响极小 | §3.3,第三档 | -**最值得先动的两件事**(2026-10-04 更新——原列的 **E-34 / E-04 已闭环**,见 §2.2): +**最值得先动的两件事**(2026-10-10 更新——原列的 **E-58 已闭环**,**E-34 / E-04 已闭环**,见 §2.2): -1. **E-59(P1,元发现)**——`adc_task` / `rs485_task` 均为空桩,驱动层多项缺陷 - (E-58 / E-50 / E-44 / E-45)**当前全部不可触发**;一旦接入真实消费者便会同时上线。 - 处置:把"接消费者前先修驱动契约 + 栈预算"固化为**强制门禁**(§5 第三档)。 -2. **E-58(P1)**——TPAFE 双缓冲角色互换 + 任务抢占 → 8 通道失去同一性, - 且**没有任何错误指示**;现因 `adc_task` 空桩而休眠,**接入消费者之日即触发**, - 属"接消费者前必修"(§5 第三档前置条件)。 +1. **E-59(P1,元发现)**——`adc_task` / `rs485_task` 均为空桩。TPAFE 侧契约缺陷 + (**E-44 / E-45 / E-58**)已于 2026-10-10 **全部修复**,RS485 侧 **E-50 / E-49 仍未修**; + 门禁已固化进 `AGENTS.md`,接消费者前仍欠"栈预算 + RS485 双缓冲/序号"。 +2. **E-50(P2)**——RS485 单缓冲无 ISR/任务同步 → 读出跨两帧拼接的数据;与 E-59 同族, + 属"接 RS485 消费者前必修"(§5 第三档前置条件)。 > **2026-09-25 变更**:**E-03 已修复**(错误码改按任务隔离,见 §3.1)。 > 它是本次审计中唯一"并发条件下会说谎"的通道,且 pending 二期不覆盖它,故独立小修先行。 @@ -110,7 +115,7 @@ --- -## 2. 已闭环总览(41 项) +## 2. 已闭环总览(44 项) 只列索引与闭环依据,明细不再展开(首版明细可从 git 历史取回)。 @@ -123,7 +128,7 @@ | **E-02** | `net_socket.c` / `lftpd` / `time_sync` | 一条空闲 FTP 控制连接把 netTask 独占 30s → **全体消费者队列停摆**(实测冻结远端显示 17.7s) | `f8d6e90`+`4a1605b`(分片)→ `a45c0cb`(L3 pending 化,结构性消灭) | | **E-63** | `App/cfg/cfg_file.c` | 外科手术保存 3 处 `s_encode(...) == 0` 判定恒假(函数成功返回**文本长度**)→ 受管值改写为**死码**:对已存在 `/config.ini` 保存时受管键从不改写(**静默不落盘**,`net_cfg_save` 仍报 OK) | **2026-10-02**(L0.5 `cfg_file` 单测首跑抓出)——判据由 `== 0` 改 `>= 0`;见 [`../测试说明/配置文件单元测试说明.md`](../测试说明/配置文件单元测试说明.md) §5 | -### 2.2 P1 十八项 +### 2.2 P1 二十一项 E-05(net 建链 API 队列化)、E-06 / E-20 / E-21 的**主要分量**(随 E-02 pending 化落地)、 E-09(UI 页面局部副本 + 显式提交)、E-17(RECONFIG 原子消费)、 @@ -131,7 +136,16 @@ E-32(SD2506 充电器寄存器回读校验)、E-33(CH395F DMA 错误通道 E-36 / E-37(RS485 契约与 init 返回码)、E-38(SD2506 负温度解码)、 E-39 / E-51(SD2506 写允许窗口 / `set_alarm` 读-改-写)、E-40(FTL erase/prog 错误分类)、 E-43(GD5F erase 容量边界)、**E-03(错误码按任务隔离,2026-09-25)**、 -**E-34(CH395F 字节命令有限超时,2026-10-03)**、**E-04(`remo_send` 返回值校验,2026-10-03)**。 +**E-34(CH395F 字节命令有限超时,2026-10-03)**、**E-04(`remo_send` 返回值校验,2026-10-03)**、 +**E-44(TPAFE `read_all` 无数据有效性校验:FRSTDATA 宏有定义、代码零引用,芯片死亡时返回 OK + 常量垃圾; +现 `read_channels` 要求"一帧内 FRSTDATA 必须跳变"、`init()` 首帧自检返 `-3`,2026-10-10)**、 +**E-45(TPAFE `s_ready` 三写者、重触发即清掉未消费帧;改帧序号 `frame_seq()` 只增不清,2026-10-10)**、 +**E-58(TPAFE `get_buf` 取指针 + 任务侧拷贝被 ISR 抢占 → 两帧混合;删 `get_buf`/`data_ready`/`clear_ready`, +改临界区原子快照 `snapshot()`,2026-10-10)**。 + +> 注:**E-44 / E-45 在 2026-09-25 重写时只留在首版 P1 速览表、未展开正文条目**(正文只余 E-59 的引用), +> 本次闭环直接进索引;实现口径与陷阱见 `AGENTS.md`「ADC 取数只用 `tpafe5160_snapshot()`」条与 +> `Drivers/BSP/TPAFE5160/README.md`。 > 注:E-05 已闭环(建链 API 并入队列),但**同族的 `net_getsockname` 残留通道** > 在 §3.1-E-24 重新入册。 @@ -148,13 +162,13 @@ E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / ra --- -## 3. 未闭环发现(17 项) +## 3. 未闭环发现(16 项) 每项字段统一为:**位置 → 机制 → 后果 → 当前状态 → 建议**。 "当前状态"是本轮(2026-09-25)回到代码实测的结论;**2026-10-03 已修复的条目 (E-04 / E-10 / E-42 / E-53 / E-56)与 2026-09-25 的 E-03 保留为留档**(标 ✅),不计入未闭环数。 -### 3.1 A 类:待处理的实质缺陷(未闭环 5 项) +### 3.1 A 类:待处理的实质缺陷(未闭环 4 项) > **本类已闭环留档**:E-03(2026-09-25)、E-04 / E-10 / E-42 / E-53 / E-56(2026-10-03)—— > 见下方各条目的 ✅ 标记与 §2 索引;此处保留详细条目仅作背景留档。 @@ -292,23 +306,6 @@ E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / ra --- -#### E-58 TPAFE 双缓冲角色互换 + 任务抢占 → 撕裂帧 — **P1** | 模式 P7 - -- **位置**:`Drivers/BSP/TPAFE5160/tpafe5160.c:322-324`(`get_buf` 换缓冲)、 - `:343` / `:354`(ISR 与任务侧) -- **机制**:`get_buf` 交换双缓冲指针后,任务侧才逐通道 `memcpy`;若在这两步之间 - 被 ISR 抢占且跨过一个整帧周期,任务拷贝的就是**混合了两帧**的 8 通道数据。 -- **后果**:8 通道失去同一性(同一时刻的 8 路值互相错位)——对外是"数据跳变", - 且**没有任何错误指示**。 -- **当前状态(复核)**:**仍然存在,属"休眠缺陷"**。当前 `adc_task.c:37-43` 是空桩 - (`osDelay` 循环),没有任何消费者调用 `get_buf`。**接入消费者之日即触发**。 -- **建议**:提供**原子快照 API**(临界区短锁 + `memcpy`,把"取指针"与"拷数据"合并为 - 一个不可分割动作),或在文档中明确"必须关中断后拷贝"并落成强制约定。 - 属"接消费者前必修"(§5 第三档前置条件)。 -- **关联**:E-59(消费者接入前置清单) - ---- - #### E-49 RS485 TX 超时立即拉低 DE — **P2** | 模式 P9 - **位置**:`Drivers/BSP/RS485/rs485.c:70-93` @@ -340,12 +337,14 @@ E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / ra - **机制**:两个任务的函数体都是 `osDelay` 空循环,谁都不调驱动层的 `get_buf` / `on_rx`。 - **后果**:**首版列出的多项驱动层 P1(E-36 / E-44 / E-45 / E-58)当前全部不可触发。** 这意味着"审计通过"是假的——一旦接入真实消费者,这些缺陷会同时上线。 -- **当前状态(复核)**:**仍然存在,且是本轮最应强调的一条**。 - 本条不是某个具体 bug,而是一个**风险闸门**:它决定了 §3.2 那一整类"契约缺口" - 是"暂时无害"还是"即将爆炸"。 -- **建议**:把"接消费者前必须先修驱动契约"写成**强制门禁**(建议入 `AGENTS.md`, - 与"改栈前跑 `stack_report.py`"同级)。前置清单 = E-36 / E-44 / E-45 / E-58, - 另加栈预算核算(`adc_task` / `rs485_task` 尚未纳入 `Src/freertos.c` 栈表)。 +- **当前状态(复核 2026-10-10)**:**仍然存在**(消费者仍未接入),但**闸门已经半开**: + ① 前置清单里 TPAFE 三项 **E-44 / E-45 / E-58 已修复**(原子快照 API + 帧序号取代 + 就绪标志 + FRSTDATA 数据有效性校验,索引见 §2.2);RS485 侧 **E-36 / E-37 早已闭环**, + 但 **E-50(单缓冲无同步)与 E-49(超时路径拉 DE)仍未修** → RS485 消费者仍被挡住。 + ② 门禁本身已固化进 `AGENTS.md`(与"改栈前跑 `stack_report.py`"同级)。 + ③ 栈预算**仍未做**:两任务尚未纳入 `Src/freertos.c` 栈表。 +- **建议**:接 **ADC** 消费者现在只欠"栈预算 + 产品定义(采样率 / 数据出口)"; + 接 **RS485** 消费者前还必须先修 E-50 / E-49。 --- @@ -480,7 +479,7 @@ E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / ra **读法提示**:三处"空白带"(事件标志、syslog、配置引擎的大部分行)不是没审, 而是首版查出的问题已闭环。**GD5F 列的项已于 2026-10-03 全部闭环**(矩阵该列已清空), -当前未闭环项集中在 **RS485/TPAFE 驱动(E-49 / E-50 / E-58)、net 层全局状态 +当前未闭环项集中在 **RS485 驱动(E-49 / E-50)、net 层全局状态 (E-24 / E-25 / E-26 / E-27 / E-28)、配置引擎(E-16)与测试基础设施(E-23 / E-30)。** --- @@ -511,11 +510,12 @@ E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / ra ### 第三档:长期 / 前置门禁 -- **E-59 门禁**:`adc_task` / `rs485_task` 接入消费者**之前**,必须先完成 - **E-36 / E-44 / E-45 / E-58**(驱动契约修复)+ 栈预算核算 - (两任务尚未纳入 `Src/freertos.c` 栈表)。建议写入 `AGENTS.md`, - 与"改栈前跑 `stack_report.py`"同级。 -- **E-50**:RS485 双缓冲 + 序号(随 E-59 门禁) +- **E-59 门禁**(**已固化进 `AGENTS.md`,2026-10-10**):`adc_task` / `rs485_task` 接入消费者 + **之前**必须先完成驱动契约修复 + 栈预算核算(两任务尚未纳入 `Src/freertos.c` 栈表), + 与"改栈前跑 `stack_report.py`"同级。清单现状:**E-36 / E-37 ✅(早前)**、 + **E-44 / E-45 / E-58 ✅(2026-10-10)**、**E-50 / E-49 ❌(RS485 未修)** + → **ADC 侧只欠"栈预算 + 产品定义",RS485 侧仍被挡住** +- **E-50**:RS485 双缓冲 + 序号(随 E-59 门禁,**接 RS485 消费者前必修**) - **E-12**:`FF_FS_LOCK` 或固化"单写者纪律"(`AGENTS.md`) - **E-31 后续 / E-62**:卷自愈触发集的回归监督(纪律已固化,需防后人误改) - **E-47 / E-48**:驱动健壮性长尾(独立超时码 / WARN 留痕) @@ -622,3 +622,13 @@ cppcheck 无告警、`git diff --check` 干净、编码无 `U+FFFD` 且 BOM 状 与 **E-60** 闭环结果——**§0 未闭环 25 → 17**(P1 5→2、P2 20→15),**§2 已闭环 33 → 41** (P0 3→4、P1 15→18、P2 15→19),§3 各类计数(A 10→5、C 8→5)与 §4 矩阵(清空 GD5F 列) 同步更新。本次订正**仅改计数与索引,未改动任何审计结论**。* + +*2026-10-10 闭环(E-59 门禁的 TPAFE 前置三项,`Drivers/BSP/TPAFE5160/`):**E-44**—— +`read_channels()` 要求"一帧读下来 FRSTDATA 必须发生跳变"(板上 R24 4.7k 上拉,芯片死亡则钉死为高), +`tpafe5160_init()` 增**首帧自检**,不满足即返回 `-3` + `[ERR]`;**E-45**——`s_ready` 电平标志改 +`s_frame_seq` 帧序号(只增不清),`start_conv_irq` 不再清标志;**E-58**——删 `get_buf` / `data_ready` / +`clear_ready`(删除前确认全仓库**零调用者**),新增临界区原子快照 `tpafe5160_snapshot()` 与 +`tpafe5160_frstdata_err()`。`@build` 0 错误 0 警告;真机 `@test` PASS + 烧录回读 `No difference found`; +开机日志 `[ADC] Init OK` 现只在自检帧通过后才打印 → 证明 FRSTDATA 在本板确实跳变、判据不是空转。 +**§0 未闭环 17 → 16**(P1 2→1)、**§2 已闭环 41 → 44**(P1 18→21)、§3.1 A 类 5→4、§4 矩阵去掉 E-58。 +门禁本身已写入 `AGENTS.md`(与"改栈前跑 `stack_report.py`"同级)。*