21.5 软件定时器控制块

xTimerCreate()/xTimerCreateStatic()函数创建一个软件定时器

  1. #if ( configUSE_TIMERS == 1 )
  2. #define tmrNO_DELAY ( TickType_t ) 0U
  3. typedef struct tmrTimerControl
  4. {
  5. //软件定时器名字,一般用于调试的,RTOS 使用定时器是通过其句柄,并不是使用其名字
  6. const char *pcTimerName; /*<< Text name. This is not used by the kernel, it is included simply to make debugging easier. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
  7. //软件定时器列表项,用于插入定时器列表
  8. ListItem_t xTimerListItem; /*<< Standard linked list item as used by all kernel features for event management. */
  9. // 软件定时器的周期, 单位为系统节拍周期,
  10. // pdMS_TO_TICKS()可以把时间单位从 ms 转换为系统节拍周期
  11. TickType_t xTimerPeriodInTicks;/*<< How quickly and often the timer expires. */
  12. //软件定时器是否自动重置, 如果该值为 pdFalse,那么创建的软件
  13. //定时器工作模式是单次模式,否则为周期模式
  14. UBaseType_t uxAutoReload; /*<< Set to pdTRUE if the timer should be automatically restarted once expired. Set to pdFALSE if the timer is, in effect, a one-shot timer. */
  15. //软件定时器 ID, 数字形式。在回调函数里面根据 ID 号来处理不同的软件定时器
  16. void *pvTimerID; /*<< An ID to identify the timer. This allows the timer to be identified when the same callback is used for multiple timers. */
  17. //软件定时器的回调函数, 当定时时间到达的时候就会调用这个函数
  18. TimerCallbackFunction_t pxCallbackFunction; /*<< The function that will be called when the timer expires. */
  19. #if( configUSE_TRACE_FACILITY == 1 )
  20. UBaseType_t uxTimerNumber; /*<< An ID assigned by trace tools such as FreeRTOS+Trace */
  21. #endif
  22. //软件定时器的回调函数, 当定时时间到达的时候就会调用这个函数
  23. #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
  24. uint8_t ucStaticallyAllocated; /*<< Set to pdTRUE if the timer was created statically so no attempt is made to free the memory again if the timer is later deleted. */
  25. #endif
  26. } xTIMER;

21.6 软件定时器函数接口

软件定时器的功能是在定时器任务(或者叫定时器守护任务) 中实现的。 软件定时器的很多 API 函数通过一个名字叫“定时器命令队列” 的队列来给定时器守护任务发送命令。该定时器命令队列由 RTOS 内核提供,且应用程序不能够直接访问,其消息队列的长度由宏 configTIMER_QUEUE_LENGTH 定义。

21.6.1 软件定时器创建函数xTimerCreate()

一种是动态创建软件定时器 xTimerCreate(), 另一种是静态创建方式 xTimerCreateStatic(),
xTimerCreate()用于创建一个软件定时器,并返回一个句柄。
软件定时器在创建成功后是处于休眠状态的, 可以使用以下的API来操作定时器。

  • xTimerStart()
  • xTimerReset()
  • xTimerStartFromISR()
  • xTimerResetFromISR()
  • xTimerChangePeriod()
  • xTimerChangePeriodFromISR()

