《架构特别篇四:CONTROL 层》
2026/7/11 16:11:58 网站建设 项目流程

架构特别篇四:CONTROL 层 — 20 个任务的编排艺术

这是架构中最大的一层——20 个 FreeRTOS 任务、7 个控制模式、50 多个模块文件。怎么组织不混乱?答案:任务按"离推进器距离"分配优先级,数据按"谁写谁读"设计单向流。


1. CONTROL 层的内部结构

CONTROL/ ├── motion/ ← 运动控制 (最大的子模块) │ ├── perception/ ← 感知: 代价地图 + 传感器融合 │ └── planning/ ← 规划: D* Lite + DWA + path_planner ├── sensor/ ← 传感器采集任务 (imu/rtk/radar/lidar/rc) ├── communication/ ← 通信 (lwIP/MQTT/主机协议) ├── power/ ← 电源管理 ├── security/ ← 安全监控 ├── storage/ ← 参数持久化 ├── thruster/ ← 推进器控制 ├── modbus/ ← Modbus 主站 ├── apm/ ← PX4 桥接 └── system/ ← 状态上报

按功能分子目录,不是按层次分。每个子目录是独立的功能域——运动控制、传感器、通信、电源。它们之间通过全局数据结构体交换信息,不直接相互调用。


2. 优先级设计:离推进器越近,优先级越高

优先级 7: IWDG Monitor ← 看门狗, 系统最后防线 优先级 6: Motion Control ← 50Hz 控制回路, 离推进器最近 优先级 5: Thruster/PWM ← 推进器输出 优先级 4: IMU/RTK/Radar/LiDAR/RC ← 传感器采集, 实时但可容一两帧延迟 优先级 3: Power/Host/IoT ← 低频任务, 不紧急 优先级 2: Ship State ← 遥测打包, 最低优先级

设计原则:如果 CPU 满载,高优先级任务总能抢占。控制回路绝对不能延迟(延迟 20ms → 船偏航)。遥测延迟 1 秒 → 地面站卡一下,没关系。

3. 任务间的三种通信模式

模式 1:全局结构体(生产者-消费者)

最常用。一个任务写,一个或多个任务读。

// radar_task.c (生产者, 33Hz)g_radar_data.min_distance=2.5f;g_radar_data.data_valid=true;g_radar_data.last_update_ms=now;// motion_control.c (消费者, 50Hz)if(g_radar_data.data_valid&&(now-g_radar_data.last_update_ms<200)){avoid_update(g_radar_data.min_distance,...);}

适用:数据单向流动,写入者单一,读取者不需要确认。

模式 2:函数调用(同步请求-响应)

// lidar_task.c → path_plannerpath_planner_update_sensors(&g_path_planner,NULL,s_lidar_ranges,count,angle_min,angle_inc);

适用:调用者需要立即得到结果(代价地图更新是同步的,不能异步排队)。

模式 3:系统健康快照(多消费者单源)

// navigation_update (10Hz) → 更新系统健康快照g_system_health.gps_ok=...;g_system_health.battery_voltage=...;// IOT 遥测 (1Hz, 只读)send_telemetry(&g_system_health);// 日志 (事件驱动, 只读)log_system_status(&g_system_health);

适用:多个消费者需要同一份数据,且数据更新频率远低于消费频率。

下面是三种通信模式的对比图示:

模式3:系统健康快照(多消费者单源)

写 g_system_health

只读

只读

生产者任务
(navigation_update, 10Hz)

全局结构体
g_system_health

消费者:IOT 遥测
(1Hz)

消费者:日志
(事件驱动)

模式2:函数调用(同步请求-响应)

直接调用并传参

立即返回结果

生产者任务
(lidar_task)

消费者函数
(path_planner_update_sensors)

模式1:全局结构体(生产者-消费者)

写 g_radar_data

生产者任务
(radar_task, 33Hz)

全局结构体
g_radar_data

消费者任务
(motion_control, 50Hz)

4. motion/ 子目录 — 核心中的核心

motion/是 CONTROL 层最大的子模块,它内部又分三层:

