[应用相关] FreeRTOS及其应用,万字长文,基础入门

[复制链接]
8980|83
 楼主| 我只会加减乘除 发表于 2022-5-1 17:39 | 显示全部楼层
4.2 开启调度

当任务创建成功后处于就绪状态(Ready),在就绪态的任务可以参与操作系统的调度。操作系统任务调度器只启动一次,之后就不会再次执行了,FreeRTOS 中启动任务调度器的函数是 vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管理都由FreeRTOS 管理,此时才是真正进入实时操作系统中的第一步。

vTaskStartScheduler开启调度时,顺便会创建空闲任务和定时器任务。

FreeRTOS 为了任务启动和任务切换使用了三个异常:SVC、PendSV 和SysTick。

SVC(系统服务调用,亦简称系统调用)用于任务启动。

PendSV(可挂起系统调用)用于完成任务切换,它是可以像普通的中断一样被挂起的,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV会延迟执行,直到高优先级中断执行完毕,这样产生的PendSV 中断就不会打断其他中断的运行。

SysTick 用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一个优先级,则每次 SysTick 中断,下一个任务将获得一个时间片。

FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。相同优先级的任务采用时间片轮转方式进行调度(也就是分时调度),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:40 | 显示全部楼层
4.3 启动方式

FreeRTOS有两种启动方式,效果一样,看个人喜好。

第一种:main 函数中将硬件初始化, RTOS 系统初始化,所有任务的创建完成,最后一步开启调度。目前看到的几个芯片SDK都是这种方式。

第二种:main 函数中将硬件和 RTOS 系统先初始化好,只创建一个任务后就启动调度器,然后在这个任务里面创建其它应用任务,当所有任务都创建成功后,启动任务再把自己删除。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:41 | 显示全部楼层
4.4 任务创建源码分析

xTaskCreate()创建任务。

  1. 1. BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,  
  2. 2.                         const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */  
  3. 3.                         const configSTACK_DEPTH_TYPE usStackDepth,  
  4. 4.                         void * const pvParameters,  
  5. 5.                         UBaseType_t uxPriority,  
  6. 6.                         TaskHandle_t * const pxCreatedTask )  
  7. 7. {  
  8. 8.     TCB_t * pxNewTCB;  
  9. 9.     BaseType_t xReturn;  
  10. 10.   
  11. 11.     /* If the stack grows down then allocate the stack then the TCB so the stack
  12. 12.      * does not grow into the TCB.  Likewise if the stack grows up then allocate
  13. 13.      * the TCB then the stack. */  
  14. 14.     #if ( portSTACK_GROWTH > 0 )  
  15. 15.         {  
  16. 16.             /**/
  17. 17.         }  
  18. 18.     #else /* portSTACK_GROWTH */  
  19. 19.         {  
  20. 20.             StackType_t * pxStack;  
  21. 21.   
  22. 22.             /* Allocate space for the stack used by the task being created. */  
  23. 23.             pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */  
  24. 24.   
  25. 25.             if( pxStack != NULL )  
  26. 26.             {  
  27. 27.                 /* Allocate space for the TCB. */  
  28. 28.                 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */  
  29. 29.   
  30. 30.                 if( pxNewTCB != NULL )  
  31. 31.                 {  
  32. 32.                     /* Store the stack location in the TCB. */  
  33. 33.                     pxNewTCB->pxStack = pxStack;  
  34. 34.                 }  
  35. 35.                 else  
  36. 36.                 {  
  37. 37.                     /* The stack cannot be used as the TCB was not created.  Free
  38. 38.                      * it again. */  
  39. 39.                     vPortFree( pxStack );  
  40. 40.                 }  
  41. 41.             }  
  42. 42.             else  
  43. 43.             {  
  44. 44.                 pxNewTCB = NULL;  
  45. 45.             }  
  46. 46.         }  
  47. 47.     #endif /* portSTACK_GROWTH */  
  48. 48.   
  49. 49.     if( pxNewTCB != NULL )  
  50. 50.     {  
  51. 51.         #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */  
  52. 52.             {  
  53. 53.                 /* Tasks can be created statically or dynamically, so note this
  54. 54.                  * task was created dynamically in case it is later deleted. */  
  55. 55.                 pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;  
  56. 56.             }  
  57. 57.         #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */  
  58. 58.   
  59. 59.         prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );  
  60. 60.         prvAddNewTaskToReadyList( pxNewTCB ); //将新任务加入到就绪链表候着
  61. 61.         xReturn = pdPASS;  
  62. 62.     }  
  63. 63.     else  
  64. 64.     {  
  65. 65.         xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;  
  66. 66.     }  
  67. 67.   
  68. 68.     return xReturn;  
  69. 69. }