参数:

  • const char * const pcTimerName:软件定时器名字
  • const TickType_t xTimerPeriodInTicks:软件定时器的周期,单位为系统节拍周期 pdMS_TO_TICKS()可以把时间单位从 ms 转换为系统节拍周期

    1. #ifndef pdMS_TO_TICKS
    2. #define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) ( ( ( TickType_t ) ( xTimeInMs ) * ( TickType_t ) configTICK_RATE_HZ ) / ( TickType_t ) 1000 ) )
    3. #endif
  • const UBaseType_t uxAutoReload:如果 uxAutoReload 设置为 pdTRUE, 那么软件定时器的工作模式就是周期模式

  • void * const pvTimerID:在回调函数里面根据 ID 号来处理不同的软件定时器
  • TimerCallbackFunction_t pxCallbackFunction:软件定时器的回调函数 ```c TimerHandle_t xTimerCreate( const char * const pcTimerName,

    1. const TickType_t xTimerPeriodInTicks,
    2. const UBaseType_t uxAutoReload,
    3. void * const pvTimerID,
    4. TimerCallbackFunction_t pxCallbackFunction ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    {

    1. Timer_t *pxNewTimer;
    2. /* 为这个软件定时器申请一块内存 */
    3. pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) );
    4. if( pxNewTimer != NULL )
    5. { /* 内存申请成功,进行初始化软件定时器 */
    6. prvInitialiseNewTimer( pcTimerName,
    7. xTimerPeriodInTicks,
    8. uxAutoReload,
    9. pvTimerID,
    10. pxCallbackFunction,
    11. pxNewTimer );
    12. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    13. {
    14. pxNewTimer->ucStaticallyAllocated = pdFALSE;
    15. }
    16. #endif /* configSUPPORT_STATIC_ALLOCATION */
    17. }
    18. return pxNewTimer;

    }

endif / configSUPPORT_STATIC_ALLOCATION /

/—————————————————————————————-/

  1. prvInitialiseNewTimer()
  2. ```c
  3. static void prvInitialiseNewTimer( const char * const pcTimerName,
  4. const TickType_t xTimerPeriodInTicks,
  5. const UBaseType_t uxAutoReload,
  6. void * const pvTimerID,
  7. TimerCallbackFunction_t pxCallbackFunction,
  8. Timer_t *pxNewTimer ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
  9. {
  10. /* 断言,判断定时器的周期是否大于 0 */
  11. configASSERT( ( xTimerPeriodInTicks > 0 ) );
  12. if( pxNewTimer != NULL )
  13. {
  14. /* 初始化软件定时器列表与创建软件定时器消息队列 下面有详情*/
  15. prvCheckForValidListAndQueue();
  16. /* 初始化软件定时信息,这些信息保存在软件定时器控制块中 */
  17. pxNewTimer->pcTimerName = pcTimerName;
  18. pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
  19. pxNewTimer->uxAutoReload = uxAutoReload;
  20. pxNewTimer->pvTimerID = pvTimerID;
  21. pxNewTimer->pxCallbackFunction = pxCallbackFunction;
  22. /* 初始化定时器列表项 */
  23. vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
  24. traceTIMER_CREATE( pxNewTimer );
  25. }
  26. }
  27. /*-----------------------------------------------------------*/
  1. 在 prvCheckForValidListAndQueue()函数中系统将初始化软件定时器列表与创建软件定时器消息队列,也叫“定时器命令队列” ,因为在使用软件定时器的时候,用户是无法直接控制软件定时器的,必须通过“定时器命令队列”向软件定时器发送一个命令,软件定时器任务被唤醒就去执行对应的命令操作。

prvCheckForValidListAndQueue()

  1. static void prvCheckForValidListAndQueue( void )
  2. {
  3. /* Check that the list from which active timers are referenced, and the
  4. queue used to communicate with the timer service, have been
  5. initialised. */
  6. /* 检查引用活动计时器的列表,队列是用来和时间服务来进行通讯的 */
  7. taskENTER_CRITICAL();
  8. {
  9. /* 没有创建定时器命令队列 */
  10. if( xTimerQueue == NULL )
  11. {
  12. /* 初始化两个队列 */
  13. vListInitialise( &xActiveTimerList1 );
  14. vListInitialise( &xActiveTimerList2 );
  15. pxCurrentTimerList = &xActiveTimerList1;
  16. pxOverflowTimerList = &xActiveTimerList2;
  17. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  18. {
  19. /* The timer queue is allocated statically in case
  20. configSUPPORT_DYNAMIC_ALLOCATION is 0. */
  21. static StaticQueue_t xStaticTimerQueue;
  22. static uint8_t ucStaticTimerQueueStorage[ configTIMER_QUEUE_LENGTH * sizeof( DaemonTaskMessage_t ) ];
  23. xTimerQueue = xQueueCreateStatic( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ), &( ucStaticTimerQueueStorage[ 0 ] ), &xStaticTimerQueue );
  24. }
  25. #else
  26. {
  27. // 用这个动态创建
  28. xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
  29. }
  30. #endif
  31. #if ( configQUEUE_REGISTRY_SIZE > 0 )
  32. {
  33. if( xTimerQueue != NULL )
  34. {
  35. // 注册到队列(注意消息队列是没有注册到队列的)
  36. vQueueAddToRegistry( xTimerQueue, "TmrQ" );
  37. }
  38. else
  39. {
  40. mtCOVERAGE_TEST_MARKER();
  41. }
  42. }
  43. #endif /* configQUEUE_REGISTRY_SIZE */
  44. }
  45. else
  46. {
  47. mtCOVERAGE_TEST_MARKER();
  48. }
  49. }
  50. taskEXIT_CRITICAL();
  51. }
  52. /*-----------------------------------------------------------*/

