184~216

16.1 任务的基本概念

任务是竞争系统资源的最小运行单元。任 何 数 量 的 任 务 可 以 共 享 同 一 个 优 先 级 , 如 果 宏configUSE_TIME_SLICING 定义为 1, 处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。

  1. FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。 同时 FreeRTOS 也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务的 CPU 使用权。 <br /> <br />任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用FreeRTOS 中的任务删除 API 函数接口显式地将其删除。

16.2 任务调度器的基本概念

FreeRTOS 中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。
优先级数值越小的任务优先级越低, 0 为最低优先级,分配给空闲任务使用,一般不建议用户来使用这个优先级 。
假如使能了 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏(在 FreeRTOSConfig.h 文
件定义), 一般强制限定最大可用优先级数目为 32。
一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级任务的过程决定了调度时间是否具有确定性,例如一个包含 n 个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和 n 相关,而下一个就绪任务抉择时间的长短将会极大的影响系统的实时性。
FreeRTOS提供两种方法,在第一章的时候已经提及过了,一种是通用的,一种是通过前导0。
FreeRTOS 内核中也允许创建相同优先级的任务。相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行。

16.3 任务状态迁移

FreeRTOS 系统中的每一个任务都有多种运行状态,他们之间的转换关系是怎么样的呢?从运行态任务变成阻塞态,或者从阻塞态变成就绪态,这些任务状态是如何进行迁移?
image.png

  1. 创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度
  2. 就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态
  3. 运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。
  4. 运行态→阻塞态(Blocked) :正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
  5. 阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态
      1. 就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除
  6. 挂起态→就绪态: 把 一 个 挂 起 状态 的 任 务 恢复的 唯 一 途 径 就 是调 用 vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。

    16.4 任务状态的概念

    FreeRTOS 系统中的每一任务都有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
    就绪( Ready):该任务在就绪列表中, 就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
    运行(Running):该状态表明任务正在执行, 此时它占用处理器, FreeRTOS 调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
    阻塞(Blocked): 如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、 任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
    挂起态(Suspended): 处于挂起态的任务对调度器而言是不可见的, 让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend()函数;而把一个挂起状态的任 恢复的唯 一 途径就是调用 vTaskResume() 或vTaskResumeFromISR()函数

16.5 常用的任务函数详解

16.5.1 任务挂起的函数

1. vTaskSuspend()

挂起指定任务。被挂起的任务绝不会得到 CPU 的使用权,不管该任务具有什么优先级。
挂起后不会参与调度。

  1. #if ( INCLUDE_vTaskSuspend == 1 )
  2. void vTaskSuspend( TaskHandle_t xTaskToSuspend )
  3. {
  4. TCB_t *pxTCB;
  5. /* 进入临界区 */
  6. taskENTER_CRITICAL();
  7. {
  8. /* 如果在此处传递 null,那么它正在被挂起的是正在运行的任务。 */
  9. /* #define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )*/
  10. pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
  11. /* 啥都没干 */
  12. traceTASK_SUSPEND( pxTCB );
  13. /* 从就绪/阻塞列表中删除任务并放入挂起列表中。
  14. 如返回值为0,则说明该任务优先级下,任务就绪表只有它一个任务,删除了该任务
  15. ,就无其他任务*/
  16. if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  17. {
  18. /* 从就绪态中删除这个任务 */
  19. taskRESET_READY_PRIORITY( pxTCB->uxPriority );
  20. }
  21. else
  22. {
  23. mtCOVERAGE_TEST_MARKER();
  24. }
  25. /* 如果任务在等待事件,也从等待事件列表中移除 */
  26. if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
  27. {
  28. ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
  29. }
  30. else
  31. {
  32. mtCOVERAGE_TEST_MARKER();
  33. }
  34. /* 将任务状态添加到挂起列表中 */
  35. vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
  36. }
  37. taskEXIT_CRITICAL();
  38. /* 如果任务调度器正在运行 */
  39. if( xSchedulerRunning != pdFALSE )
  40. {
  41. /* 重置下一个任务的解除阻塞时间。
  42. 重新计算一下还要多长时间执行下一个任务。
  43. 如果下个任务的解锁,刚好是被挂起的那个任务,
  44. 那么变量 NextTaskUnblockTime 就不对了,
  45. 所以要重新从延时列表中获取一下。 */
  46. /* 重置下一个任务的解锁时间,防止下一个解锁任务为刚刚挂起的任务 */
  47. taskENTER_CRITICAL();
  48. {
  49. prvResetNextTaskUnblockTime();
  50. }
  51. taskEXIT_CRITICAL();
  52. }
  53. else
  54. {
  55. mtCOVERAGE_TEST_MARKER();
  56. }
  57. /* 挂起的是正在运行的任务 */
  58. if( pxTCB == pxCurrentTCB )
  59. {
  60. if( xSchedulerRunning != pdFALSE )
  61. {
  62. /* 调度器在运行时,如果这个挂起的任务是当前任务,立即切换任务。 */
  63. configASSERT( uxSchedulerSuspended == 0 );
  64. portYIELD_WITHIN_API();
  65. }
  66. else
  67. {
  68. /* 如果就绪列表的任务数量等于任务总数量,一般不会执行这里,因为至少会有一个空闲任务 */
  69. if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
  70. {
  71. /* 没有其他任务准备就绪,因此将 pxCurrentTCB 设置回 NULL,
  72. 以便在创建下一个任务时 pxCurrentTCB 将被设置为指向它,
  73. 实际上并不会执行到这里 */
  74. pxCurrentTCB = NULL;
  75. }
  76. else
  77. {
  78. /* 有其他任务,则切换到其他任务 切换上下文*/
  79. vTaskSwitchContext();
  80. }
  81. }
  82. }
  83. else
  84. {
  85. mtCOVERAGE_TEST_MARKER();
  86. }
  87. }
  88. #endif /* INCLUDE_vTaskSuspend */

