架构特别篇五Middlewares 层 构建系统 — 站在巨人肩膀上的艺术无人船不是从零写的——FreeRTOS、lwIP、cJSON、MAVLink、Modbus、SEGGER RTT。七个中间件怎么选、怎么集成、怎么做到只引入需要的、不污染其他层以及 Keil MDK 工程怎么管理 150 个源文件不混乱1. 七个中间件的选型逻辑中间件 用途 选型理由 ───────────────────────────────────────────────────── FreeRTOS v11 RTOS ARM 官方推荐,静态分配,抢占式 lwIP v2.2 TCP/IP 协议栈 最轻量的 BSD 协议栈,STM32 官方支持 cJSON 遥测 JSON 编解码 单文件 .c/.h,零依赖,700 行 MAVLink v2 地面站通信协议 无人机生态标准,PX4/APM/QGC 都用 Agile Modbus 设备寄存器轮询 比 FreeModbus 更轻,支持 RTUTCP coreMQTT v2.3 MQTT 客户端 AWS 官方库,4G 云通信 SEGGER RTT v8 调试日志输出 J-Link 高速通道,不占 UART,彩色分级选型原则单文件优先cJSON 只有 2 个文件——拷进来就能用不需要配 CMake官方支持优先FreeRTOS 是 ARM Keil 原生支持的lwIP 是 ST 官方适配的生态标准优先MAVLink 选它不是因为最好而是因为 QGC/PX4/APM 都用它——和已有生态兼容2. Middlewares 目录结构Middlewares/ ├── FreeRTOS/ ← 完整 FreeRTOS 源码 (tasks/queue/timers/heap_5...) │ └── portable/ ← ARM Cortex-M7 移植层 ├── NET/ ← lwIP 完整源码 │ └── src/ ← core/ api/ netif/ apps/ ├── JSON/ ← cJSON.c cJSON.h (仅 2 个文件!) ├── MAVLINK/ ← 消息头文件 (common/ minimal/) ├── MODBUS/ ← Agile Modbus RTU TCP ├── MQTT/ ← coreMQTT 客户端 └── RTT/ ← SEGGER_RTT.c SEGGER_RTT_printf.c CAT_LOG.h每个中间件保持原始目录结构——不做改造、不做封装。升级时直接替换目录即可不用改引用路径。3. 集成方式薄封装不魔改以 lwIP 的 FreeRTOS 移植为例// sys_arch.c — lwIP 到 FreeRTOS 的胶水层,仅 200 行// 实现 lwIP 要求的信号量、邮箱、线程接口sys_sem_tsys_sem_new(u8_tcount){SemaphoreHandle_t semxSemaphoreCreateCounting(...,count);return(sys_sem_t)sem;}voidsys_sem_signal(sys_sem_tsem){xSemaphoreGive((SemaphoreHandle_t)sem);}不是封装——sys_arch.c 是实现 lwIP 要求的接口不是给 lwIP 套一层壳。上层代码CONTROL 层的 net_comm.c直接调 lwIP 原生 API// net_comm.c — 直接用 lwIP APIstructudp_pcb*pcbudp_new();udp_bind(pcb,IP_ADDR_ANY,port);udp_recv(pcb,udp_recv_callback,NULL);不经过封装层。封装层在嵌入式里是反模式——多一层就多一次函数调用、多一个故障点。4. CAT_LOG分颜色、分等级、零 UART 占用基于 SEGGER RTT 的日志系统// CAT_LOG.h — 颜色分级日志宏#defineLOGI(fmt,...)SEGGER_RTT_printf(0,RTT_CTRL_TEXT_GREEN[INFO] fmt\n,##__VA_ARGS__)#defineLOGW(fmt,...)SEGGER_RTT_printf(0,RTT_CTRL_TEXT_YELLOW[WARN] fmt\n,##__VA_ARGS__)#defineLOGE(fmt,...)SEGGER_RTT_printf(0,RTT_CTRL_TEXT_RED[ERR] fmt\n,##__VA_ARGS__)// 使用:LOGI([LIDAR] Task started, UART%d, %dbps,port,baud);LOGW([RADAR] Data timeout, radar offline);LOGE([CAN] FDCAN1 transmit error: %d,err);为什么不用 UART 做日志UART 波特率有限115200大量日志会阻塞任务RTT 用 J-Link 的调试通道——480MHz 下带宽 ~2MB/s不占用任何外设引脚5. Keil MDK 工程管理150 源文件怎么不乱NexusMcu.uvprojx (Keil 工程文件) ├── Group: APP (1文件) ├── Group: CONTROL (40 文件, 按子目录分) │ ├── motion/ │ ├── sensor/ │ ├── communication/ │ └── ... ├── Group: DEVICES (20 文件) ├── Group: SYSTEM (30 文件) ├── Group: BSP (5 文件) ├── Group: Middlewares (50 文件) ├── Group: HAL (100 文件, STM32 库) └── Group: User (启动文件 main.c)工程分组和代码目录完全对应。改 CONTROL 层代码 → 展开工程的 CONTROL 组 → 找子目录。结构就是文档。6. 构建流程BAT lnp sct 三件套Keil MDK 支持命令行编译用 ArmClang 直接调:: NexusH7.BAT — 命令行构建脚本 SET CPU_TYPESTM32H743IITx C:\Keil_v5\ARM\ARMCLANG\Bin\ArmClang.exe .\objects\main.__i C:\Keil_v5\ARM\ARMCLANG\Bin\ArmClang.exe .\objects\path_planner.__i C:\Keil_v5\ARM\ARMCLANG\Bin\ArmClang.exe .\objects\dwa_planner.__i :: ... 150 行 ... C:\Keil_v5\ARM\ARMCLANG\Bin\ArmLink.exe .\objects\NexusMcu.lnp三件套各管什么.BAT编译命令列表.lnp链接器输入所有 .o 文件列表.sct内存布局分散加载脚本这些文件都是手写的不是 KEIL 自动生成的。意味着可以版本控制、可以 diff、可以 CI 调用。未来切换到 CMake 时CMakeLists.txt就是从这三件套翻译过去的。7. 为什么还没切 CMake现在的尴尬Keil MDK 工程是手写的、能用的、调试方便的。CMake ARM GCC 是开源的未来方向但切过来有三件事要做① ARM GCC 的 startup linker script → 替代 Keil 的 ② CMSIS-DSP 库改用 GCC 编译版本 ③ H7 的 FPU 选项、优化等级对齐 工作量 ~3 天。但现在更优先的是跑稳而不是换构建系统。切换时机当需要 CI/CD 自动化编译时。当前一个人 Keil 开发完全够用。8. 完整的构建依赖链可视化main.c │ ├── bsp.h ──→ sys_config.h ──→ SYSTEM 全部 │ └──→ devices_list.h ──→ DEVICES 全部 │ └──→ control_list.h ──→ CONTROL 全部 │ ├── zscode_app.h ──→ 创建所有任务 │ └── FreeRTOS lwIP cJSON MAVLink Modbus MQTT RTT 编译链: .c/.s → ArmClang → .o → ArmLink → .axf → .hex/.bin ↑ NexusMcu.sct (内存布局) NexusMcu.lnp (链接输入)核心哲学一个#include链贯穿所有分层一个编译命令生成固件一个.sct定义内存。最小化构建复杂度是开源项目零门槛编译的前提。本文是《从零搭建无人船控制系统》架构特别篇。项目地址[煜坤 YuKun]开源准备中。