使用示例:

  1. static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
  2. static TimerHandle_t Swtmr2_Handle =NULL; /* 软件定时器句柄 */
  3. /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
  4. Swtmr1_Handle=xTimerCreate((const char*)"AutoReloadTimer",
  5. (TickType_t)1000,/* 定时器周期 1000(tick) */
  6. (UBaseType_t)pdTRUE,/* 周期模式 */
  7. (void* )1,/* 为每个计时器分配一个索引的唯一 ID */
  8. (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */
  9. if (Swtmr1_Handle != NULL)
  10. {
  11. /********************************************************************
  12. * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
  13. * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
  14. * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
  15. **********************************************************************/
  16. xTimerStart(Swtmr1_Handle,0); //开启周期定时器
  17. }
  18. /* 单次模式的软件定时器 2,定时器周期 5000(tick)*/
  19. Swtmr2_Handle=xTimerCreate((const char* )"OneShotTimer",
  20. (TickType_t)5000,/* 定时器周期 5000(tick) */
  21. (UBaseType_t )pdFALSE,/* 单次模式 */
  22. (void*)2,/* 为每个计时器分配一个索引的唯一 ID */
  23. (TimerCallbackFunction_t)Swtmr2_Callback);
  24. if (Swtmr2_Handle != NULL)
  25. {
  26. xTimerStart(Swtmr2_Handle,0); //开启单次定时器
  27. }
  28. static void Swtmr1_Callback(void* parameter)
  29. {
  30. // 回调函数
  31. }
  32. static void Swtmr2_Callback(void* parameter)
  33. {
  34. // 回调函数
  35. }

21.6.2 软件定时器启动函数

1. xTimerStart()

软件定时器在创建完成的时候是处于休眠状态的,需要用 FreeRTOS 的相关函数将软件定时器活动起来,而 xTimerStart()函数就是可以让处于休眠的定时器开始工作
在系统开始运行的时候,系统会帮我们自动创建一个软件定时器任务(prvTimerTask),在这个任务中,如果暂时没有运行中的定时器,任务会进入阻塞态等待命令, 而我们的启动函数就是通过“定时器命令队列” 向定时器任务发送一个启动命令,定时器任务获得命令就解除阻塞,然后执行启动软件定时器命令。

  1. #define xTimerStart( xTimer, xTicksToWait ) \
  2. xTimerGenericCommand( ( xTimer ), \
  3. tmrCOMMAND_START, \
  4. ( xTaskGetTickCount() ), \
  5. NULL, \
  6. ( xTicksToWait ) )

xTimerStart()函数就是一个宏定义,真正起作用的是 xTimerGenericCommand()函数。
tmrCOMMAND_START 是软件定时器启动命令,因为现在是要将软件定时器启动,该命令在 timers.h 中有定义。 xCommandID 参数可以指定多个命令,软件定时器操作支持的命令

xTaskGetTickCount() ):获取系统时间
xTicksToWait:用户指定超时阻塞时间, 单位为系统节拍周期(即 tick)。调用xTimerStart()的任务将被锁定在阻塞态, 在软件定时器把启动的命令成功发送到定时器命令队列之前。如果在 FreeRTOS 调度器开启之前调用 xTimerStart(),形参将不起作用。

支持的命令

  1. #define tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR ( ( BaseType_t ) -2 )
  2. #define tmrCOMMAND_EXECUTE_CALLBACK ( ( BaseType_t ) -1 )
  3. #define tmrCOMMAND_START_DONT_TRACE ( ( BaseType_t ) 0 )
  4. #define tmrCOMMAND_START ( ( BaseType_t ) 1 )
  5. #define tmrCOMMAND_RESET ( ( BaseType_t ) 2 )
  6. #define tmrCOMMAND_STOP ( ( BaseType_t ) 3 )
  7. #define tmrCOMMAND_CHANGE_PERIOD ( ( BaseType_t ) 4 )
  8. #define tmrCOMMAND_DELETE ( ( BaseType_t ) 5 )
  9. #define tmrFIRST_FROM_ISR_COMMAND ( ( BaseType_t ) 6 )
  10. #define tmrCOMMAND_START_FROM_ISR ( ( BaseType_t ) 6 )
  11. #define tmrCOMMAND_RESET_FROM_ISR ( ( BaseType_t ) 7 )
  12. #define tmrCOMMAND_STOP_FROM_ISR ( ( BaseType_t ) 8 )
  13. #define tmrCOMMAND_CHANGE_PERIOD_FROM_ISR ( ( BaseType_t ) 9 )

xTimerGenericCommand()源码

  1. BaseType_t xTimerGenericCommand( TimerHandle_t xTimer,
  2. const BaseType_t xCommandID,
  3. const TickType_t xOptionalValue,
  4. BaseType_t * const pxHigherPriorityTaskWoken,
  5. const TickType_t xTicksToWait )
  6. {
  7. BaseType_t xReturn = pdFAIL;
  8. DaemonTaskMessage_t xMessage;
  9. configASSERT( xTimer );
  10. /* 发送命令给定时器任务 */
  11. if( xTimerQueue != NULL )
  12. {
  13. /* 要发送的命令信息,包含命令
  14. 命令的数值(比如可以表示当前系统时间、要修改的定时器周期等)
  15. 以及要处理的软件定时器句柄 */
  16. xMessage.xMessageID = xCommandID; // 命令
  17. xMessage.u.xTimerParameters.xMessageValue = xOptionalValue; // 系统时间
  18. xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer; // 句柄
  19. /* 命令是在任务中发出的,不是在中断 */
  20. if( xCommandID < tmrFIRST_FROM_ISR_COMMAND )
  21. {
  22. /* 如果调度器已经运行了,就根据用户指定超时时间发送 */
  23. if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING )
  24. {
  25. // 将任务推送到定时器的队列
  26. xReturn = xQueueSendToBack( xTimerQueue,
  27. &xMessage,
  28. xTicksToWait );
  29. }
  30. else
  31. {
  32. /* 如果调度器还未运行,发送就行了,不需要阻塞 */
  33. xReturn = xQueueSendToBack( xTimerQueue,
  34. &xMessage,
  35. tmrNO_DELAY );
  36. }
  37. }
  38. else /* 命令是在中断中发出的 */
  39. {
  40. /* 调用从中断向消息队列发送消息的函数 */
  41. xReturn = xQueueSendToBackFromISR( xTimerQueue,
  42. &xMessage,
  43. pxHigherPriorityTaskWoken );
  44. }
  45. traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
  46. }
  47. else
  48. {
  49. mtCOVERAGE_TEST_MARKER();
  50. }
  51. return xReturn;
  52. }