任务可以调用 vTaskSuspend()这个函数来挂起任务自身,但是在挂起自身的时候会进行一次任务上下文切换,需要挂起自身就将 xTaskToSuspend 设置为 NULL 传递进来即可。无论任务是什么状态都可以被挂起,只要调用了 vTaskSuspend()这个函数就会挂起成功,不论是挂起其他任务还是挂起任务自身。
挂起的时候不会删除资源,在恢复的时候恢复原来的状态。使用方法也简单。
任务挂起例子:

  1. static TaskHandle_t LED_Task_Handle = NULL;/* LED 任务句柄 */
  2. static void KEY_Task(void* parameter)
  3. {
  4. while(1) {
  5. if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
  6. /* K1 被按下 */
  7. printf("挂起 LED 任务! \n");
  8. vTaskSuspend(LED_Task_Handle);/* 挂起 LED 任务 */
  9. }
  10. vTaskDelay(20);/* 延时 20 个 tick */
  11. }
  12. }

2. vTaskSuspendAll()

将所有的任务都挂起,其实源码很简单,也很有意思,不管三七二十一将调度器锁定,并且这个函数是可以进行嵌套的,说白了挂起所有任务就是挂起任务调度器。 调度器被挂起后则不能进行上下文切换, 但是中断还是使能的。 当调度器被挂起的时候,如果有中断需要进行上下文切换, 那么这个任务将会被挂起,在调度器恢复之后才执行切换任务。

调度器恢复可以调 用 xTaskResumeAll() 函数,调 用了多少次 的 vTaskSuspendAll() 就要调用多少次xTaskResumeAll()进行恢复

  1. void vTaskSuspendAll( void )
  2. {
  3. /* A critical section is not required as the variable is of type
  4. BaseType_t. Please read Richard Barry's reply in the following link to a
  5. post in the FreeRTOS support forum before reporting this as a bug! -
  6. http://goo.gl/wu4acr */
  7. ++uxSchedulerSuspended;
  8. }

16.5.2 任务恢复函数

1. vTaskResume()

任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。如果被恢复任务在所有就绪态任务中, 处于最高优先级列表的第一位,那么系统将进行任务上下文的切换。

  1. void vTaskResume( TaskHandle_t xTaskToResume )
  2. {
  3. /* 根据 xTaskToResume 获取对应的任务控制块 */
  4. TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;
  5. /* 检查要恢复的任务是否被挂起,如果没被挂起,恢复调用任务没有意义 */
  6. configASSERT( xTaskToResume );
  7. /* 该参数不能为 NULL,同时也无法恢复当前正在执行的任务
  8. 因为当前正在运行的任务不需要恢复
  9. 只能恢复处于挂起态的任务*/
  10. if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) )
  11. {
  12. /* 进入临界区 因为要操作链表 */
  13. taskENTER_CRITICAL();
  14. {
  15. /*判断要恢复的任务是否真的被挂起了,如果被挂起才需要恢复,
  16. 没被挂起那当然也不需要恢复。*/
  17. if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
  18. {
  19. traceTASK_RESUME( pxTCB );
  20. /* 由于我们处于临界区
  21. 即使任务被挂起,我们也可以访问任务的状态列表。
  22. 将要恢复的任务从挂起列表中删除 */
  23. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  24. /* 添加到就绪列表 */
  25. prvAddTaskToReadyList( pxTCB );
  26. /* 如果刚刚恢复的任务优先级比当前任务优先级更高
  27. 则需要进行任务的切换 */
  28. if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
  29. {
  30. /* 因为恢复的任务在当前情况下的优先级最高
  31. 调用 taskYIELD_IF_USING_PREEMPTION()进行一次任务切换*/
  32. taskYIELD_IF_USING_PREEMPTION();
  33. }
  34. else
  35. {
  36. mtCOVERAGE_TEST_MARKER();
  37. }
  38. }
  39. else
  40. {
  41. mtCOVERAGE_TEST_MARKER();
  42. }
  43. }
  44. taskEXIT_CRITICAL();/* 退出 */
  45. }
  46. else
  47. {
  48. mtCOVERAGE_TEST_MARKER();
  49. }
  50. }
  51. #endif /* INCLUDE_vTaskSuspend */

vTaskResume()函数用于恢复挂起的任务。无论任务在挂起时候调用过多少次这个vTaskSuspend()函数,也只需调用一次 vTaskResume ()函数即可将任务恢复运行,当然,无论调用多少次的 vTaskResume()函数,也只在任务是挂起态的时候才进行恢复。
例子:

  1. static TaskHandle_t LED_Task_Handle = NULL;/* LED 任务句柄 */
  2. static void KEY_Task(void* parameter)
  3. {
  4. while (1) {
  5. if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
  6. /* K2 被按下 */
  7. printf("恢复 LED 任务! \n");
  8. vTaskResume(LED_Task_Handle);/* 恢复 LED 任务! */
  9. }
  10. vTaskDelay(20);/* 延时 20 个 tick */
  11. }

2.xTaskResumeFromISR()

xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务,不一样的是 xTaskResumeFromISR() 专 门 用 在 中 断 服 务 程 序 中 。 无 论 通 过 调 用 一 次 或 多 次vTaskSuspend()函数而被挂起的任务,也只需调用一次 xTaskResumeFromISR()函数即可解挂 。
想 使 用 该 函 数 必 须 在 FreeRTOSConfig.h 中 把 INCLUDE_vTaskSuspend 和INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。
注意:任务还没有处于挂起态的时候,调用xTaskResumeFromISR()函数是没有任何意义的

  1. #if ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) )
  2. BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) //xTaskToResume 是恢复指定任务的任务句柄。
  3. {
  4. //定义一个是否需要进行任务切换的变量 xYieldRequired,默认为pdFALSE,
  5. //当任务恢复成功并且需要任务切换的话则重置为 pdTRUE,以表示需要进行任务切换
  6. BaseType_t xYieldRequired = pdFALSE;
  7. //根据 xTaskToResume 任务句柄获取对应的任务控制块
  8. TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;
  9. //定义一个变量 uxSavedInterruptStatus 用于保存关闭中断的状态
  10. UBaseType_t uxSavedInterruptStatus;
  11. //检查要恢复的任务是存在,如果不存在,调用恢复任务函数没有任何意义
  12. configASSERT( xTaskToResume );
  13. portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
  14. // 进入临界区,放置被其它中断打断。
  15. uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
  16. {
  17. // 判断确实是挂起的状态
  18. if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
  19. {
  20. traceTASK_RESUME_FROM_ISR( pxTCB );
  21. //检查可以访问的就绪列表, 检查调度器是否被挂起,如果没有被挂起,则继续执行
  22. if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
  23. {
  24. // 如果刚刚恢复的任务优先级比当前任务优先级更高需要进行一次
  25. // 任务的切换,重置 xYieldRequired = pdTRUE 表示需要进行任务切换
  26. if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
  27. {
  28. xYieldRequired = pdTRUE;
  29. }
  30. else
  31. {
  32. mtCOVERAGE_TEST_MARKER();
  33. }
  34. // 移出挂起
  35. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  36. // 恢复任务
  37. prvAddTaskToReadyList( pxTCB );
  38. }
  39. else
  40. {
  41. /* 无法访问就绪列表,
  42. 因此任务将被添加到待处理的就绪列表中
  43. 直到调度器被恢复再进行任务的处理。 */
  44. vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
  45. }
  46. }
  47. else
  48. {
  49. mtCOVERAGE_TEST_MARKER();
  50. }
  51. }
  52. portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
  53. return xYieldRequired;
  54. }
  55. #endif /* ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) ) */
  1. 使用 xTaskResumeFromISR()的时候有几个需要注意的地方:
    当函数的返回值为 pdTRUE 时: 恢复运行的任务的优先级等于或高于正在运行的任 务 , 表 明 在 中 断 服 务 函 数退出后必须进行一 次上下文切换 , 使 用portYIELD_FROM_ISR()进行上下文切换。当函数的返回值为 pdFALSE 时: 恢复运行的任务的优先级低于当前正在运行的任务,表明在中断服务函数退出后不需要进行上下文切换。
  2. xTaskResumeFromISR() 通常被认为是一个危险的函数,因为它的调用并非是固定的,中断可能随时来来临。所以, xTaskResumeFromISR()不能用于任务和中断间的同步,如果中断恰巧在任务被挂起之前到达,这就会导致一次中断丢失(任务还没有挂起,调用 xTaskResumeFromISR()函数是没有意义的,只能等下一次中断)。这种情况下,可以使用信号量或者任务通知来同步就可以避免这种情况。

例子:

  1. void vAnExampleISR( void )
  2. {
  3. BaseType_t xYieldRequired;
  4. /* 恢复被挂起的任务 */
  5. xYieldRequired = xTaskResumeFromISR( xHandle );
  6. if ( xYieldRequired == pdTRUE ) {
  7. /* 执行上下文切换, ISR 返回的时候将运行另外一个任务 */
  8. portYIELD_FROM_ISR();
  9. }
  10. }

3. xTaskResumeAll()