motion/ ├── perception/ ← "看到什么" — 传感器融合 + 代价地图 │ ├── costmap ← 栅格地图 │ └── sensor_fusion ← 雷达+LiDAR 融合 ├── planning/ ← "怎么走" — 规划器 │ ├── d_star_lite ← 全局规划 (1Hz) │ ├── dwa_planner ← 局部规划 (10Hz) │ └── path_planner ← 规划总成 ├── pid_controller ← "怎么执行" — 控制执行 ├── auto_tune ← PID 自动调参 ├── cruise_controller ← L1 航点导航 ├── nav_ekf ← EKF 导航融合 ├── motion_control ← 主控循环 (50Hz) ├── obstacle_avoidance← VFH 紧急避障 └── rc_mixer ← 差分混控

三层的数据流

perception (传感器 → 代价地图) ↓ planning (代价地图 → 速度/航向指令) ↓ motion_control (速度/航向 → PID → 推进器)

每一层只依赖其下层。规划器不知道传感器是 MR72 还是 M10——它只知道代价地图。控制器不知道规划器是 DWA 还是手动输入——它只知道速度/航向指令。

5. 模式切换 vs 传感器降级

CONTROL 层最复杂的两个状态机都在motion_control.c里:

模式切换(操作员主动/任务触发):

switch(g_motion_ctrl.mode){caseMOTION_MODE_MANUAL:rc_mixer_update();// RC 直通break;caseMOTION_MODE_POS_HOLD:position_hold_update();// 定点保持 PIDbreak;caseMOTION_MODE_AUTO:cruise_update();// 航点导航path_planner_step();// DWA + D* Litebreak;// INS, GPS_ONLY, GEOFENCE, EMERGENCY...}

传感器降级(系统自动触发):

GPS 正常 + IMU 正常 → 全功能 AUTO GPS 丢失 + IMU 正常 → 切入 INS (EKF 纯预测, 10 秒内恢复) GPS 正常 + IMU 故障 → 切入 GPS_ONLY (原始 GPS, 精度下降) GPS 丢失 + IMU 故障 → EMERGENCY 急停

两个状态机的交互:降级只改变数据源(EKF/原始 GPS/无),不改变控制模式。INS 模式下船还在 AUTO,只是使用纯 IMU 预测的位置而不是 GPS 修正后的位置。

6. 全局聚合头文件 control_list.h

// control_list.h — 一次 include 引入 CONTROL 层所有模块// 比 devices_list.h 更大, 因为 CONTROL 层模块最多#include"bsp.h"// BSP + SYSTEM + DEVICES 全部#include"control_def.h"// 通用数据结构定义#include"communication/net_init_task.h"#include"communication/iot_task.h"// ...#include"sensor/imu_task.h"#include"sensor/rtk_task.h"#include"sensor/radar_task.h"#include"sensor/lidar_task.h"// 我们新加的#include"motion/motion_control.h"// 核心#include"motion/perception/costmap.h"#include"motion/planning/path_planner.h"// ... 30+ includes

依赖链回顾

sys_config.h → devices_list.h → control_list.h → zscode_app.h (SYSTEM) (DEVICES) (CONTROL) (APP)

为什么这个链条重要?新人 clone 项目后,从main.c开始,跟着#include链往下走——SYSTEM → DEVICES → CONTROL → 所有模块一览无余。不需要"猜"哪些文件参与了编译。


7. 加一个 CONTROL 层模块的标准流程

以我们加的 LiDAR 功能为例:

① 在 sensor/ 下创建 lidar_task.h/.c → 文件头注释标注 @layer CONTROL ② 在 control_list.h 加一行 → #include "sensor/lidar_task.h" ③ 在 zscode_app.c 加 lidar_task_init() → 决定任务启动时机 ④ 如果模块输出全局数据 → 结构体定义在 lidar_task.h → 外部声明 extern ⑤ .sct 如果用 DTCM → 变量加 SECTION_DTCM 属性 ⑥ Keil 工程加 .c 文件到项目 → .uvprojx + .uvoptx + .BAT + .lnp

现有模块零改动。这就是分层架构的兑现——新功能扩展不影响现有功能。


本文是《从零搭建无人船控制系统》架构特别篇。项目地址:[煜坤 YuKun](开源准备中)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询