2. xTimerStartFromISR()

xTimerStartFromISR()是函数 xTimerStart()的中断版本, 用于启动一个先前由函数 xTimerCreate() / xTimerCreateStatic()创建的软件定时器。
image.png

使用示例:

  1. /* 这个方案假定软件定时器 xBacklightTimer 已经创建,
  2. 定时周期为 5s,执行次数为一次,即定时时间到了之后
  3. 就进入休眠态。
  4. 程序说明:当按键按下,打开液晶背光,启动软件定时器,5s 时间到,关掉液晶背光
  5. */
  6. /* 软件定时器回调函数 */
  7. void vBacklightTimerCallback( TimerHandle_t pxTimer )
  8. {
  9. /* 关掉液晶背光 */
  10. vSetBacklightState( BACKLIGHT_OFF );
  11. }
  12. /* 按键中断服务程序 */
  13. void vKeyPressEventInterruptHandler( void )
  14. {
  15. BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  16. /* 确保液晶背光已经打开 */
  17. vSetBacklightState( BACKLIGHT_ON );
  18. /* 启动软件定时器 */
  19. if ( xTimerStartFromISR( xBacklightTimer,
  20. &xHigherPriorityTaskWoken ) != pdPASS ) {
  21. /* 软件定时器开启命令没有成功执行 */
  22. }
  23. /* ...执行其他的按键相关的功能代码 */
  24. if ( xHigherPriorityTaskWoken != pdFALSE ) {
  25. /* 执行上下文切换 */
  26. }
  27. }