申请任务控制块内存,检查配置参数,初始化,将任务信息加入到就绪链表,等待调度。前面链表部分提到,freeRTOS的任务信息都是使用链表记录,在task.c有

  1. 1. PRIVILEGED_DATA static List_t pxReadyTasksLists[configMAX_PRIORITIES];//就绪
  2. 2. PRIVILEGED_DATA static List_t xDelayedTaskList1;    //延时
  3. 3. PRIVILEGED_DATA static List_t xDelayedTaskList2;
  4. 4. PRIVILEGED_DATA static List_t xPendingReadyList;  //挂起
  5. 5. PRIVILEGED_DATA static List_t xSuspendedTaskList;   //阻塞

分别记录就绪态、阻塞态和挂起的任务,其中阻塞态有2个,是因为特殊考虑,时间溢出 的问题,实际开发单片机项目计时超过24h的可以借鉴。其中pxReadyTasksLists链表数组,其下标就是任务的优先级。

18008626e55c57e5f1.png


 楼主| 我只会加减乘除 发表于 2022-5-1 17:42 | 显示全部楼层

4.5 任务调度源码分析

创建完任务的时候,vTaskStartScheduler开启调度器,空闲任务、定时器任务也是在开启调度函数中实现的。

为什么要空闲任务?因为 FreeRTOS一旦启动,就必须要保证系统中每时每刻都有一个任务处于运行态(Runing),并且空闲任务不可以被挂起与删除,空闲任务的优先级是最低的,以便系统中其他任务能随时抢占空闲任务的 CPU 使用权。这些都是系统必要的东西,也无需自己实现。

  1. 1. void vTaskStartScheduler( void )  
  2. 2. {  
  3. 3.     BaseType_t xReturn;  
  4. 4.   
  5. 5.     /* Add the idle task at the lowest priority. */  
  6. 6.     #if ( configSUPPORT_STATIC_ALLOCATION == 1 )  
  7. 7.         {  
  8. 8.      /***/
  9. 9.         }  
  10. 10.     #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */  
  11. 11.         {  
  12. 12.             /*创建空闲任务*/  
  13. 13.             xReturn = xTaskCreate( prvIdleTask,  
  14. 14.                                    configIDLE_TASK_NAME,  
  15. 15.                                    configMINIMAL_STACK_SIZE,  
  16. 16.                                    ( void * ) NULL,  
  17. 17.                                    portPRIVILEGE_BIT,  //优先级为0
  18. 18.                                    &xIdleTaskHandle );  
  19. 19.         }  
  20. 20.     #endif /* configSUPPORT_STATIC_ALLOCATION */  
  21. 21.   
  22. 22.     #if ( configUSE_TIMERS == 1 )  
  23. 23.         {  
  24. 24.             if( xReturn == pdPASS )  
  25. 25.             {  
  26. 26.                 //创建定时器task,接收开始、结束定时器等命令
  27. 27.                 xReturn = xTimerCreateTimerTask();
  28. 28.             }  
  29. 29.             else  
  30. 30.             {  
  31. 31.                 mtCOVERAGE_TEST_MARKER();  
  32. 32.             }  
  33. 33.         }  
  34. 34.     #endif /* configUSE_TIMERS */  
  35. 35.   
  36. 36.     if( xReturn == pdPASS )  
  37. 37.     {  
  38. 38.         /* freertos_tasks_c_additions_init() should only be called if the user
  39. 39.          * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
  40. 40.          * the only macro called by the function. */  
  41. 41.         #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT  
  42. 42.             {  
  43. 43.                 freertos_tasks_c_additions_init();  
  44. 44.             }  
  45. 45.         #endif  
  46. 46.   
  47. 47.         portDISABLE_INTERRUPTS();  
  48. 48.   
  49. 49.         #if ( configUSE_NEWLIB_REENTRANT == 1 )  
  50. 50.             {  
  51. 51.                 _impure_ptr = &( pxCurrentTCB->xNewLib_reent );  
  52. 52.             }  
  53. 53.         #endif /* configUSE_NEWLIB_REENTRANT */  
  54. 54.   
  55. 55.         xNextTaskUnblockTime = portMAX_DELAY;  
  56. 56.         xSchedulerRunning = pdTRUE;  
  57. 57.         xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;  
  58. 58.   
  59. 59.         portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();  
  60. 60.   
  61. 61.         traceTASK_SWITCHED_IN();  
  62. 62.   
  63. 63.         /* Setting up the timer tick is hardware specific and thus in the
  64. 64.          * portable interface. */  
  65. 65.         if( xPortStartScheduler() != pdFALSE )  
  66. 66.         {  
  67. 67.             /* 系统开始运行 */  
  68. 68.         }  
  69. 69.         else  
  70. 70.         {  
  71. 71.             /* Should only reach here if a task calls xTaskEndScheduler(). */  
  72. 72.         }  
  73. 73.     }  
  74. 74.     else  
  75. 75.     {  
  76. 76.        /*****/
  77. 77. }


 楼主| 我只会加减乘除 发表于 2022-5-1 17:43 | 显示全部楼层
4.6 任务状态切换

FreeRTOS 系统中的每一个任务都有多种运行状态,具体如下:

30686626e562786fa7.png

 任务挂起函数

  1. vTaskSuspend()

挂起指定任务,被挂起的任务绝不会得到 CPU 的使用权

  1. vTaskSuspendAll()

将所有的任务都挂起  任务恢复函数

  1. vTaskResume()
  2. vTaskResume()
  3. xTaskResumeFromISR()

任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。xTaskResumeFromISR() 专门用在中断服务程序中。无论通过调用一次或多次vTaskSuspend()函数而被挂起的任务,也只需调用一次恢复即可解挂 。

 任务删除函数 vTaskDelete()用于删除任务。当一个任务可以删除另外一个任务,形参为要删除任 务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:44 | 显示全部楼层
4.7 任务使用注意点

1、中断服务函数是不允许调用任何会阻塞运行的接口。一般在中断服务函数中只做标记事件的发生,然后通知任务,让对应任务去执行相关处理 。

2、将紧急的处理事件的任务优先级设置偏高一些。

3、空闲任务(idle 任务)是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务,永远不会挂起空闲任务,不应该陷入死循环。

4、创建任务使用的内存不要过多,按需申请。如果浪费太多,后续应用申请大空间可能提示内存不足。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:45 | 显示全部楼层
五、 队列5.1 队列的概念

队列用于任务间通信的数据结构,通过消息队列服务,任务或中断服务将消息放入消息队列中。其他任务或者自身从消息队列中获得消息。实现队列可以在任务与任务间、中断和任务间传递信息。队列操作支持阻塞等待,向已经填满的队列发送数据或者从空队列读出数据,都会导致阻塞,时间自定义。消息队列的运作过程具如下:

96382626e56a6c3805.png


 楼主| 我只会加减乘除 发表于 2022-5-1 17:46 | 显示全部楼层
5.2 队列创建

xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的句柄。队列句柄其实就是一个指向队列数据结构类型的指针。

  1. 1. master_queue = xQueueCreate(50, sizeof(task_message_struct_t));  

创建队列,占用50个单元,每个单元为sizeof(task_message_struct_t)字节,和 malloc比较类似。其最终使用的函数是 xQueueGenericCreate(),后续信号量等也是使用它创建,只是最后的队列类型不同。

申请内存后,xQueueGenericReset再对其进行初始化,队列的结构体xQUEUE成员:

  1. 1. typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */  
  2. 2. {  
  3. 3.     int8_t * pcHead;           /*< Points to the beginning of the queue storage area. */  
  4. 4.     int8_t * pcWriteTo;        /*< Points to the free next place in the storage area. */  
  5. 5.     //类型
  6. 6.     union  
  7. 7.     {  
  8. 8.         QueuePointers_t xQueue;     /*< Data required exclusively when this structure is used as a queue. */  
  9. 9.         SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */  
  10. 10.     } u;  
  11. 11.   
  12. 12.     //当前向队列写数据阻塞的任务列表或者从队列取数阻塞的链表
  13. 13.     List_t xTasksWaitingToSend;  
  14. 14.     List_t xTasksWaitingToReceive;   
  15. 15.   
  16. 16.     //队列里有多少个单元被占用,应用中需要
  17. 17.     volatile UBaseType_t uxMessagesWaiting;
  18. 18.
  19. 19.     UBaseType_t uxLength;                   /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */  
  20. 20.     UBaseType_t uxItemSize;                 /*< The size of each items that the queue will hold. */  
  21. 21.   
  22. 22.  /******/
  23. 23. } xQUEUE;  


 楼主| 我只会加减乘除 发表于 2022-5-1 17:47 | 显示全部楼层
5.3 队列删除

队列删除函数 vQueueDelete()需传入要删除的消息队列的句柄即可,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。实际应用中很少使用。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:48 | 显示全部楼层
5.4 向队列发送消息

任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队,FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的超时时间进行阻塞,消息发送接口很多,最简单的是 xQueueSend(),用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。

  1. BaseType_t xQueueSend(QueueHandle_t xQueue,const void* pvItemToQueue, TickType_t xTicksToWait);

用于向队列尾部发送一个队列消息。

参数

xQueue 队列句柄

pvItemToQueue 指针,指向要发送到队列尾部的队列消息。

xTicksToWait 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期 tick,延时为 portMAX_DELAY 将导致任务挂起(没有超时)。

返回值

消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL。

xQueueSendToBack与xQueueSend完全相同, xQueueSendFromISR()与 xQueueSendToBackFromISR(),带FromISR表示只能在中断中使用,freeRTOS所以带这个后缀的都是这个含义。xQueueSendToFront()和QueueSendToFrontFromISR()用于向队列队首发送一个消息。这些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义。

  1. 1. BaseType_t xQueueGenericSend( QueueHandle_t xQueue,   
  2. 2.                  const void * const pvItemToQueue,   
  3. 3.                          TickType_t xTicksToWait,   
  4. 4.                  const BaseType_t xCopyPosition )  //发送数据到消息队列的位置

一般使用xQueueSend和xQueueSendFromISR,如不确定当前运行的是系统服务,还是中断服务,一般ARM都支持查询中断状态寄存器判断,可以封装一层接口,只管发消息,内部判断是否使用支持中断嵌套的版本,UIS8910就是如此。特殊情况下,如发送网络数据包未收到服务器响应,期望立刻入队再次发送它,可以xQueueSendToFront向队头发消息。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:49 | 显示全部楼层
5.5 从队列读取消息

当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。所有的task主入口while循环体都是按这个执行。例如:


  1. 1. static void track_master_task_main()  
  2. 2. {  
  3. 3.     track_task_message_struct_t queue_item = {0};
  4. 4.     /****/
  5. 5.   
  6. 6.     while(1)  
  7. 7.     {  
  8. 8.         if(xQueueReceive(master_queue, &queue_item, portMAX_DELAY))//阻塞等待
  9. 9.         {  
  10. 10.             track_master_task_msg_handler(&queue_item);  
  11. 11.         }  
  12. 12.     }  
  13. 13. }  


xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。如果不想删除消息的话,就调用 xQueuePeek()函数。xQueueReceiveFromISR()与xQueuePeekFromISR()是中断版本,用于在中断服务程序中接收一个队列消息并把消息。这两个函数只能用于中断,是不带有阻塞机制的,实际项目没有使用。

 楼主| 我只会加减乘除 发表于 2022-5-1 17:50 | 显示全部楼层
5.6 查询队列使用情况

uxQueueMessagesWaiting()查询队列中存储的信息数目,具有中断保护的版本为uxQueueMessagesWaitingFromISR()。查询队列的空闲数目uxQueueSpacesAvailable()。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:51 | 显示全部楼层
5.7 队列使用注意点

使用队列函数需要注意以下几点:

1、中断中必须使用带FromISR后缀的接口;

2、发送或者是接收消息都是以拷贝的方式进行,如果消息内容过于庞大,可以将消息的地址作为消息进行发送、接收。

  1. 1. typedef struct   
  2. 2. {   
  3. 3.     TaskHandle_t src_mod_id;   
  4. 4.     int message_id;   
  5. 5.     int32_t param;   
  6. 6.     union   
  7. 7.     {   
  8. 8.         int32_t result;   
  9. 9.         int32_t socket_id;   
  10. 10.     };   
  11. 11.     void* pvdata;  //大数据使用动态申请内存保存,队列只传递指针  
  12. 12. } track_task_message_struct_t;   

3、队列并不属于任何任务,所有任务都可以向同一队列写入和读出,一个队列可以由多任务或中断读写。

4、队列的深度要结合实际,可以多申请点,前提是每个队列单元尽可能小。

5、队列存在一定限制,在队头没有取出来之前,是无法取出第二个,和STL链表存在差异。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:52 | 显示全部楼层
六、 软件定时器6.1 软件定时器的概念

定时器有硬件定时器和软件定时器之分,硬件定时器是芯片本身提供的定时功能精度高,并且是中断触发方式。软件定时器是由操作系统封装的接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制,其实现的功能与硬件定时器也是类似的。

在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍配置为configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中,一般是100或者1000。根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:53 | 显示全部楼层
6.2 软件定时器创建

软件定时器需先创建才允许使用,动态创建方式是xTimerCreate(),返回一个句柄。软件定时器在创建成功后是处于休眠状态的,没有开始计时运行。FreeRTOS的软件定时器支持单次模式和周期模式。

单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数,之后不再执行。周期模式:定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器停止或删除。

11852626e5860c256f.png

实际项目中使用这种模式对单片机喂狗就比较省事。

  1. 1. TimerHandle_t xTimerCreate( const char * const pcTimerName, //定时器名称
  2. 2.                             const TickType_t xTimerPeriodInTicks,  //定时时间
  3. 3.                             const UBaseType_t uxAutoReload,  //是否自动重载
  4. 4.                             void * const pvTimerID,  //回调函数的参数
  5. 5.                             TimerCallbackFunction_t pxCallbackFunction )  //回调函数


 楼主| 我只会加减乘除 发表于 2022-5-1 17:53 | 显示全部楼层
6.3 软件定时器开启

新创建的定时器没有开始计时启动,可以使用

  1. xTimerStart()、
  2. xTimerReset()、
  3. xTimerStartFromISR() 、xTimerResetFromISR()
  4. xTimerChangePeriod()、xTimerChangePeriodFromISR()

这些函数将其状态转换为活跃态,开始运行。区别:如果定时器设定60秒间隔,已经运行了30秒,reset是将定时器重置为原来设定的时间间隔,也就是重新开始延时60秒。ChangePeriod重新设置计时周期。

73682626e58aba3f27.png



 楼主| 我只会加减乘除 发表于 2022-5-1 17:54 | 显示全部楼层
6.4 软件定时器停止

xTimerStop() 用于停止一个已经启动的软件定时器,xTimerStopFromISR()是中断版本。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:55 | 显示全部楼层
6.5 软件定时器删除

xTimerDelete()用于删除一个已经被创建成功的软件定时器,释放资源,删除之后不能再使用。实际项目中,任务和队列都是按需创建,一直使用,但是定时器不使用的就应该删除,并且删除后一定要将句柄置为NULL。


 楼主| 我只会加减乘除 发表于 2022-5-1 17:56 | 显示全部楼层
6.6 软件定时器源码分析

软件定时器任务是在系统开始调度的时候就被创建:vTaskStartScheduler()—xTimerCreateTimerTask。

  1. 1. BaseType_t xTimerCreateTimerTask( void )  
  2. 2. {  
  3. 3.     BaseType_t xReturn = pdFAIL;  
  4. 4.   
  5. 5.     prvCheckForValidListAndQueue();  //创建定时器任务的队列
  6. 6.   
  7. 7.     if( xTimerQueue != NULL )  
  8. 8.     {  
  9. 9.         #if ( configSUPPORT_STATIC_ALLOCATION == 1 )  
  10. 10.             {  
  11. 11.                       /**/
  12. 12.             }  
  13. 13.         #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */  
  14. 14.             {  
  15. 15.                  //创建定时器任务
  16. 16.                 xReturn = xTaskCreate( prvTimerTask,  
  17. 17.                                        configTIMER_SERVICE_TASK_NAME,  
  18. 18.                                        configTIMER_TASK_STACK_DEPTH,  
  19. 19.                                        NULL,  
  20. 20.                                        ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,  
  21. 21.                                        &xTimerTaskHandle );  
  22. 22.             }  
  23. 23.         #endif /* configSUPPORT_STATIC_ALLOCATION */  
  24. 24.     }  
  25. 25.      /**/
  26. 26.     return xReturn;  
  27. 27. }  

任务创建后,等候命令执行

  1. 1.static portTASK_FUNCTION( prvTimerTask, pvParameters )  
  2. 2. {  
  3. 3.      /**/
  4. 4.   
  5. 5.     for( ; ; )  
  6. 6.     {  
  7. 7.         //最近即将超时的定时器还有多长时间溢出
  8. 8.         xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );  
  9. 9.   
  10. 10.         //阻塞等待,定时器溢出或受到命令,进入下一步(原因不明)
  11. 11.         prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );  
  12. 12.   
  13. 13.         //接收命令并处理,见下面
  14. 14.         prvProcessReceivedCommands();  
  15. 15.     }  
  16. 16. }  

