• 工作经验总结:单片机中简易时间片轮询的结构设计


    目录

    一、单片机中常见的几种模式介绍

    二、简易时间片轮询结构设计

    1、任务调度表的设计

    (1)任务调度表的结构体设计

    (2)时间片的处理与任务的启停

    (3)任务调度表的实现

    三、关于上述时间片轮询结构设计的一些补充

    1、任务调度表的优先级调度设计

    (1)任务调度表优先级与优先级计算的设计

    (2)时间片轮询处理函数的修改

    (3)任务调度函数的修改


    一、单片机中常见的几种模式介绍

    在MCU的设计与开发中有以下常见的程序结构设计:

    • 裸机:功能单一,简单的顺序执行
    • 时间片轮询:多任务、内存占用较少
    • RTOS:多任务、系统复杂性高,有较高的实时性要求和可靠性要求

    其实一般情况下能上RTOS一般就上RTOS,根本不需要考虑时间片轮询的情况,斟酌使用时间片轮询的情况主要的考虑有以下几点:

    • MCU的RAM和ROM资源问题,引入RTOS会带来额外的内存开销
    • 业务功能的拆解与多任务的设计,有时候使用时间片轮询可以让整个结构更加简单易懂,方便设计
    • 调试更为简单方便

    使用时间片轮询时要注意的点:

    • 功能任务划分的合理性,多任务的情况下,每个任务的执行都尽量设计成短小精悍。(例如:执行1个任务需要10ms,你不能只给它分配5ms的时间片)
    • 任务数量与MCU主频的简单关系估算:任务数量 = MCU主频 / (时间片大小 × 任务平均处理时间) ;例如在一个系统中有10个任务,并且每个任务需要相同的时间片大小为10ms,那么每个任务将获得1/10的CPU时间,也就是10%的CPU时间。如果我们希望系统总体运行效率高达80%,那么MCU的主频应该是所有任务的最小执行时间的8倍,即主频应该为1/(10毫秒* 8) = 1250 Hz

    二、简易时间片轮询结构设计

    1、任务调度表的设计

    (1)任务调度表的结构体设计

    其中,任务调度表中包含了任务编号(或者可以设计成表中的索引)回调的任务函数任务类型(周期或者事件类型)任务开关、任务状态(包括执行后的状态)、任务时间片计数、任务时间片初始值

    1. /******************************************
    2. * OS Task No
    3. ******************************************/
    4. #define OS_TASK_NO1 0
    5. #define OS_TASK_NO2 1
    6. #define OS_TASK_NO3 2
    7. #define OS_TASK_NO4 3
    8. #define OS_TASK_NO5 4
    9. #define OS_TASK_NO6 5
    10. #define OS_TASK_NO7 6
    11. #define OS_TASK_NO8 7
    12. #define OS_TASK_MAX_NO 8
    13. /******************************************
    14. * OS Task switch
    15. ******************************************/
    16. #define OS_TASK_OFF 0
    17. #define OS_TASK_ON 1
    18. /******************************************
    19. * OS Task Type
    20. ******************************************/
    21. #define OS_TASK_EVENT 0
    22. #define OS_TASK_CYCLC 1
    23. /******************************************
    24. * OS Task Status
    25. ******************************************/
    26. #define OS_TASK_OK 0
    27. #define OS_TASK_READY 1
    28. #define OS_TASK_ERROR 2
    29. /******************************************
    30. * Struct and Enum
    31. ******************************************/
    32. typedef struct
    33. {
    34. uint8_t os_task_number;
    35. uint8_t (*taskFunc)(void);
    36. uint8_t os_task_type;
    37. uint8_t os_task_switch;
    38. uint8_t os_task_status;
    39. uint8_t os_task_tickCnt;
    40. uint8_t os_task_tickInit;
    41. }OS_TASK_SCHEDULE_t;

    (2)时间片的处理与任务的启停

    • 时间片的处理:放置在定时器或者Systick定时器的中断中,对所有任务的时间片计数进行处理,当计数值减少到0时,把对应任务的状态赋为Ready态,并对时间片赋初始计数值
    1. void OS_TmrIsrTask(void)
    2. {
    3. uint8_t indx = 0;
    4. OS_DISABLE_INTERRUPT();
    5. for(indx = 0; indx < osTaskScheduleCfgSize; indx++)
    6. {
    7. if(osTaskScheduleCfg[indx].os_task_switch == OS_TASK_ON &&\
    8. osTaskScheduleCfg[indx].os_task_status != OS_TASK_READY)
    9. {
    10. if(osTaskScheduleCfg[indx].os_task_tickCnt > 0)
    11. {
    12. osTaskScheduleCfg[indx].os_task_tickCnt--;
    13. }
    14. if(osTaskScheduleCfg[indx].os_task_tickCnt == 0)
    15. {
    16. osTaskScheduleCfg[indx].os_task_status = OS_TASK_READY;
    17. switch(osTaskScheduleCfg[indx].os_task_type)
    18. {
    19. case OS_TASK_CYCLC:
    20. {
    21. osTaskScheduleCfg[indx].os_task_tickCnt = osTaskScheduleCfg[indx].os_task_tickInit;
    22. break;
    23. }
    24. case OS_TASK_EVENT:
    25. {
    26. break;
    27. }
    28. }
    29. }
    30. }
    31. }
    32. OS_ENABLE_INTERRUPT();
    33. }
    • 任务的启停

    1. void OS_TaskTmrEventStart(uint8_t task_number)
    2. {
    3. uint8_t indx = 0;
    4. OS_DISABLE_INTERRUPT();
    5. for(indx = 0; indx <= osTaskScheduleCfgSize; indx++)
    6. {
    7. if(task_number == osTaskScheduleCfg[indx].os_task_number)
    8. {
    9. osTaskScheduleCfg[indx].os_task_switch = OS_TASK_ON;
    10. osTaskScheduleCfg[indx].os_task_tickCnt = osTaskScheduleCfg[indx].os_task_tickInit;
    11. break;
    12. }
    13. }
    14. OS_ENABLE_INTERRUPT();
    15. }
    16. void OS_TaskTmrEventStop(uint8_t task_number)
    17. {
    18. uint8_t indx = 0;
    19. OS_DISABLE_INTERRUPT();
    20. for(indx = 0; indx <= osTaskScheduleCfgSize; indx++)
    21. {
    22. if(task_number == osTaskScheduleCfg[indx].os_task_number)
    23. {
    24. osTaskScheduleCfg[indx].os_task_switch = OS_TASK_OFF;
    25. osTaskScheduleCfg[indx].os_task_tickCnt = 0;
    26. break;
    27. }
    28. }
    29. OS_ENABLE_INTERRUPT();
    30. }

    (3)任务调度表的实现

    • 任务调度表的实现:循环遍历任务调度表中每个任务的状态,当有处于Ready状态的任务时,则去执行。
    1. void OS_StartSchedule(void)
    2. {
    3. uint8_t indx;
    4. for(;;)
    5. {
    6. for(indx = 0; indx < osTaskScheduleCfgSize; indx++)
    7. {
    8. switch(osTaskScheduleCfg[indx].os_task_status)
    9. {
    10. case OS_TASK_OK:
    11. {
    12. break;
    13. }
    14. case OS_TASK_ERROR:
    15. {
    16. break;
    17. }
    18. case OS_TASK_READY:
    19. {
    20. OS_DISABLE_INTERRUPT();
    21. if(osTaskScheduleCfg[indx].taskFunc != NULL)
    22. {
    23. osTaskScheduleCfg[indx].os_task_status = osTaskScheduleCfg[indx].taskFunc();
    24. if(osTaskScheduleCfg[indx].os_task_type == OS_TASK_EVENT)
    25. {
    26. osTaskScheduleCfg[indx].os_task_switch = OS_TASK_OFF;
    27. }
    28. }
    29. OS_ENABLE_INTERRUPT();
    30. break;
    31. }
    32. }
    33. }
    34. }
    35. }
    • 任务调度表与任务的配置:其中还可以根据对初始化tickCnt的值的差异,来控制先后执行任务的顺序
    1. /*********************************************
    2. * OS Task Schedule Config
    3. *********************************************/
    4. OS_TASK_SCHEDULE_t osTaskScheduleCfg[] =
    5. {
    6. /* level taskFunc task_type task_switch os_task_status task_tickCnt task_tickInit */
    7. {OS_TASK_NO1, os_task1, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 0, 100 },
    8. {OS_TASK_NO2, os_task2, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 1, 100 },
    9. {OS_TASK_NO3, os_task3, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 2, 100 },
    10. {OS_TASK_NO4, os_task4, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 3, 100 },
    11. {OS_TASK_NO5, os_task5, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 4, 100 },
    12. {OS_TASK_NO6, os_task6, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 5, 100 },
    13. {OS_TASK_NO7, os_task7, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 6, 100 },
    14. {OS_TASK_NO8, os_task8, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 7, 100 },
    15. };
    16. uint8_t osTaskScheduleCfgSize = sizeof(osTaskScheduleCfg) / sizeof(OS_TASK_SCHEDULE_t);
    17. /******************************************
    18. * User Task Function
    19. ******************************************/
    20. uint8_t os_task1(void)
    21. {
    22. /* do something */
    23. return OS_TASK_OK
    24. }
    25. uint8_t os_task2(void)
    26. {
    27. /* do something */
    28. return OS_TASK_OK
    29. }
    30. uint8_t os_task3(void)
    31. {
    32. /* do something */
    33. return OS_TASK_OK
    34. }
    35. uint8_t os_task4(void)
    36. {
    37. /* do something */
    38. return OS_TASK_OK
    39. }
    40. uint8_t os_task5(void)
    41. {
    42. /* do something */
    43. return OS_TASK_OK
    44. }
    45. uint8_t os_task6(void)
    46. {
    47. /* do something */
    48. return OS_TASK_OK
    49. }
    50. uint8_t os_task7(void)
    51. {
    52. /* do something */
    53. return OS_TASK_OK
    54. }
    55. uint8_t os_task8(void)
    56. {
    57. /* do something */
    58. return OS_TASK_OK
    59. }

    三、关于上述时间片轮询结构设计的一些补充

    1、任务调度表的优先级调度设计

    上述设计中,当两个任务同时Ready的时候,仅会按照顺序进行处理。当一些业务功能有优先级功能区分的时候,就无法满足这样的需求,这时候就要对上述设计进行改进。

    (1)任务调度表优先级与优先级计算的设计

    • 任务调度表优先级的设计与就绪任务队列的设计

    在原来任务调度表的基础上再新增一个优先级调度表结构体,设计如下:包含优先级(索引)任务调度表任务调度表长度、就绪任务的编号FIFO。

    (注:就绪任务队列中存放着已经准备就绪的任务编号)

    1. typedef struct
    2. {
    3. uint8_t head;
    4. uint8_t tail;
    5. uint8_t buf[OS_READY_FIFO_MAX];
    6. }OS_RdyNoFifo_t;
    7. typedef struct
    8. {
    9. uint8_t os_task_number;
    10. uint8_t (*taskFunc)(void);
    11. uint8_t os_task_type;
    12. uint8_t os_task_switch;
    13. uint8_t os_task_status;
    14. uint8_t os_task_tickCnt;
    15. uint8_t os_task_tickInit;
    16. }OS_TASK_SCHEDULE_t;
    17. typedef struct
    18. {
    19. uint8_t prio_number;
    20. OS_TASK_SCHEDULE_t *osTaskScheduleCfg;
    21. uint8_t *osTaskScheduleCfgSize;
    22. OS_RdyNoFifo_t *osRdyNofifoBuf;
    23. }OS_PRIO_SCHEDULE_t;
    • 优先级计算的设计

    假如我们需要8层的优先级,我们可以以1个字节中的每一位来充当1级优先级。数值越大,则优先级越高。设计如下:

    1. /*******************************************************************
    2. * OS Priority Schedule Number(Index)
    3. *******************************************************************/
    4. #define OS_PRIO_LEVEL_1 0
    5. #define OS_PRIO_LEVEL_2 1
    6. #define OS_PRIO_LEVEL_3 2
    7. #define OS_PRIO_LEVEL_4 3
    8. #define OS_PRIO_LEVEL_5 4
    9. #define OS_PRIO_LEVEL_6 5
    10. #define OS_PRIO_LEVEL_7 6
    11. #define OS_PRIO_LEVEL_8 7
    12. #define OS_PRIO_LEVEL_MAX 8
    13. #define OS_PRIO_VAL_LEVEL(x) (1 << (7 - x))
    14. #define OS_PRIO_VAL_LEVEL_1 (1 << (7 - OS_PRIO_LEVEL_1))
    15. #define OS_PRIO_VAL_LEVEL_2 (1 << (7 - OS_PRIO_LEVEL_2))
    16. #define OS_PRIO_VAL_LEVEL_3 (1 << (7 - OS_PRIO_LEVEL_3))
    17. #define OS_PRIO_VAL_LEVEL_4 (1 << (7 - OS_PRIO_LEVEL_4))
    18. #define OS_PRIO_VAL_LEVEL_5 (1 << (7 - OS_PRIO_LEVEL_5))
    19. #define OS_PRIO_VAL_LEVEL_6 (1 << (7 - OS_PRIO_LEVEL_6))
    20. #define OS_PRIO_VAL_LEVEL_7 (1 << (7 - OS_PRIO_LEVEL_7))
    21. #define OS_PRIO_VAL_LEVEL_8 (1 << (7 - OS_PRIO_LEVEL_8))

    以2层优先级为例,LEVEL_1与LEVEL_2,则有以下几种情况:

    1. // 仅LEVEL1 任务产生
    2. priority = 0x80;
    3. // 仅LEVEL2 任务产生
    4. priority = 0x40;
    5. // LEVEL1和LEVEL2任务同时产生,此时要先执行LEVEL1的任务
    6. priority = 0xC0;

     优先级计算函数:

    1. static bool OS_isScheduleExistTask(OS_RdyNoFifo_t *osRdyNofifoBuf)
    2. {
    3. bool ret = false;
    4. if(osRdyNofifoBuf->head != osRdyNofifoBuf->tail)
    5. {
    6. ret = true;
    7. }
    8. return ret;
    9. }
    10. static uint8_t OS_SchedulePrioCal(void)
    11. {
    12. uint8_t indx = 0;
    13. uint8_t prio_number = 0;
    14. uint8_t priority = 0;
    15. OS_RdyNoFifo_t *osRdyNofifoBuf;
    16. for(indx = 0; indx < osTaskPrioScheduleCfgSize; indx++)
    17. {
    18. osRdyNofifoBuf = osTaskPrioScheduleCfg[indx].osRdyNofifoBuf;
    19. prio_number = osTaskPrioScheduleCfg[indx].prio_number;
    20. if(OS_isScheduleExistTask(osRdyNofifoBuf))
    21. {
    22. priority += OS_PRIO_VAL_LEVEL(prio_number);
    23. }
    24. }
    25. return priority;
    26. }
    • 任务调度表与优先级调度表的配置

    1. /*********************************************
    2. * OS Task Ready FIFO Config
    3. *********************************************/
    4. OS_RdyNoFifo_t OsRdyFifoLv1 = {0};
    5. OS_RdyNoFifo_t OsRdyFifoLv2 = {0};
    6. /***********************************************************************
    7. * OS Task Schedule Config (include priority level)
    8. ***********************************************************************/
    9. OS_TASK_SCHEDULE_t osTaskScheduCfg_Lv1[] =
    10. {
    11. /* number taskFunc task_type task_switch os_task_status task_tickCnt task_tickInit */
    12. {OS_TASK_NO1, os_task1, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 0, 100 },
    13. {OS_TASK_NO2, os_task2, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 1, 100 },
    14. {OS_TASK_NO3, os_task3, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 2, 100 },
    15. };
    16. OS_TASK_SCHEDULE_t osTaskScheduCfg_Lv2[] =
    17. {
    18. /* number taskFunc task_type task_switch os_task_status task_tickCnt task_tickInit */
    19. {OS_TASK_NO1, os_task4, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 0, 100 },
    20. {OS_TASK_NO2, os_task5, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 1, 100 },
    21. {OS_TASK_NO3, os_task6, OS_TASK_CYCLC, OS_TASK_ON, OS_TASK_OK, 2, 100 },
    22. };
    23. uint8_t osTaskScheduleCfgSize_Lv1 = sizeof(osTaskScheduCfg_Lv1) / sizeof(OS_TASK_SCHEDULE_t);
    24. uint8_t osTaskScheduleCfgSize_Lv2 = sizeof(osTaskScheduCfg_Lv2) / sizeof(OS_TASK_SCHEDULE_t);
    25. /***********************************************************************
    26. * OS Task Priority Schedule
    27. ***********************************************************************/
    28. OS_PRIO_SCHEDULE_t osTaskPrioScheduleCfg[] =
    29. {
    30. /* prio_number osTaskScheduleCfg osTaskScheduleCfgSize osRdyNofifoBuf */
    31. {OS_PRIO_LEVEL_1, &osTaskScheduCfg_Lv1, &osTaskScheduleCfgSize_Lv1, &OsRdyFifoLv1},
    32. {OS_PRIO_LEVEL_2, &osTaskScheduCfg_Lv2, &osTaskScheduleCfgSize_Lv2, &OsRdyFifoLv2},
    33. };
    34. uint8_t osTaskPrioScheduleCfgSize = sizeof(osTaskPrioScheduleCfg) / sizeof(OS_PRIO_SCHEDULE_t);

    (2)时间片轮询处理函数的修改

    时间片轮询处理函仅做少数改动,遍历整张优先级表的同时,将Ready的任务Push到任务就绪队列中。

    1. void OS_TmrIsrTask(void)
    2. {
    3. uint8_t indx, taskCfgSize, task_i;
    4. OS_TASK_SCHEDULE_t * osTaskScheduleCfg;
    5. OS_RdyNoFifo_t * osRdyFifo;
    6. OS_DISABLE_INTERRUPT();
    7. for(indx = 0; indx < osTaskPrioScheduleCfgSize; indx++)
    8. {
    9. osTaskScheduleCfg = osTaskPrioScheduleCfg[indx].osTaskScheduleCfg;
    10. taskCfgSize = *osTaskPrioScheduleCfg[indx].osTaskScheduleCfgSize;
    11. osRdyFifo = osTaskPrioScheduleCfg[indx].osRdyNofifoBuf;
    12. for(task_i = 0; task_i < taskCfgSize; task_i++)
    13. {
    14. if(osTaskScheduleCfg[task_i].os_task_switch == OS_TASK_ON &&\
    15. osTaskScheduleCfg[task_i].os_task_status != OS_TASK_READY)
    16. {
    17. if(osTaskScheduleCfg[task_i].os_task_tickCnt > 0)
    18. {
    19. osTaskScheduleCfg[task_i].os_task_tickCnt--;
    20. }
    21. if(osTaskScheduleCfg[task_i].os_task_tickCnt == 0)
    22. {
    23. osTaskScheduleCfg[task_i].os_task_status = OS_TASK_READY;
    24. if(OS_PushRdyTask(osRdyFifo, osTaskScheduleCfg[task_i].os_task_number) != OS_FIFO_OK)
    25. {
    26. /* push error dispose */
    27. }
    28. switch(osTaskScheduleCfg[task_i].os_task_type)
    29. {
    30. case OS_TASK_CYCLC:
    31. {
    32. osTaskScheduleCfg[task_i].os_task_tickCnt = osTaskScheduleCfg[task_i].os_task_tickInit;
    33. break;
    34. }
    35. case OS_TASK_EVENT:
    36. {
    37. break;
    38. }
    39. }
    40. }
    41. }
    42. }
    43. }
    44. OS_ENABLE_INTERRUPT();
    45. }

    (3)任务调度函数的修改

    任务调度函数则需要先计算优先级,看优先处理哪一张任务调度表,再去从对应优先级的任务就绪队列中Pop出任务编号,然后执行对应任务。

    1. static void OS_StartPrioSchedule(uint8_t prio_number)
    2. {
    3. uint8_t prio_i, task_i;
    4. uint8_t task_number, osTaskScheduleCfgSize;
    5. OS_TASK_SCHEDULE_t * osTaskScheduleCfg;
    6. OS_RdyNoFifo_t * osRdyFifo;
    7. OS_DISABLE_INTERRUPT();
    8. osTaskScheduleCfg = osTaskPrioScheduleCfg[prio_number].osTaskScheduleCfg;
    9. osTaskScheduleCfgSize = *osTaskPrioScheduleCfg[prio_number].osTaskScheduleCfgSize;
    10. osRdyFifo = osTaskPrioScheduleCfg[prio_number].osRdyNofifoBuf;
    11. if(OS_PopRdyTask(osRdyFifo, &task_number) == OS_FIFO_OK)
    12. {
    13. switch(osTaskScheduleCfg[task_number].os_task_status)
    14. {
    15. case OS_TASK_OK:
    16. {
    17. break;
    18. }
    19. case OS_TASK_ERROR:
    20. {
    21. break;
    22. }
    23. case OS_TASK_READY:
    24. {
    25. osTaskScheduleCfg[task_number].os_task_status = osTaskScheduleCfg[task_number].taskFunc();
    26. if(osTaskScheduleCfg[task_number].os_task_type == OS_TASK_EVENT)
    27. {
    28. osTaskScheduleCfg[task_number].os_task_switch = OS_TASK_OFF;
    29. }
    30. break;
    31. }
    32. }
    33. }
    34. OS_ENABLE_INTERRUPT();
    35. }
    36. void OS_StartSchedule(void)
    37. {
    38. uint8_t indx;
    39. uint8_t priority;
    40. uint8_t task_number;
    41. for(;;)
    42. {
    43. /* Calculate the Schedule Priority */
    44. priority = OS_SchedulePrioCal();
    45. if(priority >= OS_PRIO_VAL_LEVEL_1)
    46. {
    47. OS_StartPrioSchedule(OS_PRIO_LEVEL_1);
    48. }
    49. else if(priority >= OS_PRIO_VAL_LEVEL_2)
    50. {
    51. OS_StartPrioSchedule(OS_PRIO_LEVEL_2);
    52. }
    53. }
    54. }
  • 相关阅读:
    功利点没啥!
    RocketMQ 消费者(2)客户端设计和启动流程详解 & 源码解析
    隐式类型转换
    C++入门之缺省参数与函数重载
    Android:创建jniLibs的步骤
    【附源码】计算机毕业设计java缘来有交友平台系统设计与实现
    ESP32 IDF开发 应用篇⑳ WebSocket
    Python教程:迭代器的正确使用方法
    Three.js中实现对InstanceMesh的碰撞检测
    【安装Ubuntu18.04遇到的问题】未找到WIFI适配器
  • 原文地址:https://blog.csdn.net/laifengyuan1/article/details/130748806