当调用了 vTaskSuspendAll()函数将调度器挂起,想要恢复调度器的时候我们就需要调用 xTaskResumeAll()函数。

  1. BaseType_t xTaskResumeAll( void )
  2. {
  3. TCB_t *pxTCB = NULL;
  4. BaseType_t xAlreadyYielded = pdFALSE;
  5. /* 如果 uxSchedulerSuspended 为 0,
  6. 则此函数与先前对 vTaskSuspendAll()的调用不匹配,
  7. 不需要调用 xTaskResumeAll()恢复调度器。 */
  8. configASSERT( uxSchedulerSuspended );
  9. /* 调度器在停止的时候,ISR是可以将任务添加到pendlist中去的,就是上面的那个函数在48行做的
  10. 事情,因此当调度器恢复的时候,这个会从pendlist中转换到就绪列表中*/
  11. taskENTER_CRITICAL();
  12. {
  13. // 调用vTaskSuspendAll + 1 这个需要 -1
  14. --uxSchedulerSuspended;
  15. //如果调度器恢复正常工作,也就是调度器没有被挂起,就可以将
  16. //所有待处理的就绪任务从待处理就绪列表 xPendingReadyList 移动到适当的就绪列表中
  17. if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
  18. {
  19. // 判断是否有任务 -> 肯定有啊
  20. if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
  21. {
  22. /* 将任何准备好的任务从待处理就绪列表,移动到相应的就绪列表中。 */
  23. while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
  24. {
  25. pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
  26. ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
  27. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  28. prvAddTaskToReadyList( pxTCB );
  29. /* 如果移动的任务的优先级高于当前任务
  30. 需要进行一次任务的切换
  31. xYieldPending = pdTRUE 表示需要进行任务切换 */
  32. if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
  33. {
  34. xYieldPending = pdTRUE;
  35. }
  36. else
  37. {
  38. mtCOVERAGE_TEST_MARKER();
  39. }
  40. }
  41. if( pxTCB != NULL )
  42. {
  43. /* 在调度器被挂起时,任务被解除阻塞,
  44. 这可能阻止了重新计算下一个解除阻塞时间,
  45. 在这种情况下,重置下一个任务的解除阻塞时间 */
  46. prvResetNextTaskUnblockTime();
  47. }
  48. /* 如果在调度器挂起这段时间产生滴答定时器的计时
  49. 并且在这段时间有任务解除阻塞,由于调度器的挂起导致
  50. 没法切换任务,当恢复调度器的时候应立即处理这些任务
  51. 这样确保了滴答定时器的计数不会滑动,
  52. 并且任何在延时的任务都会在正确的时间恢复。 */
  53. {
  54. UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */
  55. if( uxPendedCounts > ( UBaseType_t ) 0U )
  56. {
  57. do
  58. {
  59. // 调用 xTaskIncrementTick()函数查找是否有待进行切换的任务,如
  60. // 果有则应该进行任务切换
  61. if( xTaskIncrementTick() != pdFALSE )
  62. {
  63. xYieldPending = pdTRUE;
  64. }
  65. else
  66. {
  67. mtCOVERAGE_TEST_MARKER();
  68. }
  69. --uxPendedCounts;
  70. } while( uxPendedCounts > ( UBaseType_t ) 0U );
  71. uxPendedTicks = 0;
  72. }
  73. else
  74. {
  75. mtCOVERAGE_TEST_MARKER();
  76. }
  77. }
  78. if( xYieldPending != pdFALSE )
  79. {
  80. #if( configUSE_PREEMPTION != 0 )
  81. {
  82. xAlreadyYielded = pdTRUE;
  83. }
  84. #endif
  85. // 切换
  86. taskYIELD_IF_USING_PREEMPTION();
  87. }
  88. else
  89. {
  90. mtCOVERAGE_TEST_MARKER();
  91. }
  92. }
  93. }
  94. else
  95. {
  96. mtCOVERAGE_TEST_MARKER();
  97. }
  98. }
  99. taskEXIT_CRITICAL(); // 退出临界区。
  100. return xAlreadyYielded;
  101. }

TaskResumeAll 函 数 的 使 用 方 法 很 简 单 , 但 是 要 注 意 , 调 用 了 多 少 次vTaskSuspendAll()函数就必须同样调用多少次 xTaskResumeAll()函数

16.5.3 任务删除函数vTaskDelete()

vTaskDelete()用于删除一个任务。当一个任务删除另外一个任务时, 形参为要删除任务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。 要想使用该函数必须在FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1,删除的任务将从所有就绪,阻塞,挂起和事件列表中删除

  1. void vTaskDelete( TaskHandle_t xTaskToDelete )
  2. {
  3. TCB_t *pxTCB;
  4. taskENTER_CRITICAL();
  5. {
  6. /* 获取任务控制块,如果 xTaskToDelete 为 null
  7. 则删除任务自身 */
  8. pxTCB = prvGetTCBFromHandle( xTaskToDelete );
  9. /* 将任务从就绪列表中移除 */
  10. if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  11. {
  12. /* 清除任务的就绪优先级变量中的标志位 */
  13. taskRESET_READY_PRIORITY( pxTCB->uxPriority );
  14. }
  15. else
  16. {
  17. mtCOVERAGE_TEST_MARKER();
  18. }
  19. /* 如果当前任务在等待事件,那么将任务从事件列表中移除 */
  20. if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
  21. {
  22. ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
  23. }
  24. else
  25. {
  26. mtCOVERAGE_TEST_MARKER();
  27. }
  28. /* Increment the uxTaskNumber also so kernel aware debuggers can
  29. detect that the task lists need re-generating. This is done before
  30. portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
  31. not return. */
  32. uxTaskNumber++; // 不是很清楚
  33. if( pxTCB == pxCurrentTCB )
  34. {
  35. // 任务正在删除自己,这不能在任务本身完成,因为需要上下文切换到另外的
  36. // 任务,将任务放在结束列表中,空闲任务会检查结束列表并释放掉删除的任务
  37. // 控制块和已删除任务的堆栈的内存
  38. vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
  39. /* 增加 uxDeletedTasksWaitingCleanUp 变量,
  40. 记录有多少个任务需要释放内存
  41. 以便空闲任务知道有一个已删除的任务,然后进行内存释放
  42. 空闲任务会检查结束列表 xTasksWaitingTermination */
  43. ++uxDeletedTasksWaitingCleanUp;
  44. /* 任务删除钩子函数 */
  45. portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
  46. }
  47. else
  48. {
  49. /* 当前任务数减一, uxCurrentNumberOfTasks 是全局变量*/
  50. --uxCurrentNumberOfTasks;
  51. /* 删除任务控制块 */
  52. prvDeleteTCB( pxTCB );
  53. /* 重置下一个任务的解除阻塞时间。重新计算一下
  54. 还要多长时间执行下一个任务,如果下个任务的解锁,
  55. 刚好是被删除的任务,那么这就是不正确的,
  56. 因为删除的任务对调度器而言是不可见的,
  57. 所以调度器是无法对删除的任务进行调度
  58. 所以要重新从延时列表中获取下一个要解除阻塞的任务。
  59. 它是从延时列表的头部来获取的任务 TCB,延时列表是按延时时间排序的*/
  60. prvResetNextTaskUnblockTime();
  61. }
  62. traceTASK_DELETE( pxTCB );
  63. }
  64. taskEXIT_CRITICAL();
  65. /* 如删除的是当前的任务,则需要发起一次任务切换 */
  66. if( xSchedulerRunning != pdFALSE )
  67. {
  68. if( pxTCB == pxCurrentTCB )
  69. {
  70. configASSERT( uxSchedulerSuspended == 0 );
  71. portYIELD_WITHIN_API();
  72. }
  73. else
  74. {
  75. mtCOVERAGE_TEST_MARKER();
  76. }
  77. }
  78. }
  79. #endif /* INCLUDE_vTaskDelete */
  80. /*-----------------------------------------------------------*/

