3. 设计飞控软件分层架构

设计飞控软件分层架构 学习卡片

任务定义

为eVTOL飞控软件设计一个五层软件架构,明确各层职责、层间接口与数据交互机制。本案例交付物为:分层架构定义、层间接口契约、关键数据结构及一个验证架构合理性的最小代码骨架。

分层架构设计

飞控软件采用自底向上的五层结构,每层仅依赖其下层提供的服务,禁止跨层调用。各层职责如下:

层名称核心职责典型模块
L1硬件抽象层(HAL)屏蔽传感器、执行机构、通信外设的硬件差异,向上提供统一操作接口IMU驱动、PWM输出、UART收发
L2操作系统层(OSAL)封装RTOS任务调度、时间管理、中断与互斥机制任务创建、周期定时器、信号量
L3中间件层(MW)提供飞控专用数据通道与算法库,实现模块间解耦通信uORB消息总线、姿态解算库、日志服务
L4应用层(APP)实现具体飞行功能逻辑,组合中间件服务完成业务姿态控制器、位置估计器、导航逻辑
L5任务层(TSK)定义各功能模块的运行周期、优先级与调度策略任务配置表、调度入口

层间交互机制

层间交互遵循两条规则:依赖倒置与数据总线。L1~L3为底层服务层,L4应用层通过中间件消息总线订阅/发布数据,不直接调用L1/L2接口;L5任务层负责创建各应用任务并设定调度参数。层间接口采用结构化体传递,避免全局变量散落。

L5 任务层 (TSK) 任务配置表 · 周期调度 · 优先级管理 L4 应用层 (APP) 姿态控制器 · 位置估计器 · 导航逻辑 通过消息总线订阅/发布主题 L3 中间件层 (MW) uORB消息总线 · 姿态解算库 · 日志服务 提供发布/订阅API与算法封装 L2 操作系统层 (OSAL) 任务创建 · 周期定时器 · 信号量互斥 L1 硬件抽象层 (HAL) IMU驱动 · PWM输出 · UART收发 统一设备操作接口 调度/配置 订阅/发布 系统调用 设备操作 依赖方向:自上而下

核心接口定义

以下代码定义各层关键接口与消息数据结构,展示层间解耦方式。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),通过以下步骤确认架构正确性:

  1. 编译无错误,各层头文件独立包含无循环依赖;
  2. 静态检查:确认app_controller.c未直接调用hal_imu_read(),而是通过总线获取数据;
  3. 运行测试:模拟IMU数据发布到TOPIC_IMU,观察hal_pwm_set被调用且输出值随陀螺仪数据变化;
  4. 判定标准:若应用层仅通过总线接口通信、且任务层能独立配置周期与优先级,则分层架构设计有效。

预期输出:当向总线注入gyro[1]=0.2时,PWM通道0输出0.502,通道1输出0.498(由控制律代码直接推出)。