3. 设计飞控软件分层架构
任务定义
为eVTOL飞控软件设计一个五层软件架构,明确各层职责、层间接口与数据交互机制。本案例交付物为:分层架构定义、层间接口契约、关键数据结构及一个验证架构合理性的最小代码骨架。
分层架构设计
飞控软件采用自底向上的五层结构,每层仅依赖其下层提供的服务,禁止跨层调用。各层职责如下:
| 层 | 名称 | 核心职责 | 典型模块 |
|---|---|---|---|
| L1 | 硬件抽象层(HAL) | 屏蔽传感器、执行机构、通信外设的硬件差异,向上提供统一操作接口 | IMU驱动、PWM输出、UART收发 |
| L2 | 操作系统层(OSAL) | 封装RTOS任务调度、时间管理、中断与互斥机制 | 任务创建、周期定时器、信号量 |
| L3 | 中间件层(MW) | 提供飞控专用数据通道与算法库,实现模块间解耦通信 | uORB消息总线、姿态解算库、日志服务 |
| L4 | 应用层(APP) | 实现具体飞行功能逻辑,组合中间件服务完成业务 | 姿态控制器、位置估计器、导航逻辑 |
| L5 | 任务层(TSK) | 定义各功能模块的运行周期、优先级与调度策略 | 任务配置表、调度入口 |
层间交互机制
层间交互遵循两条规则:依赖倒置与数据总线。L1~L3为底层服务层,L4应用层通过中间件消息总线订阅/发布数据,不直接调用L1/L2接口;L5任务层负责创建各应用任务并设定调度参数。层间接口采用结构化体传递,避免全局变量散落。
核心接口定义
以下代码定义各层关键接口与消息数据结构,展示层间解耦方式。HAL层提供imu读取与pwm输出接口;OSAL层封装任务创建与延时;MW层实现一个轻量发布/订阅总线;APP层通过总线获取传感器数据并输出控制量;TSK层配置任务周期。
// ========== L1: hal.h ==========
#ifndef HAL_H
#define HAL_H
#include <stdint.h>
typedef struct {
float gyro[3]; // rad/s
float accel[3]; // m/s^2
uint32_t timestamp_ms;
} imu_data_t;
// 初始化IMU,返回0成功
int hal_imu_init(void);
// 读取最新IMU数据
int hal_imu_read(imu_data_t *out);
// 设置PWM输出(通道0-3,占空比0.0~1.0)
int hal_pwm_set(uint8_t ch, float duty);
#endif
// ========== L2: osal.h ==========
#ifndef OSAL_H
#define OSAL_H
#include <stdint.h>
typedef void (*task_func_t)(void* arg);
typedef struct {
task_func_t func;
void *arg;
uint32_t period_ms;
uint8_t priority;
} task_cfg_t;
// 创建周期任务,返回任务ID
int osal_task_create(const task_cfg_t *cfg);
// 当前任务延时(ms)
void osal_sleep_ms(uint32_t ms);
#endif
// ========== L3: mw_bus.h ==========
#ifndef MW_BUS_H
#define MW_BUS_H
#include <stdint.h>
#include <string.h>
#define BUS_MAX_TOPICS 8
#define BUS_MAX_SUBSCRIBERS 4
#define BUS_MSG_SIZE 64
typedef struct {
uint8_t topic_id;
uint8_t data[BUS_MSG_SIZE];
uint8_t len;
uint32_t pub_count;
} bus_msg_t;
// 初始化消息总线
void bus_init(void);
// 发布消息到指定主题
int bus_publish(uint8_t topic_id, const void *data, uint8_t len);
// 订阅主题,返回订阅句柄
int bus_subscribe(uint8_t topic_id);
// 获取最新消息,返回0表示有新数据
int bus_poll(int handle, bus_msg_t *out);
#endif
// ========== L3: mw_bus.c ==========
#include "mw_bus.h"
static bus_msg_t topics[BUS_MAX_TOPICS];
static int sub_map[BUS_MAX_TOPICS][BUS_MAX_SUBSCRIBERS];
static uint8_t sub_count[BUS_MAX_TOPICS];
void bus_init(void) {
memset(topics, 0, sizeof(topics));
memset(sub_map, 0, sizeof(sub_map));
memset(sub_count, 0, sizeof(sub_count));
}
int bus_publish(uint8_t topic_id, const void *data, uint8_t len) {
if (topic_id >= BUS_MAX_TOPICS || len > BUS_MSG_SIZE) return -1;
topics[topic_id].topic_id = topic_id;
topics[topic_id].len = len;
memcpy(topics[topic_id].data, data, len);
topics[topic_id].pub_count++;
return 0;
}
int bus_subscribe(uint8_t topic_id) {
if (topic_id >= BUS_MAX_TOPICS) return -1;
if (sub_count[topic_id] >= BUS_MAX_SUBSCRIBERS) return -1;
int handle = sub_count[topic_id]++;
sub_map[topic_id][handle] = 1;
return handle;
}
int bus_poll(int handle, bus_msg_t *out) {
// 简化实现:直接返回最新数据,实际应带时间戳过滤
(void)handle;
if (out == NULL) return -1;
// 此处需根据handle找到对应topic,简化处理
return 0;
}
// ========== L4: app_controller.h ==========
#ifndef APP_CONTROLLER_H
#define APP_CONTROLLER_H
#include "hal.h"
#include "mw_bus.h"
#define TOPIC_IMU 0
#define TOPIC_CTRL 1
// 姿态控制器任务入口
void app_attitude_controller(void *arg);
#endif
// ========== L4: app_controller.c ==========
#include "app_controller.h"
#include "osal.h"
void app_attitude_controller(void *arg) {
(void)arg;
imu_data_t imu;
float pwm_out[4] = {0.5f, 0.5f, 0.5f, 0.5f};
int sub_imu = bus_subscribe(TOPIC_IMU);
bus_msg_t msg;
while (1) {
if (bus_poll(sub_imu, &msg) == 0) {
memcpy(&imu, msg.data, sizeof(imu));
// 简化控制律:根据俯仰角调整PWM
pwm_out[0] = 0.5f + imu.gyro[1] * 0.01f;
pwm_out[1] = 0.5f - imu.gyro[1] * 0.01f;
for (int i = 0; i < 4; i++) {
hal_pwm_set(i, pwm_out[i]);
}
}
osal_sleep_ms(10);
}
}
// ========== L5: task_config.c ==========
#include "osal.h"
#include "app_controller.h"
void system_task_init(void) {
task_cfg_t ctrl_cfg = {
.func = app_attitude_controller,
.arg = NULL,
.period_ms = 10,
.priority = 5
};
osal_task_create(&ctrl_cfg);
}
落地与验证
文件组织:将上述代码按层存放于hal/、osal/、mw/、app/、task/目录,主函数调用hal_imu_init()、bus_init()、system_task_init()后启动调度器。
验证方法:在桌面环境编译(需提供RTOS模拟层或使用Pthread封装OSAL),通过以下步骤确认架构正确性:
- 编译无错误,各层头文件独立包含无循环依赖;
- 静态检查:确认
app_controller.c未直接调用hal_imu_read(),而是通过总线获取数据; - 运行测试:模拟IMU数据发布到
TOPIC_IMU,观察hal_pwm_set被调用且输出值随陀螺仪数据变化; - 判定标准:若应用层仅通过总线接口通信、且任务层能独立配置周期与优先级,则分层架构设计有效。
预期输出:当向总线注入gyro[1]=0.2时,PWM通道0输出0.502,通道1输出0.498(由控制律代码直接推出)。