1. prvCheckTasksWaitingTermination()

空闲 任 务 调 用 prvCheckTasksWaitingTermination() 函数进行这些相应操作来删除任务。

  1. static void prvCheckTasksWaitingTermination( void )
  2. {
  3. /* 这个函数是被空闲任务调用的 prvIdleTask */
  4. #if ( INCLUDE_vTaskDelete == 1 )
  5. {
  6. BaseType_t xListIsEmpty;
  7. /* uxDeletedTasksWaitingCleanUp 这个变量的值用于
  8. 记录需要进行内存释放的任务个数,
  9. 防止在空闲任务中过于频繁地调用 vTaskSuspendAll()。 */
  10. while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
  11. {
  12. // 挂起任务调度器
  13. vTaskSuspendAll();
  14. {
  15. /* 检查结束列表中的任务 */
  16. xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
  17. }
  18. // 恢复任务调度器
  19. ( void ) xTaskResumeAll();
  20. // 存在任务需要被删除
  21. if( xListIsEmpty == pdFALSE )
  22. {
  23. TCB_t *pxTCB;
  24. taskENTER_CRITICAL();
  25. {
  26. /* 获取对应任务控制块 */
  27. pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
  28. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  29. /* 将任务从状态列表中删除 */
  30. --uxCurrentNumberOfTasks;
  31. --uxDeletedTasksWaitingCleanUp;
  32. }
  33. taskEXIT_CRITICAL();
  34. /* 删除任务控制块与堆栈 */
  35. prvDeleteTCB( pxTCB );
  36. }
  37. else
  38. {
  39. mtCOVERAGE_TEST_MARKER();
  40. }
  41. }
  42. }
  43. #endif /* INCLUDE_vTaskDelete */
  44. }

删除任务时,只会自动释放内核本身分配给任务的内存。应用程序(而不是内核)分配给任务的内存或任何其他资源必须是删除任务时由应用程序显式释放。怎么理解这句话?就好像在某个任务中我申请了一大块内存,但是没释放就把任务删除,这块内存在任务删除之后不会自动释放的,所以我们应该在删除任务之前就把任务中的这些资源释放掉,然后再进行删除,否则很容易造成内存泄漏,删除任务的使用很简单:

  1. /* 创建一个任务,将创建的任务句柄存储在 DeleteHandle 中*/
  2. TaskHandle_t DeleteHandl
  3. if (xTaskCreate(DeleteTask,
  4. "DeleteTask",
  5. STACK_SIZE,
  6. NULL,
  7. PRIORITY,
  8. &DeleteHandle) != pdPASS )
  9. {
  10. }
  11. void DeleteTask( void )
  12. {
  13. /* 其它代码 */
  14. /* 删除任务本身 */
  15. }
  16. /* 在其他任务删除 DeleteTask 任务 */
  17. vTaskDelete( DeleteHandle );

16.5.4 任务延时函数

1. vTaskDelay()

vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。要想使用 FreeRTOS 中的 vTaskDelay()函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能。

  1. void vTaskDelay( const TickType_t xTicksToDelay )

vTaskDelay()用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU 资源。延时的时长由形参 xTicksToDelay 决定,单位为系统节拍周期, 比如系统的时钟节拍周期为 1ms,那么调用vTaskDelay(1)的延时时间则为1ms。
vTaskDelay()延时是相对性的延时,它指定的延时时间是从调用 vTaskDelay()结束后开始计算的, 经过指定的时间后延时结束。比如 vTaskDelay(100), 从调用 vTaskDelay()结束后,任务进入阻塞状态,经过 100 个系统时钟节拍周期后,任务解除阻塞。因此,vTaskDelay()并不适用与周期性执行任务的场合。此外,其它任务和中断活动, 也会影响到 vTaskDelay()的调用(比如调用前高优先级任务抢占了当前任务),进而影响到任务的下一次执行的时间。

  1. #if ( INCLUDE_vTaskDelay == 1 )
  2. void vTaskDelay( const TickType_t xTicksToDelay )
  3. {
  4. BaseType_t xAlreadyYielded = pdFALSE;
  5. /* 延时时间要大于 0 个 tick 否则会进行强制切换任务 */
  6. if( xTicksToDelay > ( TickType_t ) 0U )
  7. {
  8. configASSERT( uxSchedulerSuspended == 0 );
  9. vTaskSuspendAll();
  10. {
  11. traceTASK_DELAY();
  12. /* A task that is removed from the event list while the
  13. scheduler is suspended will not get placed in the ready
  14. list or removed from the blocked list until the scheduler
  15. is resumed.
  16. This task cannot be in an event list as it is the currently
  17. executing task. */
  18. prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
  19. }
  20. xAlreadyYielded = xTaskResumeAll();
  21. }
  22. else
  23. {
  24. mtCOVERAGE_TEST_MARKER();
  25. }
  26. /* 强制切换任务,将 PendSV 的 bit28 置 1 */
  27. if( xAlreadyYielded == pdFALSE )
  28. {
  29. portYIELD_WITHIN_API();
  30. }
  31. else
  32. {
  33. mtCOVERAGE_TEST_MARKER();
  34. }
  35. }
  36. #endif /* INCLUDE_vTaskDelay */

