[综合信息] 单片机前后台轮询控制框架

[复制链接]
 楼主| backlugin 发表于 2025-7-19 08:02 | 显示全部楼层 |阅读模式
      在单片机开发中,代码结构往往由“main主函数+一堆if语句+中断服务函数”构成,随着项目复杂化,容易出现混乱、不易维护的代码。

        为了构建清晰、可扩展、响应及时的系统框架,前后台轮询结构是一种非常实用的设计思路。

        本文对前后台架构、如何实现轮询机制,以及如何管理系统任务的执行优先级与效率进行讨论。

一、什么是“前后台轮询”结构?
        前后台结构是一种将“实时响应任务”与“非实时轮询任务”分离的单片机编程框架:

前台(中断服务):用于响应“事件驱动”的突发事务,例如:按键中断、串口接收、定时器超时等;

后台(主循环):不断轮询任务标志,根据前台产生的事件决定是否执行相应处理逻辑。

关键思想:中断只设置标志,不做处理;主循环根据标志执行实际任务。

二、最小可行例子:按键处理
  1. volatile uint8_t key_flag = 0;



  2. // 前台:中断服务函数,只置标志

  3. void EXTI0_IRQHandler(void) {

  4.     key_flag = 1;

  5.     EXTI->PR = 1 << 0;  // 清除中断

  6. }



  7. // 后台:主循环中轮询标志并处理

  8. int main(void) {

  9.     while (1) {

  10.         if (key_flag) {

  11.             key_flag = 0;

  12.             handle_key_press();  // 执行实际业务

  13.         }



  14.         other_tasks();  // 其他周期性任务

  15.     }

  16. }

优点:

中断执行极快,避免阻塞;

处理逻辑集中在主循环,方便调试、跟踪;

易于拓展其他任务与调度控制。

三、通用后台轮询任务框架
        使用结构体或函数数组,管理多个“轮询任务”,构建简易调度器:

方法1:任务函数数组轮询
  1. typedef void (*TaskFunc)(void);



  2. void task_led(void);

  3. void task_adc(void);

  4. void task_uart(void);



  5. TaskFunc task_list[] = {

  6.     task_led,

  7.     task_adc,

  8.     task_uart,

  9. };



  10. int main(void) {

  11.     while (1) {

  12.         for (int i = 0; i < sizeof(task_list)/sizeof(TaskFunc); i++) {

  13.             task_list[i]();

  14.         }

  15.     }

  16. }

  17. 方法2:任务调度结构体(含时间间隔)

  18. typedef struct {

  19.     void (*task_func)(void);

  20.     uint16_t interval_ms;

  21.     uint32_t last_tick;

  22. } Task_t;



  23. Task_t task_list[] = {

  24.     {task_led, 100, 0},

  25.     {task_adc, 500, 0},

  26.     {task_uart, 0, 0},  // 轮询,无延时

  27. };



  28. void scheduler(void) {

  29.     uint32_t now = millis();

  30.     for (int i = 0; i < sizeof(task_list)/sizeof(Task_t); i++) {

  31.         if (task_list[i].interval_ms == 0 || now - task_list[i].last_tick >= task_list[i].interval_ms) {

  32.             task_list[i].last_tick = now;

  33.             task_list[i].task_func();

  34.         }

  35.     }

  36. }

        主循环只需调用 scheduler();,实现多任务周期调度。

四、前后台通信机制:标志、环形缓冲、事件队列
标志变量(适用于单次触发)
  1. volatile uint8_t uart_rx_flag = 0;



  2. void USART_IRQHandler(void) {

  3.     uart_rx_flag = 1;

  4. }

  5. 环形缓冲区(适用于数据流)

  6. uint8_t rx_buffer[64];

  7. volatile uint8_t rx_head = 0, rx_tail = 0;



  8. void USART_IRQHandler(void) {

  9.     rx_buffer[rx_head++] = USART->DR;

  10.     rx_head %= 64;

  11. }

  12. 事件队列/事件枚举(适用于多任务系统)

  13. typedef enum {

  14.     EVENT_NONE,

  15.     EVENT_KEY,

  16.     EVENT_UART_RX,

  17.     EVENT_SENSOR_READY,

  18. } SystemEvent_t;



  19. volatile SystemEvent_t event_queue[8];

  20. volatile uint8_t evt_head = 0, evt_tail = 0;



  21. void push_event(SystemEvent_t e) {

  22.     event_queue[evt_head++] = e;

  23.     evt_head %= 8;

  24. }



  25. SystemEvent_t pop_event(void) {

  26.     if (evt_head == evt_tail) return EVENT_NONE;

  27.     SystemEvent_t e = event_queue[evt_tail++];

  28.     evt_tail %= 8;

  29.     return e;

  30. }

  31. 主循环中执行任务:



  32. void XXX_IRQHandler(void)

  33. {

  34.     push_event(EVENT_XXX);

  35. }



  36. void main(void)

  37. {

  38.     while(1){

  39.         SystemEvent_t evt = pop_event();

  40.         switch (evt) {

  41.             case EVENT_KEY: handle_key(); break;

  42.             case EVENT_UART_RX: handle_uart(); break;

  43.         }

  44.     }

  45. }



szt1993 发表于 2025-7-31 22:21 | 显示全部楼层
单片机前后台轮询控制框架
您需要登录后才可以回帖 登录 | 注册

本版积分规则

18

主题

3021

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部