目录
[4.RTOS 多任务架构(时间片轮转 / 抢占式)](#4.RTOS 多任务架构(时间片轮转 / 抢占式))
1.前言
嵌入式软件架构设计的核心是在硬件资源受限(MCU/MPU 的 RAM、Flash、算力有限)的前提下,平衡实时性、可靠性、可维护性与硬件适配性 ,区别于通用软件架构(如 Web、桌面软件),嵌入式架构必须深度绑定硬件特性(外设、中断、时钟、功耗),同时满足工业级 / 消费级的功能、非功能需求(如实时响应、抗干扰、低功耗)。软件架构设计决定了系统的可靠性、可维护性和可扩展性。优秀的架构能在有限的硬件资源上构建出稳定高效的系统,而糟糕的架构则会导致项目延期、维护困难和性能瓶颈。
嵌入式架构设计的底层逻辑是 "适配硬件、收敛复杂度",需遵循以下 6 条核心原则(优先级从高到低):
| 原则 | 核心内涵(嵌入式特有) |
|---|---|
| 实时性优先 | 紧急任务(如故障报警、刹车信号)需微秒级响应,优先通过中断 / 高优先级任务保障,而非功能需求>功能需求 |
| 硬件抽象化 | 隔离硬件细节(如外设、寄存器),通过 HAL/LL 层统一接口,避免软件直接操作寄存器,提升兼容性 |
| 高内聚低耦合 | 模块职责单一(如 "串口驱动" 仅管数据收发),模块间通过标准化接口通信,降低修改 / 升级成本 |
| 资源适配性 | 架构需匹配硬件资源(如 8 位 MCU 用前后台,32 位 MCU 可上 RTOS),拒绝 "过度设计"(如小系统拆多层) |
| 可靠性至上 | 需考虑硬件异常(掉电、电磁干扰)、软件异常(数组越界、空指针),设计容错、看门狗、异常恢复机制 |
| 可调试 / 可测试性 | 预留调试接口(UART/USB/ 调试器)、日志输出、状态监测点,便于定位硬件 / 软件问题 |
2.前后台架构(含中断机制)
核心要点
- 前台:无限循环的主任务流,顺序执行低优先级常规任务(如传感器采集、数据显示、状态监测);
- 后台:由中断触发的高优先级任务(如按键、故障、串口数据),仅处理 "最短必要逻辑"(如置标志位、保存关键数据),复杂逻辑交回前台执行。
实现要点
- 中断服务程序(ISR)必须 "短小精悍"(执行时间≤1ms),禁止在 ISR 中做耗时操作(如 Flash 写入、浮点运算);
- 前台通过 "标志位" 与后台交互(如 ISR 置
key_flag=1,前台轮询标志位后执行按键逻辑),避免 ISR 直接调用复杂函数。
适用场景
- 硬件:8 位 / 16 位 MCU(如 51、STM8),RAM<2KB、Flash<32KB;
- 业务:任务数≤5 个、逻辑简单(如智能灯控、简易计数器、温湿度记录仪)。
优缺点
| 优点 | 缺点 |
|---|---|
| 无调度器开销,资源占用极低 | 前台顺序执行,单个任务阻塞会导致系统卡顿 |
| 开发 / 调试成本低,易上手 | 仅支持中断级优先级,无法处理多优先级常规任务 |
| 硬件耦合度低,适配快 | 扩展性差,新增任务需修改主循环,易耦合 |
实例:51 单片机智能灯控
cpp
// 全局标志位(前台-后台交互)
bit key_flag = 0;
// 后台:外部中断(按键触发)
void Int0_ISR() interrupt 0 {
key_flag = 1; // 仅置标志位,不处理复杂逻辑
}
// 前台:主循环
void main() {
EA = 1; // 开启总中断
EX0 = 1; // 开启外部中断0
IT0 = 1; // 下降沿触发
while(1) { // 前台无限循环
// 常规任务1:采集光照传感器数据
uint8_t light = adc_read(0);
// 常规任务2:根据光照自动调光
pwm_set(light);
// 处理后台中断触发的任务
if(key_flag) {
key_flag = 0;
led_switch(); // 执行按键调光逻辑
}
// 常规任务3:刷新LCD显示
lcd_show(light);
}
}
3.时间片轮询架构(无操作系统)
核心思想
将 CPU 时间划分为固定长度的 "时间片",按顺序为每个任务分配时间片,实现多任务的轮流执行。该架构解决了前后台架构中前台任务顺序执行的阻塞问题,是嵌入式从裸机向 RTOS 过渡的关键架构,也是多任务操作系统的基础调度策略。
核心原理
- 定时器中断触发时间片:配置硬件定时器(如 SysTick、TIM)按固定周期(如 10ms)产生中断,每次中断代表一个时间片结束;
- 任务列表与时间片分配:定义任务数组,为每个任务分配相同 / 不同的时间片长度,记录任务的 "当前剩余时间片" 和 "运行状态";
- 轮询执行任务:在主循环中遍历任务列表,若任务有剩余时间片且处于就绪状态,则执行该任务,直到时间片耗尽或任务主动放弃 CPU。
实现要点
- 时间片长度选择:通常为 1~20ms,过短会增加中断开销(如 1ms 中断,CPU 占用率高),过长会导致任务响应延迟(如 50ms 时间片,用户操作卡顿);
- 任务设计原则:每个任务必须是 "可重入" 且 "短时间完成" 的,禁止在任务中执行无限循环(如死循环采集传感器),需拆分为 "状态机"(如采集→处理→等待下一个时间片);
- 无抢占机制 :裸机时间片轮询为非抢占式,任务一旦开始执行,会持续到时间片耗尽,高优先级任务无法打断低优先级任务。
优缺点
| 优点 | 缺点 |
|---|---|
| 无 OS 开销,资源占用极低(仅需少量 RAM 存储任务状态) | 非抢占式,高优先级任务无法及时响应 |
| 开发门槛低,无需掌握 RTOS 原理 | 任务执行时间超过时间片会导致系统卡顿 |
| 代码结构清晰,易调试 | 不支持任务挂起 / 唤醒,灵活性差 |
代码示例:基于 STM32 裸机的时间片轮询
以 STM32F103 为例,实现 3 个任务的时间片轮询(时间片长度均为 10ms):
cpp
#include "stm32f10x.h"
// 时间片长度(10ms)
#define TIME_SLICE 10
// 任务数量
#define TASK_NUM 3
// 任务函数类型
typedef void (*TaskFunc)(void);
// 任务结构体
typedef struct {
TaskFunc func; // 任务函数
uint16_t time_slice; // 分配的时间片长度
uint16_t remain_time;// 剩余时间片
uint8_t ready; // 任务就绪标志(1:就绪,0:未就绪)
} Task;
// 声明任务函数
void task1(void); // LED闪烁
void task2(void); // 串口打印
void task3(void); // 温湿度采集
// 任务列表初始化
Task task_list[TASK_NUM] = {
{task1, TIME_SLICE, TIME_SLICE, 1},
{task2, TIME_SLICE, TIME_SLICE, 1},
{task3, TIME_SLICE, TIME_SLICE, 1}
};
// 定时器中断服务程序(SysTick,10ms中断一次)
void SysTick_Handler(void) {
// 遍历任务列表,更新剩余时间片
for(uint8_t i=0; i<TASK_NUM; i++) {
if(task_list[i].ready) {
if(task_list[i].remain_time > 0) {
task_list[i].remain_time--;
} else {
// 时间片耗尽,重置剩余时间片
task_list[i].remain_time = task_list[i].time_slice;
}
}
}
}
// 任务1:LED闪烁(每100ms翻转一次)
static uint16_t task1_cnt = 0;
void task1(void) {
task1_cnt++;
if(task1_cnt >= 10) { // 10*10ms=100ms
GPIO_WriteBit(GPIOA, GPIO_Pin_0, !GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0));
task1_cnt = 0;
}
}
// 任务2:串口打印(每200ms打印一次)
static uint16_t task2_cnt = 0;
void task2(void) {
task2_cnt++;
if(task2_cnt >= 20) { // 20*10ms=200ms
USART_SendString(USART1, "Task2 running...\r\n");
task2_cnt = 0;
}
}
// 任务3:温湿度采集(每500ms采集一次)
static uint16_t task3_cnt = 0;
void task3(void) {
task3_cnt++;
if(task3_cnt >= 50) { // 50*10ms=500ms
uint16_t temp = DHT11_Read_Temp(); // 读取温度
uint16_t humi = DHT11_Read_Humi(); // 读取湿度
task3_cnt = 0;
}
}
// 主函数:轮询执行任务
int main(void) {
// 初始化:GPIO、USART、DHT11、SysTick(10ms中断)
GPIO_InitConfig();
USART_InitConfig(115200);
DHT11_Init();
SysTick_Config(SystemCoreClock / 100); // 10ms中断(100Hz)
while(1) {
// 遍历任务列表,执行就绪且有剩余时间片的任务
for(uint8_t i=0; i<TASK_NUM; i++) {
if(task_list[i].ready && (task_list[i].remain_time > 0)) {
task_list[i].func();
break; // 每次只执行一个任务,保证时间片独占
}
}
}
}
4.RTOS 多任务架构(时间片轮转 / 抢占式)
核心结构
- 基于实时操作系统(FreeRTOS、uC/OS-II、RT-Thread),将系统拆分为多个独立任务,由 RTOS 调度器按 "优先级 + 时间片" 分配 CPU 资源;
- 支持 "抢占式调度"(高优先级任务可打断低优先级任务)、"时间片轮转"(同优先级任务公平执行)。
实现要点
- 任务划分:按 "功能 + 优先级" 拆分(如紧急故障>串口通信>数据采集>显示),任务数控制在 8~16 个(过多增加调度开销);
- 通信机制:任务间通过队列(Queue)、信号量(Semaphore)、事件组(Event Group)通信,禁止全局变量裸奔;
- 中断与 RTOS 结合:ISR 中通过
xSemaphoreGiveFromISR()/xQueueSendFromISR()唤醒任务,不直接处理业务逻辑。
适用场景
- 硬件:32 位 MCU(STM32、ESP32、NXP IMX),RAM≥8KB、Flash≥64KB;
- 业务:任务数>5 个、需多优先级调度(如工业控制器、智能手表、车载传感器)。
优缺点
| 优点 | 缺点 |
|---|---|
| 多任务并发,实时性可控 | 调度器有开销(RAM 占用 2~10KB,CPU 占用 5%~10%) |
| 任务解耦,可维护性强 | 开发门槛高(需掌握 RTOS 原理、任务通信) |
| 支持优先级抢占,紧急任务优先 | 易出现死锁、优先级反转(需通过互斥锁规避) |
实例:FreeRTOS 工业控制器(任务划分)
| 任务名称 | 优先级 | 功能 | 通信方式 |
|---|---|---|---|
| 故障处理任务 | 最高 | 急停、过流保护 | 中断 + 信号量 |
| 串口通信任务 | 高 | 接收上位机指令 | 队列 |
| 数据采集任务 | 中 | 采集温度 / 压力数据 | 消息队列 |
| 控制输出任务 | 中 | 控制电机 / 阀门 | 事件组 |
| 显示日志任务 | 最低 | 刷新 LCD / 记录日志 | 共享内存(带互斥锁) |
以 FreeRTOS 为例,创建 3 个同优先级任务,实现时间片轮转调度:
cpp
#include "FreeRTOS.h"
#include "task.h"
// 任务优先级(同优先级)
#define TASK_PRIO 1
// 任务栈大小
#define TASK_STACK_SIZE 128
// 任务句柄
TaskHandle_t task1_handle, task2_handle, task3_handle;
// 任务1:LED闪烁
void task1(void* arg) {
while(1) {
GPIO_WriteBit(GPIOA, GPIO_Pin_0, !GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0));
// 不主动延迟,依赖时间片切换
}
}
// 任务2:串口打印
void task2(void* arg) {
while(1) {
USART_SendString(USART1, "Task2 running...\r\n");
// 不主动延迟,依赖时间片切换
}
}
// 任务3:温湿度采集
void task3(void* arg) {
while(1) {
uint16_t temp = DHT11_Read_Temp();
uint16_t humi = DHT11_Read_Humi();
// 不主动延迟,依赖时间片切换
}
}
// 主函数
int main(void) {
// 硬件初始化
GPIO_InitConfig();
USART_InitConfig(115200);
DHT11_Init();
// 创建同优先级任务
xTaskCreate(task1, "Task1", TASK_STACK_SIZE, NULL, TASK_PRIO, &task1_handle);
xTaskCreate(task2, "Task2", TASK_STACK_SIZE, NULL, TASK_PRIO, &task2_handle);
xTaskCreate(task3, "Task3", TASK_STACK_SIZE, NULL, TASK_PRIO, &task3_handle);
// 启动调度器
vTaskStartScheduler();
while(1); // 调度器启动失败会进入此处
}
说明 :FreeRTOS 默认时间片长度为系统 Tick 周期(如 10ms),3 个同优先级任务会轮流占用 10ms CPU 时间,实现伪并发。若需修改单个任务的时间片,可调用vTaskSetTaskSlice() API。
5.嵌入式分层架构
核心结构
嵌入式分层架构需弱化 "表现层",强化 "硬件抽象层",典型分层(从下到上):
硬件层(MCU/外设/传感器)→ 硬件抽象层(HAL)→ 驱动层 → 中间件层 → 业务逻辑层 → 应用层
| 级 | 核心职责(嵌入式特有) | 示例 |
|---|---|---|
| 硬件抽象层(HAL) | 屏蔽硬件寄存器差异,提供统一接口 | STM32 HAL 库(HAL_UART_Transmit())、自定义 GPIO 接口 |
| 驱动层 | 实现外设 / 传感器的底层驱动(基于 HAL) | 串口驱动、温湿度传感器(DHT11)驱动、电机驱动 |
| 中间件层 | 通用功能封装(跨项目复用) | 协议解析(Modbus/MQTT)、CRC 校验、低功耗管理 |
| 业务逻辑层 | 核心业务逻辑(与硬件解耦) | 工业控制器的 "自动控制逻辑"、智能门锁的 "开锁验证逻辑" |
| 应用层 | 任务调度、事件触发(对接 RTOS / 前后台) | FreeRTOS 任务创建、主循环任务分发 |
实现要点
- HAL 层是核心:所有硬件操作必须通过 HAL 层,禁止业务层直接调用
GPIO_WritePin()等底层接口; - 驱动层 "高内聚":单个驱动仅对应一个外设(如 UART 驱动不掺杂 I2C 逻辑),提供 "初始化 - 读 - 写 - 销毁" 标准接口;
- 业务层 "纯逻辑":不包含任何硬件操作,仅调用驱动层 / 中间件层接口,便于移植(如从 STM32 移植到 NXP MCU 仅需替换 HAL / 驱动层)。
适用场景
- 硬件:32 位 MCU/MPU,需跨硬件平台移植(如同一产品适配多款 MCU);
- 业务:中大型嵌入式系统(工业控制、车载设备、智能家居网关),需团队协作开发。
优缺点
| 优点 | 缺点 |
|---|---|
| 硬件解耦,移植性强 | 层间调用有开销(如业务层调用驱动层需 2~3 层转发) |
| 模块复用率高,开发效率提升 | 初期设计成本高(需定义清晰的层间接口) |
| 问题定位精准(如通信异常查驱动层) | 简单系统易 "过度分层"(如小工具拆 4 层) |
硬件抽象层(HAL)设计示例
cpp
// HAL接口定义
typedef struct {
int (*init)(void);
int (*read)(uint8_t* buffer, size_t size);
int (*write)(const uint8_t* buffer, size_t size);
int (*ioctl)(uint32_t command, void* arg);
int (*deinit)(void);
} DeviceDriver;
// SPI设备抽象
typedef struct {
SPI_TypeDef* spi_instance;
GPIO_TypeDef* cs_port;
uint16_t cs_pin;
SPI_InitTypeDef config;
} SPIDevice;
int spi_device_init(SPIDevice* dev) {
// 初始化GPIO
GPIO_Init(dev->cs_port, dev->cs_pin, GPIO_MODE_OUTPUT);
// 初始化SPI外设
SPI_Init(dev->spi_instance, &dev->config);
return 0;
}
// 应用层通过统一接口访问
int read_sensor_data(uint8_t* buffer, size_t size) {
return sensor_device.read(buffer, size);
}
组件通信机制
cpp
// 基于接口的组件设计
typedef struct ISensor {
int (*init)(void);
int (*read)(SensorData* data);
int (*calibrate)(void);
void (*set_callback)(DataCallback callback);
} ISensor;
// 具体传感器实现
static int bme280_init(void) {
// BME280特定初始化
return 0;
}
static int bme280_read(SensorData* data) {
// 读取BME280数据
return 0;
}
// 注册到组件系统
ISensor bme280_sensor = {
.init = bme280_init,
.read = bme280_read,
.calibrate = NULL,
.set_callback = NULL
};
// 组件管理器
typedef struct {
ISensor* sensor;
uint32_t update_interval;
TimerHandle_t timer;
} SensorComponent;
void sensor_component_init(void) {
components[0].sensor = &bme280_sensor;
components[0].update_interval = 1000; // 1秒
components[0].timer = xTimerCreate(...);
}
6.嵌入式事件驱动架构(EDA)
核心结构
以 "事件" 为核心,模块间无直接依赖,通过 "事件发布 - 订阅" 模式通信:
- 事件发布者:触发事件(如 "按键按下""数据采集完成""故障发生");
- 事件订阅者:监听特定事件,执行对应逻辑;
- 事件总线:转发事件(嵌入式中通常为全局事件队列 + 事件分发函数)。
实现要点
- 事件标准化:定义事件类型(枚举)、事件数据结构(如
struct Event { uint8_t type; void* data; }); - 轻量化实现:无需复杂中间件,通过 "事件队列 + 回调函数" 实现订阅 / 发布(如
event_subscribe(EVENT_KEY, key_callback)); - 结合 RTOS:事件队列基于 RTOS 队列实现,订阅者作为独立任务运行。
适用场景
- 硬件:32 位 MCU(ESP32、STM32H7);
- 业务:交互密集型系统(智能家居中控、车载中控、工业人机界面 HMI)。
实例:嵌入式事件驱动核心代码
cpp
// 事件类型枚举
typedef enum {
EVENT_KEY_PRESS, // 按键按下
EVENT_DATA_READY, // 数据采集完成
EVENT_FAULT // 故障发生
} EventType;
// 事件结构体
typedef struct {
EventType type;
void* data;
} Event;
// 事件队列(基于FreeRTOS)
QueueHandle_t event_queue;
// 订阅者回调函数类型
typedef void (*EventCallback)(Event*);
EventCallback callbacks[3] = {NULL}; // 对应3类事件
// 事件发布函数
void event_post(Event* evt) {
xQueueSend(event_queue, evt, 0); // 发送到事件队列
}
// 事件分发任务(订阅者核心)
void event_dispatch_task(void* arg) {
Event evt;
while(1) {
if(xQueueReceive(event_queue, &evt, portMAX_DELAY)) {
// 调用对应订阅者的回调函数
if(callbacks[evt.type]) {
callbacks[evt.type](&evt);
}
}
}
}
// 订阅事件
void event_subscribe(EventType type, EventCallback cb) {
callbacks[type] = cb;
}
// 示例:按键事件发布(ISR中)
void key_isr() {
Event evt = {.type = EVENT_KEY_PRESS, .data = (void*)&key_id};
event_post(&evt); // 发布事件
}
// 示例:按键事件订阅(业务层)
void key_callback(Event* evt) {
uint8_t key = *(uint8_t*)evt->data;
// 执行按键业务逻辑
}
7.总结
嵌入式软件架构设计的本质是 "取舍":在有限的硬件资源下,优先满足实时性、可靠性,再兼顾可维护性、扩展性。
- 小系统(8 位 MCU):优先前后台架构,极简为王;
- 中系统(32 位 MCU):RTOS + 分层架构,平衡性能与工程化;
- 大系统(多核 / 分布式):RTOS + 事件驱动 + 多核通信,适配复杂业务。
核心准则:架构服务于硬件和业务,而非 "为了架构而架构",嵌入式架构的最优解永远是 "适配当前场景的最简架构"。