2. prvAddCurrentTaskToDelayedList();

注意:下面的代码没配置宏的删除了。

  1. static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
  2. {
  3. // xCanBlockIndefinitely:表示是否可以永久阻塞,如果 pdFALSE 表
  4. // 示不允许永久阻塞, 也就是不允许挂起当前任务, 而如果是 pdTRUE,则可以永久阻塞
  5. TickType_t xTimeToWake;
  6. const TickType_t xConstTickCount = xTickCount;
  7. /* 在将任务添加到阻止列表之前,从就绪列表中删除任务 */
  8. if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  9. {
  10. portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
  11. }
  12. else
  13. {
  14. mtCOVERAGE_TEST_MARKER();
  15. }
  16. #if ( INCLUDE_vTaskSuspend == 1 )
  17. {
  18. if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
  19. {
  20. /* 支持挂起,则将当前任务挂起,
  21. 直接将任务添加到挂起列表,而不是延时列表! */
  22. vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
  23. }
  24. else
  25. {
  26. /* 计算唤醒任务的时间 */
  27. xTimeToWake = xConstTickCount + xTicksToWait;
  28. /* 列表项将按唤醒时间顺序插入 */
  29. listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
  30. if( xTimeToWake < xConstTickCount )
  31. {
  32. /* 唤醒时间如果溢出了,则会添加到延时溢出列表中 */
  33. vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  34. }
  35. else
  36. {
  37. /* 没有溢出,添加到延时列表中 */
  38. vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  39. /* 如果进入阻塞状态的任务被放置在被阻止任务列表的头部,
  40. 也就是下一个要唤醒的任务就是当前任务,那么就需要更新
  41. xNextTaskUnblockTime 的值 */
  42. if( xTimeToWake < xNextTaskUnblockTime )
  43. {
  44. xNextTaskUnblockTime = xTimeToWake;
  45. }
  46. else
  47. {
  48. mtCOVERAGE_TEST_MARKER();
  49. }
  50. }
  51. }
  52. }
  53. }
  1. 支 持 挂 起 , 则将当前任务挂起 , 此 操 作必须将INCLUDE_vTaskSuspend 宏定义使能,并且 xCanBlockIndefinitely 为pdTRUE。
  2. 两个延时列表 ```c PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;

PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;

  1. 任务的延时在实际中运用特别多,因为需要暂停一个任务,让任务放弃 CPU,延时结束后再继续运行该任务,如果任务中没有阻塞的话,比该任务优先级低的任务则无法得到CPU 的使用权,就无法运行
  2. <a name="XTL33"></a>
  3. #### 3. vTaskDelayUntil()
  4. FreeRTOS中, 除了相对延时函数,还有绝对延时函数 vTaskDelayUntil(),这个绝对延时常用于较精确的周期运行任务,比如我有一个任务, 希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。
  5. ```c
  6. #if ( INCLUDE_vTaskDelayUntil == 1 )
  7. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement);

要想使用该函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelayUntil 定义为1来使能。
vTaskDelayUntil() 与 vTaskDelay () 一 样都是用来实现任务的周期延时。但vTaskDelay ()的延时是相对的,是不确定的,它的延时是等 vTaskDelay ()调用完毕后开始计算的。 并且 vTaskDelay ()延时的时间到了之后,如果有高优先级的任务或者中断正在执行,被延时阻塞的任务并不会马上解除阻塞,所有每次执行任务的周期并不完全确定。而vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务。 当(*pxPreviousWakeTime +xTimeIncrement)时间到达后, vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,那么任务会立马解除阻塞, 所以说 vTaskDelayUntil()函数的延时是绝对性的。
总结:
vTaskDelay () :只是考虑延时
vTaskDelayUntil():考虑到了周期
vTaskDelayUntil:实现方式是通过程序来不断的更新对应的计数值来达到按照一定频率来运行的。

  1. #if ( INCLUDE_vTaskDelayUntil == 1 )
  2. /*pxPreviousWakeTime:指针,指向一个变量,该变量保存任务最后一次解除阻塞的的时
  3. 刻。第一次使用时,该变量必须初始化为当前时间, 之后这个变量会在 vTaskDelayUntil()
  4. 函数内自动更新。*/
  5. /*xTimeIncrement: 周期循环时间。 当时间等于(*pxPreviousWakeTime +
  6. xTimeIncrement)时,任务解除阻塞。如果不改变参数 xTimeIncrement 的值,调用该函数的
  7. 任务会按照固定频率执行*/
  8. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
  9. {
  10. TickType_t xTimeToWake;
  11. BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
  12. configASSERT( pxPreviousWakeTime );
  13. configASSERT( ( xTimeIncrement > 0U ) );
  14. configASSERT( uxSchedulerSuspended == 0 );
  15. vTaskSuspendAll();
  16. {
  17. /* 获取开始进行延时的时间点 */
  18. const TickType_t xConstTickCount = xTickCount;
  19. /* 计算延时到达的时间,也就是唤醒任务的时间 */
  20. xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
  21. /* pxPreviousWakeTime 中保存的是上次唤醒时间
  22. 唤醒后需要一定时间执行任务主体代码,
  23. 如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/
  24. if( xConstTickCount < *pxPreviousWakeTime )
  25. {
  26. /* 如果唤醒的时间小于上次唤醒时间
  27. 并且唤醒时间大于开始计时的时间
  28. 这样子就是相当于没有溢出
  29. 也就是保了证周期性延时时间大于任务主体代码的执行时间*/
  30. if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
  31. {
  32. xShouldDelay = pdTRUE;
  33. }
  34. else
  35. {
  36. mtCOVERAGE_TEST_MARKER();
  37. }
  38. }
  39. else
  40. {
  41. /* 只是唤醒时间溢出的情况
  42. 或者都没溢出,
  43. 保证了延时时间大于任务主体代码的执行时间*/
  44. if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
  45. {
  46. xShouldDelay = pdTRUE;
  47. }
  48. else
  49. {
  50. mtCOVERAGE_TEST_MARKER();
  51. }
  52. }
  53. /* 更新上一次的唤醒时间 */
  54. *pxPreviousWakeTime = xTimeToWake;
  55. // 说明正常没有溢出
  56. if( xShouldDelay != pdFALSE )
  57. {
  58. traceTASK_DELAY_UNTIL( xTimeToWake );
  59. /* prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间
  60. 而不是唤醒时间,因此减去当前的滴答计数。 */
  61. prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
  62. }
  63. else
  64. {
  65. mtCOVERAGE_TEST_MARKER();
  66. }
  67. }
  68. // 恢复调度器
  69. xAlreadyYielded = xTaskResumeAll();
  70. /* 强制执行一次上下文切换 */
  71. if( xAlreadyYielded == pdFALSE )
  72. {
  73. portYIELD_WITHIN_API();
  74. }
  75. else
  76. {
  77. mtCOVERAGE_TEST_MARKER();
  78. }
  79. }
  80. #endif /* INCLUDE_vTaskDelayUntil */