所有定时器接口,都是使用xTimerGenericCommand向队列发送控制命令,命令如下:

  1. 1. #define tmrCOMMAND_START_DONT_TRACE             ( ( BaseType_t ) 0 )  
  2. 2. #define tmrCOMMAND_START                        ( ( BaseType_t ) 1 )  
  3. 3. #define tmrCOMMAND_RESET                        ( ( BaseType_t ) 2 )  
  4. 4. #define tmrCOMMAND_STOP                         ( ( BaseType_t ) 3 )  
  5. 5. #define tmrCOMMAND_CHANGE_PERIOD                ( ( BaseType_t ) 4 )  
  6. 6. #define tmrCOMMAND_DELETE                       ( ( BaseType_t ) 5 )  


 楼主| 我只会加减乘除 发表于 2022-5-1 17:57 | 显示全部楼层
6.7 软件定时器使用注意点

1、查看其他开源代码,对定时器的使用并不多,但实际项目中过多依赖定时器,导致应用逻辑混乱。

2、freeRTOS 的定时器不是无限制的,其根源是接收定时器控制命令消息的队列,默认只有10个单元。

  1. 1. xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );  

定时器过多,可能出现发起定时器命令失败,原因是队列已满。可以将默认的10扩大为15,后续尽量使用信号量来优化代码。

4、软件定时器的回调函数要快进快出,而且不能有任何阻塞任务运行的情况,不能有vTaskDelay() 以及其它能阻塞任务运行的函数。特别说明,其回调函数是在定时器任务执行的,并不是开启定时器的任务。


您需要登录后才可以回帖 登录 | 注册

本版积分规则

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