# GD32VW55x_RTThread **Repository Path**: End-ING/gd32vw55x-rtthread ## Basic Information - **Project Name**: GD32VW55x_RTThread - **Description**: GD32VW55x_RTThread - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-30 - **Last Updated**: 2026-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GD32vw55x-RTThread GD32VW553H-EVAL 的独立 RT-Thread 5.3.1 BSP,单一正式版固件。板载 WiFi(GD32VW55x MACSW 包 + lwIP 2.1.2 + netdev + WLAN 框架)、自研存储栈(QSPI NOR → FAL → LittleFS/FlashDB/ulog 文件后端, 带授权与 fail-closed 生命周期管理)、KEY2 LCD 仪表盘、HTTP 天气、WiFi 自动重连与联网校时,以及主机侧 HIL 验收工具链。 - **目录** — [快速开始](#快速开始) · [目录结构](#目录结构) · [软件分层与存储栈](#软件分层与存储栈) · [运行与命令](#运行与命令) · [WiFi](#wifi) · [时间与-rtc](#时间与-rtc) · [LCD 多页面面板](#lcd-多页面面板) · [构建](#构建) · [下载与调试维护](#下载与调试维护) · [测试与验收](#测试与验收) · [接线与存储边界](#接线与存储边界) · [已知限制](#已知限制) ## 快速开始 ```sh # 1. 构建(需要 Python 3 / SCons / RISC-V GCC) scons -j8 # 产物 output/scons/rtthread.{elf,bin,hex,map} make -j8 # 等价的 Makefile 构建,产物在 output/make/ # 2. 白名单自检(不碰硬件) python tools/check_product_commands.py output/scons/rtthread.elf # 3. 下载(先关串口终端与其他调试会话;自动做维护握手) python tools/flash_openocd.py --elf output/scons/rtthread.elf # 4. 上电即自动启动存储;串口(115200 8N1)里用 status / wifi join 操作 ``` > 工作区路径**不要含空格**:SCons 在 Windows 上按空白切分命令,含空格的路径会让 `-I` 参数被截断。 > 可用 `subst X: "含空格的目录"` 建无空格盘符后从 `X:` 构建。 ## 目录结构 | 目录 | 内容 | |---|---| | `applications/` | 应用入口与产品功能:`main`、KEY2 控制的五页 LCD 信息屏(首页/天气/万年历/运行概况/网络)、Wi-Fi 自动重连、HTTP 天气、联网校时、受控 `reboot`、`rtc_get`、一次性 API 验收镜像 | | `board/` | 板级基础:MTIMER tick、串口、看门狗复位(`board_reset`)、`drv_wlan`/`wifi` 胶水 | | `board/port/` | 自研移植层,按功能分目录(见下表) | | `libraries/` | GD32VW55x 固件库 + `gd32_drivers`(RTC 等 RT-Thread 设备驱动) | | `packages/` | vendored `gd32vw55x-wifi` 包(MACSW/lwIP/mbedTLS,含本 BSP 的编译修复) | | `rt-thread/` | vendored RT-Thread 5.3.1 内核与组件(dfs/fal/littlefs/flashdb/ulog,含少量修复) | | `tools/` | 主机侧 Python:存储握手控制、OpenOCD 下载/调试、ELF 命令白名单检查、HIL 套件与单元测试 | | `docs/` | 芯片手册、原理图、评估板指南、无线评估固件 bin | `board/port/` 子目录职责: | 目录 | 职责 | |---|---| | `runtime/` | 存储生命周期总编排:状态机、readiness 事件、admission 门、lease 互斥、maintenance/reboot/status | | `qspi/` | 自写 QSPI 控制器驱动 + GD25Q16B 只读探测、分区授权、1→0 编程预检/页校验 | | `fal/` | FAL 静态分区表(内部 XIP 只读 + 外部 2MB NOR)、布局校验、FlashDB 扇区头识别、分区授权桥 | | `littlefs/` | DFS v1 适配:注册、只读挂载证明、provision、卸载句柄计数 | | `flashdb/` | FlashDB 2.2.0 板级策略与 KVDB/TSDB 的 NOT_FORMAT 打开/关闭 | | `ulog/` | 延迟启动的 ulog 文件后端:`/log` 下 3×16KB 轮转、停机 drain barrier + fsync | | `lcd/` | SPI LCD(240×320,8×16 ASCII + 外部 16×16 GB2312 字库)注册/显式初始化/绘制 | | `at24c02/` | I2C0 EEPROM 互斥访问 | | `bus/` | 自写 I2C0/SPI 适配器(带 deadline 与自旋上限,无 GPIO 死等) | | `storage_config.h` | 功能宏集中定义(被 `rtconfig.h` include) | ## 软件分层与存储栈 ```text 应用 lcd_clock / net_time_sync / product_acceptance / kv_get 等 —— 必须 wait_ready + 取 lease 才能触碰存储 协调 storage_runtime —— 唯一的生命周期编排者(启动/关闭/maintenance/reboot) 消费 littlefs(挂载 /)、FlashDB KVDB/TSDB、ulog 文件后端 —— 均经授权桥 FAL 静态四分区:lfs 1.5MB / kvdb 128KB / tsdb 256KB / 保留尾区 128KB(永不可写) 设备 nor.c(授权+页校验+备份确认)→ drv_qspi.c → GD25Q16B 2MB 外部 NOR ``` 一句话原则:**写路径全部显式授权、永不自动格式化、失败不回滚已写数据、掉电不伪造恢复**;保留尾区与内部 XIP FAL 只读。 ### 各层职责 | 层 | 位置 | 关键接口 | 职责 | |---|---|---|---| | 应用 | `applications/` | `storage_runtime_wait_ready` / `_acquire` / `_release` | 先等就绪、再取 lease,服务 I/O 期间持锁 | | 协调 | `board/port/runtime/storage_runtime.c` | `_schedule/_start/_stop/_prepare_reboot/_status`、`_admission_blocked`、`_clock_estimated/_mark_set` | 唯一生命周期编排者与状态机 | | 消费者 | `board/port/{littlefs,flashdb,ulog}/` | `littlefs_port_*`、`flashdb_port_*`、`ulog_file_port_*` | 挂载/打开/写日志,均经授权桥 | | 授权桥 | `board/port/fal/flash_port.c` | `flash_port_init`、`flash_runtime_begin/end`、`flash_partition_open_existing/open/provision/close/ready` | FAL 设备/分区表、布局校验、DB 扇区头识别 | | 设备策略 | `board/port/qspi/nor.c` | `nor_probe`、`nor_partition_enable/open_existing/close`、`nor_backup_confirm`、`nor_read/write/erase`、`nor_lock_writes` | 身份校验、授权位、1→0 预检、逐页/逐扇区校验 | | 控制器 | `board/port/qspi/drv_qspi.c` | `drv_qspi_init`、`drv_qspi_exec(msg, authorized)`、`drv_qspi_select_official_pins` | QSPI 总线 `qspi0` / 设备 `nor0`,指令白名单 | ### 三个同步原语(协调层) | 原语 | 载体 | 语义 | |---|---|---| | readiness | 粘性事件 `storage_rdy` + `ready_result` | `wait_ready(timeout)` 返回就绪快照;**READY 是快照,不是 I/O 租约**,业务前仍需取 lease | | admission 门 | `admission_blocked`(初值 1) | 仅在存储 RUNNING 且三个消费者就绪时打开;维护/停止期对非编排线程一律 `-RT_EBUSY` | | lease | 互斥 `storage_ctl` | `acquire/release` 包住服务 I/O;取到后二次复查 admission,失败即归还并返回 `-RT_EBUSY` | ### NOR 写授权三态(RAM-only,复位 / 换引脚 / 重探测即失效) | 状态 | 建立者 | 前置条件 | 允许什么 | |---|---|---|---| | runtime 资格 | `nor_runtime_begin()` | 身份有效 + 状态无 WIP/保护位 | **仅**允许"识别既有分区";无 grant、无备份、无写入、无格式化 | | existing grant | `nor_partition_open_existing()` | 资格有效 + 分区**非空** + DB 扇区头识别 / lfs 只读挂载证明 | 该分区的编程/擦除(仍经写预检) | | provision grant | `nor_partition_enable()` | `nor_backup_confirm(0x4241434B, 2MiB)` 整片主机备份确认 + 分区**全空**扫描 | 该分区的显式 format | 保留尾区 `reserved` 永不授权;请求不得跨分区边界。注意 existing 打开要求分区**非空**(借既有打开走 provisioning 会被拒绝),provision 打开要求分区**全空**,两者互为反向门。 ### NOR 写与擦除路径(`nor.c`) - 写:范围校验 → `mutation_allowed`(身份有效 + 备份确认或 runtime 资格 + 命中 grant + 无 WIP/块保护/QE)→ **整段 `check_programmable` 只允许 1→0 或幂等,绝不 0→1** → 按 256 B 页 `WREN(0x06)` → 页写 `0x02` → WIP 等待 → 回读 `memcmp` 校验。 - 擦:地址与长度 4096 B 对齐 → 逐扇区 `WREN` → `0x20` → WIP 等待 → `check_blank` 校验。 - 任何错误:`revoke()` 清掉全部 grant 与两种策略(**可能已部分写入,不做回滚**),消费者必须重查 grant;不提供 chip erase、flash reset、QE/块保护写入;块保护/QE 位一旦非零即拒绝且永不自行解除。 - 控制器侧:通用 `rt_spi` 路径强制 `authorized=FALSE`,只放行只读白名单(`0x9F`/`0x05`/`0x35`/`0x03`);只有后端私有 `drv_qspi_exec(..., TRUE)` 能发写/擦指令(`0x06`/`0x04`/`0x02`/`0x20`)。 ### 启动链(`storage_boot` 线程,优先级 21 / 栈 6144) 对应状态机 `last_stage`,逐级门禁,任一步失败即跳 `failed`: ```text qspi-pins nor_select_official_pins(TRUE) 占用 PA4..PA7 与 PB3/PB4(启用后完整 JTAG 不可用) nor-probe nor_probe 只读校验 JEDEC 0xC84015 / SR1 / SR2 / sample fal flash_port_init nor_ready + fal_init==4(分区表 4 项) + layout_valid existing-policy flash_runtime_begin 校验身份与布局,仅打开"识别资格" littlefs littlefs_port_mount_root 只读 lfs_mount 证明 → flash_partition_open("lfs") flashdb flashdb_port_open 逐 4KB 扇区头识别(FDB0/TSL0) → NOT_FORMAT init clock ensure_clock 编译时间/上次 TS 取较大下界,必要时回写 RTC ulog-file ulog_file_port_start 挂载+RTC 就绪后准备 /log 下 3×16KB 轮转 readiness publish_readiness state=running、打开 admission、广播 READY ``` 失败路径:`close_locked` 回滚 → `state=error` → `storage_maintenance_requested=1` → 打印 `STORAGE_ERROR stage=...`,**永不自动格式化**,原有数据保留。 ### 关闭链(`close_locked`,顺序固定) ```text log-stop ulog_file_port_stop drain barrier 排空日志 worker → 校验 fsync/close db-close flashdb_port_close deinit KVDB/TSDB → 关闭分区 grant unmount littlefs_prepare_unmount + dfs_unmount("/") 句柄计数归零后才卸载 → 关闭 lfs grant policy-stop flash_runtime_end 撤销识别资格(仍有未关闭 grant 时返回 -RT_EBUSY) jtag-restore nor_select_official_pins(FALSE) 仅 maintenance enter 时恢复 PB3/PB4 ``` `maintenance enter` 走完整关闭并恢复 JTAG;`reboot` 走同一关闭链但不恢复 JTAG(`prepare_reboot`),保存失败则拒绝复位。 ### 时钟门(estimated 标记) - `RTC_BKP3 == 0x45535431` 表示 `clock=estimated`:`ensure_clock` 用编译时间与上次持久化 TS 记录取较大下界回写 RTC 并置标记,避免回到 2000 年,但**不声称恢复离线时长**。 - 可信时间源(SNTP)调用 `storage_runtime_clock_mark_set()` 清除标记 → `clock=retained-or-set`;`status` 直接按 `RTC_BKP3` 判定。 - 时钟只在 provisioning(显式格式化)路径被强制要求;既有卷的正常写不因缺 RTC 而被拒绝。 ## 运行与命令 上电立即自动启动存储,无需手动初始化(只有 `maintenance enter` 才进入维护态)。 成功后 LittleFS 挂载到 `/`,KV/TS 数据库打开,ulog 文件后端启动。 **挂载不等于格式化**:仅识别并打开已有卷;空白、未知、损坏卷不会自动格式化。 产品 shell 仅导出以下 **18 条**命令: | 类别 | 命令 | |---|---| | 状态/信息 | `help` `version` `free` `status` `date` `rtc_get` | | 文件/数据库 | `ls` `cat` `df` `kv_get` `kv_set` | | 网络 | `wifi` `ifconfig` `netstat` `dns` `ping` | | 生命周期 | `maintenance` `reboot` | - `status` 是唯一状态入口:人类可读摘要 + 工具可读的 `state=...`、`clock=...` 及组件就绪信息。 - `kv_set key value` 是真实持久化写入,不是只读测试;`kv_get 已有key` 读取。 - `reboot` 拒绝任何参数(包括 force):先阻止新服务调用、排空日志缓冲并同步/关闭受管消费者, 成功才用看门狗复位;保存失败时拒绝复位(fail closed)。应用仍须自行 `fsync`/关闭所持句柄, 不能把正常重启保存视为任意断电保护;正常启动会自动重新打开存储。 - 产品不开放 raw/format/provision/test/rotate 命令;文件、FlashDB TS、日志由应用 API 使用。 ## WiFi WiFi 属于 GD32VW553 芯片内部 MAC/RF。初始化链路: `drv_wlan`(设备级)→ `platform_init`(RF/电源/NVDS/RTC 复用)→ `wifi_init` 任务 → `rt_wlan_set_mode` → netdev 注册。命令面:`wifi scan` / `wifi join SSID [密码]` / `wifi disc` / `wifi ap SSID [密码]` / `wifi ap_stop` / `wifi status`(`wifi help` 看全部);`ifconfig`/`netstat`/`dns`/`ping` 来自 netdev 驱动。 实板验证(2026-10-07,Xiaomi_070B,本机 COM15): | 场景 | 结果 | |---|---| | 正确密码 WPA2 join | 4-1..4-4 → 密钥安装 → DHCP 得地址 `192.168.31.203` → `ping` 网关 4/4、0% 丢包 | | `reboot` 后重新 join | 同样成功;RTC 与存储随启动自动恢复 | | `wifi scan` | 发现 18 个 AP | | SoftAP | `wifi ap` 起 `192.168.237.1/24` + DHCP,`wifi ap_stop` 正常 | | 错误密码 join | 3 轮 4-1/4-2 → EAPOL 超时 → deauth → 干净返回 failed,无踩踏 | | `wifi disc` | deauth 断开,`WIFI disconnected` | 最新实板记录(2026-10-12,COM9,Xiaomi_070B):启动自动加载 `/wlan.cfg`,取得 `192.168.31.204`; `wifi disc` 后自动重连成功,Ping 网关 `192.168.31.1` 4/4、0% 丢包;真实 RSSI 约 -35..-50 dBm。 `wifi status` 的框架 `Auto Connect: Disable` 指框架内置重连开关,应用层 `wifi_auto` 仍在运行。 天气客户端成功输出 `WEATHER OK city=Shenzhen temp=28.8 forecast=3`,联网校时输出 `NET_TIME_SYNC OK`。 IP/温度/RSSI 是该次测试值,运行中随网络与接口更新。 ### 曾经的问题与修复(已解决) 原始现象:正确密码 join 在 4-4 之后(首次数据帧 / DHCP 阶段)触发内存踩踏,校验和越过 RAM 末尾 (早期观察 `0x20050000`)。2026-10-07 用 JTAG 断点冻结现场取证后确认并修复了四处: 1. **RAM 常驻代码区被 ROM/MAC 固件的栈覆盖(根因)**。厂商平台链接脚本 `plf/riscv/env/gd32vw55x.lds` 明确保留 SRAM 最前 **0x200 字节**给 ROM/MAC(其栈/暂存), 而本 BSP 的 `link.lds` 用 `ORIGIN = 0x20000000` 把 `.code_to_sram` 压在该保留区上。 取证:`lwip_standard_chksum`(`0x20000044` 起)在 `0x20000138` 被替换成调用链数据 (`sys_calloc`/`sys_mfree`/`snprintf`/`printf` 返回地址),执行垃圾指令后 `mepc=0x20000144, mtval=0x43008049`;ELF 段字节与 flash 一致、仅 RAM 不同,证明是运行时覆盖。 修复:`ORIGIN = 0x20000000 + 0x200, LENGTH = (288K - 0x200)`。 2. **启动文件缺失**。`env_Eclipse/start.S`、`entry.S` 被 `.gitignore` 的 `*.s` 规则忽略 (Windows 下大小写不敏感,连 `.S` 一起忽略)。已恢复官方 `RT-Thread-packages/gd32-risc-v-series` 版本:含 `SW_handler` + `MTVT2` 重映射 (RT-Thread 抢占/中断切换依赖)与启用的 `_sicode`→`_scode` 拷贝循环。 用包内 2023 SDK 版会启动即崩(RAM 代码未拷贝,PC 卡 `0x20004E04`;或全寄存器 `0xdeadbeef` 崩溃循环)。 3. **`TCPIP_THREAD_STACKSIZE` 416→1024**(wrapper 按字 ×4 = 4KB): `LWIP_TCPIP_CORE_LOCKING_INPUT` 下整个协议栈收包在 tcpip 线程上跑,首波 DHCP/ARP 是栈深峰值。 4. **窗口必须是真实堆区**。wrapper 实现了厂商 heap_4 语义(`vPortAddHeapRegion` 等价): ROM MAC 用 `sys_malloc` 从 `0x20048000` 窗口分配 RX 帧池(厂商主堆仅 2KB,池必然落在窗口); `sys_malloc` 窗口优先、`rt_malloc` 兜底,`sys_mfree` 按地址区间分派,注册后记账只报区域空闲, `sys_heap_block_size` 返回 16(= `heapMINIMUM_BLOCK_SIZE`)。 旧的"只登记不写入"记账会让池留在主堆,而 ROM 仍按窗口算术访问。 上游对照(2026-10-05):本包与官方 `RT-Thread-packages/gd32vw55x-wifi` HEAD 逐文件一致 (仅差本 BSP 的编译修复);官方 BSP(`bsp/gd32/risc-v/gd32vw553h-eval`)提交的配置 WiFi 是关闭的, 其 `link.lds` 同样缺 0x200 保留,从未在 MAC ROM 激活下验证过,故未暴露此问题。 ### 硬件与内存要点 - **顶部 32KB**(`0x20048000..0x2004FFFF`,PHY/LA/MAC 共享 RAM)由 MAC 池占用:窗口是真实的第二堆区 (厂商 heap_5/heap_4 语义),不要往窗口写无关数据;**SRAM 最前 0x200 字节是 ROM 保留区**,任何段都不得占用。 - **NVDS**:位于内部 Flash `0x3FB000-0x3FF000`,由厂商 ROM API 读写,与外部 NOR 的 FAL 分区无关; 完整断电可丢失校准/设置数据,属硬件行为。 - **RTC 由 RT-Thread 驱动持有**:WiFi 平台代码保留正在运行的日历和预分频器,仅在冷启动 (无备份电源、时钟源未选)时按板级因子初始化,不再把日期砸回 2020。 - MAC/RF 固件符号来自预编译 `libwifi/libwpas/librf` + ROM 符号表(`--just-symbols`); **不要对 `libwifi.a` 使用 `--whole-archive`**(会拖入 256KB 的 LA 段)。 - 288KB RAM:最新 SCons 的 data/bss 为 2236/142820 字节(另有 RAM 常驻代码/栈),实板主堆约 129208 字节;五页新版 BIN 为 896980 字节,在内部 4MB Flash 内,与 NVDS 区无重叠。 - 崩溃恢复:固件异常时打印 bloodhound 格式现场后看门狗复位(`handlers.c`);现场取证用 JTAG 断点冻结。 ## 时间与 RTC RTC 断电可能丢失日历;固件仅用构建时间/已有 TS 记录作为下界,并标记 `clock=estimated`—— 这不是实际离线时长恢复。 **联网自动校时**:WiFi 拿到地址后,`net_time_sync` 线程启动 SNTP(`ntp.aliyun.com` / `cn.pool.ntp.org`,每小时刷新),成功后写入硬件 RTC 并清除 estimated 标记,`status` 随即显示 `clock=retained-or-set`。实测(2026-10-07):join 后打印 `NET_TIME_SYNC OK epoch=...`, `rtc_get`/`date` 与主机 UTC 一致(秒级),重启后 RTC 保持。 也可关闭串口终端后从主机主动同步(需要板卡访问授权): ```sh python tools/storage_control.py sync-time --port COM14 ``` 工具用本地 UTC+8 `date` 设置时间,必须同时验证 setter 无失败且有 old/now 回执、`rtc_get` epoch 与主机预期差不超过默认 3 秒、`status` 明确 `clock=retained-or-set` 且无 estimated 标记, 才报告 `SYNC_TIME PASS`;缺证据即失败。未指定端口时使用 `RTT_SERIAL_PORT` 或唯一 CH340 自动检测。 ## LCD 多页面面板 240×320 SPI LCD 上电默认停留首页,**KEY2(PA0)每短按一次切换下一页**:首页 → 天气 → 万年历 → 运行概况 → 网络 → 首页。 无定时自动翻页;短按在释放后切页,按键 20 ms 采样、三个一致样本去抖。万年历页长按 800 ms 进入选月, 选月中单按(300 ms 双击窗口结束后)进下月、双按退上月、长按回本月并退出选月;长按释放不额外翻页。启动时一直按住 KEY2 仍保留进入维护态的行为, 先释放后才能触发运行期翻页。连续三个 20 ms 采样一致才判定有效按键,边沿发生到确认约 40–60 ms。顶部显示页码指示点,底部显示按键提示。 | 页面 | 实际显示内容 | |---|---| | 首页 | 大时钟、日期/星期、深圳天气、Wi-Fi/DHCP、NTP、RTC、HTTP 状态 | | 网络 | STA SSID、IP/子网/网关/DNS、真实 RSSI、天气 HTTP 与 NTP 状态;MQTT 未配置明确显示 | | 万年历 | 公历月历、今日高亮、周末/节日标记、今日农历、下一常见节日倒计时;选月时展示本月前两个常见节日 | | 运行概况 | 运行/离线/等待校时/天气待更新摘要、运行时长、Wi-Fi、最近校时/天气获取时间、存储就绪、内存余量/峰值;CPU 估计作辅助信息 | | 天气 | HTTP API 实际温度、湿度、空气质量、风向/等级、未来三天的温度/天气、接口更新时间 | 万年历公历月份浏览范围 1901-01 至 2100-12;离线农历转换支持 1901-02-19 至 2100-12-31。 年表位于 `applications/lunar_years.h`,常量约 800 字节 Flash;日期算法无需联网或动态分配。 节日涵盖常见固定公历节日、春节/元宵/端午/七夕/中秋/重阳/腊八/除夕,以及母亲节/父亲节。 每个日期取一个主要节日;这些是节日名称,不包含官方放假/调休安排、清明/节气推算。 浏览其它月份时,“今日农历”仍对应 RTC 今天,本月节日摘要对应正在浏览的月份。 `python tools/test_calendar.py` 检查春节、闰月、除夕、月份边界和导航,并在 Windows 用 .NET ChineseLunisolarCalendar 交叉验证 73,000 个日期;主机 GCC 编译测试,PowerShell 提供全日期基准。 按 500 ms 采样数据,只重绘变化字段。变化文字先在局部 RGB565 缓冲合成,直接覆盖旧值,不先清空区域; 等长文字只更新变化字形(时钟秒变化通常为 24×48 像素),仪表环与百分比一起合成写屏。 共享文字缓冲约 7.6 KB;整屏只在切页、天气可用状态改变或驱动恢复时清屏。 使用 8×16 ASCII 与 `/font16.bin` 的 16×16 GB2312 一级汉字,小图标/环形仪表采用局部 RGB565 缓冲, 没有全屏 framebuffer。字库没有的汉字显示占位符。 Wi-Fi 状态要求 STA 已关联且 LINK_UP、有有效 IP;RTC 可读不冒充 NTP 已同步。 `wifi_auto` 在断开后每 30 秒重试保存的网络,Wi-Fi 配置写入先 fsync 再 rename。GD32 的 DHCP_START 事件 在认证后恢复 lwIP 链路标志,避免首次连接正常、第二次连接 DHCP 超时;AP 断开事件同步到 WLAN 管理层。 天气使用 `t.weather.itboy.net` 的深圳 HTTP 接口,每 10 分钟刷新;失败每 30 秒重试,断网时等待网络恢复。 DNS 与 TCP 全程 30 秒超时;旧响应不能覆盖新请求。JSON 按实际 forecast 数组读取明日起三天, 失败保留缓存并标 OLD,不填入虚构温度。MQTT 需另行配置/实现 Broker 接入,目前显示未配置。 `maintenance enter` 暂停绘图,当前帧的存储 lease 保证字库读取完成后才关闭/卸载;退出维护后恢复。 LCD 传输失败不提交字段缓存,下一次刷新重新初始化并绘制,串口记录具体错误。 已有 `/font16.bin` 时支持中文;缺失字库会显示占位符,固件不会自动上传字库。主机可用 `python tools/make_font.py` 生成字体资源(需 Pillow 和系统中文字体);完整字库部署仍需要外部文件写入流程。 主机测试 `python tools/test_dashboard.py` 检查按键去抖/不重复、天气 TCP EOF/迟到回调/溢出、JSON 解析、布局边界、单数字刷新、缩短文字覆盖及绘图失败恢复, 输出布局像素预览;主机模拟不能证明实物屏幕方向、按键接线或网络可达性。 ## 构建 只有**一个正式版固件**(`BSP_DEVELOPMENT=0` 常量)。原先的 product/development 双档构建与仅调试用的 shell 命令(nor/eeprom/i2c/spi/lcd/fs/db/log 诊断、memtrace/memcheck、rtc_test 等)已移除。 | 命令 | 中间文件 | 规范产物 | |---|---|---| | `scons -j8` | `build/scons/` | `output/scons/rtthread.{elf,bin,hex,map}` | | `make -j8` | `build/make/` | `output/make/rtthread.{elf,bin,hex,map}` | `output/rtthread.*` 是最近构建的副本;**验收/下载请指定明确 ELF**。 一次性 API 验收镜像是可选的隔离构建(产物与正式产物完全隔离): ```sh scons -j8 --acceptance-marker <32位小写hex> # -> output/scons/product-api// ``` 环境:Python 3、SCons、RISC-V GCC;下载另需 pyserial/OpenOCD。 `RTT_EXEC_PATH`、Make `TOOLCHAIN_DIR`/`CROSS_COMPILE`、`OPENOCD` 可覆盖工具发现。 Windows 下避免含空格的构建路径(见[快速开始](#快速开始))。 ## 下载与调试维护 QSPI 与完整 JTAG 共用 PB3/PB4,因此 `maintenance` **只为调试器共享引脚服务**,不是正常挂载前置步骤: 1. `maintenance enter` 保存/关闭存储;**仅**收到精确行 `MAINTENANCE_ENTER PASS result=0 jtag_ready=1` 才连接调试器。 2. 使用调试器/下载器。 3. 先关闭 OpenOCD/GDB,再用 `maintenance exit` 启动存储并把引脚归还 QSPI。 ```sh # 仅在允许硬件访问后运行;先关串口终端与其他调试会话 python tools/storage_control.py stop python tools/flash_openocd.py --elf output/scons/rtthread.elf python tools/storage_control.py start ``` - 主机 CLI `status`/`start`/`stop` 分别映射 `status`/`maintenance exit`/`maintenance enter`; 只有 `command not found` 才回退旧 `storage`。失败、busy、超时不绕过握手,不启动调试器,不杀其他程序。 - **硬件恢复**:按住 K2 WAKEUP(PA0 低有效),按下并释放 K1 RESET;确认该次启动已保持维护状态后释放 K2。 - 如 UART 不可用,仅在明确确认安全维护/JTAG-ready 后才用 downloader `--maintenance-held` 绕过 UART; 不能对仍持有共享引脚的运行固件使用该绕过。 - VSCode F5 使用维护预任务与 `tools/storage_debug.gdb`。 ## 测试与验收 ```sh # 主机-only,不访问板卡 python -m unittest discover -s tools -p test_storage_control.py -v python tools/check_product_commands.py output/scons/rtthread.elf python tools/check_product_commands.py output/make/rtthread.elf # 仅获硬件授权后执行;默认只读,不下载、不复位 python tools/accept_product.py --elf output/scons/rtthread.elf --port COM14 ``` - 白名单解析实际 ELF Finsh 表(名称/函数指针),不拿字符串扫描代替导出检查;产品固定 18 条命令。 - 产品验收默认只查询状态、时间、目录、容量,可加 `--read-file`/`--read-kv` 读取现有对象; 仅显式 `--allow-writes` 才先确认唯一 `product.accept.` key 不存在,再做 KV 写/readback, 报告新建 `output/hil/product-/results.json`。 - 文件写/`fsync`、TS append、日志 emit/轮转、看门狗重启持久化、物理掉电均明确 NOT_TESTED, 需单独应用 HIL;KV/status 成功不等于全功能通过。 - 旧硬件报告不覆盖新版镜像。新版实板验证:产品启动、18 命令、KV、watchdog reboot、维护进出、 WiFi 全链路(见 [WiFi](#wifi))、联网校时与 RTC 保持。 - 历史证据与原始备份见 [tools/README-HIL.md](tools/README-HIL.md) 与 `output/hil/`(`output/` 不入库); [README-PORTING.md](README-PORTING.md) 保留移植历史,其手动初始化流程不是产品日常步骤。 GD-Link 配置 `adapter speed 1000` 的单位为 kHz(1 MHz)。若 Windows 显示 GDLinker_V2 Code 10 或 OpenOCD 找不到 CMSIS-DAP,先恢复下载器 USB/驱动;发生写入中断后必须重新完整烧录并取得 `Verified OK`。 本板整份镜像一次写入曾发生 USB 管道中断,使用 `python tools/flash_openocd.py --elf output/scons/rtthread.elf --port COM9 --chunked` 可将指定 ELF 转成临时 BIN、按 16 KiB 分块写入,最后校验整份镜像(`CHUNKED Verified OK`),校验成功才复位。 分块模式保持维护握手;临时文件自动清理,不读取可能过期的同名 BIN。 ## 接线与存储边界 所有跳线仅在断开全部电源/USB 后调整。 | 外设 | 引脚/跳线 | 说明 | |---|---|---| | QSPI NOR | JP2 1-3/2-4、JP3 1-3/2-4、JP5 1-2、JP21 1-2 | PA4 SCK、PA5 CS、PA6 IO0、PA7 IO1、PB3 IO2、PB4 IO3;当前单线传输,非 quad/cJTAG 验证 | | SPI LCD | JP20 | PA9 MOSI、PA10 MISO、PA11 SCK、PA12 CS、PB12 RESET、PB13 DC;不要短接 MOSI/MISO | | AT24C02C | I2C0 PA2/PA3 | 地址 0x50,256B/page8 | 外部 GD25Q16B 2MiB 分区(偏移/长度):LittleFS `0x000000`/`0x180000`、KV `0x180000`/`0x020000`、 TS `0x1A0000`/`0x040000`、保留尾区 `0x1E0000`/`0x020000`。保留尾区不可写,内部 XIP FAL 只读。 禁止把启动错误当格式化许可;显式破坏性测试必须事先完整备份并独立授权。 ## 已知限制 - `netstat` 在当前 netdev 配置下回报 "not supported";`ifconfig`/`dns`/`ping` 正常。 - `tools/` 中的 `hil_runtime.py`、`hil_msh.py`、LCD/NOR/storage/persistence 套件依赖已移除的 development 档与调试命令,需改造后才能用于单一正式版固件。 - 本板已目视确认五页界面与万年历单按/双按/长按正常;局部合成刷新通过主机像素测试,不承诺 SPI 扫描完全无撕裂。启动时 K2/K1 维护恢复路径未专项复测。 - 物理掉电安全、文件写/`fsync`、TS append、日志轮转的持久化未纳入当前验收。 - `drv_usart.c` 的 `gd32_uart_putc` 无界等待 TBE,异常时可能长时间占用控制台路径。 - TS 记录在 RTC 不可用时保持单调并在首次告警,此时时间戳不代表真实墙钟时间。