21.6.3 软件定时器停止函数

1. xTimerStop()

xTimerStop() 用于停止一个已经启动的软件定时器, 该函数的实现也是通过“定时器命令队列”发送一个停止命令给软件定时器任务,从而唤醒软件定时器任务去将定时器停止。
image.png
使用示例:

  1. static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
  2. /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
  3. Swtmr1_Handle=xTimerCreate((const char* )"AutoReloadTimer",
  4. (TickType_t )1000,/* 定时器周期 1000(tick) */
  5. (UBaseType_t )pdTRUE,/* 周期模式 */
  6. (void*)1,/* 为每个计时器分配一个索引的唯一 ID */
  7. (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */
  8. if (Swtmr1_Handle != NULL)
  9. {
  10. /********************************************************************
  11. * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
  12. * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
  13. * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
  14. *******************************************************************/
  15. xTimerStart(Swtmr1_Handle,0); //开启周期定时器
  16. }
  17. static void test_task(void* parameter)
  18. {
  19. while (1) {
  20. /* 用户自己实现任务代码 */
  21. xTimerStop(Swtmr1_Handle,0); //停止定时器
  22. }
  23. }

2.xTimerStopFromISR()

中断版本:
image.png
示例:

  1. /* 这个方案假定软件定时器 xTimer 已经创建且启动
  2. 当中断发生时,停止软件定时器 */
  3. /* 停止软件定时器的中断服务函数*/
  4. void vAnExampleInterruptServiceRoutine( void )
  5. {
  6. BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  7. if (xTimerStopFromISR(xTimer,&xHigherPriorityTaskWoken)!=pdPASS ) {
  8. /* 软件定时器停止命令没有成功执行 */
  9. }
  10. if ( xHigherPriorityTaskWoken != pdFALSE ) {
  11. /* 执行上下文切换 */
  12. }
  13. }

21.6.4 软件定时器任务

软件定时器回调函数运行的上下文环境是任务,那么软件定时器任务是在干什么的呢?如何创建的呢?下面跟我一步步来分析软件定时器的工作过程。
软件定时器任务是在系统开始调度(vTaskStartScheduler()函数) 的时候就被创建的,前提 是将宏定 义 configUSE_TIMERS 开启。

  1. #if ( configUSE_TIMERS == 1 )
  2. {
  3. if( xReturn == pdPASS )
  4. {
  5. xReturn = xTimerCreateTimerTask();
  6. }
  7. else
  8. {
  9. mtCOVERAGE_TEST_MARKER();
  10. }
  11. }
  12. #endif /* configUSE_TIMERS */

xTimerCreateTimerTask()函数:

  1. BaseType_t xTimerCreateTimerTask( void )
  2. {
  3. BaseType_t xReturn = pdFAIL;
  4. prvCheckForValidListAndQueue();
  5. if( xTimerQueue != NULL ) /* 静态创建任务 */
  6. {
  7. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  8. {
  9. StaticTask_t *pxTimerTaskTCBBuffer = NULL;
  10. StackType_t *pxTimerTaskStackBuffer = NULL;
  11. uint32_t ulTimerTaskStackSize;
  12. vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
  13. xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
  14. "Tmr Svc",
  15. ulTimerTaskStackSize,
  16. NULL,
  17. ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
  18. pxTimerTaskStackBuffer,
  19. pxTimerTaskTCBBuffer );
  20. if( xTimerTaskHandle != NULL )
  21. {
  22. xReturn = pdPASS;
  23. }
  24. }
  25. #else /* 动态创建任务 */
  26. {
  27. xReturn = xTaskCreate( prvTimerTask,
  28. "Tmr Svc",
  29. configTIMER_TASK_STACK_DEPTH,
  30. NULL,
  31. ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
  32. &xTimerTaskHandle );
  33. }
  34. #endif /* configSUPPORT_STATIC_ALLOCATION */
  35. }
  36. else
  37. {
  38. mtCOVERAGE_TEST_MARKER();
  39. }
  40. configASSERT( xReturn );
  41. return xReturn;
  42. }

系统调用 xTaskCreate()函数创建了一个软件定时器任务,任务的入口函数是 prvTimerTask, 任务的优先级是 configTIMER_TASK_PRIORITY。任务是 prvTimerTask() ,主要是在这个任务里面干活。

prvTimerTask() 主要的任务

  1. static void prvTimerTask( void *pvParameters )
  2. {
  3. TickType_t xNextExpireTime;
  4. BaseType_t xListWasEmpty;
  5. /* Just to avoid compiler warnings. */
  6. ( void ) pvParameters;
  7. for( ;; )
  8. {
  9. /* 获取下一个要到期的软件定时器的时间 */
  10. // 这个函数猜测:选取第一个要到期的任务
  11. xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty ); //(1)
  12. /* 处理定时器或者将任务阻塞到下一个到期的软件定时器时间 */
  13. prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty ); //(2)
  14. /* 读取“定时器命令队列”,处理相应命令 */
  15. prvProcessReceivedCommands(); //3
  16. }
  17. }
  18. /*-----------------------------------------------------------*/
  1. 在这个函数中:
  2. xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty ); //(1)
  3. 这个函数用来判断是否有任务到期了。
  4. prvProcessTimerOrBlockTask
  5. 这个函数判断了是否溢出,如果溢出就切换列表。然后进入延时等待唤醒
  6. prvProcessReceivedCommands
  7. 这个函数主要是用来接收队列里面的消息,然后执行对应的命令

