在单片机开发中,代码结构往往由“main主函数+一堆if语句+中断服务函数”构成,随着项目复杂化,容易出现混乱、不易维护的代码。
为了构建清晰、可扩展、响应及时的系统框架,前后台轮询结构是一种非常实用的设计思路。
本文对前后台架构、如何实现轮询机制,以及如何管理系统任务的执行优先级与效率进行讨论。
一、什么是“前后台轮询”结构?
前后台结构是一种将“实时响应任务”与“非实时轮询任务”分离的单片机编程框架:
前台(中断服务):用于响应“事件驱动”的突发事务,例如:按键中断、串口接收、定时器超时等;
后台(主循环):不断轮询任务标志,根据前台产生的事件决定是否执行相应处理逻辑。
关键思想:中断只设置标志,不做处理;主循环根据标志执行实际任务。
二、最小可行例子:按键处理
- volatile uint8_t key_flag = 0;
- // 前台:中断服务函数,只置标志
- void EXTI0_IRQHandler(void) {
- key_flag = 1;
- EXTI->PR = 1 << 0; // 清除中断
- }
- // 后台:主循环中轮询标志并处理
- int main(void) {
- while (1) {
- if (key_flag) {
- key_flag = 0;
- handle_key_press(); // 执行实际业务
- }
- other_tasks(); // 其他周期性任务
- }
- }
优点:
中断执行极快,避免阻塞;
处理逻辑集中在主循环,方便调试、跟踪;
易于拓展其他任务与调度控制。
三、通用后台轮询任务框架
使用结构体或函数数组,管理多个“轮询任务”,构建简易调度器:
方法1:任务函数数组轮询
- typedef void (*TaskFunc)(void);
- void task_led(void);
- void task_adc(void);
- void task_uart(void);
- TaskFunc task_list[] = {
- task_led,
- task_adc,
- task_uart,
- };
- int main(void) {
- while (1) {
- for (int i = 0; i < sizeof(task_list)/sizeof(TaskFunc); i++) {
- task_list[i]();
- }
- }
- }
- 方法2:任务调度结构体(含时间间隔)
- typedef struct {
- void (*task_func)(void);
- uint16_t interval_ms;
- uint32_t last_tick;
- } Task_t;
- Task_t task_list[] = {
- {task_led, 100, 0},
- {task_adc, 500, 0},
- {task_uart, 0, 0}, // 轮询,无延时
- };
- void scheduler(void) {
- uint32_t now = millis();
- for (int i = 0; i < sizeof(task_list)/sizeof(Task_t); i++) {
- if (task_list[i].interval_ms == 0 || now - task_list[i].last_tick >= task_list[i].interval_ms) {
- task_list[i].last_tick = now;
- task_list[i].task_func();
- }
- }
- }
主循环只需调用 scheduler();,实现多任务周期调度。
四、前后台通信机制:标志、环形缓冲、事件队列
标志变量(适用于单次触发)
|