超级循环程序设计:裸机编程的基础架构¶
概述¶
超级循环(Super Loop)是裸机编程中最基础、最直观的程序架构模式。它将所有任务放在一个无限循环中顺序执行,是许多简单嵌入式应用的首选架构。完成本教程后,你将能够:
- 理解超级循环架构的核心原理和执行机制
- 掌握在超级循环中实现任务调度的方法
- 学会使用时间片和标志位管理任务执行
- 了解超级循环的优缺点和适用场景
- 能够设计和实现基于超级循环的完整应用
背景知识¶
什么是超级循环?¶
超级循环(Super Loop)是一种程序架构模式,其核心是一个永不退出的while循环,所有的应用任务都在这个循环中按顺序执行。
基本结构:
int main(void) {
// 系统初始化
SystemInit();
// 超级循环
while(1) {
Task1(); // 任务1
Task2(); // 任务2
Task3(); // 任务3
// ... 更多任务
}
}
为什么叫"超级"循环?¶
"超级"(Super)这个词强调了这个循环的特殊地位: - 它是程序的**主循环**,控制整个程序的执行流程 - 它**永不退出**,程序的整个生命周期都在这个循环中 - 它是**顶层调度器**,决定各个任务的执行顺序
相关概念¶
任务(Task):完成特定功能的代码单元,在超级循环中通常表现为一个函数。
轮询(Polling):主动检查某个条件或状态的方法,是超级循环中常用的技术。
时间片(Time Slice):分配给每个任务的执行时间,用于实现简单的任务调度。
标志位(Flag):用于指示某个事件是否发生或某个条件是否满足的布尔变量。
核心内容¶
超级循环的基本原理¶
执行流程¶
超级循环的执行流程非常简单直观:
程序启动
↓
系统初始化
↓
进入while(1)循环
↓
执行Task1 → 执行Task2 → 执行Task3 → ...
↓ ↑
└────────────────────────────────────┘
循环往复,永不退出
关键特点: 1. 顺序执行:任务按照代码中的顺序依次执行 2. 循环往复:一轮任务执行完毕后,立即开始下一轮 3. 阻塞执行:当前任务未完成时,后续任务无法执行
基础示例¶
让我们从一个最简单的例子开始:
#include <stdint.h>
// 硬件定义(以STM32为例)
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014)
#define ADC_DR (*(volatile uint32_t *)0x40012040)
// 全局变量
uint16_t adc_value = 0;
uint8_t led_state = 0;
// 任务1:读取ADC
void Task_ReadADC(void) {
adc_value = ADC_DR & 0xFFF; // 读取12位ADC值
}
// 任务2:处理数据
void Task_ProcessData(void) {
// 简单的数据处理
if(adc_value > 2048) {
led_state = 1;
} else {
led_state = 0;
}
}
// 任务3:更新LED
void Task_UpdateLED(void) {
if(led_state) {
GPIOA_ODR |= (1 << 5); // LED亮
} else {
GPIOA_ODR &= ~(1 << 5); // LED灭
}
}
int main(void) {
// 系统初始化
SystemInit();
GPIO_Init();
ADC_Init();
// 超级循环
while(1) {
Task_ReadADC(); // 读取传感器
Task_ProcessData(); // 处理数据
Task_UpdateLED(); // 更新输出
}
return 0;
}
代码说明: - 三个任务按顺序执行:读取ADC → 处理数据 → 更新LED - 每轮循环都会完整执行这三个任务 - 任务之间通过全局变量共享数据
任务调度方法¶
在超级循环中,我们需要控制任务的执行频率和时机。以下是几种常用的调度方法:
1. 基于时间的调度¶
使用时间基准控制任务执行频率:
#include <stdint.h>
// 系统时间基准(由SysTick中断更新)
volatile uint32_t system_ticks = 0;
// 任务时间戳
uint32_t task1_timestamp = 0;
uint32_t task2_timestamp = 0;
uint32_t task3_timestamp = 0;
// SysTick中断服务程序(1ms中断)
void SysTick_Handler(void) {
system_ticks++;
}
// 任务1:每10ms执行一次
void Task1_FastUpdate(void) {
// 快速任务:读取按键状态
uint8_t key_state = ReadKey();
ProcessKey(key_state);
}
// 任务2:每100ms执行一次
void Task2_MediumUpdate(void) {
// 中速任务:读取传感器
uint16_t sensor_value = ReadSensor();
ProcessSensor(sensor_value);
}
// 任务3:每1000ms执行一次
void Task3_SlowUpdate(void) {
// 慢速任务:更新显示
UpdateDisplay();
}
int main(void) {
SystemInit();
SysTick_Init(); // 初始化1ms时间基准
while(1) {
// 任务1:每10ms执行
if((system_ticks - task1_timestamp) >= 10) {
task1_timestamp = system_ticks;
Task1_FastUpdate();
}
// 任务2:每100ms执行
if((system_ticks - task2_timestamp) >= 100) {
task2_timestamp = system_ticks;
Task2_MediumUpdate();
}
// 任务3:每1000ms执行
if((system_ticks - task3_timestamp) >= 1000) {
task3_timestamp = system_ticks;
Task3_SlowUpdate();
}
}
return 0;
}
优点: - 每个任务可以有不同的执行频率 - 时间控制精确 - 适合周期性任务
注意事项:
- 使用减法比较避免溢出问题:(current - last) >= period
- 确保任务执行时间小于其周期
2. 基于标志位的调度¶
使用标志位控制任务执行,通常配合中断使用:
#include <stdint.h>
#include <stdbool.h>
// 任务标志位
volatile bool flag_uart_rx = false; // UART接收标志
volatile bool flag_timer = false; // 定时器标志
volatile bool flag_button = false; // 按键标志
// UART接收中断
void UART_RxHandler(void) {
// 读取数据到缓冲区
uart_buffer[uart_index++] = UART_DR;
// 设置标志位
flag_uart_rx = true;
}
// 定时器中断(每100ms)
void Timer_Handler(void) {
flag_timer = true;
}
// 外部中断(按键)
void EXTI_Handler(void) {
flag_button = true;
}
// 任务1:处理UART数据
void Task_ProcessUART(void) {
// 处理接收到的数据
ParseCommand(uart_buffer, uart_index);
uart_index = 0;
}
// 任务2:定时任务
void Task_TimerUpdate(void) {
// 定时更新
UpdateSensors();
UpdateDisplay();
}
// 任务3:处理按键
void Task_ProcessButton(void) {
// 按键处理
HandleButtonPress();
}
int main(void) {
SystemInit();
UART_Init();
Timer_Init();
GPIO_Init();
while(1) {
// 检查UART标志
if(flag_uart_rx) {
flag_uart_rx = false; // 清除标志
Task_ProcessUART();
}
// 检查定时器标志
if(flag_timer) {
flag_timer = false;
Task_TimerUpdate();
}
// 检查按键标志
if(flag_button) {
flag_button = false;
Task_ProcessButton();
}
// 可以添加空闲任务或进入低功耗模式
// __WFI(); // 等待中断
}
return 0;
}
优点: - 事件驱动,响应及时 - 中断处理简短,主要逻辑在主循环 - 适合异步事件处理
最佳实践: - 标志位使用volatile修饰 - 在主循环中清除标志位 - 中断中只设置标志,不做复杂处理
3. 混合调度策略¶
结合时间和标志位,实现更灵活的调度:
#include <stdint.h>
#include <stdbool.h>
// 时间基准
volatile uint32_t system_ticks = 0;
// 任务标志
volatile bool flag_urgent_event = false;
// 任务时间戳
uint32_t task_periodic_timestamp = 0;
void SysTick_Handler(void) {
system_ticks++;
}
void UrgentEvent_Handler(void) {
flag_urgent_event = true;
}
int main(void) {
SystemInit();
while(1) {
// 优先处理紧急事件(标志位驱动)
if(flag_urgent_event) {
flag_urgent_event = false;
HandleUrgentEvent();
}
// 周期性任务(时间驱动)
if((system_ticks - task_periodic_timestamp) >= 50) {
task_periodic_timestamp = system_ticks;
PeriodicTask();
}
// 后台任务(总是执行)
BackgroundTask();
}
return 0;
}
状态管理技巧¶
在超级循环中,合理的状态管理可以让程序更加清晰和可维护。
1. 简单状态变量¶
使用状态变量控制程序行为:
#include <stdint.h>
typedef enum {
STATE_IDLE, // 空闲状态
STATE_RUNNING, // 运行状态
STATE_PAUSED, // 暂停状态
STATE_ERROR // 错误状态
} SystemState_t;
SystemState_t system_state = STATE_IDLE;
void Task_StateMachine(void) {
switch(system_state) {
case STATE_IDLE:
// 空闲状态处理
if(StartButtonPressed()) {
system_state = STATE_RUNNING;
InitializeSystem();
}
break;
case STATE_RUNNING:
// 运行状态处理
ProcessData();
UpdateOutputs();
if(PauseButtonPressed()) {
system_state = STATE_PAUSED;
}
if(ErrorDetected()) {
system_state = STATE_ERROR;
}
break;
case STATE_PAUSED:
// 暂停状态处理
if(ResumeButtonPressed()) {
system_state = STATE_RUNNING;
}
if(StopButtonPressed()) {
system_state = STATE_IDLE;
}
break;
case STATE_ERROR:
// 错误处理
HandleError();
if(ResetButtonPressed()) {
system_state = STATE_IDLE;
ClearError();
}
break;
}
}
int main(void) {
SystemInit();
while(1) {
Task_StateMachine();
// 其他任务...
}
return 0;
}
2. 任务状态管理¶
为每个任务维护独立的状态:
#include <stdint.h>
#include <stdbool.h>
// 任务状态结构
typedef struct {
bool enabled; // 任务是否使能
uint32_t period; // 执行周期(ms)
uint32_t last_run; // 上次执行时间
void (*function)(void); // 任务函数指针
} Task_t;
// 任务函数
void Task_LED(void) {
ToggleLED();
}
void Task_Sensor(void) {
ReadSensor();
}
void Task_Display(void) {
UpdateDisplay();
}
// 任务表
Task_t task_table[] = {
{true, 100, 0, Task_LED}, // LED任务,100ms周期
{true, 500, 0, Task_Sensor}, // 传感器任务,500ms周期
{true, 1000, 0, Task_Display}, // 显示任务,1000ms周期
};
#define TASK_COUNT (sizeof(task_table) / sizeof(Task_t))
// 任务调度器
void TaskScheduler(void) {
uint32_t current_time = system_ticks;
for(uint8_t i = 0; i < TASK_COUNT; i++) {
if(task_table[i].enabled) {
if((current_time - task_table[i].last_run) >= task_table[i].period) {
task_table[i].last_run = current_time;
task_table[i].function(); // 执行任务
}
}
}
}
int main(void) {
SystemInit();
while(1) {
TaskScheduler();
}
return 0;
}
优点: - 任务管理集中化 - 易于添加、删除、使能/禁用任务 - 代码结构清晰
定时任务实现¶
精确的定时是超级循环中的重要需求。
软件定时器实现¶
#include <stdint.h>
#include <stdbool.h>
#define MAX_TIMERS 8
// 软件定时器结构
typedef struct {
bool active; // 定时器是否激活
bool expired; // 定时器是否到期
uint32_t period; // 定时周期
uint32_t counter; // 当前计数值
void (*callback)(void); // 回调函数
} SoftTimer_t;
// 定时器数组
SoftTimer_t timers[MAX_TIMERS];
// 初始化定时器
void Timer_Init(uint8_t id, uint32_t period, void (*callback)(void)) {
if(id < MAX_TIMERS) {
timers[id].active = true;
timers[id].expired = false;
timers[id].period = period;
timers[id].counter = period;
timers[id].callback = callback;
}
}
// 启动定时器
void Timer_Start(uint8_t id) {
if(id < MAX_TIMERS) {
timers[id].active = true;
timers[id].counter = timers[id].period;
}
}
// 停止定时器
void Timer_Stop(uint8_t id) {
if(id < MAX_TIMERS) {
timers[id].active = false;
}
}
// 定时器更新(在SysTick中断中调用)
void Timer_Update(void) {
for(uint8_t i = 0; i < MAX_TIMERS; i++) {
if(timers[i].active && timers[i].counter > 0) {
timers[i].counter--;
if(timers[i].counter == 0) {
timers[i].expired = true;
timers[i].counter = timers[i].period; // 自动重载
}
}
}
}
// 定时器处理(在主循环中调用)
void Timer_Process(void) {
for(uint8_t i = 0; i < MAX_TIMERS; i++) {
if(timers[i].expired) {
timers[i].expired = false;
if(timers[i].callback != NULL) {
timers[i].callback(); // 执行回调
}
}
}
}
// 定时器回调函数示例
void Timer0_Callback(void) {
ToggleLED();
}
void Timer1_Callback(void) {
ReadSensor();
}
int main(void) {
SystemInit();
// 初始化定时器
Timer_Init(0, 500, Timer0_Callback); // 500ms定时器
Timer_Init(1, 1000, Timer1_Callback); // 1000ms定时器
while(1) {
Timer_Process(); // 处理到期的定时器
// 其他任务...
}
return 0;
}
// SysTick中断服务程序
void SysTick_Handler(void) {
system_ticks++;
Timer_Update(); // 更新所有定时器
}
实践示例¶
完整示例:多功能控制系统¶
让我们实现一个完整的超级循环应用,包含多个任务和完善的调度机制:
#include <stdint.h>
#include <stdbool.h>
// ============ 硬件抽象层 ============
#define LED_PIN 5
#define BUTTON_PIN 0
void LED_On(void) {
GPIOA_ODR |= (1 << LED_PIN);
}
void LED_Off(void) {
GPIOA_ODR &= ~(1 << LED_PIN);
}
void LED_Toggle(void) {
GPIOA_ODR ^= (1 << LED_PIN);
}
bool Button_IsPressed(void) {
return (GPIOA_IDR & (1 << BUTTON_PIN)) == 0;
}
uint16_t ADC_Read(void) {
return ADC_DR & 0xFFF;
}
// ============ 全局变量 ============
volatile uint32_t system_ticks = 0;
// 任务时间戳
uint32_t task_led_timestamp = 0;
uint32_t task_button_timestamp = 0;
uint32_t task_adc_timestamp = 0;
uint32_t task_display_timestamp = 0;
// 系统状态
typedef enum {
MODE_NORMAL,
MODE_FAST,
MODE_SLOW
} SystemMode_t;
SystemMode_t system_mode = MODE_NORMAL;
// 数据缓冲
uint16_t adc_buffer[10];
uint8_t adc_index = 0;
uint32_t adc_average = 0;
// 按键状态
uint8_t button_count = 0;
bool button_last_state = false;
// ============ SysTick中断 ============
void SysTick_Handler(void) {
system_ticks++;
}
// ============ 任务定义 ============
// 任务1:LED闪烁(根据模式调整频率)
void Task_LED_Blink(void) {
uint32_t period;
switch(system_mode) {
case MODE_FAST:
period = 200; // 快速闪烁
break;
case MODE_SLOW:
period = 2000; // 慢速闪烁
break;
default:
period = 500; // 正常闪烁
break;
}
if((system_ticks - task_led_timestamp) >= period) {
task_led_timestamp = system_ticks;
LED_Toggle();
}
}
// 任务2:按键检测(带消抖)
void Task_Button_Scan(void) {
if((system_ticks - task_button_timestamp) >= 20) { // 20ms扫描一次
task_button_timestamp = system_ticks;
bool current_state = Button_IsPressed();
// 检测按键按下(下降沿)
if(current_state && !button_last_state) {
button_count++;
// 根据按键次数切换模式
if(button_count >= 3) {
button_count = 0;
// 循环切换模式
system_mode = (system_mode + 1) % 3;
}
}
button_last_state = current_state;
}
}
// 任务3:ADC采样和滤波
void Task_ADC_Sample(void) {
if((system_ticks - task_adc_timestamp) >= 100) { // 100ms采样一次
task_adc_timestamp = system_ticks;
// 读取ADC值
adc_buffer[adc_index] = ADC_Read();
adc_index = (adc_index + 1) % 10;
// 计算移动平均值
uint32_t sum = 0;
for(uint8_t i = 0; i < 10; i++) {
sum += adc_buffer[i];
}
adc_average = sum / 10;
}
}
// 任务4:显示更新
void Task_Display_Update(void) {
if((system_ticks - task_display_timestamp) >= 1000) { // 1秒更新一次
task_display_timestamp = system_ticks;
// 通过串口输出状态信息
printf("Mode: %d, ADC: %lu, Uptime: %lu s\r\n",
system_mode, adc_average, system_ticks / 1000);
}
}
// ============ 主程序 ============
int main(void) {
// 系统初始化
SystemInit();
GPIO_Init();
ADC_Init();
UART_Init();
SysTick_Init(); // 配置为1ms中断
printf("System Started!\r\n");
// 超级循环
while(1) {
Task_LED_Blink(); // LED任务
Task_Button_Scan(); // 按键任务
Task_ADC_Sample(); // ADC任务
Task_Display_Update(); // 显示任务
// 可选:进入低功耗模式等待中断
// __WFI();
}
return 0;
}
示例说明:
这个完整的示例展示了超级循环的实际应用:
- 多任务管理:
- LED闪烁任务(可变频率)
- 按键扫描任务(带消抖)
- ADC采样任务(带滤波)
-
显示更新任务
-
时间管理:
- 每个任务有独立的执行周期
-
使用时间戳避免阻塞
-
状态管理:
- 系统模式控制LED闪烁频率
-
按键计数实现模式切换
-
数据处理:
- ADC移动平均滤波
- 按键消抖处理
运行效果¶
程序运行后: - LED以500ms间隔闪烁(正常模式) - 按下按键3次,切换到快速模式(200ms闪烁) - 再按3次,切换到慢速模式(2000ms闪烁) - 再按3次,回到正常模式 - 每秒通过串口输出系统状态
优缺点分析¶
超级循环的优势¶
- 简单直观
- 程序流程清晰,易于理解
- 调试方便,容易跟踪执行流程
-
学习曲线平缓
-
资源占用小
- 无需任务切换开销
- 内存占用少
-
代码体积小
-
确定性好
- 执行顺序固定
- 便于分析和优化
-
适合简单的实时需求
-
开发效率高
- 快速原型开发
- 无需复杂的同步机制
- 适合小型项目
超级循环的局限¶
- 响应时间不确定
- 任务响应时间取决于前面任务的执行时间
- 最坏情况响应时间 = 所有任务执行时间之和
-
不适合严格的实时要求
-
任务阻塞问题
- 一个任务阻塞会影响所有后续任务
- 长时间任务会降低系统响应性
-
需要将任务拆分成小片段
-
扩展性差
- 任务增多时难以管理
- 时序关系复杂
-
代码耦合度高
-
功能受限
- 难以实现复杂的任务调度
- 不支持任务优先级
- 缺少任务间同步机制
适用场景¶
超级循环适合以下应用场景:
推荐使用: - 简单的控制应用(LED控制、简单传感器读取) - 任务数量少(3-5个) - 实时性要求不严格 - 资源极度受限的系统 - 学习和原型验证
不推荐使用: - 复杂的多任务系统 - 严格的实时性要求 - 任务数量多且相互依赖 - 需要任务优先级管理 - 大型商业项目
深入理解¶
任务执行时间分析¶
理解任务执行时间对于设计超级循环至关重要。
循环周期计算¶
假设有3个任务: - Task1: 执行时间 5ms - Task2: 执行时间 10ms - Task3: 执行时间 3ms
循环周期 = 5ms + 10ms + 3ms = 18ms
这意味着: - 每个任务最多每18ms执行一次 - Task1的最坏响应时间 = 10ms + 3ms = 13ms(等待Task2和Task3) - Task3的最坏响应时间 = 5ms + 10ms = 15ms(等待Task1和Task2)
时间分配示例¶
// 测量任务执行时间
void MeasureTaskTime(void) {
uint32_t start, end, duration;
start = system_ticks;
Task1();
end = system_ticks;
duration = end - start;
printf("Task1 execution time: %lu ms\r\n", duration);
}
避免任务阻塞¶
长时间任务会阻塞整个循环,需要将其拆分:
错误示例(阻塞)¶
void Task_SendData(void) {
// 错误:阻塞式发送
for(int i = 0; i < 1000; i++) {
UART_SendByte(data[i]);
Delay_ms(1); // 总共阻塞1000ms!
}
}
正确示例(非阻塞)¶
// 使用状态机拆分任务
typedef enum {
SEND_IDLE,
SEND_BUSY
} SendState_t;
SendState_t send_state = SEND_IDLE;
uint16_t send_index = 0;
uint32_t send_timestamp = 0;
void Task_SendData(void) {
switch(send_state) {
case SEND_IDLE:
// 等待发送请求
if(send_request) {
send_state = SEND_BUSY;
send_index = 0;
send_request = false;
}
break;
case SEND_BUSY:
// 每次只发送一个字节
if((system_ticks - send_timestamp) >= 1) {
send_timestamp = system_ticks;
UART_SendByte(data[send_index]);
send_index++;
if(send_index >= 1000) {
send_state = SEND_IDLE;
send_complete = true;
}
}
break;
}
}
改进说明: - 将长时间任务拆分成多个小步骤 - 每次循环只执行一小部分 - 使用状态机管理任务进度 - 不阻塞其他任务的执行
中断与超级循环的配合¶
合理使用中断可以提高超级循环的响应性:
// 中断服务程序:快速处理,设置标志
void UART_RxHandler(void) {
// 读取数据
rx_buffer[rx_index++] = UART_DR;
// 检查是否接收完整帧
if(rx_buffer[rx_index-1] == '\n') {
flag_rx_complete = true;
}
// 防止溢出
if(rx_index >= RX_BUFFER_SIZE) {
rx_index = 0;
}
}
// 主循环:复杂处理
void Task_ProcessUART(void) {
if(flag_rx_complete) {
flag_rx_complete = false;
// 在主循环中处理数据(可以耗时)
ParseCommand(rx_buffer, rx_index);
ExecuteCommand();
rx_index = 0;
}
}
设计原则: - 中断中只做必要的快速处理 - 复杂逻辑放在主循环中 - 使用标志位传递信息 - 注意共享数据的保护
性能优化技巧¶
1. 减少不必要的检查¶
// 优化前:每次循环都检查
while(1) {
if((system_ticks - task1_timestamp) >= 10) {
task1_timestamp = system_ticks;
Task1();
}
if((system_ticks - task2_timestamp) >= 100) {
task2_timestamp = system_ticks;
Task2();
}
// ... 更多任务
}
// 优化后:使用标志位减少时间比较
volatile bool flag_task1 = false;
volatile bool flag_task2 = false;
void SysTick_Handler(void) {
static uint8_t count = 0;
system_ticks++;
count++;
if(count >= 10) {
flag_task1 = true;
}
if(count >= 100) {
flag_task2 = true;
count = 0;
}
}
while(1) {
if(flag_task1) {
flag_task1 = false;
Task1();
}
if(flag_task2) {
flag_task2 = false;
Task2();
}
}
2. 任务优先级排序¶
将重要的任务放在前面:
while(1) {
// 高优先级任务(紧急、时间敏感)
Task_UrgentEvent();
// 中优先级任务(正常业务逻辑)
Task_NormalProcess();
// 低优先级任务(后台任务)
Task_BackgroundWork();
}
3. 空闲时进入低功耗¶
while(1) {
bool has_work = false;
// 检查并执行所有任务
if(flag_task1) {
flag_task1 = false;
Task1();
has_work = true;
}
if(flag_task2) {
flag_task2 = false;
Task2();
has_work = true;
}
// 如果没有任务需要执行,进入低功耗模式
if(!has_work) {
__WFI(); // 等待中断唤醒
}
}
常见问题¶
Q1: 超级循环中如何处理长时间任务?¶
A: 有几种方法:
- 任务拆分:将长任务拆分成多个小步骤,使用状态机管理
- 分时执行:每次循环只执行一部分,多次循环完成整个任务
- 后台处理:使用DMA或中断处理耗时操作
- 优先级调整:将长任务放在循环末尾
示例:
// 拆分长任务
typedef enum {
STEP_INIT,
STEP_PROCESS_1,
STEP_PROCESS_2,
STEP_COMPLETE
} TaskStep_t;
TaskStep_t current_step = STEP_INIT;
void Task_LongProcess(void) {
switch(current_step) {
case STEP_INIT:
InitProcess();
current_step = STEP_PROCESS_1;
break;
case STEP_PROCESS_1:
ProcessPart1();
current_step = STEP_PROCESS_2;
break;
case STEP_PROCESS_2:
ProcessPart2();
current_step = STEP_COMPLETE;
break;
case STEP_COMPLETE:
FinalizeProcess();
current_step = STEP_INIT;
break;
}
}
Q2: 如何在超级循环中实现任务优先级?¶
A: 超级循环本身不支持真正的优先级,但可以通过以下方法模拟:
- 执行顺序:高优先级任务放在循环前面
- 执行频率:高优先级任务执行更频繁
- 条件执行:高优先级任务优先检查和执行
while(1) {
// 最高优先级:总是执行
if(flag_critical) {
flag_critical = false;
CriticalTask();
}
// 高优先级:频繁执行
if((system_ticks - high_priority_timestamp) >= 10) {
high_priority_timestamp = system_ticks;
HighPriorityTask();
}
// 低优先级:偶尔执行
if((system_ticks - low_priority_timestamp) >= 1000) {
low_priority_timestamp = system_ticks;
LowPriorityTask();
}
}
Q3: 超级循环和RTOS有什么本质区别?¶
A: 主要区别:
| 特性 | 超级循环 | RTOS |
|---|---|---|
| 任务调度 | 顺序执行 | 抢占式调度 |
| 优先级 | 无真正优先级 | 支持任务优先级 |
| 响应时间 | 不确定 | 可预测 |
| 资源占用 | 小 | 较大 |
| 复杂度 | 简单 | 复杂 |
| 适用场景 | 简单应用 | 复杂应用 |
何时从超级循环迁移到RTOS: - 任务数量超过5个 - 需要严格的实时性 - 需要任务间复杂的同步和通信 - 项目规模较大
Q4: 如何调试超级循环程序?¶
A: 常用的调试方法:
-
LED指示:
-
串口输出:
-
性能监控:
-
看门狗:
最佳实践¶
设计原则¶
- 保持任务简短
- 每个任务执行时间应该很短(< 10ms)
-
长任务必须拆分
-
使用时间基准
- 建立统一的时间基准(如1ms SysTick)
-
所有定时都基于这个时间基准
-
避免阻塞
- 不使用阻塞式延时
-
不在主循环中等待硬件
-
合理使用中断
- 中断处理要快
-
复杂逻辑放主循环
-
状态机思维
- 使用状态机管理复杂逻辑
- 每次循环只执行一个状态
代码组织建议¶
// ============ 文件结构 ============
// main.c
// ├── 硬件抽象层
// ├── 全局变量定义
// ├── 中断服务程序
// ├── 任务函数定义
// └── 主函数
// ============ 推荐的代码模板 ============
#include <stdint.h>
#include <stdbool.h>
// 1. 硬件抽象层
void HAL_Init(void);
void HAL_LED_Toggle(void);
// ...
// 2. 全局变量
volatile uint32_t system_ticks = 0;
// ...
// 3. 中断服务程序
void SysTick_Handler(void) {
system_ticks++;
}
// 4. 任务函数
void Task1(void) {
// 任务实现
}
void Task2(void) {
// 任务实现
}
// 5. 主函数
int main(void) {
// 初始化
HAL_Init();
// 超级循环
while(1) {
Task1();
Task2();
}
return 0;
}
性能检查清单¶
在部署超级循环程序前,检查以下项目:
- 每个任务的执行时间是否已测量?
- 循环周期是否满足最快任务的要求?
- 是否有任务会阻塞循环?
- 中断服务程序是否足够短?
- 是否正确处理了共享数据?
- 是否有看门狗保护?
- 是否考虑了最坏情况?
总结¶
超级循环是裸机编程中最基础的程序架构:
核心要点: - 基本结构:初始化 + while(1) + 顺序任务 - 调度方法:时间驱动、标志驱动、混合驱动 - 状态管理:使用状态变量和状态机 - 定时实现:基于SysTick的软件定时器
优势: - 简单直观,易于理解和调试 - 资源占用小,适合简单应用 - 开发效率高,快速原型
局限: - 响应时间不确定 - 任务阻塞问题 - 扩展性差
适用场景: - 简单控制应用 - 任务数量少(3-5个) - 实时性要求不严格 - 学习和原型验证
关键技巧: - 保持任务简短 - 避免阻塞操作 - 使用状态机拆分长任务 - 合理配合中断使用
延伸阅读¶
推荐进一步学习的内容:
- 状态机设计模式实战 - 学习如何用状态机管理复杂逻辑
- 时间片轮询调度算法 - 了解更高级的调度方法
- 软件定时器实现 - 深入学习定时器设计
- 协作式多任务调度器实现 - 超级循环的进阶版本
参考资料¶
- "Patterns for Time-Triggered Embedded Systems" - Michael J. Pont
- "Embedded Software Development with C" - Kai Qian
- "Real-Time Systems Design and Analysis" - Phillip A. Laplante
- ARM Cortex-M Programming Guide
实践练习:
- 基础练习:实现一个超级循环程序,包含3个任务:
- LED以1Hz频率闪烁
- 按键检测(带消抖)
-
串口输出系统运行时间
-
进阶练习:设计一个温度监控系统:
- 每100ms读取温度传感器
- 温度超过阈值时LED报警
- 每秒通过串口输出温度值
-
按键可以调整报警阈值
-
挑战练习:实现一个简单的任务调度器:
- 支持至少5个任务
- 每个任务可配置执行周期
- 可以动态使能/禁用任务
- 提供任务执行时间统计
下一步:建议学习 状态机设计模式实战,掌握更强大的程序设计方法。