软件定时器任务的处理很简单,如果当前有软件定时器在运行,那么它大部分的时间都在等待定时器到期时间的到来,或者在等待对软件定时器操作的命令,而如果没有软件定时器在运行,那定时器任务的绝大部分时间都在阻塞中等待定时器的操作命令。

  1. 获取下一个要到期的软件定时器的时间,因为软件定时器是由定时器列表维护的,并且按照到期的时间进行升序排列,只需获取软件定时器列表中的第一个定时器到期时间就是下一个要到期的时间。
  2. 处理定时器或者将任务阻塞到下一个到期的软件定时器时间,因为系统时间节拍随着系统的运行可能会溢出,那么就需要处理溢出的情况,如果没有溢出,那么就等待下一个定时器到期时间的到来。该函数每次调用都会记录节拍值, 下一次调用,通过比较相邻两次调用的值判断节拍计数器是否溢出过。当节拍计数器溢出,需要处理掉当前定时器列表上的定时器(因为这条定时器列表上的定时器都已经溢出了),然后切换定时器列表。

软件定时器是一个任务,在下一个定时器到了之前的这段时间,系统要把任务状态转移为阻塞态,让其他的任务能正常运行,这样子就使得系统的资源能充分利用。

prvTimerTask
prvProcessTimerOrBlockTask() xListWasEmpty

  1. static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime,
  2. BaseType_t xListWasEmpty )
  3. {
  4. TickType_t xTimeNow;
  5. BaseType_t xTimerListsWereSwitched;
  6. vTaskSuspendAll();
  7. {
  8. // 获取当前系统时间节拍并判断系统节拍计数是否溢出
  9. // 如果是, 那么就处理当前列表上的定时器,并切换定时器列表
  10. xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
  11. // 系统节拍计数器没有溢出
  12. if( xTimerListsWereSwitched == pdFALSE )
  13. {
  14. // 判断是否有定时器是否到期
  15. // 定时器列表非空并且定时器的时间已比当前时间小,说明定时器到期了
  16. if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
  17. {
  18. // 恢复调度器
  19. ( void ) xTaskResumeAll();
  20. //执行相应定时器的回调函数
  21. // 对于需要自动重载的定时器, 更新下一次溢出时间, 插回列表
  22. prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
  23. }
  24. else
  25. {
  26. // 当前定时器列表中没有定时器
  27. if( xListWasEmpty != pdFALSE )
  28. {
  29. //发生这种情况的可能是系统节拍计数器溢出了,
  30. //定时器被添加到溢出列表中,所以判断定时器溢出列表上是否有定时
  31. xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
  32. }
  33. // 定时器定时时间还没到,将当前任务挂起,
  34. // 直到定时器到期才唤醒或者收到命令的时候唤醒
  35. vQueueWaitForMessageRestricted( xTimerQueue,
  36. ( xNextExpireTime - xTimeNow ),
  37. xListWasEmpty );
  38. // 恢复调度器,看看是否有任务需要切换,如果有则进行任务切换
  39. // 疑问:这里判是false不就是不需要任务切换,但是下面又进行了切换
  40. if( xTaskResumeAll() == pdFALSE )
  41. {
  42. // 进行任务切换
  43. portYIELD_WITHIN_API();
  44. }
  45. else
  46. {
  47. mtCOVERAGE_TEST_MARKER();
  48. }
  49. }
  50. }
  51. else
  52. {
  53. ( void ) xTaskResumeAll();
  54. }
  55. }
  56. }
  57. /*-----------------------------------------------------------*/