注意记住下面单词表示的含义:

  • xTimeIncrement:任务周期时间。
  • pxPreviousWakeTime:上一次唤醒任务的时间点。
  • xTimeToWake:本次要唤醒任务的时间点。
  • xConstTickCount:进入延时的时间点。

image.png
只是唤醒时间 xTimeToWake 溢出的情况,或者是 xTickCount 与xTimeToWake 都没溢出的情况,都是符合要求的,因为都保证了周期性延时时间大于任务主体代码的执行时间。
image.png
image.png关键点:无论是溢出还是没有溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。也就是说任务执行的时间必须小于任务周期时间 xTimeIncrement,总不能存在任务周期为 10ms 的任务,其主体代码执行时间为 20ms,这样子根本执行不完任务主体代码。计算的唤醒时间合法后,就将当前任务加入延时列表。

使用示例

  1. void vTaskA( void * pvParameters )
  2. {
  3. /* 用于保存上次时间。调用后系统自动更新 */
  4. static portTickType PreviousWakeTime;
  5. /* 设置延时时间,将时间转为节拍数 */
  6. const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
  7. /* 获取当前系统时间 */
  8. PreviousWakeTime = xTaskGetTickCount();
  9. while (1)
  10. {
  11. /* 调用绝对延时函数,任务时间间隔为 1000 个 tick */
  12. vTaskDelayUntil( &PreviousWakeTime TimeIncrement );
  13. /* 任务执行代码 */
  14. }

注意:在使用的时候要将延时时间转化为系统节拍,在任务主体之前要调用延时函数。任务会先调用 vTaskDelayUntil()使任务进入阻塞态,等到时间到了就从阻塞中解除,然后执行主体代码,任务主体代码执行完毕。会继续调用 vTaskDelayUntil()使任务进入阻塞态,然后就是循环这样子执行。

16.6 任务的设计要点

任务的优先级信息,任务与中断的处理,任务的运行时间、逻辑、状态等都要知道,才能设计出好的系统,所以,在设计的时候需要根据需求制定框架。在设计之初就应该考虑下面几点因素:任务运行的上下文环境、任务的执行时间合理设计。
FreeRTOS中程序运行的上下文包括:

  • 中断服务函数
  • 普通任务
  • 空闲任务

1. 中断服务函数

  1. 中断运行在特权模式
  2. 不允许挂起当前任务、不允许调用阻塞运行的
  3. 建议短小精悍,快进快出
  4. 一般在中断中进行标记、在任务中处理。

2. 任务

注意:如果一个任务中的程序出现了死循环操作(此处的死循环是指没有阻塞机制的任务循环体),那么比这个任务优先级低的任务都将无法执行。因此要避免这种情况。

  1. 一些紧急的任务优先级设置的高一点

3. 空闲任务

  1. 当调用 vTaskStartScheduler()时, 调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。
  2. 可以通过空闲任务的钩子函数添加额外的功能
  3. 会清理一些残留的任务。也就是进行资源的回收
  4. 空闲任务是唯一一个不允许出现阻塞情况的任务

对于空闲任务钩子上挂接的空闲钩子函数,它应该满足以下的条件

  • 永远不会挂起空闲任务;
  • 不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。

4. 任务的执行时间

  1. 任务从开始到结束的时间
  2. 任务的周期

注意:必须考虑任务的时间,一般来说处理时间更短的任务优先级应设置更高一些。

16.7 任务管理实验

创建两个任务,一个是 LED 任务,另一个是按键任务。
LED 任务是显示任务运行的状态,而按键任务是通过检测按键的按下与否来进行对 LED 任务的挂起与恢复。

  1. /* 包含头文件 ----------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
  2. #include "stm32f10x.h"
  3. /* FreeRTOS头文件 */
  4. #include "FreeRTOS.h"
  5. #include "task.h"
  6. /* 开发板硬件bsp头文件 */
  7. #include "bsp_usart.h"
  8. #include "bsp_led.h"
  9. #include "bsp_key.h"
  10. /* 私有类型定义 --------------------------------------------------------------*/
  11. /* 私有宏定义 ----------------------------------------------------------------*/
  12. /* 私有变量 ------------------------------------------------------------------*/
  13. /*
  14. * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
  15. * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
  16. * 这个句柄可以为NULL。
  17. */
  18. /* 创建任务句柄 */
  19. static TaskHandle_t AppTaskCreate_Handle = NULL;
  20. /* LED任务句柄 */
  21. static TaskHandle_t LED_Task_Handle = NULL;
  22. /* LED2任务句柄 */
  23. static TaskHandle_t KEY_Task_Handle = NULL;
  24. /* 扩展变量 ------------------------------------------------------------------*/
  25. /* 私有函数原形 --------------------------------------------------------------*/
  26. static void BSP_Init(void);
  27. static void AppTaskCreate(void); /* 创建任务 */
  28. static void LED_Task(void* parameter); /* LED_Task 任务实现 */
  29. static void KEY_Task(void* parameter); /* KEY_Task 任务实现 */
  30. /* 函数体 --------------------------------------------------------------------*/
  31. /**
  32. * 函数功能: 主函数.
  33. * 输入参数: 无
  34. * 返 回 值: 无
  35. * 说 明: 无
  36. */
  37. int main(void)
  38. {
  39. BaseType_t xReturn = pdPASS;
  40. BSP_Init();
  41. /* 创建AppTaskCreate任务 */
  42. xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
  43. (const char* )"AppTaskCreate", /* 任务名字 */
  44. (uint16_t )512, /* 任务栈大小 */
  45. (void* )NULL, /* 任务入口函数参数 */
  46. (UBaseType_t )1, /* 任务的优先级 */
  47. (TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指针 */
  48. /* 启动任务调度 */
  49. if(pdPASS == xReturn)
  50. vTaskStartScheduler(); /* 启动任务,开启调度 */
  51. else
  52. return -1;
  53. }
  54. /***********************************************************************
  55. * @ 函数名 : AppTaskCreate
  56. * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  57. * @ 参数 : 无
  58. * @ 返回值 : 无
  59. **********************************************************************/
  60. static void AppTaskCreate(void)
  61. {
  62. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  63. taskENTER_CRITICAL(); //进入临界区
  64. /* 创建LED_Task任务 */
  65. xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
  66. (const char* )"LED_Task", /* 任务名字 */
  67. (uint16_t )512, /* 任务栈大小 */
  68. (void* )NULL, /* 任务入口函数参数 */
  69. (UBaseType_t )2, /* 任务的优先级 */
  70. (TaskHandle_t* )&LED_Task_Handle); /* 任务控制块指针 */
  71. if(pdPASS == xReturn)
  72. printf("创建LED1_Task任务成功!\r\n");
  73. /* 创建LED_Task任务 */
  74. xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */
  75. (const char* )"KEY_Task",/* 任务名字 */
  76. (uint16_t )512, /* 任务栈大小 */
  77. (void* )NULL, /* 任务入口函数参数 */
  78. (UBaseType_t )3, /* 任务的优先级 */
  79. (TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */
  80. if(pdPASS == xReturn)
  81. printf("创建KEY_TASK任务成功!\r\n");
  82. vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  83. taskEXIT_CRITICAL(); //退出临界区
  84. }
  85. /**********************************************************************
  86. * @ 函数名 : LED_Task
  87. * @ 功能说明: LED_Task任务主体
  88. * @ 参数 :
  89. * @ 返回值 : 无
  90. ********************************************************************/
  91. static void LED_Task(void* parameter)
  92. {
  93. while (1)
  94. {
  95. LED1_ON;
  96. vTaskDelay(500); /* 延时500个tick */
  97. printf("LED_Task Running,LED1_ON\r\n");
  98. LED1_OFF;
  99. vTaskDelay(500); /* 延时500个tick */
  100. printf("LED_Task Running,LED1_OFF\r\n");
  101. }
  102. }
  103. /**********************************************************************
  104. * @ 函数名 : LED_Task
  105. * @ 功能说明: LED_Task任务主体
  106. * @ 参数 :
  107. * @ 返回值 : 无
  108. ********************************************************************/
  109. static void KEY_TASK(void* parameter)
  110. {
  111. while (1)
  112. {
  113. if ( KEY1_StateRead() == KEY_DOWN)
  114. {
  115. /* KEY1 被按下 */
  116. printf("挂起LED任务");
  117. vTaskSuspend(LED_Task_Handle);
  118. }
  119. if ( KEY2_StateRead() == KEY_DOWN)
  120. {
  121. /* KEY1 被按下 */
  122. printf("恢复LED任务");
  123. vTaskResume(LED_Task_Handle);/* 恢复 LED 任务! */
  124. }
  125. }
  126. }
  127. /***********************************************************************
  128. * @ 函数名 : BSP_Init
  129. * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  130. * @ 参数 :
  131. * @ 返回值 : 无
  132. *********************************************************************/
  133. static void BSP_Init(void)
  134. {
  135. /*
  136. * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
  137. * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
  138. * 都统一用这个优先级分组,千万不要再分组,切忌。
  139. */
  140. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  141. /* LED初始化 */
  142. LED_GPIO_Init();
  143. /* 串口初始化 */
  144. USART_Config();
  145. /* 初始化按键 */
  146. KEY_GPIO_Init();
  147. }