在单片机裸机编程中,状态机是一种非常强大的工具,能够有效管理复杂的逻辑和任务切换。除了状态机,还有其他几种编程模式可以在不使用 RTOS 的情况下实现高效的程序设计。以下是一些常见的方法:
1. 状态机编程
状态机通过定义系统的不同状态和状态之间的转换规则,将复杂的逻辑分解为简单的状态和事件处理。它适用于事件驱动的系统,能够有效管理任务切换和逻辑复杂性。
实现思路:
-
定义状态枚举类型。
-
使用状态变量记录当前状态。
-
在主循环中根据当前状态执行对应的任务。
-
根据事件或条件触发状态转换。
示例代码:
cpp
typedef enum {
STATE_IDLE,
STATE_PROCESS_SENSOR,
STATE_HANDLE_BUTTON,
STATE_UPDATE_DISPLAY
} StateTypeDef;
StateTypeDef currentState = STATE_IDLE;
void process_sensor_data(void) {
// 处理传感器数据
}
void handle_button_press(void) {
// 处理按钮事件
}
void update_display(void) {
// 更新显示
}
void main(void) {
while (1) {
switch (currentState) {
case STATE_IDLE:
if (sensor_data_ready) {
currentState = STATE_PROCESS_SENSOR;
} else if (button_pressed) {
currentState = STATE_HANDLE_BUTTON;
}
break;
case STATE_PROCESS_SENSOR:
process_sensor_data();
currentState = STATE_IDLE;
break;
case STATE_HANDLE_BUTTON:
handle_button_press();
currentState = STATE_IDLE;
break;
case STATE_UPDATE_DISPLAY:
update_display();
currentState = STATE_IDLE;
break;
}
}
}
2. 时间片轮询(Super Loop + 定时器)
时间片轮询是一种模拟多任务调度的方法,通过定时器中断实现时间片的管理。每个任务被分配一个固定的时间片,在主循环中依次执行各个任务的一部分。当时间片用完时,切换到下一个任务。
实现思路:
-
设置一个定时器中断,用于记录时间片的结束。
-
在主循环中,根据时间片的计数器决定当前任务是否继续执行。
示例代码:
cpp
#define TASK_COUNT 3
#define TIME_QUANTUM 10 // 时间片大小,单位为毫秒
typedef struct {
void (*taskFunc)(void); // 任务函数指针
int remainingTime; // 剩余时间片
} TaskTypeDef;
TaskTypeDef tasks[TASK_COUNT] = {
{task1, TIME_QUANTUM},
{task2, TIME_QUANTUM},
{task3, TIME_QUANTUM}
};
void task1(void) {
// 执行任务1
}
void task2(void) {
// 执行任务2
}
void task3(void) {
// 执行任务3
}
void main(void) {
int currentTask = 0;
while (1) {
if (tasks[currentTask].remainingTime > 0) {
tasks[currentTask].taskFunc(); // 执行当前任务
tasks[currentTask].remainingTime--;
}
currentTask = (currentTask + 1) % TASK_COUNT; // 轮询下一个任务
}
}
3. 中断驱动编程
中断驱动是一种利用单片机中断机制来处理事件的方法。通过配置中断源(如 GPIO、定时器、串口等),可以在事件发生时直接跳转到中断服务例程(ISR),从而实现快速响应。
实现思路:
-
配置中断源,设置中断优先级。
-
在中断服务例程中处理事件,避免在 ISR 中执行耗时操作。
示例代码:
cpp
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
// 处理按键中断事件
EXTI_ClearITPendingBit(EXTI_Line0);
handle_button_press();
}
}
void handle_button_press(void) {
// 按键处理逻辑
}
void main(void) {
// 初始化中断
NVIC_EnableIRQ(EXTI0_IRQn);
while (1) {
// 主循环可以执行其他任务
}
}
4. 非阻塞式编程
非阻塞式编程通过轮询或定时器检测事件状态,而不是在事件未发生时阻塞程序。这种方式可以提高程序的响应速度,避免因等待某个事件而导致程序卡顿。
实现思路:
-
使用定时器或计数器检测事件状态。
-
在主循环中不断检查事件是否发生,并根据状态执行相应操作。
示例代码:
cpp
#include "bsp_dwt.h" // 假设使用硬件定时器库
#define TIMEOUT 100000 // 超时时间,单位为微秒
void handle_button_press(void) {
// 按键处理逻辑
}
void handle_timeout(void) {
// 超时处理逻辑
}
void main(void) {
uint32_t start_time = DWT_GetTimeline_us(); // 获取当前时间
while (1) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == SET) {
// 按键按下,执行任务
handle_button_press();
start_time = DWT_GetTimeline_us(); // 重置计时器
}
if (DWT_GetTimeline_us() - start_time > TIMEOUT) {
// 超时处理
handle_timeout();
start_time = DWT_GetTimeline_us(); // 重置计时器
}
}
}
5. 超级循环(Super Loop)
超级循环是一种简单的多任务实现方式,通过在一个大循环中轮流执行不同的任务。每个任务函数执行一个任务的一部分,然后将控制权交给下一个任务。
实现思路:
-
在主循环中按顺序调用各个任务函数。
-
可以通过条件语句或计数器控制任务的执行频率。
示例代码:
cpp
void task1(void) {
// 执行任务1
}
void task2(void) {
// 执行任务2
}
void main(void) {
while (1) {
task1();
task2();
}
}
总结
在单片机裸机编程中,状态机、时间片轮询、中断驱动、非阻塞式编程和超级循环都是常见的编程模式。它们各有优缺点,适用于不同的场景:
-
状态机:适用于复杂逻辑和事件驱动的系统,能够有效管理任务切换和逻辑复杂性。
-
时间片轮询:适合多任务并发但对实时性要求不高的场景。
-
中断驱动:适合对实时性要求较高的系统。
-
非阻塞式编程:适合需要快速响应多个事件的系统。
-
超级循环:适合简单的多任务场景。