TPAFE驱动闭环E-44/E-45/E-58取数契约,固化E-59门禁,adc_task挂进Keil工程
This commit is contained in:
@@ -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` 的最坏链)。故**日志不是免费的**——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补
|
||||
|
||||
<!-- TASK_TABLE:BEGIN generated by scripts/gen_task_table.py -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)→ **禁止**跨转换器做相位/功率/功率因数类运算
|
||||
|
||||
## 已知问题
|
||||
|
||||
|
||||
@@ -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 <string.h> /* 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;
|
||||
}
|
||||
|
||||
@@ -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 调用)
|
||||
|
||||
@@ -872,6 +872,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\task\ui_task.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>adc_task.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\task\adc_task.c</FilePath>
|
||||
</File>
|
||||
</Files>
|
||||
</Group>
|
||||
<Group>
|
||||
|
||||
@@ -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 核心转发职责** | 未定义 | 采集到数据后无处转发 |
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| 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%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -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) |
|
||||
|
||||
|
||||
+10
-7
@@ -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 预算(基线已重建、测试缓冲已瘦身,剩构建实测闭环)
|
||||
|
||||
@@ -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` 现只在自检帧通过后才打印 → 判据在本板成立、非空转 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+54
-44
@@ -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`"同级)。*
|
||||
|
||||
Reference in New Issue
Block a user