prvProcessTimerOrBlockTask
prvSampleTimeNow()

  1. static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )
  2. {
  3. TickType_t xTimeNow;
  4. // 定义一个静态变量 记录上一次调用时系统时间节拍值
  5. PRIVILEGED_DATA static TickType_t xLastTime = ( TickType_t ) 0U; /*lint !e956 Variable is only accessible to one task. */
  6. //获取当前系统时间节拍
  7. xTimeNow = xTaskGetTickCount();
  8. //判断是否溢出了
  9. //当前系统时间节拍比上一次调用时间节拍的值小,这种情况是溢出的情况
  10. if( xTimeNow < xLastTime )
  11. {
  12. // 发生溢出, 处理当前定时器列表上所有定时器并切换定时器列表
  13. prvSwitchTimerLists();
  14. *pxTimerListsWereSwitched = pdTRUE;
  15. }
  16. else
  17. {
  18. *pxTimerListsWereSwitched = pdFALSE;
  19. }
  20. // 更新本次系统时间节拍
  21. xLastTime = xTimeNow;
  22. return xTimeNow;
  23. }
  24. /*-----------------------------------------------------------*/


prvTimerTask
读取“定时器命令队列”,处理相应命令,
prvProcessReceivedCommands 有删减 删除了配置中断中要设置的一些任务(譬如事件,在中断中不进行判断,而是设置任务到TIMER进程去处理事件)

  1. static void prvProcessReceivedCommands( void )
  2. {
  3. DaemonTaskMessage_t xMessage;
  4. Timer_t *pxTimer;
  5. BaseType_t xTimerListsWereSwitched, xResult;
  6. TickType_t xTimeNow;
  7. while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL ) /*lint !e603 xMessage does not have to be initialised as it is passed out, not in, and it is not used unless xQueueReceive() returns pdTRUE. */
  8. {
  9. /* 判断定时器命令是否有效 */
  10. if( xMessage.xMessageID >= ( BaseType_t ) 0 )
  11. {
  12. /* 获取定时器消息,获取命令指定处理的定时器, */
  13. pxTimer = xMessage.u.xTimerParameters.pxTimer;
  14. /* 如果定时器在列表中,将定时器移除 */
  15. if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE )
  16. {
  17. ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
  18. }
  19. else
  20. {
  21. mtCOVERAGE_TEST_MARKER();
  22. }
  23. traceTIMER_COMMAND_RECEIVED( pxTimer, xMessage.xMessageID, xMessage.u.xTimerParameters.xMessageValue );
  24. // 判断节拍计数器是否溢出过,如果有就处理并切换定时器列表
  25. // 因为下面的操作可能有新定时器项插入确保定时器列表对应
  26. xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
  27. switch( xMessage.xMessageID )
  28. {
  29. case tmrCOMMAND_START :
  30. case tmrCOMMAND_START_FROM_ISR :
  31. case tmrCOMMAND_RESET :
  32. case tmrCOMMAND_RESET_FROM_ISR :
  33. case tmrCOMMAND_START_DONT_TRACE :
  34. // 以上的命令都是让定时器启动
  35. // 求出定时器到期时间并插入到定时器列表中
  36. if( prvInsertTimerInActiveList( pxTimer,
  37. xMessage.u.xTimerParameters.xMessageValue
  38. + pxTimer->xTimerPeriodInTicks,
  39. xTimeNow,
  40. xMessage.u.xTimerParameters.xMessageValue ) != pdFALSE )
  41. {
  42. // 该定时器已经溢出赶紧执行其回调函数
  43. pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
  44. traceTIMER_EXPIRED( pxTimer );
  45. // 如果定时器是重载定时器,就重新启动
  46. if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
  47. {
  48. xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, NULL, tmrNO_DELAY );
  49. configASSERT( xResult );
  50. ( void ) xResult;
  51. }
  52. else
  53. {
  54. mtCOVERAGE_TEST_MARKER();
  55. }
  56. }
  57. else
  58. {
  59. mtCOVERAGE_TEST_MARKER();
  60. }
  61. break;
  62. case tmrCOMMAND_STOP :
  63. case tmrCOMMAND_STOP_FROM_ISR :
  64. break;
  65. // 如果命令是停止定时器,那就将定时器移除,
  66. // 在开始的时候已经从定时器列表移除,
  67. // 此处就不需要做其他操作
  68. case tmrCOMMAND_CHANGE_PERIOD :
  69. case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
  70. // 更新定时器配置
  71. // 插入到定时器列表,也重新启动了定时器
  72. pxTimer->xTimerPeriodInTicks = xMessage.u.xTimerParameters.xMessageValue;
  73. configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );
  74. ( void ) prvInsertTimerInActiveList( pxTimer, ( xTimeNow + pxTimer->xTimerPeriodInTicks ), xTimeNow, xTimeNow );
  75. break;
  76. case tmrCOMMAND_DELETE :
  77. // 删除定时器
  78. #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
  79. {
  80. vPortFree( pxTimer );
  81. }
  82. break;
  83. default :
  84. break;
  85. }
  86. }
  87. }
  88. }
  89. /*-----------------------------------------------------------*/

