# STM32F4-Base > **编译验证:所有代码修改后必须执行 `@build` 验证编译通过(0 错误 0 警告),否则不要提交。** ## 构建 - **仅支持 Keil MDK-ARM v5 (ARMCC)**,无其他工具链 - 命令行编译:`@build`(执行 `MDK-ARM/build.bat`,从仓库根目录用**相对路径**调用) - `build.bat` 硬编码 Keil 路径 `C:\Keil_v5\UV4\UV4.exe`(本机已验证存在;换机器需改) - 退出码:0 = 0 错误 0 警告 | 1 = 有警告或错误(查看 `MDK-ARM/build_log.txt`) - ⚠️ **旧绝对路径陷阱**:仓库曾位于 `D:\Code\DTU 程序\STM32F4-Base`,`.opencode/command/*.md` 与 `test/run_boot_test.ps1` 中若出现该路径一律视为失效,改用仓库相对路径(如 `& "MDK-ARM\build.bat"`) ## 硬件 | 外设 | 接口 | 引脚 | |------|------|------| | CH395F 以太网 | SPI2 | PB12 CS, PB13 SCK, PB14 SDO, PB15 SDI | | GD5F2GQ5UE NAND | SPI1 | PE0 CS, PB3 SCK, PB4 MISO, PB5 MOSI, PB8 WP, PE1 HOLD | | TPAFE5160 ADC | 并行16位 | PG0-PG15 数据, PD3 RD, PD4 CONVST, PD7 BUSY | | USART1 | 串口 | PA9 TX, PA10 RX (115200bps) | | RS-485 | UART5 | PC12 TX, PD2 RX, PD0 DE(半双工) | | 6x LED | GPIO | PC4, PC5, PB1, PB2, PF11, PF12(低电平点亮) | MCU: STM32F407ZGTx @ 168MHz (HSE 8MHz → PLL M=4/N=168/P=2,见 `*.ioc`/`stm32f4xx_hal_conf.h`) ## 架构 ``` Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart → [FreeRTOS tasks: defaultTask, netTask, ftpTask, testTask, uiTask, timeSyncTask] ``` - **app_main_init 顺序**(时间子系统优先,使后续日志带时间戳):`sd2506_init` → `sys_clock_init`(墙钟基准)→ `rs485_init` → `gd5f2gq5ue_init` → `tpafe5160_init` → SD2506 诊断 → `tpafe5160_start_conv_irq` - **就绪屏障**(`App/sys_events.h` Event Flags,取代魔法延时/全局轮询):`SYS_EVT_NET_READY`(bit0, netTask 置位) / `SYS_EVT_STORAGE_READY`(bit1, defaultTask 置位) / `SYS_EVT_FTP_READY`(bit2) / `SYS_EVT_TIMESYNC_READY`(bit3);消费者用 `osEventFlagsWait` 阻塞等待 - **defaultTask**:FatFS 挂载(`f_mount` forced,无卷自动 `f_mkfs` FAT32)→ 置 `SYS_EVT_STORAGE_READY` → LED 闪烁循环 - **netTask**:延时 2s → `net_stack_bring_up()`(net_init + MAC + PHY 强制 100M 全双工 + 等链路 ≤3s)→ 置 `SYS_EVT_NET_READY` → 循环 `net_poll()` + `net_process_messages()` + `osDelay(1)`(tick=1ms) - **ftpTask**:`osEventFlagsWait(NET_READY | STORAGE_READY, WaitAll)` 后 `lftpd_start("/", 21, &ftp)`(已无 7s 魔法延时) - **testTask**:测试套件入口(`test/test_config.h` 选择;产品构建下空转) - **uiTask**:等 `SYS_EVT_NET_READY` 后 `ui_port_init` → `ui_menu_init` → 启动 UI net sink - **timeSyncTask**:TCP 墙钟时间同步(PC 推送) - 功能编译开关:`App/app_cfg.h`(`APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE`) ### 任务栈 | Task | 栈大小(words) | 优先级 | |---|---|---| | defaultTask | 2048 | Normal | | netTask | 4096 | Normal | | ftpTask | 4096 | Normal | | testTask | 4096 | BelowNormal | | uiTask | 16384 | BelowNormal | | timeSyncTask | 2048 | Normal | ## CH395F 设计原则 - **单连接 socket**:Socket 0 复用监听和数据通道,收到 CONNECT 后切换为数据通道,DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN - **中断统一处理**:所有 CH395F 中断在 `net_poll()` 中通过 `GET_GLOB_INT_STATUS` 集中读取并清除,不得在 ISR 或其他地方单独处理 ## NET 网络层 - `net_socket.h/c` — BSD Socket API;**单连接模式**(`ch395f_set_fun_para(0x09)`:多 Socket + TCP Server 单连接 + 手动关闭),连接停留在 Socket 0 - `net_select.h/c` — select/poll I/O 多路复用 - 线程安全通过消息队列(`netMsgQueue`)实现,所有 CH395F 操作在 netTask 中串行处理 - `net_init` 顺序:check_exist → reset → `set_fun_para(0x09)` → MSS/重传/KeepAlive → IP → `ch395f_init()` → Socket 0~7 缓冲分配 → ping 使能 - 静态 IP 192.168.1.100/24(gw 192.168.1.1),PHY 强制 100M 全双工 - **新文件需手动添加到 Keil MDK 工程**才能编译 ## 测试构建模式 - `test/test_config.h`:`TEST_SUITE_CH395F` / `TEST_SUITE_STORAGE` / `TEST_SUITE_GD5F` / `TEST_SUITE_RTC` **互斥**(一次构建只能启用一个,`#error`);全部注释 = 正常产品构建 - CH395F 套件沿用 `test/ch395f_test.h` 阶段宏:硬件层 Phase 1~5 与 NET 层 Phase 6~11 **互斥**(`#error`);硬件层模式下 netTask 不创建,testTask 独占 CH395F - 各模块测试指南见 `docs/测试说明/`(CH395F / GD5F / SD2506 / FTP) ## 关键文件 | 路径 | 说明 | |---|---| | `Src/main.c` | 入口,CubeMX 生成 + USER CODE 区域 | | `Src/freertos.c` | FreeRTOS task 创建,CubeMX 生成 + USER CODE 区域 | | `App/app_main.c` | 应用初始化流程编排 | | `App/sys_events.h` | 系统就绪事件(Event Flags 就绪屏障) | | `App/app_cfg.h` | 功能模块编译开关 | | `App/task/net_task.c` | netTask 主循环(网络初始化 + 轮询) | | `test/test_config.h` | 测试套件选择(互斥开关) | | `Drivers/BSP/NET/` | BSD Socket API 网络抽象层 + lftpd | | `Drivers/BSP/CH395F/` | CH395F SPI 驱动 | | `Drivers/BSP/GD5F2GQ5UE/` | NAND Flash 驱动 + dhara FTL + FatFS diskio | | `Drivers/BSP/TPAFE5160/` | ADC 并行接口驱动 | | `Drivers/BSP/SD2506/` | RTC 驱动 | | `Drivers/BSP/RS485/` | RS-485 通信(UART5 + PD0 DE) | | `Drivers/BSP/UI/` | LVGL UI 组件(与 PC 模拟器共用源码) | ## 驱动关键点 详见 `docs/` 目录: - `docs/问题记录/CH395F_Trap_Records.md` — CH395F 已知陷阱 - `docs/问题记录/GD5F2GQ5UE_Trap_Records.md` — GD5F2GQ5UE 已知陷阱 - `docs/问题记录/SD2506_Trap_Records.md` — SD2506 RTC 已知陷阱 - `docs/测试说明/CH395F_Test_Guide.md` — 网络测试说明(含构建模式与执行顺序) ## 串口调试(serial-mcp-server MCP/CLI) 通过 **Adancurusul/serial-mcp-server** 自主读取/控制 **USART1 串口**(PA9 TX / PA10 RX,115200bps)的打印流。 - **串口端口单一配置源:`.opencode/serial_config.json`**(当前本机为 `COM6`)。端口变化只改这个文件。 - 用法、采集窗口模式、已知陷阱等详见 **`docs/测试说明/serial-mcp-server_串口调试.md`** ## 自动化测试(AI 迭代闭环) 改完代码后运行 **`@test`** 可自动执行完整闭环:**构建验证 → 串口捕获武装(serial-mcp-server 采集窗口)→ 烧录(`MDK-ARM/download.bat`,下载后 Reset & Run)→ 解析捕获 JSON → `test/verify_log.ps1` 标记校验 → PASS/FAIL 判定并迭代**。 - **推荐一键命令**:`test/run_boot_test.ps1` = 采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 全流程,退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=日志文件错误 - 判定标准:`@test` 内部先 `@build`(0 错误 0 警告),再 `@download`(退出码 0),日志用 `verify_log.ps1` 校验(默认 Required:`App init OK`/`Network Init`/`net init: OK`;Forbidden:`FAIL`/`ERROR`/`HardFault`) - **采集必须在烧录前武装**(采集进程先打开 COM 等 first-byte,烧录触发复位后才抓得到开机日志);`run_boot_test.ps1` 的 `-FlashCmd` 分支已保证顺序 - 每次按模块定制标记(网络/存储/RTC/ADC/RS485)见 `docs/测试说明/自动化测试流程.md` - UI 已有 DBG 打印(`UI port init: OK` / `UI menu init: OK` / `UI net sink: started`),可用 `-Required "UI port init: OK;UI menu init: OK"` 自动回归 UI 初始化;屏幕内容仍需人工目检 ## 代码规范与 pre-commit - **提交被 pre-commit 钩子门禁**,任一失败即拦截:cppcheck 静态分析(warning/performance/portability/style)+ 代码规范机械检查 + U+FFFD 乱码检查 + gitlint commit message - 代码规范 V1.0(`docs/代码规范/嵌入式C语言代码规范(V1.0).md`):命名小写下划线(`g_`/`s_`/`p_`/`a_` 前缀)、头文件保护宏 `XXX_H`、常量在右、文件头模块注释块等;可机械校验项由 `scripts/check_coding_standard.py` 强制 - 检查范围仅项目自有代码;第三方(`Lib/`、`Middlewares/`、CMSIS/HAL、`lftpd`、`Drivers/BSP/UI`、`test/`)被排除 - 新机器启用:`pre-commit install`;全库扫描:`pre-commit run --all-files` - 详见 `docs/代码规范/pre-commit使用说明.md`、`docs/代码规范/cppcheck使用说明.md` ## LVGL PC 模拟器 `simulator/`(CMake + SDL2 2.32.10 预编译库)可在 PC 上开发调试 UI,**与 MCU 端共用 UI 源码**(`Drivers/BSP/UI/ui_menu.c`、`ui_font_cn_12.c`),无需烧录: ```powershell cd simulator cmake -B build -G "Visual Studio 16 2019" -A x64 cmake --build build --config Debug # 运行 simulator\build\Debug\lvgl_sim.exe ``` - 详见 `docs/UI 设计/LVGL模拟器部署与使用说明.md` - 中文字体:`ui_cn_chars.txt`(字符集)+ `tools/font_gen/` 生成 `Drivers/BSP/UI/ui_font_cn_12.c`,见 `docs/UI 设计/UI_中文字体生成与配置.md` ## 注意 - `Inc/` 和 `Src/` 中 CubeMX 生成的文件带有 `USER CODE BEGIN`/`END` 标记,自定义代码写在这些区域之间 - `Drivers/BSP/` 下的驱动文件为纯手工代码,不受 CubeMX 保护 - CH395F 每次 SPI 事务需用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹 - CH395F SPI PCB 设计问题,分频系数必须为 8,速度相对较低 - FreeRTOS V10.3.1 via CMSIS-RTOS V2,HAL 时基用 TIM7(非 SysTick),tick = 1ms - NVIC 优先级分组 4 位,外设中断优先级 ≥ 5(`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`) - **内存预算紧张**:任务栈目前普遍偏大(见 `待处理内容.md`),新增任务/缓冲/栈前必须先核算 RAM 余量 ## 源码编辑与编码安全(铁律) 本工程源码含大量中文注释,曾因错误的文本重写方式导致整文件乱码(`U+FFFD` 替换符,不可逆)。以下规则必须遵守: - **绝不用 `Get-Content` / `Set-Content` / `cmd` 文本重写含中文的源码文件**。这类"整文件重写"会按系统默认编码重新解码再写回,多字节中文必被 `U+FFFD` 替换且不可逆。 - **改代码只用 Edit 工具**(精确子串替换,不重新编解码整个文件,安全);批量替换用 Edit 的 `replaceAll`。 - 必须脚本级替换时,使用显式且一致的编码(**本工程源码为 UTF-8 无 BOM**,如 Python `open(p, encoding="utf-8")` 同编码回写)——但**最稳的是别碰**,只用 Edit / git。 - **发现乱码先别急着 `git checkout`**:`git checkout` 会丢弃所有未提交工作,使损失扩大。先评估能否用 Edit 仅改坏掉的那几行。 - 验证是否真损坏用字节级检查(统计 `EF BF BD` 出现次数),不要依赖终端/Read 渲染判断。