diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index e3207705..07af0409 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -20,6 +20,7 @@ #include #include "ch395f.h" #include "spi.h" +#include "gd5f2gq5ue.h" /* SPI1 分支转调 gd5f_spi1_notify_*(HAL SPI 回调全局唯一,本文件代管分发) */ #include "usart.h" /* @@ -173,14 +174,11 @@ static int ch395f_spi_dma_transfer(uint16_t len) { * 入口参数:hspi - SPI句柄指针 SPI_HandleTypeDef* * 函数说明:由HAL SPI驱动在DMA TX+RX完成后调用 */ -/* SPI1 DMA完成标志(定义在gd5f2gq5ue.c中) */ -extern volatile uint8_t g_spi1_dma_done; - void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; } else if (hspi->Instance == SPI1) { - g_spi1_dma_done = 1U; + gd5f_spi1_notify_done(); /* 归 GD5F 所有:本文件只代 HAL 分发,不碰它的标志 */ } } @@ -193,7 +191,7 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; } else if (hspi->Instance == SPI1) { - g_spi1_dma_done = 1U; + gd5f_spi1_notify_error(); /* 勿删:少了这行 GD5F 的 DMA 出错只能靠超时退出 */ } } diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c index 6eb4eb42..f06b4695 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c @@ -45,8 +45,14 @@ static uint8_t s_bbt_best[GD5F_BBT_SIZE]; /* 最高版本位图缓存 */ static const uint8_t s_bbt_magic[4] = { 'G', 'B', 'B', 'T' }; /* BBT 存储魔数 "GBBT" */ -/* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */ -volatile uint8_t g_spi1_dma_done; +/* DMA 完成标志(本模块私有,2026-10-22 收编): + * HAL 的 SPI 完成/错误回调是**全局唯一符号**(整个工程只能有一份实现),当前由 + * Drivers/BSP/CH395F/ch395f.c 实现并按 hspi->Instance 分发——SPI1 分支必须转调本 + * 模块导出的 gd5f_spi1_notify_done() / gd5f_spi1_notify_error(),不得反过来由 + * CH395F 直接写本模块的变量(原先是跨模块裸 extern,删掉那两行不会报错,只会让 + * 下面的 DMA 等待循环在出错时退化成干等 GD5F_SPI_TIMEOUT)。 + */ +static volatile uint8_t s_spi1_dma_done; /* DMA 阈值(字节数超过此值使用 DMA 传输):取值定义在头文件,理由如下—— * 阈值以下走 CPU 轮询。存在把**栈上小缓冲**直接传进本驱动做收发的大小 @@ -68,11 +74,11 @@ volatile uint8_t g_spi1_dma_done; * 入口参数:hspi - 触发回调的 SPI 句柄 SPI_HandleTypeDef* * 返回值:无 * 限定条件:由 HAL DMA 中断调用 - * 函数说明:仅当 SPI1 传输完成时置位 g_spi1_dma_done 标志 + * 函数说明:仅当 SPI1 传输完成时置位 s_spi1_dma_done 标志 */ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { - g_spi1_dma_done = 1U; + s_spi1_dma_done = 1U; } } @@ -84,10 +90,32 @@ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { */ void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { - g_spi1_dma_done = 1U; + s_spi1_dma_done = 1U; } } +/* + * 函数功能:SPI1 DMA 完成通知(供实际注册 HAL 回调的一方转调) + * 入口参数:无 + * 返回值:无 + * 限定条件:由 HAL SPI 回调(中断上下文)调用;只写一个标志,不阻塞 + * 函数说明:HAL 的 SPI 回调在整个工程里只能有一份实现,本模块不抢占 Error 回调 + * 的所有权,只导出这两个通知入口。**转调方不得省略本调用**——少了它, + * 出错路径只能靠 GD5F_SPI_TIMEOUT 超时退出(能自保但会静默变慢)。 + */ +void gd5f_spi1_notify_done(void) { + s_spi1_dma_done = 1U; +} + +/* + * 函数功能:SPI1 DMA 错误通知(释放等待者;语义与 notify_done 相同) + * 入口参数:无 + * 返回值:无 + * 限定条件:由 HAL SPI 错误回调(中断上下文)调用 + */ +void gd5f_spi1_notify_error(void) { + s_spi1_dma_done = 1U; +} /* ======================== 私有函数声明 ======================== */ static int gd5f_page_program(uint32_t page_addr, uint16_t column, @@ -318,12 +346,12 @@ int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, if (use_dma != 0U) { uint32_t tick_start = HAL_GetTick(); - g_spi1_dma_done = 0U; + s_spi1_dma_done = 0U; if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) { GD5F_CS_HIGH(); return GD5F_ERROR; } - while (g_spi1_dma_done == 0U) { + while (s_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); @@ -399,13 +427,13 @@ int32_t gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) { if (use_dma != 0U) { uint32_t tick_start = HAL_GetTick(); - g_spi1_dma_done = 0U; + s_spi1_dma_done = 0U; if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size) != HAL_OK) { GD5F_CS_HIGH(); return GD5F_ERROR; } - while (g_spi1_dma_done == 0U) { + while (s_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h index 9e72971d..5fb1ef38 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h @@ -324,6 +324,19 @@ void nand_ftl_wd_beat(void); */ void nand_ftl_wd_feed(void); +/* + * 函数功能:SPI1 DMA 完成 / 错误通知(由实际注册 HAL SPI 回调的模块转调) + * 入口参数:无 + * 返回值:无 + * 限定条件:中断上下文(HAL SPI 回调内),两者只写一个完成标志 + * 函数说明:HAL 的 SPI 完成/错误回调是全局唯一符号,本模块只实现 TxCplt/RxCplt + * 两个(供自己的收发用),Error 回调的所有权留给回调的实际注册方 + * (当前 = Drivers/BSP/CH395F/ch395f.c,按 hspi->Instance 分发)。**SPI1 + * 分支必须转调这两个函数**,否则本模块的 DMA 等待在出错时只能靠超时 + * 兜底。2026-10-22:取代原先被 CH395F 跨模块裸 extern 直接写的全局变量。 + */ +void gd5f_spi1_notify_done(void); +void gd5f_spi1_notify_error(void); #ifdef __cplusplus } #endif diff --git a/docs/images/任务栈预算.svg b/docs/images/任务栈预算.svg index 023bfc38..e30d1cf6 100644 --- a/docs/images/任务栈预算.svg +++ b/docs/images/任务栈预算.svg @@ -13,7 +13,7 @@ defaultTask - 配置 5120B | 实测峰值 2164B | 最坏 3024B | 余量 2096B (40%) PASS + 配置 5120B | 实测峰值 2152B | 最坏 3024B | 余量 2096B (40%) PASS netTask @@ -21,7 +21,7 @@ ftpTask - 配置 4096B | 实测峰值 756B | 最坏 1456B | 余量 2640B (64%) PASS + 配置 4096B | 实测峰值 764B | 最坏 1456B | 余量 2640B (64%) PASS uiTask @@ -41,11 +41,11 @@ iec104Task - 配置 2560B | 实测峰值 544B | 最坏 1456B | 余量 1104B (43%) PASS + 配置 2560B | 实测峰值 540B | 最坏 1456B | 余量 1104B (43%) PASS protectTask - 配置 2048B | 实测峰值 828B | 最坏 872B | 余量 1176B (57%) PASS + 配置 2048B | 实测峰值 624B | 最坏 872B | 余量 1176B (57%) PASS idle 512B(未实测) diff --git a/docs/images/内存布局_FLASH.svg b/docs/images/内存布局_FLASH.svg index 91b2ac34..f483f73b 100644 --- a/docs/images/内存布局_FLASH.svg +++ b/docs/images/内存布局_FLASH.svg @@ -10,32 +10,32 @@ Flash (1MB) 使用布局 — 2026-10-22 @build 代码段 (Code) 208.2KB - + 只读数据 (RO) 18.1KB - + Flash 空闲 796.5KB -已用: 227.5KB (22.2%) 空闲: 796.5KB (77.8%) ER_IROM1(Code+RO): 226.3KB (22.1%) 总量: 1MB (= ROM 合计口径) -Code + RO 分布(ER_IROM1 226.3KB,按 .map 对象归类) +已用: 227.5KB (22.2%) 空闲: 796.5KB (77.8%) ER_IROM1(Code+RO): 226.4KB (22.1%) 总量: 1MB (= ROM 合计口径) +Code + RO 分布(ER_IROM1 226.4KB,按 .map 对象归类) 85.7KB - + 55.7KB - - 25.1KB - - 17.5KB - - - - - - + + 25.0KB + + 17.5KB + + + + + + LVGL 库 85.7KB 应用代码/UI 55.7KB - CH395F+FTP 25.1KB + CH395F+FTP 25.0KB HAL 驱动 17.5KB diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 597c7b8f..306952af 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -38,10 +38,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 231,768 B (226.34 KB) | 1,048,576 B (1024 KB) | 22.1% | +| ER_IROM1 | 231,784 B (226.35 KB) | 1,048,576 B (1024 KB) | 22.1% | | RW_IRAM1 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **232,952 B (227.5 KB)** | 1,048,576 B (1024 KB) | **22.2%** | +| **Flash (ROM 合计)** | **232,968 B (227.5 KB)** | 1,048,576 B (1024 KB) | **22.2%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index cb8f73da..47f9210a 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 231,768 B | 1,048,576 B | 22.1% | 参考 | +| ER_IROM1 (Flash) | 231,784 B | 1,048,576 B | 22.1% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 75,264 B | 131,072 B | 57.4% | PASS | diff --git a/docs/内存管理/data/ram_report_20261022.md b/docs/内存管理/data/ram_report_20261022.md index cd7243f4..bb26458e 100644 --- a/docs/内存管理/data/ram_report_20261022.md +++ b/docs/内存管理/data/ram_report_20261022.md @@ -1,24 +1,24 @@ # STM32F4-Base RAM/Flash Usage Report -> Generated: 2026-10-22 12:06 Map: `STM32F407-Demo.map` +> Generated: 2026-10-22 19:14 Map: `STM32F407-Demo.map` ## 1. Grand Totals (全局总量) | Metric | Value | |---|---| -| Code (inc. data) | 213,188 B (208.2 KB) | +| Code (inc. data) | 213,204 B (208.2 KB) | | RO Data | 18,580 B (18.1 KB) | | RW Data (initialized) | 1,184 B (1.16 KB) | | ZI Data (zero-init) | 131,504 B (128.4 KB) | -| **Total RO (Code + RO)** | **231,768 B (226.3 KB)** | +| **Total RO (Code + RO)** | **231,784 B (226.4 KB)** | | **Total RW (RW + ZI) = SRAM used** | **132,688 B (129.58 KB)** | -| **Total ROM (Code + RO + RW init)** | **232,952 B (227.5 KB)** | +| **Total ROM (Code + RO + RW init)** | **232,968 B (227.5 KB)** | ## 2. 执行区水位 (Execution Regions) | Region | Base | Size | Max | Util% | |---|---|---|---|---| -| ER_IROM1 | 0x08000000 | 231,768 B (226.34 KB) | 1,048,576 B (1024 KB) | 22.1% | +| ER_IROM1 | 0x08000000 | 231,784 B (226.35 KB) | 1,048,576 B (1024 KB) | 22.1% | | RW_CCM | 0x10000000 | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | | UNINIT_CCM | 0x1000FF00 | 48 B (0.05 KB) | 256 B (0 KB) | 18.8% | | RW_IRAM1 | 0x20000000 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% | @@ -140,7 +140,7 @@ | 4 | ui_net_sink.o | 1,126 | 67 | 20 | 6,746 | 6,766 | | 5 | ui_port.o | 418 | 0 | 12 | 6,408 | 6,420 | | 6 | nand_ftl.o | 2,352 | 175 | 16 | 6,200 | 6,216 | -| 7 | ch395f.o | 3,986 | 603 | 10 | 4,152 | 4,162 | +| 7 | ch395f.o | 3,978 | 603 | 10 | 4,152 | 4,162 | | 8 | console_log.o | 980 | 55 | 24 | 4,096 | 4,120 | | 9 | sys_log_mcu.o | 3,074 | 83 | 36 | 3,552 | 3,588 | | 10 | default_task.o | 2,332 | 280 | 12 | 2,724 | 2,736 | @@ -150,7 +150,7 @@ | 14 | cfg_file_store_mcu.o | 380 | 24 | 0 | 1,104 | 1,104 | | 15 | soe_store.o | 816 | 28 | 20 | 1,064 | 1,084 | | 16 | startup_stm32f407xx.o | 36 | 392 | 0 | 1,024 | 1,024 | -| 17 | gd5f2gq5ue.o | 3,930 | 478 | 20 | 800 | 820 | +| 17 | gd5f2gq5ue.o | 3,954 | 478 | 20 | 800 | 820 | | 18 | iec104_serv.o | 1,188 | 16 | 16 | 712 | 728 | | 19 | cfg_file.o | 5,970 | 810 | 20 | 704 | 724 | | 20 | spi.o | 584 | 0 | 0 | 560 | 560 | @@ -204,7 +204,7 @@ | 68 | vol_lock.o | 92 | 0 | 4 | 0 | 4 | | 69 | errno.o | 20 | 0 | 4 | 0 | 4 | | 70 | ui_page_net_cfg.o | 1,004 | 105 | 2 | 0 | 2 | -| | **All .o files** | **213,188** | **18,580** | **1,184** | **131,504** | **132,688** | +| | **All .o files** | **213,204** | **18,580** | **1,184** | **131,504** | **132,688** | ## 5. Flash (Code + RO Data) Top-30(按大小降序) @@ -215,11 +215,11 @@ | 3 | lftpd.o | 6,108 | 509 | 6,617 | | 4 | net_socket.o | 4,916 | 443 | 5,359 | | 5 | lv_indev.o | 4,928 | 0 | 4,928 | -| 6 | ch395f.o | 3,986 | 603 | 4,589 | +| 6 | ch395f.o | 3,978 | 603 | 4,581 | | 7 | ui_font_cn_12.o | 0 | 4,545 | 4,545 | | 8 | lv_obj_pos.o | 4,526 | 16 | 4,542 | | 9 | lv_refr.o | 4,486 | 0 | 4,486 | -| 10 | gd5f2gq5ue.o | 3,930 | 478 | 4,408 | +| 10 | gd5f2gq5ue.o | 3,954 | 478 | 4,432 | | 11 | tasks.o | 4,342 | 0 | 4,342 | | 12 | lv_obj_scroll.o | 4,126 | 0 | 4,126 | | 13 | lv_obj_style.o | 4,106 | 0 | 4,106 | @@ -240,6 +240,6 @@ | 28 | printfa.o | 2,312 | 0 | 2,312 | | 29 | stm32f4xx_hal_i2c.o | 2,294 | 0 | 2,294 | | 30 | lv_display.o | 2,284 | 0 | 2,284 | -| | **All .o files** | **213,188** | **18,580** | **231,768** | +| | **All .o files** | **213,204** | **18,580** | **231,784** | -> 自洽校验:per-file 213,188/18,580/1,184/131,504 vs Grand Totals 213,188/18,580/1,184/131,504(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS** +> 自洽校验:per-file 213,204/18,580/1,184/131,504 vs Grand Totals 213,204/18,580/1,184/131,504(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS** diff --git a/docs/内存管理/data/stack_report_20261022.md b/docs/内存管理/data/stack_report_20261022.md index 0acd4d45..f688fb42 100644 --- a/docs/内存管理/data/stack_report_20261022.md +++ b/docs/内存管理/data/stack_report_20261022.md @@ -1,7 +1,7 @@ # Stack budget report - Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph) -- Linker run: Thu Oct 22 10:36:17 2026 +- Linker run: Thu Oct 22 19:12:02 2026 - Global maximum stack usage: **3024 B** (any call tree) - Runtime watermark: 8 task(s) from 串口采集日志 `.dsh\skills\serial-assistant\scripts\state\serial.log` @@ -9,15 +9,15 @@ Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B | Task | Entry | Cfg (B) | Static max (B) | Runtime peak (B) | Worst (B) | Margin (B) | Margin % | Verdict | |---|---|---|---|---|---|---|---|---| -| defaultTask | `StartDefaultTask` | 5120 | 3024 | 2164 | 3024 | 2096 | 40% | PASS | +| defaultTask | `StartDefaultTask` | 5120 | 3024 | 2152 | 3024 | 2096 | 40% | PASS | | netTask | `StartNetTask` | 3840 | 1912 | 1116 | 1912 | 1928 | 50% | PASS | -| ftpTask | `StartFtpTask` | 4096 | 1456 | 756 | 1456 | 2640 | 64% | PASS | +| ftpTask | `StartFtpTask` | 4096 | 1456 | 764 | 1456 | 2640 | 64% | PASS | | uiTask | `StartUiTask` | 16384 | 1336 | 4052 | 4052 | 12332 | 75% | PASS | | testTask | `start_test_task` | 8192 | 736 | - | 736 | 7456 | 91% | PASS | | timeSyncTask | `time_sync_task` | 2560 | 1264 | 616 | 1264 | 1296 | 50% | PASS | | adcTask | `adc_task_func` | 2560 | 1320 | 952 | 1320 | 1240 | 48% | PASS | -| iec104Task | `iec104_serv_task` | 2560 | 1456 | 544 | 1456 | 1104 | 43% | PASS | -| protectTask | `protect_task` | 2048 | 872 | 828 | 872 | 1176 | 57% | PASS | +| iec104Task | `iec104_serv_task` | 2560 | 1456 | 540 | 1456 | 1104 | 43% | PASS | +| protectTask | `protect_task` | 2048 | 872 | 624 | 872 | 1176 | 57% | PASS | `Static max` = armlink worst-case call-tree depth; `Runtime peak` = measured high-water mark (boot + whatever workload ran before the sample); `Worst` = max of the two, which is what the verdict is computed from. diff --git a/docs/环境与部署/项目部署环境要求.md b/docs/环境与部署/项目部署环境要求.md index f3462436..8f3a3cb9 100644 --- a/docs/环境与部署/项目部署环境要求.md +++ b/docs/环境与部署/项目部署环境要求.md @@ -504,7 +504,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级 | 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 | |---|---|---|---|---|---| -| Flash(`ER_IROM1` = Code + RO) | 231,768 B(226.3 KB) | 1 MB | 22.1% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 | +| Flash(`ER_IROM1` = Code + RO) | 231,784 B(226.4 KB) | 1 MB | 22.1% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 | | 主 SRAM1(静态区;FreeRTOS 堆已迁 CCM) | 75,264 B(73.5 KB) | 128 KB | 57.4% | 55,808 B | 已不紧张——2026-10-11 堆迁 CCM 与 2026-10-15 `LV_MEM_SIZE` 40→16 KB 两步的结果 | | CCM(`RW_CCM` 段) | 57,376 B(56.0 KB) | 65,280 B | **87.9%** | **7,904 B** | **当前真正的红线**:加任务 / 抬栈 / 加堆会先在这里撞墙;`mem-budget` 钩子在 92% 拦提交 | diff --git a/docs/问题记录/项目审查与优化建议报告.md b/docs/问题记录/项目审查与优化建议报告.md index dd5d47a6..1c875bd1 100644 --- a/docs/问题记录/项目审查与优化建议报告.md +++ b/docs/问题记录/项目审查与优化建议报告.md @@ -49,7 +49,7 @@ | 内存 RAM | N-14 🟡(N-22) | FTL 单页写回缓存无空闲/掉电强制 flush(整页写与换页已 flush,`f_close` 经 `CTRL_SYNC` 已落盘) | 用户裁定 2026-10-22 暂不做;残余窗口只是"单条记录" | | Flash | **F-5 🟡 观察项**(N-28) | uvprojx 201 个 LVGL `.c` 里 **127 个空 TU**;增量构建 4~5 s、CI 不跑 `@build` | 有实测全量耗时痛点再做,用 `` | | 结构 | **S-1 ❌**(口径已按实测更正) | **不是"命名错位",是真实的 BSP→App 反向依赖**:`Drivers/BSP/NETCFG/{net_cfg,ftp_cfg}.c` include `App/cfg/cfg_file.h`(10 处调用点:`cfg_file_load/save/poll/register`),`Drivers/BSP/UI` 另有 3 个文件 include App 层头(`ui_page_runlog.c`、`ui_page_selftest.c`、`ui_w_records.h`);21 个 BSP 文件用 `dbg_log.h`(横切,待显式豁免) | **反转依赖**(约 7 文件 / 15 处)。⚠ 修正一条我先前写下的理由:这不是"运行时属主纪律被破"——UI 只投递 `net_cfg_save_request()`,写盘仍在 defaultTask,且 NETCFG 已有 `net_cfg_set_save_req_cb` / `net_cfg_request_apply` 的**回调注入**先例;也不是"测不了"——模拟器今天就在主机侧跑 `net_cfg_load → poll → save` 整条链(`../../simulator/README.md` 的验证链)。要修的只是**静态分层方向**(BSP 引用了 App 的头)+ 加一条「`Drivers/BSP/**` 不得 include `App/**`」的门禁;**目录搬迁暂缓**(远程 CI 仍不跑 `@build`,兜底未成立) | -| 结构 | **S-2 大半已消**(24 处手写已消 22 处) | **该修的手写跨模块裸 `extern` 共 24 处,今天已消 22 处,只剩 2 处**:`ch395f.c:177`(CH395F 裸写 GD5F 的 `g_spi1_dma_done`,还在 `HAL_SPI_ErrorCallback` 的 SPI1 分支里**替 GD5F 释放等待者**——谁清理那两行「看着无关」的分支,编译零报错,GD5F 的 DMA 出错就退化成干等 `GD5F_SPI_TIMEOUT`)与 `net_socket.c:77`(`netMsgQueueHandle` 跨文件裸引)。已消的 22 处 = 2 处与 `Inc/spi.h` / `Inc/i2c.h` 重复(GD5F/SD2506)+ 19 处任务句柄与属性(`default_task.c:52-69` 的 18 行 + `adc_task.c:125`)+ 1 处 `main.c` 函数内的 `g_rcc_csr_latched`。**两类不算债要分清**:`stm32f4xx_it.c:241-256` 那 16 条是 CubeMX「External variables」生成块(删了会被重生成写回来);`:183/195/207/220` 那 4 条是手写在 `__asm` Handler 里引用汇编标签 `fault_entry_*`(ARMCC `__asm` 取不到 C 数据符号,**合法例外**)。上一版写的「25 处」把这两类混进来了,已更正 | 剩余两步:① `ch395f.c` 改为经 GD5F 导出的 `gd5f_spi1_notify_done()` / `notify_error()` 转调(标志收回 GD5F 内部)② `netMsgQueueHandle` 收进 net_socket 内部 + 提供投递 API | +| 结构 | **S-2 只剩 1 处**(24 处手写已消 23) | 今天已消 23 处:2 处与 `Inc/spi.h`/`Inc/i2c.h` 重复(GD5F/SD2506)+ 19 处任务句柄与属性(新建 `App/task/task_handles.h`)+ 1 处 `main.c` 函数内 `g_rcc_csr_latched`(改由 `App/app/app_main.h` 声明)+ 1 处 `ch395f.c:177`(**改为契约转调**:GD5F 的完成标志收为文件内 `static`,导出 `gd5f_spi1_notify_done()` / `gd5f_spi1_notify_error()`,CH395F 那个"HAL 全局唯一回调"的 SPI1 分支只转调、不再直接写它的变量;`HAL_SPI_AbortCpltCallback` 仍无人实现 → 另列观察)。**只剩 `net_socket.c:77` 的 `netMsgQueueHandle`**。**两类不算债**:`stm32f4xx_it.c:241-256` 是 CubeMX「External variables」生成块(删了会被写回来);`:183/195/207/220` 是 `__asm` Handler 里的汇编标签引用(ARMCC `__asm` 取不到 C 数据符号,**合法例外**) | 剩下一步:`netMsgQueueHandle` 收进 net_socket 内部 + 提供投递 API | | 驱动规范 | S-5 ❌ | `bsp_err_t` 至今只出现在本报告里(无统一错误码/超时/重试约定) | 定义后进 `docs/代码规范/` | | 网络 | N-09 / PHY ❌(N-25) | `NET_MAX_SOCKETS` 仍为 8(与单连接复用语义不符);PHY 强制 100M 无自协商回退;PASV 每次新建监听 | 见 §4.2;随 N-25 一并处置 | | 存储 | N-15 🟡(N-25) | `nand_ftl.o` 静态 6,216 B——三块 2 KB 级页缓冲并存未合并 | 与 R-3 同批评估 | @@ -88,7 +88,7 @@ git ls-files -z | xargs -0 stat -c '%s' | 名词 | 口径 | |---|---| -| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **231,768 B(22.1%)**;`ROM 合计` 再加 `RW` 初始化表 = 232,952 B(22.2%) | +| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **231,784 B(22.1%)**;`ROM 合计` 再加 `RW` 初始化表 = 232,968 B(22.2%) | | SRAM 占用 | `RW_IRAM1`(DMA 可达区)与 `RW_CCM`(仅 CPU)分开计,二者不可互换(规则 R2/R3) | | 最坏栈 | `max(armlink 静态调用图深度, 真机 [STACK] 高水位)`;判定线 `余量 ≥ max(1024 B ISR, 配置×30%)` | | 编译单元 | uvprojx 的 `` 计数(今日 **326 个 `.c`**,其中 LVGL 201);"自研 `.c`" 另按目录口径 114 个 | @@ -225,8 +225,8 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 | 指标 | 2026-10-22 | 2026-10-15 | 说明 | |---|---|---|---| -| `Code` | 213,188 B | 213,520 B | F-6 收口 −332 B | -| `ER_IROM1`(Code+RO) | **231,768 B / 22.1%** | 232,280 B / 22.2% | 余 ≈804 KB | +| `Code` | 213,204 B | 213,520 B | F-6 收口 −332 B;同日 SPI 回调归属纠正 +16 B | +| `ER_IROM1`(Code+RO) | **231,784 B / 22.1%** | 232,280 B / 22.2% | 余 ≈804 KB | | `RW_IRAM1` | **75,264 B / 57.4%**(余 55,808) | 73,568 B / 56.1% | 增量来自 B5/B8 功能码 | | `RW_CCM` | **57,376 B / 87.9%**(余 7,904) | 57,376 B / 87.9% | **当前唯一红线** | | FreeRTOS 堆 | 56 KB(57,344 B)@CCM;实测 `free=15,800` / `min_ever=8,464` | 同 | 2026-10-22 真机 | @@ -266,7 +266,7 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 | **F-6** 双精度软浮点 | ≈1 KB | **归因错**:全库 0 处 `%f`;剩余 `dadd/dmul/ddiv/depilogue/dfixul` 1,018 B 锁在 MicroLIB `printfa.o` 的 `_printf_core → _fp_digits`,只要用 `snprintf` 就删不掉。但**根因侧确有可修点**:`App/algo/prot_set.h` 两个 Q10 宏写成 `double` | ✅ **已落地 2026-10-22**:改单精度 → `@build` −332 B、5 个 helper(300 B)消失;数值逐位等价(穷举 42 个字面量 + int32 全域 ÷1024 往返 4.49×10⁶ 例零差异);真机 `@test` PASS | **当前可回收 Flash = 0 B**(F-4/F-6 已定论,图片链同理不打补丁)。LVGL 仍占镜像 **37.8%** -(87,706 / 231,768 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。 +(87,706 / 231,784 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。 ### 5.5 内存治理长效机制(常开,不受暂缓影响) @@ -374,7 +374,7 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止 | **D-1** | 外部资料与工程文档同仓同树 | ❌ 暂不处理 | 本文 §3.3、登记簿 N-29 | | D-2 ~ D-7 | 文档治理条目(链接/同源/编号/BOM 等) | ✅ 多数已制度化为钩子 | 本文 §3.2、`../文档规范.md` | | D-9 | 文档面残余观察 | 🟡 观察 | 本节即定义位 | -| **S-1 / S-2** | 分层反向依赖 / 跨模块裸 `extern` | 🟡 半办(2026-10-22:S-2 的 24 处手写已消 22 处、剩 2 处;S-1 未动 = NETCFG+UI 反向 include) | 本文 §0.4、登记簿 C5 | +| **S-1 / S-2** | 分层反向依赖 / 跨模块裸 `extern` | 🟡 半办(2026-10-22:24 处手写裸 extern 已消 23 处,只剩 `net_socket.c:77`;S-1 未动 = NETCFG+UI 反向 include) | 本文 §0.4、登记簿 C5 | | **S-4** | `app_cfg.h` 未接线占位开关 | ✅ **已闭环 2026-10-22**(4 个零消费点宏删除) | `App/app/app_cfg.h` 修改记录 v1.2;口径同步见 `../环境与部署/项目部署环境要求.md` §3.2/§3.5、`../产品需求/产品需求设计文档.md`、`../任务规划/排期详述.md` C5 | | **S-3** | `.ioc` 侧 USART1 配置 | ❌ 在策 | 登记簿 S-3 | | **S-5** | 统一 `bsp_err_t` 错误码约定 | ❌ 未做 | 本文 §0.4、`docs/代码规范/`(待建) | @@ -409,6 +409,27 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止 **改判的三条旧结论**:① §6 的 U1/U3/U4 原写「❌ 未建」,实际已落地(`ui_tokens.h` 69 行、`ui_page_registry.c` 描述符注册表、`ui-golden`+`font-charset` 两枚钩子);U2 只有 `ui_w_records` 一个组件;"27 页未落地"实为 7 页。② "新增一页 ≤30 行"目标**未达**(最省 40 行),如实记为未达项。③ §0.4 与登记簿已漂开 → 本版把待办清单降级为**快照 + 指向登记簿**,权威只留一处。 **刻意保留的三层**(删了会出事):§9 **编号族登记处**——本文是 `id_registry.json` 里 `N-RPT`/`D-RPT`/`S-RPT`/`R-RPT`(游标 R-9)/`F-RPT`/`U-RPT`(游标 U5) 六族的属主登记处,且外部存在「报告 N-14」「报告 F-4/F-5/F-6」「报告 D-1」「报告 R-2/R-3」式紧邻引用与 `check_dangling`,删定义位即触发编号门禁;§1.2/§1.3 方法与口径(数字可重跑);文末**变更记录 + 已销项去处表**原样冻结保留(52 行)。被外部点名的锚点(`§0.3`/`§3.1`/`§4.1`/`§4.2`/`§4.3`/`§5.3`×9/`§5.4`/`§5.5`×4/`§6.2`/`§6.3`/`§6.5`)逐个保留,**68 处入站引用零改动**。 **门禁**:`check_doc_links.py --all --sections-strict` → 146 文档 0 问题(章节引用 2,499、可判定 1,483);`check_ids.py --strict` 增量拦截通过、**存量债 650 → 641(−9)**、可判定比例 82.98% → **82.79%**(仍高于基线 82.6%,但缓冲从 0.38pp 缩到 0.19pp——删掉的那批引用 99% 是可判定的,属预期副作用);`mem-numbers` / `ram_baseline --gate` / `task-table` / `cubemx-survival` 全 0。字节卫生:BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格 / LF 结尾。 | +| **2026-10-22** | **S-2 第三步:SPI1 完成标志的回调归属拆清**(推荐清单第 4a 步) | 问题不是风格:HAL 的 SPI 完成/错误回调 +是**全局唯一符号**,全工程只能有一份实现,当前由 CH395F 代管并按 `hspi->Instance` 分发——于是 GD5F 的 +`g_spi1_dma_done` 被 CH395F 跨模块裸 `extern` 直接写,**GD5F 出错时的"释放等待者"寄居在别人的 .c 里**。 +谁哪天做"CH395F 清理",把那两个 `else if (SPI1)` 分支当无关代码删掉:**编译零报错**,GD5F 的 DMA 等待 +在出错时只能干等 `GD5F_SPI_TIMEOUT` 兜底。 +| | **改法(时序一字不动,只换所有权)**:标志收为 GD5F 文件内 `static s_spi1_dma_done`;GD5F 导出 +`gd5f_spi1_notify_done()` / `gd5f_spi1_notify_error()`(头文件里写明"SPI1 分支必须转调这两个函数"); +CH395F 删掉裸 `extern`、include `gd5f2gq5ue.h`、两个 SPI1 分支改为转调并就地注明"勿删"。 +`HAL_SPI_AbortCpltCallback`(`HAL_SPI_DMAStop()` 会触发)仍无人实现,**本次刻意不做**——它会改变中止 +语义,属真机专项,当前超时兜底可用。 +| | **体量代价**:`ch395f.o` −8 B、`gd5f2gq5ue.o` +24 B → `Code` 213,188 → **213,204 B(+16 B)**, +RO/RW/ZI 全部不变,`ER_IROM1` 231,768 → **231,784 B(22.1%)**。16 B 换一个静默失效类,划算。 +数字联动一并做掉:`data/ram_report_20261022.md`、`data/stack_report_20261022.md` 重跑;`ram_baseline +--update/--write`;`check_mem_numbers --write`;三张配图常量更新后重新生成(逐文本按字号与锚点复查 +越界 = **0**);本文 §1.3/§5.1/§5.4 与 `docs/环境与部署/项目部署环境要求.md` §3.3 的 Flash 数字同步。 +| | **真机验证 `@test` PASS**:无 `[ERR]`/Fault,复位原因 software;`[STACK] heap free=15800 +min_ever=8464`**与改前逐项相同**;各任务实测峰值 default 2152 / net 1116 / ftp 764 / ui 4052 / +timesync 616 / adc 952 / iec104 540 / protect 624,**静态最坏链全部未变**(3024/1912/1456/4052/1264/ +1320/1456/872)→ 9/9 PASS。⚠ 记录一条读数陷阱:测试脚本打印的"boot to net init: OK = 7,907 ms"是 +采集窗口锚点造成的度量偏差,真机日志里 `boot: syslog init OK → net init: OK` 实测 ≈699 ms,与改 +前一致,**不是性能回退**。 | | **2026-10-22** | **S-2 第二步:19 处任务句柄裸 extern 收拢成引用契约**(推荐清单第 3 步) | 新建 `App/task/task_handles.h`——**只有声明、没有 `.c`**:头文件不需要双端注册,`../App/task` 本来就在 uvprojx 的 `` 里,所以这一步零构建面。`default_task.c:52-69` 的 18 行 + `adc_task.c:125` 的 1 行局部 extern 全部删除改 include(栈水位表本体仍留在 `default_task.c`);同批把 `Src/main.c` 函数内的 `extern volatile uint32_t g_rcc_csr_latched;` 移进 `App/app/app_main.h`(该文件 `main.c` 本来就 include), USER CODE 区内只留「读 RCC_CSR → 写 RMVF 清除」两句、顺序约束的原注释保留。**价值**:原先每加一个任务都得记得往 `default_task.c` 再抄两行 extern,抄漏一行 = 读别人的栈(静默);现在只有一个改动点,且 CubeMX 若把句柄改名会在链接期报错而不是静默通过。**镜像逐项体积不变**(`Code=213,188 RO=18,580 RW=1,184 ZI=131,504`,`@build` 0 错 0 警告)证明行为中性。`check_cubemx_survival.py` 对这次有意的 USER CODE 区改动报「漂移」,已按其文档流程 `--write`重建基线(`.cubemx_survival_baseline.json` 仅 1 行变化),`--check` 复跑通过、易失清单 29 项全部到位;顺带把 §0.4 的 S-2 口径改成精确分类:**手写该修 24 处(已消 22)+ CubeMX 生成块 16 条(不算债)+ `__asm` 汇编标签 4 条(合法例外)**——上一版「25 处」混了这三类。 | | **2026-10-22** | **结构债 S-4 闭环 + S-2 第一步(用户批准「先只跑 1+2」)** | **S-4**:删除 `App/app/app_cfg.h` 里 4 个**零消费点**宏(`APP_NET_ENABLE`/`APP_ADC_ENABLE`/`APP_RS485_ENABLE`/`APP_SYS_TIME_SRC`),文件降格为「决策占位说明」——修改记录 v1.2 写明为什么删与将来怎么接回;并去掉 `app_main.c` 里只为它存在的 include。留「看着能裁剪、其实不生效」的宏比不留更糟:它不产生任何编译期效果,却让评审误信模块可裁剪;此前为了说明它,AGENTS 之外还得在 6 处文档各写一句「未接线占位,别当开关用」。同步改口径 **17 处**:`App/app/README.md`、`App/README.md`、根 `README.md`、`项目架构总览.md`、`项目部署环境要求.md`(§3.2 两行 + 配置表一行)、`产品需求设计文档.md`(B0 结论 + 配置表)、`基线与待确认.md`、`排期详述.md` C5、`登记簿.md` N-23 残余、`RAM使用分析与优化方案.md` 三处。 diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index f41da585..468008b2 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -4,4 +4,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-10-14 212296 18740 1112 154456 231036 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924 2026-10-15 213540 18740 1136 131520 232280 22.2 75232 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 2026-10-20 213356 18580 1184 131480 231936 22.1 75240 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 -2026-10-22 213188 18580 1184 131504 231768 22.1 75264 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 +2026-10-22 213204 18580 1184 131504 231784 22.1 75264 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 diff --git a/tools/arch_diagram/gen_memory_svg.py b/tools/arch_diagram/gen_memory_svg.py index f5e1a41f..e8b8caca 100644 --- a/tools/arch_diagram/gen_memory_svg.py +++ b/tools/arch_diagram/gen_memory_svg.py @@ -47,11 +47,11 @@ RAM2_CATEGORIES = [ # ── Flash breakdown (1MB = 1,048,576 B; 2026-10-22 @build) ────────────────── FLASH_TOTAL = 1048576 -FLASH_CODE = 213188 +FLASH_CODE = 213204 FLASH_RO = 18580 FLASH_RW = 1184 FLASH_USED = FLASH_CODE + FLASH_RO + FLASH_RW # ROM 合计(含 RW 初始化表) -FLASH_IROM = FLASH_CODE + FLASH_RO # ER_IROM1 区用量 = 231,768 B(22.1%) +FLASH_IROM = FLASH_CODE + FLASH_RO # ER_IROM1 区用量 = 231,784 B(22.1%) FLASH_FREE = FLASH_TOTAL - FLASH_USED FLASH_CATEGORIES = [ @@ -61,14 +61,14 @@ FLASH_CATEGORIES = [ ("Flash 空闲", FLASH_FREE, "#E0E0E0"), ] -# Flash Code+RO 分布(= ER_IROM1 231,768 B;2026-10-22 按 .map 对象表逐对象归类, +# Flash Code+RO 分布(= ER_IROM1 231,784 B;2026-10-22 按 .map 对象表逐对象归类, # 口径同 data/ram_report_20261022.md;"其他"含链接器填充) FLASH_CODE_SUB = [ ("LVGL 库", 87706, "#4CAF50"), ("应用代码/UI", 57011, "#2196F3"), - ("CH395F+FTP", 25652, "#F44336"), + ("CH395F+FTP", 25644, "#F44336"), ("HAL 驱动", 17876, "#607D8B"), - ("dhara / FTL", 10907, "#FF9800"), + ("dhara / FTL", 10931, "#FF9800"), ("FreeRTOS 内核", 10762, "#00BCD4"), ("FatFS", 8468, "#9C27B0"), ("C 库 (microlib)", 5486, "#795548"), @@ -81,15 +81,15 @@ FLASH_CODE_SUB = [ # worst = max(静态最坏深度, 实测峰值); margin = cfg - worst # 9 行含 testTask(仅测试构建创建、无实测 → 取静态最坏链,峰位列填同值) TASK_STACKS = [ - ("defaultTask", 5120, "Normal", 3024, 2164, 2096, 40, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + LED"), + ("defaultTask", 5120, "Normal", 3024, 2152, 2096, 40, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + LED"), ("netTask", 3840, "Normal", 1912, 1116, 1928, 50, "PASS", "CH395F 轮询、消息处理"), - ("ftpTask", 4096, "Normal", 1456, 756, 2640, 64, "PASS", "lftpd FTP 服务(大缓冲已静态化)"), + ("ftpTask", 4096, "Normal", 1456, 764, 2640, 64, "PASS", "lftpd FTP 服务(大缓冲已静态化)"), ("uiTask", 16384, "BelowNormal", 4052, 4052, 12332, 75, "PASS", "LVGL UI 主循环 + 远程显示推流"), ("testTask", 8192, "BelowNormal", 736, 736, 7456, 91, "PASS", "测试套件(产品构建不创建,取静态最坏链)"), ("timeSyncTask", 2560, "Normal", 1264, 616, 1296, 50, "PASS", "TCP 时间同步(8888)"), ("adcTask", 2560, "AboveNormal", 1320, 952, 1240, 48, "PASS", "1 kHz 节拍消费者(L1 采集,纯取数)"), - ("iec104Task", 2560, "Normal", 1456, 544, 1104, 43, "PASS", "IEC 60870-5-104 上行(2404)"), - ("protectTask", 2048, "Normal", 872, 828, 1176, 57, "PASS", "20 ms 保护判据 + 闭锁 + 定值校验和巡检"), + ("iec104Task", 2560, "Normal", 1456, 540, 1104, 43, "PASS", "IEC 60870-5-104 上行(2404)"), + ("protectTask", 2048, "Normal", 872, 624, 1176, 57, "PASS", "20 ms 保护判据 + 闭锁 + 定值校验和巡检"), ] KERNEL_STACKS = [("idle", 512), ("timer", 1024)] # 128/256 words(未实测)