Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bb237e404 | |||
| b3beec633b | |||
| 0ccd07ee12 | |||
| a165c70875 | |||
| 961fe18ee6 | |||
| 4bfa0c0b61 | |||
| cbdeda6d90 | |||
| ed0d23d79d | |||
| 381ce0c8b7 | |||
| 792719747a | |||
| ad035c2508 | |||
| ff78e93883 | |||
| 8d2dcd09a6 | |||
| 205f3c2460 | |||
| a408c82ccb | |||
| 10b1d465c4 | |||
| 9a2480c712 | |||
| 400aba3590 | |||
| 64afbd71ed | |||
| 7b46a929b0 | |||
| 5074d0e8ac | |||
| 90c7f694aa | |||
| a347cd18cb | |||
| 9aa86bd561 | |||
| bb407fe867 | |||
| c7b799223c | |||
| 4340170305 | |||
| df59cd3d33 | |||
| bc07701ed5 | |||
| 0b59118ddd | |||
| 652efb848d | |||
| 78eff31de7 | |||
| 565f23ea18 | |||
| 02a05a7a63 | |||
| 4e9ac0d9c3 | |||
| 615a9b13e9 | |||
| cf33171c8f | |||
| 88160a5134 | |||
| 0a707034cc | |||
| d6bc3f6253 | |||
| 4375873083 | |||
| fc88bc8752 | |||
| a7f367f6a3 | |||
| c646df5c2a | |||
| 038a2542af | |||
| 46a74f24f3 | |||
| 75cf4c9fbf | |||
| 6d404d136e | |||
| d48be81478 | |||
| 66a1e8fd6a | |||
| f50b9c9e66 | |||
| fbe0726991 | |||
| e06d849007 | |||
| 9ed4c22022 | |||
| 4eb728583b | |||
| 1669cd216d | |||
| f72d08328f | |||
| fe107ea9af | |||
| 3466c4c062 | |||
| c8c7ec0992 | |||
| 9943b410f4 |
@@ -1 +0,0 @@
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -3,10 +3,10 @@ description: Build the STM32F4-Base Keil MDK project. Use ONLY when the user ask
|
||||
agent: build
|
||||
---
|
||||
|
||||
Run the Keil MDK-ARM build by executing the build script with the bash tool. The repo root is the working directory; use the repo-relative path (the old absolute path `D:\Code\DTU 程序\...` is stale):
|
||||
Run the Keil MDK-ARM build by executing the build script with the bash tool. Use the absolute path, NOT relative cd:
|
||||
|
||||
```powershell
|
||||
& "MDK-ARM\build.bat"
|
||||
& "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\build.bat"
|
||||
```
|
||||
|
||||
Then read the tail of `MDK-ARM/build_log.txt` and report the result.
|
||||
|
||||
@@ -3,10 +3,10 @@ description: Download (flash) the program to the STM32 via Keil MDK. Use ONLY wh
|
||||
agent: build
|
||||
---
|
||||
|
||||
Run the Keil MDK flash download by executing the download script with the bash tool. The repo root is the working directory; use the repo-relative path (the old absolute path `D:\Code\DTU 程序\...` is stale):
|
||||
Run the Keil MDK flash download by executing the download script with the bash tool. Use the absolute path, NOT relative cd:
|
||||
|
||||
```powershell
|
||||
& "MDK-ARM\download.bat"
|
||||
& "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat"
|
||||
```
|
||||
|
||||
Then read the tail of `MDK-ARM/download_log.txt` and report the result.
|
||||
|
||||
@@ -24,10 +24,10 @@ Read `.opencode/serial_config.json` for the authoritative `portName` (COM6) and
|
||||
|
||||
### Step 1 — Build verify
|
||||
|
||||
Run the build script (repo-relative; the repo root is the working directory):
|
||||
Run the build script (absolute path):
|
||||
|
||||
```powershell
|
||||
& "MDK-ARM\build.bat"
|
||||
& "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\build.bat"
|
||||
```
|
||||
|
||||
Interpretation (per AGENTS.md): exit code 0 = PASS (0 errors, 0 warnings). 1/2+ = FAIL. If it fails, read `MDK-ARM/build_log.txt`, fix, and rerun `@build` until exit code 0 **before** touching the hardware.
|
||||
@@ -39,10 +39,10 @@ then flashes, then collects and verifies — all in one call so the capture neve
|
||||
times out before flashing:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File "test\run_boot_test.ps1" `
|
||||
powershell -ExecutionPolicy Bypass -File "D:\Code\DTU 程序\STM32F4-Base\test\run_boot_test.ps1" `
|
||||
-Port COM6 -BaudRate 115200 `
|
||||
-DurationMs 30000 -InitialTimeoutMs 90000 -IdleTimeoutMs 8000 -MaxBytes 65536 `
|
||||
-FlashCmd "MDK-ARM\download.bat" `
|
||||
-FlashCmd "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat" `
|
||||
-Required "App init OK;Network Init;net init: OK" `
|
||||
-Forbidden "FAIL;ERROR;HardFault" `
|
||||
-OutDir "C:\Users\16241\AppData\Local\Temp\opencode"
|
||||
@@ -70,5 +70,5 @@ For interactive command/response (not boot-log capture):
|
||||
- **⚠️ 无历史缓冲**:serial-mcp-server 的 `read` 只能读调用后的字节。抓开机日志必须走 `run_boot_test.ps1`(先采集等 first-byte → 再烧录触发)。
|
||||
- **⚠️ EOF 陷阱**:COM 刚释放立即重开会报 `UnexpectedEof`(tokio-serial 读 0 字节)。`serial_capture.ps1` 内置自动重试;别裸跑 CLI capture。
|
||||
- **参数**:`IdleTimeoutMs` 需 ≥ boot 自然静默间隙(本机 ~3-4s,用 8000);`InitialTimeoutMs` 需覆盖编译+烧录耗时(用 90000)。
|
||||
- UI (LVGL) task `ui_task` 已有 `UI port init: OK` / `UI menu init: OK` 打印,可用 `-Required "UI port init: OK;UI menu init: OK"` 自动回归 UI 初始化。
|
||||
- UI (LVGL) task `ui_task` 增加 `UI port init: OK` / `UI menu init: OK` 打印后,即可用 `-Required "UI port init: OK;UI menu init: OK"` 自动回归 UI 初始化。
|
||||
- Full capture/analysis details: `docs/测试说明/serial-mcp-server_串口调试.md` and `docs/测试说明/自动化测试流程.md`.
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
## 构建
|
||||
|
||||
- **仅支持 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"`)
|
||||
- 命令行编译:`@build`(自动执行 `MDK-ARM/build.bat`)
|
||||
- 退出码:0 = 成功 | 1 = 有警告(不通过)| 2+ = 错误,查看 `MDK-ARM/build_log.txt`
|
||||
|
||||
## 硬件
|
||||
|
||||
@@ -18,7 +16,6 @@
|
||||
| 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`)
|
||||
@@ -26,30 +23,22 @@ MCU: STM32F407ZGTx @ 168MHz (HSE 8MHz → PLL M=4/N=168/P=2,见 `*.ioc`/`stm32
|
||||
## 架构
|
||||
|
||||
```
|
||||
Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
→ [FreeRTOS tasks: defaultTask, netTask, ftpTask, testTask, uiTask, timeSyncTask]
|
||||
Src/main.c → 外设初始化 → app_main_init → gd5f2gq5ue_init → tpafe5160_init
|
||||
→ osKernelInitialize → MX_FREERTOS_Init → osKernelStart
|
||||
→ [FreeRTOS tasks: defaultTask, netTask, ftpTask]
|
||||
```
|
||||
|
||||
- **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`)
|
||||
- **defaultTask**:FTL + FatFS 初始化测试(开机一次性)
|
||||
- **netTask**:`net_init()` → `net_poll()` + `net_process_messages()` 每 10ms
|
||||
- **ftpTask**:`lftpd_start("/", 21, &ftp)` 延迟 7s 后启动
|
||||
|
||||
### 任务栈
|
||||
|
||||
| Task | 栈大小(words) | 优先级 |
|
||||
|---|---|---|
|
||||
| defaultTask | 2048 | Normal |
|
||||
| netTask | 4096 | Normal |
|
||||
| ftpTask | 4096 | Normal |
|
||||
| testTask | 4096 | BelowNormal |
|
||||
| uiTask | 16384 | BelowNormal |
|
||||
| timeSyncTask | 2048 | Normal |
|
||||
| Task | 栈大小 |
|
||||
|---|---|
|
||||
| defaultTask | 2048 |
|
||||
| netTask | 4096 |
|
||||
| ftpTask | 4096 |
|
||||
|
||||
## CH395F 设计原则
|
||||
|
||||
@@ -58,45 +47,32 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
|
||||
## NET 网络层
|
||||
|
||||
- `net_socket.h/c` — BSD Socket API;**单连接模式**(`ch395f_set_fun_para(0x09)`:多 Socket + TCP Server 单连接 + 手动关闭),连接停留在 Socket 0
|
||||
- `net_socket.h/c` — BSD Socket API,多连接模式下 Socket 0 监听、Socket 1~7 数据
|
||||
- `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 全双工
|
||||
- 网络初始化顺序(多连接):`ch395f_set_fun_para(0x02)` → `ch395f_init()` → 配置 Socket 1~7(缓冲区+端口+协议,不 OPEN)→ 配置 Socket 0(TCP LISTEN)
|
||||
- **新文件需手动添加到 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/NET/` | BSD Socket API 网络抽象层 |
|
||||
| `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` — 网络测试说明(含构建模式与执行顺序)
|
||||
- `CH395F_Trap_Records.md` — CH395F 已知陷阱
|
||||
- `GD5F2GQ5UE_Trap_Records.md` — GD5F2GQ5UE 已知陷阱
|
||||
- `CH395F_Test_Guide.md` — 网络测试说明
|
||||
|
||||
## 串口调试(serial-mcp-server MCP/CLI)
|
||||
|
||||
@@ -109,43 +85,20 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
|
||||
改完代码后运行 **`@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/run_boot_test.ps1` = 采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 全流程,退出码 0=PASS/1=FAIL/2=错误
|
||||
- 判定标准:`@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`
|
||||
- UI(`App/task/ui_task.c`)当前无 DBG 打印,无法从串口自动验证;如需 UI 回归,先在固件加 `UI port init: OK` / `UI menu init: OK` 打印再纳入 Required
|
||||
|
||||
## 注意
|
||||
|
||||
- `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
|
||||
- CH395F SPI PCB设计问题,现在分频系数必须为 8,速度相对较低
|
||||
- FreeRTOS V10.3.1 via CMSIS-RTOS V2,HAL 时基用 TIM7(非 SysTick)
|
||||
- NVIC 优先级分组 4 位,外设中断优先级 ≥ 5(`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`)
|
||||
- **内存预算紧张**:任务栈目前普遍偏大(见 `待处理内容.md`),新增任务/缓冲/栈前必须先核算 RAM 余量
|
||||
|
||||
## 源码编辑与编码安全(铁律)
|
||||
|
||||
@@ -153,6 +106,6 @@ cmake --build build --config Debug
|
||||
|
||||
- **绝不用 `Get-Content` / `Set-Content` / `cmd` 文本重写含中文的源码文件**。这类"整文件重写"会按系统默认编码重新解码再写回,多字节中文必被 `U+FFFD` 替换且不可逆。
|
||||
- **改代码只用 Edit 工具**(精确子串替换,不重新编解码整个文件,安全);批量替换用 Edit 的 `replaceAll`。
|
||||
- 必须脚本级替换时,使用显式且一致的编码(**本工程源码为 UTF-8 无 BOM**,如 Python `open(p, encoding="utf-8")` 同编码回写)——但**最稳的是别碰**,只用 Edit / git。
|
||||
- 必须脚本级替换时,使用显式且一致的编码(如 Python `open(p, encoding="utf-8")` 同编码回写),并先确认工程编码(Keil 工程常用 GBK)——但**最稳的是别碰**,只用 Edit / git。
|
||||
- **发现乱码先别急着 `git checkout`**:`git checkout` 会丢弃所有未提交工作,使损失扩大。先评估能否用 Edit 仅改坏掉的那几行。
|
||||
- 验证是否真损坏用字节级检查(统计 `EF BF BD` 出现次数),不要依赖终端/Read 渲染判断。
|
||||
|
||||
+4
-2
@@ -40,7 +40,8 @@
|
||||
* 5) SD2506 诊断信息
|
||||
* 6) 触发首次 ADC 转换
|
||||
*/
|
||||
int app_main_init(void) {
|
||||
int app_main_init(void)
|
||||
{
|
||||
/*
|
||||
* 1) 时间子系统初始化(尽早完成,后续所有日志带时间戳)
|
||||
* sd2506_init() 从 RTC 读取初始时间
|
||||
@@ -112,7 +113,8 @@ int app_main_init(void) {
|
||||
* 限定条件:FreeRTOS 调度器启动后不再返回
|
||||
* 函数说明:空闲任务可调用的应用层处理入口
|
||||
*/
|
||||
void app_main_run(void) {
|
||||
void app_main_run(void)
|
||||
{
|
||||
for (;;) {
|
||||
/* 应用层空闲处理 */
|
||||
}
|
||||
|
||||
+14
-7
@@ -54,7 +54,8 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX;
|
||||
* 函数说明:1. 采用 Zeller 公式(变种),结果 0 表示周日
|
||||
* 2. 与 unix_to_sd2506() 内星期算法保持一致
|
||||
*/
|
||||
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) {
|
||||
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
|
||||
{
|
||||
uint32_t wy = y;
|
||||
uint32_t wm = m;
|
||||
|
||||
@@ -78,7 +79,8 @@ static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) {
|
||||
* 2. 星期由 Zeller 公式(与 rtc_calc_week 同算法)得出
|
||||
* 3. SD2506 年份字段为相对值(0~99,+2000),故回写时减 2000
|
||||
*/
|
||||
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) {
|
||||
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
||||
{
|
||||
uint8_t h, mi, s;
|
||||
|
||||
/* 时分秒 */
|
||||
@@ -161,7 +163,8 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) {
|
||||
* 4. 注意:sd2506_get_time() 返回的 year 已是完整年份(驱动内 +2000),
|
||||
* 换算时不得再加 2000,否则年份虚增导致 Unix 秒数超 uint32 回绕
|
||||
*/
|
||||
int sys_clock_init(void) {
|
||||
int sys_clock_init(void)
|
||||
{
|
||||
sd2506_time_t sd_time;
|
||||
uint32_t year, month, day, hour, minute, second;
|
||||
int64_t unix_secs = 0;
|
||||
@@ -285,7 +288,8 @@ int sys_clock_init(void) {
|
||||
* 的整除差推算流逝秒数(避免每毫秒累加,减少漂移与溢出风险)
|
||||
* 2. 若推算结果为负(基准未建立),返回 UINT32_MAX 表示无效
|
||||
*/
|
||||
uint32_t sys_clock_get(void) {
|
||||
uint32_t sys_clock_get(void)
|
||||
{
|
||||
uint32_t now_tick = HAL_GetTick();
|
||||
int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL);
|
||||
|
||||
@@ -307,7 +311,8 @@ uint32_t sys_clock_get(void) {
|
||||
* 2. 通过 unix_to_sd2506() 转换为 SD2506 结构后格式化
|
||||
* 3. 使用 snprintf 防止缓冲区溢出
|
||||
*/
|
||||
char *sys_clock_get_str(char *buf, size_t buf_size) {
|
||||
char *sys_clock_get_str(char *buf, size_t buf_size)
|
||||
{
|
||||
uint32_t unix_secs = sys_clock_get();
|
||||
if (unix_secs == UINT32_MAX) {
|
||||
snprintf(buf, buf_size, "----/--/-- --:--:--");
|
||||
@@ -333,7 +338,8 @@ char *sys_clock_get_str(char *buf, size_t buf_size) {
|
||||
* 函数说明:1. 重置基准 Tick 与基准 Unix 秒数,使后续 sys_clock_get() 以此为准
|
||||
* 2. 若 sd_time 非 NULL,将其写回 SD2506 RTC,实现断电保持
|
||||
*/
|
||||
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) {
|
||||
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
|
||||
{
|
||||
/* Step 1: 更新墙钟基准 */
|
||||
s_base_tick = HAL_GetTick();
|
||||
s_base_unix_secs = unix_secs;
|
||||
@@ -360,7 +366,8 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) {
|
||||
* 函数说明:将 Unix 秒数转换为 SD2506 时间结构,再调用 sys_clock_set()
|
||||
* 更新运行期基准并写回 RTC,实现掉电保持
|
||||
*/
|
||||
int sys_clock_set_unix(uint32_t unix_secs) {
|
||||
int sys_clock_set_unix(uint32_t unix_secs)
|
||||
{
|
||||
sd2506_time_t sd;
|
||||
|
||||
/* 内部静态函数:将 Unix 秒数拆分为 SD2506 时间结构 */
|
||||
|
||||
+4
-2
@@ -22,7 +22,8 @@
|
||||
* 限定条件:TPAFE5160 硬件已上电
|
||||
* 函数说明:配置采样参数,准备数据缓冲区
|
||||
*/
|
||||
int adc_task_init(void) {
|
||||
int adc_task_init(void)
|
||||
{
|
||||
DBG_INFO("adc_task_init");
|
||||
return 0;
|
||||
}
|
||||
@@ -34,7 +35,8 @@ int adc_task_init(void) {
|
||||
* 限定条件:adc_task_init() 已成功调用
|
||||
* 函数说明:定时触发 TPAFE5160 同步采样并处理数据
|
||||
*/
|
||||
void adc_task_func(void const *arg) {
|
||||
void adc_task_func(void const *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
for (;;) {
|
||||
|
||||
+6
-3
@@ -31,7 +31,8 @@
|
||||
* NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)netTask 不创建,
|
||||
* 由 testTask 调用后独占 CH395F。
|
||||
*/
|
||||
int net_stack_bring_up(void) {
|
||||
int net_stack_bring_up(void)
|
||||
{
|
||||
DBG_INFO("=== Network Init ===");
|
||||
|
||||
if (net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") != 0) {
|
||||
@@ -86,7 +87,8 @@ int net_stack_bring_up(void) {
|
||||
* 限定条件:CH395F 协议栈已初始化
|
||||
* 函数说明:创建 TCP Server 监听 Socket,注册回调等
|
||||
*/
|
||||
int net_task_init(void) {
|
||||
int net_task_init(void)
|
||||
{
|
||||
DBG_INFO("net_task_init");
|
||||
return 0;
|
||||
}
|
||||
@@ -100,7 +102,8 @@ int net_task_init(void) {
|
||||
* 仅 NET 层测试模式(阶段 6~10)会创建本任务;
|
||||
* 硬件层模式下 netTask 不创建,CH395F 由 testTask 独占。
|
||||
*/
|
||||
void net_task_func(void const *arg) {
|
||||
void net_task_func(void const *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
osDelay(2000);
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
* 限定条件:UART 和 GPIO 已由 CubeMX 初始化
|
||||
* 函数说明:配置 RS-485 通信参数,注册接收回调
|
||||
*/
|
||||
int rs485_task_init(void) {
|
||||
int rs485_task_init(void)
|
||||
{
|
||||
DBG_INFO("rs485_task_init");
|
||||
return 0;
|
||||
}
|
||||
@@ -34,7 +35,8 @@ int rs485_task_init(void) {
|
||||
* 限定条件:rs485_task_init() 已成功调用
|
||||
* 函数说明:轮询接收缓冲区并解析协议帧
|
||||
*/
|
||||
void rs485_task_func(void const *arg) {
|
||||
void rs485_task_func(void const *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
for (;;) {
|
||||
|
||||
+4
-2
@@ -25,7 +25,8 @@
|
||||
* 限定条件:sys_evtHandle 为 NULL 时直接返回
|
||||
* 函数说明:无限期等待 SYS_EVT_NET_READY 标志
|
||||
*/
|
||||
static void ui_task_wait_net_ready(void) {
|
||||
static void ui_task_wait_net_ready(void)
|
||||
{
|
||||
if (sys_evtHandle != NULL) {
|
||||
osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
|
||||
osFlagsWaitAny, osWaitForever);
|
||||
@@ -40,7 +41,8 @@ static void ui_task_wait_net_ready(void) {
|
||||
* 函数说明:1. 等待 SYS_EVT_NET_READY 后依次初始化 ui_port/ui_menu/ui_net_sink
|
||||
* 2. 主循环周期调用 ui_port_step 与 ui_net_sink_poll 刷新界面
|
||||
*/
|
||||
void ui_task_func(void *argument) {
|
||||
void ui_task_func(void *argument)
|
||||
{
|
||||
(void)argument;
|
||||
|
||||
DBG_INFO("UI task: started, wait net ready");
|
||||
|
||||
+6
-3
@@ -33,7 +33,8 @@
|
||||
* 单连接模式下监听 Socket 在收到连接后会转为数据通道,连接关闭后即被释放,
|
||||
* 因此每次调用都会新建监听 Socket(与 lftpd 同模式),失败自动重试。
|
||||
*/
|
||||
static int time_sync_open_listener(void) {
|
||||
static int time_sync_open_listener(void)
|
||||
{
|
||||
int retry;
|
||||
|
||||
for (retry = 0; retry < 5; retry++) {
|
||||
@@ -77,7 +78,8 @@ static int time_sync_open_listener(void) {
|
||||
* 返回值:无
|
||||
* 函数说明:阻塞读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后写回 RTC,然后关闭连接
|
||||
*/
|
||||
static void time_sync_handle_conn(int conn_sock) {
|
||||
static void time_sync_handle_conn(int conn_sock)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
int len;
|
||||
|
||||
@@ -104,7 +106,8 @@ static void time_sync_handle_conn(int conn_sock) {
|
||||
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> 关闭 -> 重建监听。
|
||||
* 全部 socket 操作经消息队列由 netTask 串行执行,与本任务/其它网络任务并发安全。
|
||||
*/
|
||||
void time_sync_task(void *argument) {
|
||||
void timeSyncTask(void *argument)
|
||||
{
|
||||
(void)argument;
|
||||
|
||||
/* 等网络栈真正就绪(取代拍脑袋的 8s 魔法延时),确保 netTask 已开始处理消息 */
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* 经消息队列(由 netTask 串行执行)完成 socket 操作,更新系统墙钟。
|
||||
* 该任务与 FTP 等其它网络任务并发安全。
|
||||
*/
|
||||
void time_sync_task(void *argument);
|
||||
void timeSyncTask(void *argument);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+4
-2
@@ -89,7 +89,8 @@ static const uint8_t s_crc8_table[256] = {
|
||||
* 限定条件:data 为非空指针
|
||||
* 函数说明:多项式 0x8005,初始值 0xFFFF,结果异或 0x0000
|
||||
*/
|
||||
uint16_t crc16_modbus(const uint8_t *data, size_t len) {
|
||||
uint16_t crc16_modbus(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
size_t i;
|
||||
|
||||
@@ -108,7 +109,8 @@ uint16_t crc16_modbus(const uint8_t *data, size_t len) {
|
||||
* 限定条件:data 为非空指针
|
||||
* 函数说明:多项式 0x07,初始值 0x00
|
||||
*/
|
||||
uint8_t crc8(const uint8_t *data, size_t len) {
|
||||
uint8_t crc8(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
size_t i;
|
||||
|
||||
|
||||
+18
-9
@@ -19,7 +19,8 @@
|
||||
* 限定条件:rb 和 pool 均为非空指针
|
||||
* 函数说明:size 保留一个空位用于区分空/满状态
|
||||
*/
|
||||
void ringbuf_init(ringbuf_t *rb, uint8_t *pool, size_t size) {
|
||||
void ringbuf_init(ringbuf_t *rb, uint8_t *pool, size_t size)
|
||||
{
|
||||
rb->buf = pool;
|
||||
rb->size = size;
|
||||
rb->head = 0;
|
||||
@@ -35,7 +36,8 @@ void ringbuf_init(ringbuf_t *rb, uint8_t *pool, size_t size) {
|
||||
* 限定条件:rb 和 data 均为非空指针
|
||||
* 函数说明:空间不足时写入能容纳的最大字节数
|
||||
*/
|
||||
size_t ringbuf_put(ringbuf_t *rb, const uint8_t *data, size_t len) {
|
||||
size_t ringbuf_put(ringbuf_t *rb, const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t cap = 0;
|
||||
size_t n = 0;
|
||||
size_t i = 0;
|
||||
@@ -60,7 +62,8 @@ size_t ringbuf_put(ringbuf_t *rb, const uint8_t *data, size_t len) {
|
||||
* 限定条件:rb 和 data 均为非空指针
|
||||
* 函数说明:读取后数据从缓冲区移除
|
||||
*/
|
||||
size_t ringbuf_get(ringbuf_t *rb, uint8_t *data, size_t len) {
|
||||
size_t ringbuf_get(ringbuf_t *rb, uint8_t *data, size_t len)
|
||||
{
|
||||
size_t n = 0;
|
||||
size_t i = 0;
|
||||
|
||||
@@ -84,7 +87,8 @@ size_t ringbuf_get(ringbuf_t *rb, uint8_t *data, size_t len) {
|
||||
* 限定条件:rb 和 data 均为非空指针
|
||||
* 函数说明:与 ringbuf_get 的区别是不移动 tail 指针
|
||||
*/
|
||||
size_t ringbuf_peek(const ringbuf_t *rb, uint8_t *data, size_t len) {
|
||||
size_t ringbuf_peek(const ringbuf_t *rb, uint8_t *data, size_t len)
|
||||
{
|
||||
size_t n = 0;
|
||||
size_t i = 0;
|
||||
|
||||
@@ -105,7 +109,8 @@ size_t ringbuf_peek(const ringbuf_t *rb, uint8_t *data, size_t len) {
|
||||
* 返 回 值:有效数据字节数
|
||||
* 限定条件:rb 为非空指针
|
||||
*/
|
||||
size_t ringbuf_avail(const ringbuf_t *rb) {
|
||||
size_t ringbuf_avail(const ringbuf_t *rb)
|
||||
{
|
||||
if (rb->head >= rb->tail) {
|
||||
return rb->head - rb->tail;
|
||||
}
|
||||
@@ -118,7 +123,8 @@ size_t ringbuf_avail(const ringbuf_t *rb) {
|
||||
* 返 回 值:剩余空间字节数(保留一个空位)
|
||||
* 限定条件:rb 为非空指针
|
||||
*/
|
||||
size_t ringbuf_space(const ringbuf_t *rb) {
|
||||
size_t ringbuf_space(const ringbuf_t *rb)
|
||||
{
|
||||
return rb->size - 1 - ringbuf_avail(rb);
|
||||
}
|
||||
|
||||
@@ -128,7 +134,8 @@ size_t ringbuf_space(const ringbuf_t *rb) {
|
||||
* 返 回 值:1 - 空,0 - 非空
|
||||
* 限定条件:rb 为非空指针
|
||||
*/
|
||||
int ringbuf_is_empty(const ringbuf_t *rb) {
|
||||
int ringbuf_is_empty(const ringbuf_t *rb)
|
||||
{
|
||||
return (rb->head == rb->tail) ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -138,7 +145,8 @@ int ringbuf_is_empty(const ringbuf_t *rb) {
|
||||
* 返 回 值:1 - 满,0 - 非满
|
||||
* 限定条件:rb 为非空指针
|
||||
*/
|
||||
int ringbuf_is_full(const ringbuf_t *rb) {
|
||||
int ringbuf_is_full(const ringbuf_t *rb)
|
||||
{
|
||||
return (ringbuf_avail(rb) == rb->size - 1) ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -148,7 +156,8 @@ int ringbuf_is_full(const ringbuf_t *rb) {
|
||||
* 返 回 值:无
|
||||
* 限定条件:rb 为非空指针
|
||||
*/
|
||||
void ringbuf_reset(ringbuf_t *rb) {
|
||||
void ringbuf_reset(ringbuf_t *rb)
|
||||
{
|
||||
rb->head = 0;
|
||||
rb->tail = 0;
|
||||
}
|
||||
|
||||
@@ -60,12 +60,12 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
int i;
|
||||
|
||||
/* 检查输入参数合法性 */
|
||||
if ((nfds <= 0) || (nfds > NET_MAX_SOCKETS)) {
|
||||
if ((0 >= nfds) || (NET_MAX_SOCKETS < nfds)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 计算超时时间(毫秒) */
|
||||
if (timeout != NULL) {
|
||||
if (NULL != timeout) {
|
||||
timeout_ms = (uint32_t)(timeout->tv_sec * 1000) +
|
||||
(uint32_t)(timeout->tv_usec / 1000);
|
||||
}
|
||||
@@ -80,12 +80,12 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
for (i = 0; i < nfds && i < NET_MAX_SOCKETS; i++) {
|
||||
const net_sock_t *p_sock = net_get_sock(i);
|
||||
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 检查可读事件 */
|
||||
if ((readfds != NULL) && NET_FD_ISSET(i, readfds)) {
|
||||
if ((NULL != readfds) && NET_FD_ISSET(i, readfds)) {
|
||||
if (check_socket_readable(i)) {
|
||||
NET_FD_SET(i, &result_read);
|
||||
ready_count++;
|
||||
@@ -93,7 +93,7 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
}
|
||||
|
||||
/* 检查可写事件 */
|
||||
if ((writefds != NULL) && NET_FD_ISSET(i, writefds)) {
|
||||
if ((NULL != writefds) && NET_FD_ISSET(i, writefds)) {
|
||||
if (check_socket_writable(i)) {
|
||||
NET_FD_SET(i, &result_write);
|
||||
ready_count++;
|
||||
@@ -101,7 +101,7 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
}
|
||||
|
||||
/* 检查异常事件 */
|
||||
if ((exceptfds != NULL) && NET_FD_ISSET(i, exceptfds)) {
|
||||
if ((NULL != exceptfds) && NET_FD_ISSET(i, exceptfds)) {
|
||||
if (check_socket_error(i)) {
|
||||
NET_FD_SET(i, &result_except);
|
||||
ready_count++;
|
||||
@@ -110,13 +110,13 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
}
|
||||
|
||||
/* 有事件就绪,返回 */
|
||||
if (ready_count > 0) {
|
||||
if (0 < ready_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 超时检测 */
|
||||
if (timeout != NULL) {
|
||||
if ((timeout_ms > 0) &&
|
||||
if (NULL != timeout) {
|
||||
if ((0 < timeout_ms) &&
|
||||
((HAL_GetTick() - start_tick) >= timeout_ms)) {
|
||||
break; /* 超时返回 0 */
|
||||
}
|
||||
@@ -127,13 +127,13 @@ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||||
}
|
||||
|
||||
/* 复制结果集合 */
|
||||
if (readfds != NULL) {
|
||||
if (NULL != readfds) {
|
||||
readfds->fd_bits = result_read.fd_bits;
|
||||
}
|
||||
if (writefds != NULL) {
|
||||
if (NULL != writefds) {
|
||||
writefds->fd_bits = result_write.fd_bits;
|
||||
}
|
||||
if (exceptfds != NULL) {
|
||||
if (NULL != exceptfds) {
|
||||
exceptfds->fd_bits = result_except.fd_bits;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||||
int ready = 0;
|
||||
int i;
|
||||
|
||||
if ((fds == NULL) || (nfds <= 0)) {
|
||||
if ((NULL == fds) || (0 >= nfds)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||||
fds[i].revents = 0;
|
||||
|
||||
/* 检查 fd 有效性 */
|
||||
if ((fds[i].fd < 0) || (fds[i].fd >= NET_MAX_SOCKETS)) {
|
||||
if ((0 > fds[i].fd) || (NET_MAX_SOCKETS <= fds[i].fd)) {
|
||||
fds[i].revents |= NET_POLLNVAL;
|
||||
ready++;
|
||||
continue;
|
||||
@@ -180,7 +180,7 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||||
|
||||
const net_sock_t *p_sock = net_get_sock(fds[i].fd);
|
||||
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
fds[i].revents |= NET_POLLNVAL;
|
||||
ready++;
|
||||
continue;
|
||||
@@ -215,12 +215,12 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ready > 0) {
|
||||
if (0 < ready) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 超时检测 */
|
||||
if (timeout >= 0) {
|
||||
if (0 <= timeout) {
|
||||
if ((HAL_GetTick() - start_tick) >= (uint32_t)timeout) {
|
||||
break;
|
||||
}
|
||||
@@ -250,7 +250,7 @@ static int check_socket_readable(int sockfd) {
|
||||
const net_sock_t *p_sock;
|
||||
|
||||
p_sock = net_get_sock(sockfd);
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ static int check_socket_writable(int sockfd) {
|
||||
const net_sock_t *p_sock;
|
||||
|
||||
p_sock = net_get_sock(sockfd);
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -307,12 +307,12 @@ static int check_socket_error(int sockfd) {
|
||||
const net_sock_t *p_sock;
|
||||
|
||||
/* 无效 fd 视为错误 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
if ((0 > sockfd) || (NET_MAX_SOCKETS <= sockfd)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
p_sock = net_get_sock(sockfd);
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
return 1; /* 未使用的 Socket 视为错误 */
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
|
||||
ch395f_set_keepalive_cnt(3); /* 最多探 3 次*/
|
||||
|
||||
/* 设置网络参数(必须在 INIT_CH395 之前调用 */
|
||||
if (ip != NULL) {
|
||||
if (NULL != ip) {
|
||||
uint8_t ip_arr[4];
|
||||
uint8_t mask_arr[4];
|
||||
uint8_t gw_arr[4];
|
||||
@@ -300,7 +300,7 @@ int net_poll(void) {
|
||||
void net_process_messages(void) {
|
||||
net_msg_t msg;
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ void net_process_messages(void) {
|
||||
switch (msg.type) {
|
||||
case NET_MSG_SEND: {
|
||||
net_sock_t *p_sock = net_get_sock(msg.sockfd);
|
||||
if (p_sock == NULL || p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
if (NULL == p_sock || p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
msg.result = -1;
|
||||
} else {
|
||||
msg.result = net_send_locked(p_sock, msg.buf, msg.len);
|
||||
@@ -317,7 +317,7 @@ void net_process_messages(void) {
|
||||
}
|
||||
case NET_MSG_RECV: {
|
||||
net_sock_t *p_sock = net_get_sock(msg.sockfd);
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
msg.result = -1;
|
||||
} else {
|
||||
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags);
|
||||
@@ -326,7 +326,7 @@ void net_process_messages(void) {
|
||||
}
|
||||
case NET_MSG_CLOSE: {
|
||||
net_sock_t *p_sock = net_get_sock(msg.sockfd);
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
msg.result = -1;
|
||||
} else {
|
||||
msg.result = net_close_locked(p_sock);
|
||||
@@ -346,7 +346,7 @@ void net_process_messages(void) {
|
||||
case NET_MSG_ACCEPT: {
|
||||
struct net_sockaddr *p_addr = (struct net_sockaddr *)msg.buf;
|
||||
int addrlen = msg.len;
|
||||
int *p_addrlen = (p_addr != NULL && addrlen > 0) ? &addrlen : NULL;
|
||||
int *p_addrlen = (NULL != p_addr && addrlen > 0) ? &addrlen : NULL;
|
||||
msg.result = net_accept_locked(msg.sockfd, p_addr, p_addrlen);
|
||||
break;
|
||||
}
|
||||
@@ -423,7 +423,7 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
if (NULL == addr || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -567,7 +567,7 @@ int net_listen(int sockfd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
@@ -627,7 +627,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
|
||||
}
|
||||
|
||||
/* 填充对端地址(若调用方需要) */
|
||||
if (addr != NULL && addrlen != NULL) {
|
||||
if (NULL != addr && NULL != addrlen) {
|
||||
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)addr;
|
||||
p_addr_in->sin_family = NET_AF_INET;
|
||||
p_addr_in->sin_port = net_htons(p_listen_sock->remote_port);
|
||||
@@ -656,7 +656,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
@@ -664,7 +664,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
msg.type = NET_MSG_ACCEPT;
|
||||
msg.sockfd = sockfd;
|
||||
msg.buf = (void *)addr;
|
||||
msg.len = (addrlen != NULL) ? *addrlen : 0;
|
||||
msg.len = (NULL != addrlen) ? *addrlen : 0;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -679,7 +679,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
|
||||
if (addrlen != NULL &&addr != NULL) {
|
||||
if (NULL != addrlen &&NULL != addr) {
|
||||
*addrlen = sizeof(struct net_sockaddr_in);
|
||||
}
|
||||
|
||||
@@ -709,12 +709,12 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
if (NULL == addr || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -767,7 +767,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
if (NULL == addr || addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -865,7 +865,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
uint32_t tick_start;
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
if (NULL == buf || len <= 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -928,7 +928,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
uint32_t tick_start;
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
if (NULL == buf || len <= 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -1117,7 +1117,7 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
}
|
||||
|
||||
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR; /* 网络层未初始化 */
|
||||
return -1;
|
||||
}
|
||||
@@ -1172,7 +1172,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
@@ -1219,7 +1219,7 @@ int net_close(int sockfd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
if (NULL == netMsgQueueHandle) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
@@ -1277,13 +1277,13 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
if (NULL == buf || len <= 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* UDP 模式:设置目标地址 */
|
||||
if (dest_addr != NULL && addrlen >= (int)sizeof(struct net_sockaddr_in)) {
|
||||
if (NULL != dest_addr && addrlen >= (int)sizeof(struct net_sockaddr_in)) {
|
||||
p_addr_in = (const struct net_sockaddr_in *)dest_addr;
|
||||
|
||||
/* 转换 IP 为大端数组(同 TCP 客户端,set_des_ip 期望大端) */
|
||||
@@ -1365,7 +1365,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
if (NULL == buf || len <= 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -1384,7 +1384,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
recv_len -= 8;
|
||||
|
||||
/* 填充来源地址 */
|
||||
if (src_addr != NULL &&addrlen != NULL &&
|
||||
if (NULL != src_addr &&NULL != addrlen &&
|
||||
*addrlen >= (int)sizeof(struct net_sockaddr_in)) {
|
||||
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
|
||||
p_addr_in->sin_family = NET_AF_INET;
|
||||
@@ -1436,7 +1436,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
* 函数说明:直接调用 net_send_locked 完成发送
|
||||
*/
|
||||
int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
@@ -1455,7 +1455,7 @@ int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
|
||||
* 函数说明:直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
|
||||
*/
|
||||
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
@@ -1470,7 +1470,7 @@ int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
* 函数说明:直接操作 Socket 控制块,不经过消息队列。TCP Socket 先断开连接再释放控制块
|
||||
*/
|
||||
int net_close_sock(net_sock_t *p_sock) {
|
||||
if (p_sock == NULL) {
|
||||
if (NULL == p_sock) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
@@ -1530,7 +1530,7 @@ uint32_t net_inet_addr(const char *cp) {
|
||||
uint32_t addr = 0;
|
||||
int i;
|
||||
|
||||
if (cp == NULL) {
|
||||
if (NULL == cp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1561,7 +1561,7 @@ uint32_t net_inet_addr(const char *cp) {
|
||||
char *net_inet_ntoa(uint32_t addr, char *buf) {
|
||||
uint32_t naddr = NET_NTOHL(addr);
|
||||
|
||||
if (buf == NULL) {
|
||||
if (NULL == buf) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1787,7 +1787,7 @@ static void handle_timeout_event(net_sock_t *p_sock) {
|
||||
* 2. 回调参数为 ch395_sock、event、event_arg
|
||||
*/
|
||||
static void fire_event(net_sock_t *p_sock, net_event_t event) {
|
||||
if (p_sock->event_cb != NULL) {
|
||||
if (NULL != p_sock->event_cb) {
|
||||
p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg);
|
||||
}
|
||||
}
|
||||
@@ -1805,7 +1805,7 @@ static void fire_event(net_sock_t *p_sock, net_event_t event) {
|
||||
static void parse_ip_string(const char *str, uint8_t *ip_arr) {
|
||||
int i;
|
||||
|
||||
if (str == NULL ||ip_arr == NULL) {
|
||||
if (NULL == str ||NULL == ip_arr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1864,7 +1864,7 @@ int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr == NULL ||addrlen == NULL || *addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
if (NULL == addr ||NULL == addrlen || *addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ rs485_handle_t g_rs485;
|
||||
void rs485_init(rs485_handle_t *p_handle,
|
||||
UART_HandleTypeDef *p_huart,
|
||||
GPIO_TypeDef *p_dir_port,
|
||||
uint16_t dir_pin) {
|
||||
uint16_t dir_pin)
|
||||
{
|
||||
p_handle->huart = p_huart;
|
||||
p_handle->dir_port = p_dir_port;
|
||||
p_handle->dir_pin = dir_pin;
|
||||
@@ -70,7 +71,8 @@ void rs485_init(rs485_handle_t *p_handle,
|
||||
HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle,
|
||||
const uint8_t *p_data,
|
||||
uint16_t len,
|
||||
uint32_t timeout) {
|
||||
uint32_t timeout)
|
||||
{
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
|
||||
if (timeout == 0U) {
|
||||
@@ -104,7 +106,8 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle,
|
||||
* HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数。
|
||||
* 用户须在回调中重新调用本函数重新开启接收。
|
||||
*/
|
||||
HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle) {
|
||||
HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle)
|
||||
{
|
||||
p_handle->rx_size = 0;
|
||||
return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_handle->rx_buf, RS485_RX_BUF_SIZE);
|
||||
}
|
||||
@@ -117,7 +120,8 @@ HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle) {
|
||||
* 限定条件:须在 HAL_UARTEx_RxEventCallback 中调用
|
||||
* 函数说明:将 HAL 回调中接收到的数据大小回写到句柄的 rx_size 字段
|
||||
*/
|
||||
void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) {
|
||||
void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size)
|
||||
{
|
||||
p_handle->rx_size = size;
|
||||
}
|
||||
|
||||
@@ -128,7 +132,8 @@ void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) {
|
||||
* 限定条件:须在 HAL_UARTEx_RxEventCallback 触发后调用
|
||||
* 函数说明:获取 rx_size 字段中缓存的接收字节数
|
||||
*/
|
||||
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) {
|
||||
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle)
|
||||
{
|
||||
return p_handle->rx_size;
|
||||
}
|
||||
|
||||
@@ -138,7 +143,8 @@ uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) {
|
||||
* 应用层接收钩子默认实现(弱符号,可被应用重定义)。
|
||||
* 在中断上下文调用,仅做占位,业务解析由应用提供。
|
||||
*/
|
||||
__weak void rs485_on_rx(uint8_t *buf, uint16_t size) {
|
||||
__weak void rs485_on_rx(uint8_t *buf, uint16_t size)
|
||||
{
|
||||
(void)buf;
|
||||
(void)size;
|
||||
}
|
||||
@@ -148,7 +154,8 @@ __weak void rs485_on_rx(uint8_t *buf, uint16_t size) {
|
||||
* 当 RS-485 总线空闲超过 1 个字符时间后触发,由 HAL 自动调用。
|
||||
* 注意:回调按实例分发,仅处理本驱动绑定的 UART,不写死 UART5。
|
||||
*/
|
||||
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
|
||||
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
|
||||
{
|
||||
if (huart == g_rs485.huart) {
|
||||
rs485_rx_set_size(&g_rs485, Size);
|
||||
|
||||
@@ -164,7 +171,8 @@ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
|
||||
* HAL UART 错误回调(ORE/NE/FE/PE 等)。
|
||||
* 读 SR + DR 清除错误标志后重新开启接收,避免中断锁死。
|
||||
*/
|
||||
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
|
||||
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart == g_rs485.huart) {
|
||||
/* STM32F4 通过读 SR + DR 清除 ORE/NE/FE/PE 错误标志 */
|
||||
(void)huart->Instance->SR;
|
||||
|
||||
@@ -65,7 +65,8 @@ static void tpafe5160_read_channels(uint8_t count, int16_t *buf);
|
||||
* 2. 确保 RD 为高、CONVST 为低
|
||||
* 3. 等待 BUSY 释放(转换空闲)
|
||||
*/
|
||||
int tpafe5160_init(void) {
|
||||
int tpafe5160_init(void)
|
||||
{
|
||||
/* 设置默认过采样:无过采样 (OS[2:0] = 000) */
|
||||
tpafe5160_set_os(TPAFE5160_OS_NONE);
|
||||
|
||||
@@ -90,7 +91,8 @@ int tpafe5160_init(void) {
|
||||
* 限定条件:GPIO 已初始化
|
||||
* 函数说明:通过 OS[2:0] 引脚设置过采样率,在下一次 BUSY 下降沿锁存生效
|
||||
*/
|
||||
void tpafe5160_set_os(tpafe5160_os_t os) {
|
||||
void tpafe5160_set_os(tpafe5160_os_t os)
|
||||
{
|
||||
TPAFE5160_OS0(os & 0x01); /* OS0 = bit0 */
|
||||
TPAFE5160_OS1((os >> 1) & 0x01); /* OS1 = bit1 */
|
||||
TPAFE5160_OS2((os >> 2) & 0x01); /* OS2 = bit2 */
|
||||
@@ -103,7 +105,8 @@ void tpafe5160_set_os(tpafe5160_os_t os) {
|
||||
* 限定条件:GPIO 已初始化
|
||||
* 函数说明:产生 CONVST 脉冲上升沿,启动全部8通道同步采样与转换
|
||||
*/
|
||||
void tpafe5160_start_conv(void) {
|
||||
void tpafe5160_start_conv(void)
|
||||
{
|
||||
TPAFE5160_CONVST_PULSE();
|
||||
}
|
||||
|
||||
@@ -114,7 +117,8 @@ void tpafe5160_start_conv(void) {
|
||||
* 限定条件:已调用 tpafe5160_start_conv()
|
||||
* 函数说明:轮询 BUSY 引脚等待下降沿
|
||||
*/
|
||||
int tpafe5160_wait_busy(uint32_t timeout_ms) {
|
||||
int tpafe5160_wait_busy(uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t tick_start = HAL_GetTick();
|
||||
|
||||
/* 等待 BUSY 释放(低电平表示空闲) */
|
||||
@@ -134,7 +138,8 @@ int tpafe5160_wait_busy(uint32_t timeout_ms) {
|
||||
* 限定条件:GPIO 已初始化
|
||||
* 函数说明:读取 BUSY 引脚电平
|
||||
*/
|
||||
uint8_t tpafe5160_is_busy(void) {
|
||||
uint8_t tpafe5160_is_busy(void)
|
||||
{
|
||||
return (TPAFE5160_BUSY_READ() == GPIO_PIN_SET) ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -147,7 +152,8 @@ uint8_t tpafe5160_is_busy(void) {
|
||||
* 2. 连续8次拉低RD读取各通道数据
|
||||
* 3. 通过 FRSTDATA 验证第一通道
|
||||
*/
|
||||
int tpafe5160_read_all(int16_t *buf) {
|
||||
int tpafe5160_read_all(int16_t *buf)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* 启动转换 */
|
||||
@@ -174,7 +180,8 @@ int tpafe5160_read_all(int16_t *buf) {
|
||||
* 限定条件:GPIO 已初始化
|
||||
* 函数说明:启动转换并等待完成后,连续读取至指定通道
|
||||
*/
|
||||
int tpafe5160_read_channel(uint8_t channel, int16_t *value) {
|
||||
int tpafe5160_read_channel(uint8_t channel, int16_t *value)
|
||||
{
|
||||
int ret = 0;
|
||||
int16_t buf[TPAFE5160_CH_NUM] = {0};
|
||||
|
||||
@@ -200,7 +207,8 @@ int tpafe5160_read_channel(uint8_t channel, int16_t *value) {
|
||||
* ±10V量程时 LSB = 20V / 65536 ≈ 305.18μV
|
||||
* 硬件 RANGE 引脚接 GND,默认 ±5V 量程
|
||||
*/
|
||||
float tpafe5160_to_voltage(int16_t raw) {
|
||||
float tpafe5160_to_voltage(int16_t raw)
|
||||
{
|
||||
/* RANGE 接 GND → ±5V 量程,满量程 10V */
|
||||
return (float)raw * (10.0f / 65536.0f);
|
||||
}
|
||||
@@ -212,7 +220,8 @@ float tpafe5160_to_voltage(int16_t raw) {
|
||||
* 限定条件:RD 为低或 CS 与 RD 已拉低
|
||||
* 函数说明:读取 GPIOG->IDR 低16位,对应 DB[15:0]
|
||||
*/
|
||||
uint16_t tpafe5160_read_bus(void) {
|
||||
uint16_t tpafe5160_read_bus(void)
|
||||
{
|
||||
return TPAFE5160_READ_BUS();
|
||||
}
|
||||
|
||||
@@ -232,7 +241,8 @@ uint16_t tpafe5160_read_bus(void) {
|
||||
* t14 数据建立 ≤ 21ns → 数据在 RD 下降沿后 21ns 内有效
|
||||
* 5 个 NOP (~30ns) 覆盖建立时间
|
||||
*/
|
||||
static void tpafe5160_read_channels(uint8_t count, int16_t *buf) {
|
||||
static void tpafe5160_read_channels(uint8_t count, int16_t *buf)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
@@ -262,7 +272,8 @@ static volatile uint8_t s_buf_sel = 0; /* 0 = 写 B / 读 A, 1 = 写 A / 读 B
|
||||
* 限定条件:CubeMX 已完成 GPIO 和 NVIC 配置
|
||||
* 函数说明:正常启动流程无需调用,仅在 irq_disable() 后需要重新使能时使用
|
||||
*/
|
||||
void tpafe5160_irq_enable(void) {
|
||||
void tpafe5160_irq_enable(void)
|
||||
{
|
||||
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
|
||||
}
|
||||
|
||||
@@ -273,7 +284,8 @@ void tpafe5160_irq_enable(void) {
|
||||
* 限定条件:已调用 tpafe5160_irq_enable()
|
||||
* 函数说明:仅禁用 NVIC 中断,GPIO 配置保持 CubeMX 设定
|
||||
*/
|
||||
void tpafe5160_irq_disable(void) {
|
||||
void tpafe5160_irq_disable(void)
|
||||
{
|
||||
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
|
||||
}
|
||||
|
||||
@@ -284,7 +296,8 @@ void tpafe5160_irq_disable(void) {
|
||||
* 限定条件:已调用 tpafe5160_irq_enable()
|
||||
* 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据
|
||||
*/
|
||||
void tpafe5160_start_conv_irq(void) {
|
||||
void tpafe5160_start_conv_irq(void)
|
||||
{
|
||||
s_ready = 0;
|
||||
TPAFE5160_CONVST_PULSE();
|
||||
}
|
||||
@@ -296,7 +309,8 @@ void tpafe5160_start_conv_irq(void) {
|
||||
* 限定条件:中断模式已启用
|
||||
* 函数说明:在 EXTI 回调中置位,主循环读取后需调用 tpafe5160_clear_ready() 清除
|
||||
*/
|
||||
uint8_t tpafe5160_data_ready(void) {
|
||||
uint8_t tpafe5160_data_ready(void)
|
||||
{
|
||||
return s_ready;
|
||||
}
|
||||
|
||||
@@ -307,7 +321,8 @@ uint8_t tpafe5160_data_ready(void) {
|
||||
* 限定条件:中断模式已启用
|
||||
* 函数说明:主循环处理完数据后调用
|
||||
*/
|
||||
void tpafe5160_clear_ready(void) {
|
||||
void tpafe5160_clear_ready(void)
|
||||
{
|
||||
s_ready = 0;
|
||||
}
|
||||
|
||||
@@ -318,7 +333,8 @@ void tpafe5160_clear_ready(void) {
|
||||
* 限定条件:tpafe5160_data_ready() 返回 1 时调用
|
||||
* 函数说明:双缓冲切换,ISR 写另一个缓冲区,主循环安全读取当前缓冲区
|
||||
*/
|
||||
const int16_t* tpafe5160_get_buf(void) {
|
||||
const int16_t* tpafe5160_get_buf(void)
|
||||
{
|
||||
return (s_buf_sel == 0) ? s_buf_a : s_buf_b;
|
||||
}
|
||||
|
||||
@@ -329,11 +345,13 @@ const int16_t* tpafe5160_get_buf(void) {
|
||||
* 限定条件:BUSY EXTI 已使能
|
||||
* 函数说明:转换完成后自动读取 8 通道数据到缓冲区,耗时约 0.5µs (168MHz)
|
||||
*/
|
||||
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
|
||||
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
||||
{
|
||||
tpafe5160_exti_handler(GPIO_Pin);
|
||||
}
|
||||
|
||||
void tpafe5160_exti_handler(uint16_t GPIO_Pin) {
|
||||
void tpafe5160_exti_handler(uint16_t GPIO_Pin)
|
||||
{
|
||||
if (GPIO_Pin != TP_BUSY_Pin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,26 +21,22 @@
|
||||
|
||||
## 项目架构
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph APP["应用层 (App/)"]
|
||||
direction LR
|
||||
A1["app_main.c"] ~~~ A2["net_task.c"] ~~~ A3["ui_task.c"] ~~~ A4["time_sync.c"]
|
||||
end
|
||||
subgraph BSP["驱动层 (Drivers/BSP/)"]
|
||||
direction LR
|
||||
B1["CH395F"] ~~~ B2["GD5F2GQ5UE"] ~~~ B3["TPAFE5160"] ~~~ B4["SD2506"] ~~~ B5["RS485"] ~~~ B6["UI"]
|
||||
end
|
||||
subgraph MW["中间件 (Middlewares/ & Lib/)"]
|
||||
direction LR
|
||||
C1["FreeRTOS"] ~~~ C2["FatFs"] ~~~ C3["dhara FTL"] ~~~ C4["LVGL"]
|
||||
end
|
||||
subgraph HAL["HAL 层 / CMSIS"]
|
||||
direction LR
|
||||
D1["STM32F4xx_HAL_Driver/"] ~~~ D2["CMSIS/"]
|
||||
end
|
||||
HW["硬件 (STM32F407ZGTx)"]
|
||||
APP --> BSP --> MW --> HAL --> HW
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 应用层 (App/) │
|
||||
│ app_main.c │ net_task.c │ ui_task.c │ time_sync.c │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 驱动层 (Drivers/BSP/) │
|
||||
│ CH395F/ │ GD5F2GQ5UE/ │ TPAFE5160/ │ SD2506/ │ RS485/ │ UI/ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 中间件 (Middlewares/ & Lib/) │
|
||||
│ FreeRTOS │ FatFs │ dhara FTL │ LVGL │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HAL 层 (Drivers/STM32F4xx_HAL_Driver/) │
|
||||
│ CMSIS (Drivers/CMSIS/) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 硬件 (STM32F407ZGTx) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 启动流程
|
||||
@@ -50,13 +46,11 @@ main.c
|
||||
├─ HAL_Init()
|
||||
├─ SystemClock_Config() // 168MHz 系统时钟
|
||||
├─ MX_GPIO/DMA/UART/SPI/I2C_Init() // CubeMX 外设初始化
|
||||
├─ app_main_init() // 应用层初始化(时间→RS485→NAND→ADC→诊断)
|
||||
├─ app_main_init() // 应用层初始化(时间→RS485→NAND→ADC→RTC)
|
||||
│ ├─ sd2506_init() + sys_clock_init() // 时间子系统优先
|
||||
│ ├─ rs485_init()
|
||||
│ ├─ gd5f2gq5ue_init()
|
||||
│ ├─ tpafe5160_init()
|
||||
│ ├─ SD2506 诊断(ID/时间/温度/电池)
|
||||
│ └─ tpafe5160_start_conv_irq() // 首次 ADC 转换
|
||||
│ └─ tpafe5160_init()
|
||||
├─ osKernelInitialize()
|
||||
├─ MX_FREERTOS_Init() // 创建任务和事件组
|
||||
└─ osKernelStart() // 启动调度器
|
||||
@@ -120,8 +114,7 @@ STM32F4-Base/
|
||||
│ │ │ ├── net_socket.c/h # Socket API
|
||||
│ │ │ ├── net_select.c/h # select/poll 多路复用
|
||||
│ │ │ └── lftpd/ # 轻量 FTP 服务器
|
||||
│ │ ├── UI/ # LVGL UI 组件
|
||||
│ │ └── dbg_log.h / dbg_cfg.h # 调试日志(DBG_INFO 等)
|
||||
│ │ └── UI/ # LVGL UI 组件
|
||||
│ ├── STM32F4xx_HAL_Driver/ # ST HAL 库
|
||||
│ └── CMSIS/ # ARM CMSIS
|
||||
├── Lib/ # 第三方库
|
||||
@@ -139,42 +132,20 @@ STM32F4-Base/
|
||||
│ ├── run_boot_test.ps1 # 自动化启动测试
|
||||
│ ├── verify_log.ps1 # 日志校验脚本
|
||||
│ ├── serial_capture.ps1 # 串口捕获脚本
|
||||
│ ├── test_config.c/h # 测试任务配置
|
||||
│ ├── ch395f_test_task.c/h # CH395F 硬件测试
|
||||
│ ├── gd5f_test_task.c/h # NAND Flash 测试
|
||||
│ ├── net_test_task.c/h # 网络层测试
|
||||
│ ├── sd2506_test_task.c/h # RTC 测试
|
||||
│ ├── storage_test_task.c/h # 存储性能测试
|
||||
│ ├── ch395f_socket_test.py # Socket 通信测试(主机端)
|
||||
│ ├── ftp_test.py # FTP 服务器测试(主机端)
|
||||
│ ├── ftp_up_dl_test.py # FTP 上传下载测试(主机端)
|
||||
│ ├── ftp_speed_test.py # FTP 传输速度测试(主机端)
|
||||
│ ├── ftp_capture.py # FTP 流量抓取(主机端)
|
||||
│ └── time_sync_server.py # 时间同步推送服务(主机端)
|
||||
│ └── storage_test_task.c/h # 存储性能测试
|
||||
├── docs/ # 项目文档
|
||||
│ ├── 代码规范/ # C 代码规范、pre-commit / cppcheck 使用说明
|
||||
│ ├── 问题记录/ # 已知陷阱与解决方案
|
||||
│ ├── 测试说明/ # 测试指南
|
||||
│ ├── 芯片手册/ # 硬件数据手册
|
||||
│ ├── 测试开发标准流程.md # 测试开发标准流程
|
||||
│ ├── 测试文档模板规范.md # 测试文档模板规范
|
||||
│ └── *.md # 存储架构 / UI 规划 / 墙钟时间 / 启动顺序 / LVGL 模拟器
|
||||
├── scripts/ # pre-commit 本地钩子脚本
|
||||
│ ├── check_coding_standard.py # 代码规范机械检查
|
||||
│ └── check_mojibake.py # U+FFFD 乱码检查
|
||||
├── simulator/ # LVGL PC 模拟器(CMake + SDL2)
|
||||
│ ├── CMakeLists.txt # 模拟器构建脚本
|
||||
│ ├── main.c / sim_port.c/h # 模拟器入口与移植层
|
||||
│ ├── lv_conf.h # LVGL 配置(1bpp + SDL)
|
||||
│ └── SDL2/ # SDL2 2.32.10 预编译库
|
||||
├── tools/ # 开发工具
|
||||
│ └── font_gen/ # 中文字体生成工具
|
||||
├── ui_cn_chars.txt # 中文字体字符集
|
||||
├── STM32F407-Demo.ioc # CubeMX 工程文件
|
||||
├── .pre-commit-config.yaml # pre-commit 钩子配置
|
||||
├── .gitlint # commit message 检查规则
|
||||
├── opencode.json # opencode 配置
|
||||
├── .opencode/ # opencode 本地配置(串口等)
|
||||
└── AGENTS.md # AI 开发规范
|
||||
```
|
||||
|
||||
@@ -206,17 +177,6 @@ MDK-ARM\build.bat
|
||||
MDK-ARM\download.bat
|
||||
```
|
||||
|
||||
### LVGL PC 模拟器
|
||||
|
||||
PC 端 UI 开发调试(无需烧录 MCU),详见 `docs/LVGL模拟器部署与使用说明.md`:
|
||||
|
||||
```powershell
|
||||
cd simulator
|
||||
cmake -B build -G "Visual Studio 16 2019" -A x64
|
||||
cmake --build build --config Debug
|
||||
# 运行 simulator\build\Debug\lvgl_sim.exe
|
||||
```
|
||||
|
||||
## 驱动开发指南
|
||||
|
||||
### CH395F 以太网
|
||||
@@ -266,17 +226,6 @@ powershell -ExecutionPolicy Bypass -File test\run_boot_test.ps1
|
||||
| RTC | `sd2506_test_task.c` | 时间读写、温度、电池 |
|
||||
| 存储性能 | `storage_test_task.c` | FTL/FatFS 读写速度 |
|
||||
|
||||
### 主机端测试脚本(Python)
|
||||
|
||||
| 脚本 | 说明 |
|
||||
|------|------|
|
||||
| `ch395f_socket_test.py` | Socket 通信测试 |
|
||||
| `ftp_test.py` | FTP 服务器基础测试 |
|
||||
| `ftp_up_dl_test.py` | FTP 上传/下载测试 |
|
||||
| `ftp_speed_test.py` | FTP 传输速度测试 |
|
||||
| `ftp_capture.py` | FTP 流量抓取 |
|
||||
| `time_sync_server.py` | 时间同步推送服务(PC → 板子) |
|
||||
|
||||
### 串口调试
|
||||
|
||||
通过 `serial-mcp-server` 自主读取 USART1 串口打印:
|
||||
@@ -318,8 +267,6 @@ powershell -ExecutionPolicy Bypass -File test\run_boot_test.ps1
|
||||
- U+FFFD 乱码(拦截,不可逆损坏)
|
||||
- commit message 格式(gitlint,拦截)
|
||||
|
||||
本地钩子脚本:`scripts/check_coding_standard.py`、`scripts/check_mojibake.py`(由 `.pre-commit-config.yaml` 引用)。
|
||||
|
||||
```bash
|
||||
# 新机器启用钩子
|
||||
pre-commit install
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ void MX_FREERTOS_Init(void) {
|
||||
#endif
|
||||
|
||||
/* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */
|
||||
timeSyncTaskHandle = osThreadNew(time_sync_task, NULL, &timeSyncTask_attributes);
|
||||
timeSyncTaskHandle = osThreadNew(timeSyncTask, NULL, &timeSyncTask_attributes);
|
||||
/* USER CODE END RTOS_THREADS */
|
||||
|
||||
/* Create the event(s) */
|
||||
|
||||
@@ -43,7 +43,7 @@ python tools/font_gen/extract_cn.py
|
||||
|
||||
```
|
||||
scanned 9 files; unique non-ASCII chars: 83
|
||||
-> D:\Code\DTU Code\STM32F4-Base\ui_cn_chars.txt
|
||||
-> D:\Code\DTU 程序\STM32F4-Base\ui_cn_chars.txt
|
||||
```
|
||||
|
||||
产物 `ui_cn_chars.txt` 为本工程实际显示的汉字清单(当前 83 字),可留作字符集记录。
|
||||
@@ -0,0 +1,199 @@
|
||||
# UI 图形界面实施规划(160×160 单色 · 树形菜单 · 离屏渲染 + 网络推流)
|
||||
|
||||
> 本文档用于在我(实现方)与你(需求方)之间对齐"如何规划与实施 UI"。
|
||||
> 当前阶段:**需求澄清 + 方案设计**,尚未写任何 UI 源码。
|
||||
> ✅ = 已根据你的最近答复冻结;【待确认】= 仍需你拍板;**(建议)** = 我的默认推荐。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与现状
|
||||
|
||||
| 项 | 现状 |
|
||||
|---|---|
|
||||
| MCU | STM32F407ZGTx,168MHz,192KB RAM,1MB Flash |
|
||||
| 工具链 | 仅 Keil MDK-ARM v5 (ARMCC) |
|
||||
| OS | FreeRTOS (CMSIS-RTOS V2),已有 `defaultTask/netTask/ftpTask` |
|
||||
| 网络 | CH395F 以太网(SPI2),单连接 socket 模型(Socket0 复用监听/数据) |
|
||||
| 显示 | **无真实屏**:UI 在内存中离屏渲染,按请求经网络返回帧 |
|
||||
| 目标屏规格 | ✅ **160×160 像素,单色(1bpp,黑白)**,"少占用 RAM" |
|
||||
| 参考项目 | `D:\Code\DTU 程序\DTU-HMI`(自建 MVP 菜单,过于复杂,改用成熟方案复现菜单) |
|
||||
| 参考协议 | ✅ **RemoDispBus**(`DTU-HMI/src/remoteDisplay.c`,帧格式见第 3.4 节) |
|
||||
| 源码规范 | 中文注释;新增 .c/.h 需手动加入 Keil 工程;禁止整文件文本重写 |
|
||||
|
||||
### 关键推论
|
||||
- 我们目标 **1bpp**:整帧 = 160×160÷8 = **3200 字节(3.1KB)**,直接整帧作绘制缓冲。git
|
||||
- 参考项目 `key.h` 已有 `g_tRemoteKey`(`bUseRkey` 远程按键)→ ✅ 菜单导航由远端发键值驱动(双向交互已定)。
|
||||
- 参考项目 `def.h`:✅ 中文 12px、行高 14px、可视约 11 行。
|
||||
|
||||
---
|
||||
|
||||
## 2. 复现目标(已明确)
|
||||
|
||||
1. MCU 内存中渲染 160×160、1bpp 画面。
|
||||
2. ✅ **菜单驱动、多页面可切换**:复现 DTU-HMI 树形菜单(第 3.3 节),菜单进入不同页。
|
||||
3. ✅ **不含折线/柱状图**:内容页以文字 + 数值列表 + 简单指示为主;YC(遥测)页数值列表,无曲线。
|
||||
4. ✅ **A 复现范围**:仅复现**菜单树结构与导航**;所有内容页先**占位**(含装置信息/遥测也先占位)。
|
||||
5. ✅ **双向交互(pull 模型)**:远端按 RemoDispBus 发 `CMD_LCDMEM` 拉帧、`CMD_KEY` 下发键;MCU 渲染后应答帧。
|
||||
6. 全程 RAM 占用尽量低,不挤占现有 netTask/ftp/FatFs/dhara。
|
||||
|
||||
---
|
||||
|
||||
## 3. 参考项目与协议
|
||||
|
||||
### 3.1 输入键码(来自 `DTU-HMI/src/Drv/key.h`)
|
||||
| 键 | 键值 | LVGL 映射 |
|
||||
|---|---|---|
|
||||
| 上 KEY_U | 0x02 | LV_KEY_UP |
|
||||
| 下 KEY_D | 0x40 | LV_KEY_DOWN |
|
||||
| 左 KEY_L | 0x10 | LV_KEY_LEFT |
|
||||
| 右 KEY_R | 0x08 | LV_KEY_RIGHT |
|
||||
| 确认 KEY_ENT | 0x20 | LV_KEY_ENTER |
|
||||
| 返回 KEY_ESC | 0x01 | LV_KEY_ESC |
|
||||
|
||||
### 3.2 视觉规格(来自 `def.h`)
|
||||
- ✅ 160×160,1bpp;中文 12px,行高 14px,可视约 11 行。
|
||||
- ✅ 菜单视觉:采用 LVGL `lv_menu` 整屏列表 + 返回栈(小屏最清晰),不复原级联框。
|
||||
|
||||
### 3.3 菜单树(取自 `model.c` 的 `menuTab[]`,仅复现结构与导航)
|
||||
```
|
||||
[0] 主界面 (root)
|
||||
├[1] 装置信息 → 占位页
|
||||
├[1] 实时数据
|
||||
│ ├[2] 交流量 / [2] 直流量 / [2] 遥信量 → 占位页(YC类)
|
||||
├[1] 参数定值
|
||||
│ ├[2] 整定 → [3]参数/定值/控制字/软压板 → 占位
|
||||
│ └[2] 查看 → [3]参数/定值/控制字/软压板 → 占位
|
||||
├[1] 三遥设置 → [2]遥测死区/系数/遥信类型/防抖/双点遥信 → 占位
|
||||
├[1] 装置维护 → [2]时钟设置/强制复归/手动录波/清除记录 → 占位
|
||||
├[1] 通讯参数 → [2]通讯设置/网口设置/SNTP设置 → 占位
|
||||
└[1] 记录查询 → [2]SOE/事故/操作/保护告警/保护启动/遥控/自检/运行记录/运行报告 → 占位
|
||||
[0] 厂家设置 (root, 密码 CN_COP_PWD)
|
||||
├[1] 元件配置 / [1]恢复默认([2]全部/默认参数/定值/软压板/元件配置) / [1]交流显示
|
||||
├[1] 装置调试 → [2]虚拟遥信/交流虚遥测/直流虚遥测/电度虚遥测/动作虚事件/告警虚事件/动作出口/信号出口 → 占位
|
||||
└[1] 版本信息 → 占位
|
||||
```
|
||||
> 叶子(含"装置信息""实时数据"等)统一进入**占位页**(显示菜单路径 + "功能未实现"),仅验证导航与返回栈。
|
||||
|
||||
### 3.4 RemoDispBus 协议(✅ E 与参考项目一致,来自 `remoteDisplay.c`)
|
||||
|
||||
**帧格式(请求/应答通用)**:`[TAG][cmd][len_hi][len_lo][data...][crc]`
|
||||
- `TAG`:客户端→设备 `0xAA`;设备→客户端 `0xBB`
|
||||
- `cmd`:`0` KEEPLIVE,`1` INIT,`2` KEY,`3` LCDMEM
|
||||
- `len`:data 区长度,**大端 16 位**
|
||||
- `crc`:data 区逐字节**异或**取低 8 位
|
||||
- 整帧长度 = `4 + data_len + 1`
|
||||
|
||||
**命令语义**
|
||||
| cmd | 方向 | data | 说明 |
|
||||
|---|---|---|---|
|
||||
| INIT(1) | 设备应答 | `[W_hi,W_lo,H_hi,H_lo, memsize(4B)]` = 8B | 回报分辨率与显存大小 |
|
||||
| LCDMEM(3) | 客户端请求 | `4B 大端起始偏移` | 请求从 offset 开始的显存 |
|
||||
| LCDMEM(3) | 设备应答 | `[4B 偏移][显存数据]` | 回当前帧缓冲内容 |
|
||||
| KEY(2) | 客户端请求 | `[键值]`(见 3.1) | 设备写入按键队列驱动导航 |
|
||||
| KEEPLIVE(0) | 双向 | 空 | 保活 ACK |
|
||||
|
||||
**我们的落地适配**
|
||||
- ✅ **传输层完全照搬** RemoDispBus(TAG/cmd/len/crc、四命令)。
|
||||
- ✅ **1bpp 内容**:帧缓冲 3200 字节。INIT 回报 `memsize=3200`;远端客户端据 `bpp = memsize*8/(W*H)` 自行推导为 1bpp(=1),无需改协议即可兼容。
|
||||
- ✅ **pull 模型**:设备持续用 `lv_timer_handler()` 刷新帧缓冲;收到 `CMD_LCDMEM` 即回当前 3200 字节(通常 offset=0 全帧)。
|
||||
- ⚠️ **位序约定**:1bpp 行优先、每字节 **MSB-first**(pixel(x,y) 存于 `byte(y*W/8 + x/8)` 的 bit `7-(x&7)`)。LVGL `LV_COLOR_FORMAT_L1` 的位序若与此不同,在 `ui_net_sink` 发送前做位重排(实现期用一块临时缓冲转换,或在 `lv_conf` 选匹配格式)。
|
||||
- ⚠️ **与旧客户端兼容说明**:参考项目旧 `remo_disp_server.py` 期望 8bpp(25600B)。若你要求**字节级完全兼容旧客户端**(不改客户端),则需改为 8bpp(25600B)——但会多占 ~22KB RAM,与"少占用 RAM"冲突。默认按 1bpp(3200B) 实施;如需兼容旧客户端请告知(见第 8 节备注)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 总体架构方案(建议)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 应用数据层 (现有: ADC/RTC/网络/NAND/LED,本阶段仅占位不接) │
|
||||
└───────────────┬──────────────────────────────────────────┘
|
||||
│ (本阶段不接数据;仅菜单导航)
|
||||
┌───────────────▼──────────────────────────────────────────┐
|
||||
│ UI 引擎 (LVGL v9, LV_COLOR_DEPTH=1, 关动画) │
|
||||
│ - lv_menu 主菜单(复现 3.3 树) + 占位内容页 │
|
||||
│ - lv_indev(KEYPAD) 读远端键队列 → 导航/切换 │
|
||||
│ - lv_timer_handler() 周期调用 → 渲染到 3200B 帧缓冲 │
|
||||
└───────────────┬──────────────────────────────────────────┘
|
||||
│ flush_cb → 写入 fbuf[3200] (CCM RAM)
|
||||
┌───────────────▼──────────────────────────────────────────┐
|
||||
│ 推流/应答适配层 (Drivers/BSP/UI/ui_net_sink.c) │
|
||||
│ - 解析 RemoDispBus 请求(INIT/LCDMEM/KEY/KEEPLIVE) │
|
||||
│ - LCDMEM → 回当前 fbuf;KEY → 入键队列;INIT → 报 3200 │
|
||||
└───────────────┬──────────────────────────────────────────┘
|
||||
│ net_socket_send() / recv()
|
||||
┌───────────────▼──────────────────────────────────────────┐
|
||||
│ 网络层 (CH395F,✅ 新开专用 TCP 监听 socket,端口 7070) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 模块落点(贴合现有 BSP 风格)
|
||||
| 新增文件 | 职责 |
|
||||
|---|---|
|
||||
| `Lib/lvgl/` | LVGL v9 源码(放入后**手动加进 Keil 工程**) |
|
||||
| `Drivers/BSP/UI/lv_conf.h` | 1bpp、关动画/日志、字号、键盘 indev 使能 |
|
||||
| `Drivers/BSP/UI/ui_port.c/.h` | `lv_display` + `flush_cb`(写 fbuf);`lv_indev` 读键队列 |
|
||||
| `Drivers/BSP/UI/ui_menu.c/.h` | 复现 3.3 菜单树(lv_menu 构建) |
|
||||
| `Drivers/BSP/UI/ui_page_placeholder.c` | 占位内容页(显示路径+"未实现") |
|
||||
| `Drivers/BSP/UI/ui_net_sink.c/.h` | RemoDispBus 解析/应答(INIT/LCDMEM/KEY/KEEPLIVE) |
|
||||
| `App/task/ui_task.c/.h` | 独立 FreeRTOS 任务:跑 `lv_timer_handler` + 网络收发 |
|
||||
|
||||
> **不要**把 UI 渲染塞进 `netTask` 的 10ms 轮询——渲染 CPU 密集。新建 `ui_task`(栈 1024–1536,1bpp 下很轻),优先级低于 `netTask`。网络收发走现有 `net_socket` 串行消息队列。
|
||||
|
||||
---
|
||||
|
||||
## 5. 需讨论条件 — 当前状态
|
||||
|
||||
| 项 | 状态 | 结论 |
|
||||
|---|---|---|
|
||||
| A 复现范围 | ✅ 已定 | 仅菜单树+导航;所有内容页占位 |
|
||||
| B 菜单视觉 | ✅ 已定 | lv_menu 整屏列表+返回栈 |
|
||||
| C socket 策略 | ✅ 已定 | 新开专用 TCP(端口 7070) |
|
||||
| D 刷新/动画 | ✅ 已定 | pull 模型(按 LCDMEM 应答);关过渡动画 |
|
||||
| E 帧编码 | ✅ 已定 | RemoDispBus(见 3.4);1bpp 内容,memsize=3200 |
|
||||
| F 单色定义 | ✅ 默认 | 纯 1bpp,无灰阶抖动 |
|
||||
| G 字体/中文 | ✅ 默认 | 中文 12px;仅收录菜单/占位用到的字(fontconverter 造 1bpp 字库) |
|
||||
| H 旋转/扫描 | ✅ 已定 | 默认不旋转,行优先 |
|
||||
| I RAM/Flash | ✅ 按建议 | 帧缓冲+draw buffer 放 **CCM RAM**;集成时实测空闲余量 |
|
||||
| J 库选型 | ✅ 默认 | LVGL v9(LV_COLOR_DEPTH=1) |
|
||||
| K 数据通道 | ⏸ 本期不涉及 | 占位阶段业务数据不接 UI;后续事件/共享结构体 |
|
||||
| L 持久化 | ✅ 默认 | 不需;每次上电回根菜单 |
|
||||
| M 异常 | ✅ 建议 | 客户端掉线暂停应答,不影响主业务;ui_task 软看门狗;键队列溢出丢 oldest |
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施阶段分解(建议顺序)
|
||||
|
||||
1. **阶段 0 · 决策冻结**:本文件第 5 节已冻结(仅 K/L/M 默认)。
|
||||
2. **阶段 1 · 骨架接入**:导入 LVGL v9 到 Keil;`lv_conf.h`(1bpp/关动画/字号);`ui_task` 渲染测试图;帧缓冲在 CCM。
|
||||
3. **阶段 2 · RemoDispBus 应答**:`ui_net_sink` 实现 INIT/LCDMEM/KEY/KEEPLIVE;新开 CH395F TCP(7070);远端按协议拉到 3200B 帧。
|
||||
4. **阶段 3 · 菜单复现**:`lv_menu` 复现 3.3 树(中文 12px 字库);远端 `CMD_KEY` 驱动上下/确认/返回导航。
|
||||
5. **阶段 4 · 占位页**:叶子进占位页(显示路径+"未实现"),验证返回栈与多级导航。
|
||||
6. **阶段 5 · 健壮性**:掉线恢复、软看门狗、RAM 余量复测、`@build` 0 警告验证。
|
||||
7. (后续)阶段 6 · 真实数据联动:按需把 ADC/RTC/网络/NAND/LED 接入 AppInfo/YC 等页。
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与约束
|
||||
|
||||
- **RAM 最紧**:3200B 帧缓冲 + LVGL 运行时(约 4–8KB)+ draw buffer(整帧即可,同 3200B 或更小)放 CCM;集成时实测 `netTask/ftp/FatFs/dhara` 后空闲堆/栈。
|
||||
- **ARMCC 兼容**:LVGL 纯 C99 可编译;`lv_conf.h` 关浮点/动画等单色屏用不到特性。
|
||||
- **不阻塞 netTask**:UI 渲染/收发在独立任务,走 `net_socket` 串行队列。
|
||||
- **位序对齐**:发送前确保 1bpp 位序(MSB-first 行优先)与客户端一致(见 3.4 ⚠️)。
|
||||
- **编码安全**:含中文,仅用 Edit/Write 工具改,绝不用 PowerShell 文本重写。
|
||||
|
||||
---
|
||||
|
||||
## 8. 决策待办清单
|
||||
|
||||
- [x] A 复现范围(仅菜单树+导航,其余占位)
|
||||
- [x] B 菜单视觉(lv_menu 整屏列表)
|
||||
- [x] C UI 通道(新开专用 TCP 7070)
|
||||
- [x] D 刷新/动画(pull 应答,关动画)
|
||||
- [x] E 帧编码(RemoDispBus,1bpp memsize=3200)
|
||||
- [x] F 单色(纯 1bpp,无抖动)
|
||||
- [x] G 字号(中文 12px)
|
||||
- [x] H 朝向(不旋转)
|
||||
- [x] I RAM(CCM 帧缓冲,集成实测)
|
||||
- [x] J 库(LVGL v9 1bpp)
|
||||
- [x] L 持久化(不需)
|
||||
- [x] M 异常(掉线暂停+软看门狗)
|
||||
@@ -131,21 +131,12 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
| S3 | 3.4.1 | `#define` 宏名必须全大写+下划线+数字 |
|
||||
| S4 | 3.5.1 | typedef 类型名必须以 `_t` 结尾 |
|
||||
| S5 | 6.3.2 | switch 必须包含 default 分支 |
|
||||
| S6 | 7.5 | 禁用 strcpy/strcat/sprintf/gets/scanf/strtok(改用安全版本) |
|
||||
| S6 | 7.5 | 禁用 strcpy/strcat/sprintf/gets(改用 strncpy/strncat/snprintf) |
|
||||
| S7 | 6.3.3 | 禁止 goto |
|
||||
| S8 | 4.1.1 | 禁止 Tab 缩进(4 空格) |
|
||||
| S9 | 5.3 | 函数定义必须有函数注释(含「函数功能」) |
|
||||
| S10 | 3.3.1 | 函数名必须全小写(HAL_ 厂商 API 回调名豁免) |
|
||||
| S11 | 2.3.5 | 禁止相对/绝对路径 include |
|
||||
| S12 | 7.6 | 禁止可变参数函数(`...`) |
|
||||
| S13 | 7.7 | 禁止递归函数 |
|
||||
| S15 | 6.1.4 | 文件作用域指针变量必须初始化为 NULL |
|
||||
| S16 | 3.2.2 | 文件作用域非 static 全局变量必须 `g_` 前缀 |
|
||||
| S17 | 2.2 | 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过) |
|
||||
| S18 | 6.3.1 | 禁止 Yoda 条件(字面量/常量必须在运算符右侧) |
|
||||
| S19 | 4.4.2 | 函数定义左大括号必须与右括号同行 |
|
||||
|
||||
**不在检查范围**(语义类规则,无法机械可靠判定):函数长度/单一职责、参数数量、错误处理、注释掉的代码等,仍需人工评审把关。
|
||||
**不在检查范围**(语义类规则,无法机械可靠判定):函数长度/单一职责、参数数量、错误处理、递归、注释掉的代码等,仍需人工评审把关。
|
||||
|
||||
**排除文件**(与 cppcheck 钩子范围一致,另加):`Src/`、`Inc/`(CubeMX 生成,重新生成即覆盖)、`Drivers/BSP/GD5F2GQ5UE/nand_ftl.*`(dhara/FatFS 接口适配层,函数名由外部 API 固定)。
|
||||
|
||||
@@ -276,7 +267,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
|
||||
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
|
||||
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
|
||||
| `scripts/check_coding_standard.py` | scripts/ | 代码规范检查脚本(规范 V1.0 可机械校验项) |
|
||||
| `docs/代码规范/嵌入式C语言代码规范(V1.0).md` | docs/代码规范/ | 代码规范原文(S1~S19 规则来源) |
|
||||
| `docs/代码规范/嵌入式C语言代码规范(V1.0).md` | docs/代码规范/ | 代码规范原文(S1~S9 规则来源) |
|
||||
| `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) |
|
||||
| `docs/代码规范/pre-commit使用说明.md` | 本文档 | 部署与使用说明 |
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ serial-mcp-server read --port COM6 --baud 115200 \
|
||||
**顺序必须:先启动采集(打开 COM 等 first-byte)→ 再烧录触发复位 → 采集自动抓取。**
|
||||
|
||||
```powershell
|
||||
# 一键:采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 校验(在仓库根目录执行,路径用仓库相对路径)
|
||||
powershell -ExecutionPolicy Bypass -File "test\run_boot_test.ps1" `
|
||||
# 一键:采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 校验
|
||||
powershell -ExecutionPolicy Bypass -File "D:\Code\DTU 程序\STM32F4-Base\test\run_boot_test.ps1" `
|
||||
-Port COM6 -BaudRate 115200 -DurationMs 30000 -InitialTimeoutMs 90000 `
|
||||
-IdleTimeoutMs 8000 -MaxBytes 65536 `
|
||||
-FlashCmd "MDK-ARM\download.bat" `
|
||||
-FlashCmd "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat" `
|
||||
-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL" `
|
||||
-Forbidden "FAIL;ERROR;HardFault"
|
||||
```
|
||||
|
||||
+13
-21
@@ -95,29 +95,21 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX; /* 基准对应的 Unix
|
||||
|
||||
## 6. 初始化流程(`sys_clock_init`)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant SC as sys_clock_init
|
||||
participant RTC as SD2506 RTC
|
||||
participant HAL as HAL Tick
|
||||
```
|
||||
Step 1 从 SD2506 读取初始时间 sd2506_get_time(&sd_time)
|
||||
├─ 注意:sd_time.year 已是完整年份(驱动内已 +2000),
|
||||
│ 后续换算【不得】再加 2000,否则年份虚增 → Unix 秒数超 uint32 回绕
|
||||
└─ 可选校时(见第 9 节 RTC_DEFAULT_INIT_* 宏)
|
||||
|
||||
Note over SC,RTC: Step 1 从 SD2506 读取初始时间
|
||||
SC->>RTC: sd2506_get_time(sd_time)
|
||||
RTC-->>SC: sd_time(year 已是完整年份)
|
||||
Note right of SC: 后续换算【不得】再加 2000<br>否则年份虚增 → Unix 秒数超 uint32 回绕
|
||||
Note right of SC: 可选校时(见第 9 节 RTC_DEFAULT_INIT_* 宏)
|
||||
Step 2 将 RTC 时间换算为 Unix epoch 秒数
|
||||
├─ 累计 1970 至当前年份的整年天数(含闰年 366 天判定)
|
||||
├─ 加上当年 1 月至当前月的累计天数
|
||||
├─ 加当月已过天数、时/分/秒
|
||||
└─ 用 int64_t 累加,避免中间溢出;结果截断存入 uint32_t(受 2038 限制,见第 12 节)
|
||||
|
||||
Note over SC: Step 2 将 RTC 时间换算为 Unix epoch 秒数
|
||||
SC->>SC: 累计 1970 至当前年份的整年天数(含闰年 366 天判定)
|
||||
SC->>SC: 加上当年 1 月至当前月的累计天数
|
||||
SC->>SC: 加当月已过天数、时/分/秒
|
||||
SC->>SC: 用 int64_t 累加,避免中间溢出;结果截断存入 uint32_t(受 2038 限制,见第 12 节)
|
||||
|
||||
Note over SC,HAL: Step 3 建立基准
|
||||
SC->>HAL: HAL_GetTick()
|
||||
HAL-->>SC: 当前 tick
|
||||
Note right of SC: s_base_tick = HAL_GetTick()<br>s_base_unix_secs = (uint32_t)unix_secs
|
||||
Step 3 建立基准
|
||||
s_base_tick = HAL_GetTick();
|
||||
s_base_unix_secs = (uint32_t)unix_secs;
|
||||
```
|
||||
|
||||
初始化后,所有后续取时都基于 Step 3 的基准推算,不再访问 RTC。
|
||||
|
||||
@@ -10,23 +10,13 @@ C 语言代码规范检查(《嵌入式C语言代码规范(V1.0)》可机
|
||||
S3 (3.4.1) #define 宏名必须全大写 + 下划线 + 数字
|
||||
S4 (3.5.1) typedef 类型名必须以 _t 结尾
|
||||
S5 (6.3.2) switch 必须包含 default 分支
|
||||
S6 (7.5) 禁用不安全字符串/输入函数 strcpy/strcat/sprintf/gets/scanf/strtok
|
||||
S6 (7.5) 禁用不安全字符串函数 strcpy/strcat/sprintf/gets
|
||||
S7 (6.3.3) 禁止使用 goto
|
||||
S8 (4.1.1) 禁止 Tab 缩进(使用 4 个空格)
|
||||
S9 (5.3) 函数定义必须有函数注释(含「函数功能」)
|
||||
S10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
S11 (2.3.5) 禁止相对/绝对路径 include
|
||||
S12 (7.6) 禁止可变参数函数(...)
|
||||
S13 (7.7) 禁止递归函数
|
||||
S15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
S16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
S17 (2.2) 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过)
|
||||
S18 (6.3.1) 禁止 Yoda 条件(字面量/常量必须在运算符右侧)
|
||||
S19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
|
||||
语义类规则(函数长度、单一职责、错误处理等)无法机械可靠判定,不在此脚本范围。
|
||||
语义类规则(函数长度、单一职责、错误处理、递归等)无法机械可靠判定,不在此脚本范围。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
@@ -36,77 +26,6 @@ FUNC_DEF_RE = re.compile(
|
||||
)
|
||||
CTRL_RE = re.compile(r'^\s*(if|for|while|switch|return|do|else)\b')
|
||||
|
||||
C_KEYWORDS = {'if', 'for', 'while', 'switch', 'do', 'else', 'return',
|
||||
'sizeof', 'struct', 'union', 'enum'}
|
||||
|
||||
NAME_PAREN_RE = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\s*\(')
|
||||
|
||||
|
||||
def _mask(text):
|
||||
"""注释与字符串字面量替换为等长空白(保留换行,行号不变),防止误报。
|
||||
|
||||
单次组合正则扫描,避免字符串内的 // 或注释内的引号造成顺序误掩。
|
||||
"""
|
||||
def _repl(m):
|
||||
return ''.join(c if c == '\n' else ' ' for c in m.group(0))
|
||||
pat = re.compile(r'"(\\.|[^"\\\n])*"|/\*.*?\*/|//[^\n]*', re.S)
|
||||
return pat.sub(_repl, text)
|
||||
|
||||
|
||||
def _line(text, pos):
|
||||
return text[:pos].count('\n') + 1
|
||||
|
||||
|
||||
def _fn_defs(masked):
|
||||
"""线性扫描找函数定义 name(...) {,返回 [(name, name行, body, pre, 大括号行, 右括号行)]。
|
||||
|
||||
body 为定义大括号内文本,pre 为名称前 200 字符(用于判定 static)。
|
||||
"""
|
||||
out = []
|
||||
n = len(masked)
|
||||
for m in NAME_PAREN_RE.finditer(masked):
|
||||
name = m.group(1)
|
||||
if name in C_KEYWORDS or name == 'defined':
|
||||
continue
|
||||
ls = masked.rfind('\n', 0, m.start()) + 1
|
||||
if masked[ls:].lstrip()[:1] == '#':
|
||||
continue
|
||||
i = m.end() - 1 # '(' 下标
|
||||
depth, j = 0, i
|
||||
ok = False
|
||||
while j < n:
|
||||
c = masked[j]
|
||||
if c == '(':
|
||||
depth += 1
|
||||
elif c == ')':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
ok = True
|
||||
break
|
||||
elif c in '{};':
|
||||
break
|
||||
j += 1
|
||||
if not ok:
|
||||
continue
|
||||
k = j + 1
|
||||
while k < n and masked[k] in ' \t\r\n':
|
||||
k += 1
|
||||
if k >= n or masked[k] != '{':
|
||||
continue
|
||||
depth, b = 0, k
|
||||
while b < n:
|
||||
if masked[b] == '{':
|
||||
depth += 1
|
||||
elif masked[b] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
b += 1
|
||||
out.append((name, _line(masked, m.start()), masked[k + 1:b],
|
||||
masked[max(0, m.start() - 200):m.start()],
|
||||
_line(masked, k), _line(masked, j)))
|
||||
return out
|
||||
|
||||
|
||||
def check_file(path):
|
||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
@@ -161,10 +80,10 @@ def check_file(path):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S5 6.3.2', 'switch 缺少 default 分支'))
|
||||
|
||||
# S6 (7.5) 不安全字符串/输入函数
|
||||
for m in re.finditer(r'\b(strcpy|strcat|sprintf|gets|scanf|strtok)\s*\(', text):
|
||||
# S6 (7.5) 不安全字符串函数
|
||||
for m in re.finditer(r'\b(strcpy|strcat|sprintf|gets)\s*\(', text):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S6 7.5', '不安全字符串/输入函数: %s (改用安全版本)'
|
||||
v.append((ln, 'S6 7.5', '不安全字符串函数: %s (改用 strncpy/strncat/snprintf)'
|
||||
% m.group(1)))
|
||||
|
||||
# S7 (6.3.3) 禁止 goto
|
||||
@@ -188,93 +107,6 @@ def check_file(path):
|
||||
v.append((idx, 'S9 5.3', '函数定义缺少函数注释(函数功能): %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
masked = _mask(text)
|
||||
mlines = masked.splitlines()
|
||||
defs = _fn_defs(masked)
|
||||
|
||||
# S10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
for name, ln, _body, _pre, _bl, _pl in defs:
|
||||
if re.search(r'[A-Z]', name) and not name.startswith('HAL_'):
|
||||
v.append((ln, 'S10 3.3.1', '函数名须全小写: %s' % name))
|
||||
|
||||
# S11 (2.3.5) 禁止相对/绝对路径 include(基于原始文本,字符串掩码会吃掉引号)
|
||||
for m in re.finditer(r'^\s*#\s*include\s*[<"]([^">]+)[">]', text, re.M):
|
||||
inc = m.group(1)
|
||||
if '/' in inc or '\\' in inc or '..' in inc:
|
||||
v.append((_line(text, m.start()), 'S11 2.3.5',
|
||||
'禁止相对/绝对路径 include: %s' % inc))
|
||||
|
||||
# S12 (7.6) 禁止可变参数函数
|
||||
for m in re.finditer(r'\(\s*[^();{}]*\.\.\.[^();{}]*\)\s*[;{]', masked):
|
||||
v.append((_line(masked, m.start()), 'S12 7.6', '禁止可变参数函数(...)'))
|
||||
|
||||
# S13 (7.7) 禁止递归函数
|
||||
for name, ln, body, _pre, _bl, _pl in defs:
|
||||
if re.search(r'\b%s\s*\(' % re.escape(name), body):
|
||||
v.append((ln, 'S13 7.7', '递归函数: %s' % name))
|
||||
|
||||
# S15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
for idx, line in enumerate(mlines, 1):
|
||||
if not line or line[0] in ' \t':
|
||||
continue
|
||||
if not line.rstrip().endswith(';') or '(' in line or '#' in line:
|
||||
continue
|
||||
first = line.split(None, 1)[0]
|
||||
if first in ('extern', 'typedef', 'struct', 'enum', 'union', 'static',
|
||||
'return', 'if', 'for', 'while', 'switch', 'do', 'else'):
|
||||
continue
|
||||
if '*' not in line or '=' in line:
|
||||
continue
|
||||
v.append((idx, 'S15 6.1.4', '文件作用域指针变量须初始化为 NULL: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
for idx, line in enumerate(mlines, 1):
|
||||
if not line or line[0] in ' \t' or line[0] in '}]':
|
||||
continue
|
||||
if not line.rstrip().endswith(';') or '(' in line or '#' in line:
|
||||
continue
|
||||
first = line.split(None, 1)[0]
|
||||
if first in ('extern', 'typedef', 'struct', 'enum', 'union', 'static',
|
||||
'const', 'volatile', 'return', 'if', 'for', 'while',
|
||||
'switch', 'do', 'else'):
|
||||
continue
|
||||
m2 = re.search(r'([A-Za-z_][A-Za-z0-9_]*)\s*(\[[^\]]*\])?\s*;\s*$', line)
|
||||
if not m2:
|
||||
continue
|
||||
if not m2.group(1).startswith('g_'):
|
||||
v.append((idx, 'S16 3.2.2', '非 static 全局变量须 g_ 前缀: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S17 (2.2) 非 static 函数必须在配对头文件声明(HAL_ 豁免;无配对头则跳过)
|
||||
if path.endswith('.c'):
|
||||
hpath = path[:-2] + '.h'
|
||||
if os.path.exists(hpath):
|
||||
with open(hpath, 'r', encoding='utf-8', errors='replace') as f:
|
||||
htext = f.read()
|
||||
for name, ln, _body, pre, _bl, _pl in defs:
|
||||
if name.startswith('HAL_'):
|
||||
continue
|
||||
seg = pre[pre.rfind(';') + 1:]
|
||||
seg = seg[seg.rfind('}') + 1:]
|
||||
if re.search(r'\bstatic\b', seg):
|
||||
continue
|
||||
if re.search(r'\b%s\s*\(' % re.escape(name), htext):
|
||||
continue
|
||||
v.append((ln, 'S17 2.2', '非 static 函数须在配对头文件声明: %s' % name))
|
||||
|
||||
# S18 (6.3.1) 禁止 Yoda 条件(常量在右)
|
||||
for m in re.finditer(r'\b(0[xX][0-9a-fA-F]+(?:[uUlL]+)?|\d+(?:[uUlL]+)?'
|
||||
r'|NULL|true|false)\s*(==|!=|<=|>=|<|>)\s+[A-Za-z_]',
|
||||
masked):
|
||||
v.append((_line(masked, m.start()), 'S18 6.3.1',
|
||||
'Yoda 条件(常量应在右): %s' % ' '.join(m.group(0).split())))
|
||||
|
||||
# S19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
for name, ln, _body, _pre, bl, pl in defs:
|
||||
if bl != pl:
|
||||
v.append((ln, 'S19 4.4.2', '函数定义左大括号须与右括号同行: %s' % name))
|
||||
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ param(
|
||||
[int]$InitialTimeoutMs = 90000,
|
||||
[int]$IdleTimeoutMs = 8000,
|
||||
[int]$MaxBytes = 65536,
|
||||
[string]$FlashCmd = "",
|
||||
[string]$FlashCmd = "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat",
|
||||
[string]$Required = "App init OK;Network Init;net init: OK",
|
||||
[string]$Forbidden = "FAIL;ERROR;HardFault",
|
||||
[string]$OutDir = "$env:TEMP\opencode",
|
||||
@@ -46,12 +46,6 @@ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$captureScript = Join-Path $scriptDir "serial_capture.ps1"
|
||||
$verifyScript = Join-Path $scriptDir "verify_log.ps1"
|
||||
|
||||
# Default flash command: resolve relative to this script's repo location
|
||||
# (the old hardcoded absolute path pointed at a stale checkout location).
|
||||
if (-not $FlashCmd) {
|
||||
$FlashCmd = Join-Path $scriptDir "..\MDK-ARM\download.bat"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $captureScript)) {
|
||||
Write-Output "RUN_TEST_ERROR: serial_capture.ps1 not found"
|
||||
exit 2
|
||||
|
||||
Reference in New Issue
Block a user