21.6.5 软件定时器删除函数 xTimerDelete()

TimerDelete()用于删除一个已经被创建成功的软件定时器, 删除之后就无法使用该定时器, 并且定时器相应的资源也会被系统回收释放。
image.png
从软件定时器删除函数 xTimerDelete()的原型可以看出, 删除一个软件定时器也是在软件定时器任务中删除, 调用 xTimerDelete()将删除软件定时器的命令发送给软件定时器任务,软件定时器任务在接收到删除的命令之后就进行删除操作, 该函数的使用方法很简单。

  1. static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
  2. /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
  3. Swtmr1_Handle=xTimerCreate((const char* )"AutoReloadTimer",
  4. (TickType_t )1000,/* 定时器周期 1000(tick) */
  5. (UBaseType_t)pdTRUE,/* 周期模式 */
  6. (void* )1,/* 为每个计时器分配一个索引的唯一 ID */
  7. (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */
  8. if (Swtmr1_Handle != NULL)
  9. {
  10. xTimerStart(Swtmr1_Handle,0);
  11. }
  12. static void test_task(void* parameter)
  13. {
  14. while (1) {
  15. /* 用户自己实现任务代码 */
  16. xTimerDelete(Swtmr1_Handle,0); //删除软件定时器
  17. }
  18. }