背景:为什么需要”降级”

管道监控系统最初围绕 STM32H562ZI 搭建:Cortex-M33 内核、256KB SRAM、QSPI 外挂 W25Q128、FSMC 驱动 320×480 LCD,跑 RT-Thread 完整版 + LVGL v9.1.0 + LittleFS。这套配置在实验室里很舒服,但进入现场小批量交付阶段时暴露出几个问题:

  • 成本:H562ZI 单价偏高,外加 W25Q128、LCD 模组、SRAM,BOM 成本压不下来。
  • 供货:H5 系列在国内小批量渠道的现货和交期都不稳定,现场维修替换不方便。
  • 现场实际需求收敛:经过一段时间的部署验证,现场人员更习惯用手机 Flutter App 通过后端查看数据,本地 LCD 几乎没人看;采样历史也已由后端 MySQL 统一存储,本地 Flash 落盘变成了冗余。

换句话说,H562 那套”大而全”的配置在现场场景里大部分算力都被浪费了。核心业务其实只有两件事:周期性 Modbus 采集 + JSON 上行到 DR154。这两件事 Cortex-M3 级别的 STM32F103VC 完全扛得住,而且成本只有 H562 的零头,供货也更稳。

于是有了这次”降级”迁移:把 H562 上的业务逻辑搬到 F103VC,同时砍掉本地显示和 Flash 存储这两条已经不再需要的链路。

芯片与平台差异

两个版本的 rtconfig.h 开头就交代了平台基调:

/* F103VC */
#define SOC_STM32F103VC
#define BOARD_STM32F103VC_PIPE_MONITOR
#define ARCH_ARM_CORTEX_M3

/* H562 */
#define SOC_STM32H562ZI
#define BOARD_STM32H562_NUCLEO
#define ARCH_ARM_CORTEX_M33
维度STM32F103VCSTM32H562ZI
内核Cortex-M3Cortex-M33
SRAM48KB256KB
片上 Flash256KB1MB
外挂 FlashW25Q128(QSPI)
LCD320×480 FSMC
文件系统LittleFS on FAL
GUILVGL v9.1.0
HAL 驱动包STM32F1 HALSTM32H5 HAL

F103VC 的资源约束直接决定了后续的架构裁剪方向。

上行 UART 引脚迁移

上行链路接的是 USR DR154 4G DTU,走 RS485 透传 JSON。H562 上为了避开 LCD 占用的 PD8/PD9(FSMC 数据线),把上行挂到了 UART7:

/* H562 main.c */
/* 上行链路已迁移到 UART7 PA15/PA8,避开 LCD 使用的 PD8/PD9。 */
#define APP_UPLINK_ENABLE            1U

对应的 rtconfig.h 启用了 BSP_USING_UART7。F103VC 没有 LCD 这个”引脚大户”,引脚冲突的约束消失了,但 F1 系列引脚资源本身也少,最终选了 USART3 的 PB10/PB11,方向控制复用 PB14:

/* F103VC main.c */
/* 上行链路在 F103VC 上使用 USART3 PB10/PB11,PB14 控制 RS485 DE/RE。 */
#define APP_UPLINK_ENABLE            1U

两个版本的 UART 使能情况对比:

/* F103VC rtconfig.h */
#define BSP_USING_UART1   /* debug console */
#define BSP_USING_UART2   /* RS485 Modbus 主机 */
#define BSP_USING_UART3   /* 上行 DR154 */

/* H562 rtconfig.h */
#define BSP_USING_UART1   /* debug console */
#define BSP_USING_UART2   /* RS485 Modbus 主机 */
#define BSP_USING_UART7   /* 上行 DR154 */

上行服务模块 uplink_service 本身与具体 UART 解耦,迁移时业务代码几乎零改动,只需调整 BSP 层引脚和 DMA 配置。这也是前期把上行抽成独立服务层的好处之一。

显示策略:从 LVGL 线程到无屏

H562 版本有一个独立的 lcd_ui.c,里面跑着 LVGL v9.1.0,栈 6144 字节、优先级 19、500ms 刷新一屏 11 行测点数据:

/* H562 lcd_ui.c */
#define LCD_UI_THREAD_STACK        6144U
#define LCD_UI_THREAD_PRIORITY     19U
#define LCD_UI_REFRESH_MS          500U
#define LCD_UI_WIDTH_DEFAULT       320U
#define LCD_UI_HEIGHT_DEFAULT      480U

main.c 在初始化阶段会显式拉起这个线程:

/* H562 main.c */
/* LCD UI 独立线程只拷贝最新采样快照,不消费测量事件,避免影响上行线程。 */
if (lcd_ui_start() != RT_EOK)
{
    rt_kprintf("[APP] lcd ui start failed\r\n");
}

F103VC 版本彻底移除了这条链路。main.c 里不再 #include "lcd_ui.h",初始化流程里也找不到 lcd_ui_start()rtconfig.hPKG_USING_LVGLBSP_USING_QSPIBSP_USING_W25Q128BSP_USING_SRAM 这些配置全部消失。

这个决策的依据是现场使用数据:现场人员习惯用手机 App 看实时数据和曲线,本地 LCD 长期处于背光关闭状态。与其维护一条没人用的显示链路,不如直接砍掉,既省 BOM 又省固件复杂度。

Flash 存储边界变化

H562 版本的 rtconfig.h 启用了完整的存储栈:

/* H562 rtconfig.h */
#define RT_USING_DFS
#define RT_USING_FAL
#define RT_USING_SFUD
#define RT_USING_QSPI
#define BSP_USING_W25Q128
#define PKG_USING_LITTLEFS

早期方案会在 LittleFS 上写 measurements.csv 落盘历史采样,断网时还要写 /data/pending.jsonl 缓存待补发的上行帧。

F103VC 版本砍掉了 DFS、FAL、SFUD、QSPI、LittleFS 整条链路。rtconfig.h 里这些宏全部消失,对应 main.c 里采集线程的注释也很直白:

/* F103VC main.c - app_measure_thread_entry */
/* 采样和上行失败都不写入 flash,传感器数据只保留在内存和实时上行链路中。 */

这里有一个工程判断:后端 MySQL 已经在存全量历史,DR154 的 4G 链路也足够稳定,本地 Flash 缓存的实际收益已经低于它带来的复杂度(文件系统损坏、写磨损、Rotate 逻辑)。把存储边界收缩到”只存错误/警告日志”后,固件里少了一整个线程和一套文件系统依赖,现场出问题的面也小了。

线程模型精简

H562 版本有 6 个业务线程,F103VC 砍掉了一半:

线程H562F103VC说明
app_init优先级 8优先级 8一次性初始化,保留
app_meas优先级 10优先级 10周期采集,保留
app_disp优先级 15移除LCD 渲染,无屏后不需要
app_store优先级 18移除CSV 写入,无文件系统后不需要
uplink优先级 12uplink_service_start 启动JSON 上行,保留
app_led优先级 20优先级 20心跳灯,保留

F103VC 的 app_init_thread_entry 启动顺序也相应简化——少了 lcd_ui_start() 和存储线程的拉起,初始化流程更短、启动更快。看门狗的监控掩码也从原来的四任务缩成三任务(measure + heartbeat + uplink)。

采集线程本身的核心逻辑两个版本几乎一致:同样的 2s 采样周期、同样的 Modbus 访问顺序和 30ms 间隔、同样的 app_publish_measurement 快照发布机制。这说明迁移时重点放在了”裁剪”而不是”重写”,业务逻辑的稳定性得以保留。

开发历程:从 PLAN0 到 PLAN11

这次迁移不是拍脑袋决定的,而是 PLAN0 到 PLAN11 一路演进的结果。

PLAN0-legacy-draft.md 是最初的设计草案,基于 H562 规划了完整的全栈方案:STM32 采集 + DR154 透传 + EMQX + MySQL + Node.js 后端 + Flutter App,分 6 个 Phase 落地。当时的设计假设是”设备需要本地 LCD + 本地存储”,所以 H562 的资源配置是合理的。

随着 Phase 1 到 Phase 5 逐步落地,现场反馈开始改变设计假设:Flutter App 的实时推送和历史曲线体验已经超过本地 LCD,后端 MySQL 的存储可靠性也覆盖了本地 CSV 的诉求。到了 PLAN11-completed.md 阶段,待办事项已经变成”手机端真实数据验收""MQTT 1883/8883 路线确认""多应用入口站”这些收尾工作——设备侧的功能形态已经稳定,剩下的都是云端和 App 侧的优化。

正是在这个节点上,设备侧”减负”的性价比凸显出来:既然现场不再需要 LCD 和本地存储,那支撑这两条链路的 H562 + W25Q128 + SRAM 就可以换成 F103VC,在保持业务逻辑不变的前提下大幅降低 BOM 和供货风险。

“降级”不一定是倒退

这次迁移看起来是芯片”降级”,实际上是架构”收敛”。几个关键点:

  • 业务逻辑零重写:采集、报警、上行协议三层代码几乎原样搬过来,说明前期的模块化设计(app_measurement.h 公共接口、uplink_service 独立服务层、事件驱动解耦)起了作用。
  • 引脚迁移有据可依:H562 当年为了避开 LCD 的 FSMC 引脚才用 UART7,F103VC 没有 LCD 约束后回归 USART3,反而更贴近 F1 系列的常规布线习惯。
  • 存储边界主动收缩:把”本地存历史”的职责完全交给后端,固件只负责实时采集和上行,职责单一化后现场故障面大幅收窄。
  • 成本与供货的现实考量:H5 系列的供货波动在小批量交付场景里是硬伤,F1 系列的现货渠道和价格优势更匹配现场维护节奏。

小结

  • 从 H562 迁移到 F103VC 的核心动机是成本、供货和现场需求收敛,不是性能不足。
  • 上行 UART 从 UART7 PA15/PA8 迁移到 USART3 PB10/PB11,原因是 F103VC 没有 LCD 引脚冲突约束。
  • 移除了 lcd_ui.c 线程和 LVGL 依赖,本地显示职责完全交给 Flutter App + 后端。
  • Flash 存储边界收缩:不再保存采样历史,传感器数据只存在于内存快照和实时上行链路。
  • 线程模型从 6 个精简到 4 个,砍掉了显示线程和存储线程,看门狗监控掩码同步收窄。
  • PLAN0 到 PLAN11 的演进记录了从”大而全”到”收敛聚焦”的工程决策过程。

后续阅读