• FreeRTOS个人笔记-软件定时器


    根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

    配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

    软件定时器

    定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。
    定时器有硬件定时器和软件定时器之分:
    硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。
    硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。

    软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。

    使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;
    而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数),在回调函数中处理信息。

    注意:软件定时器回调函数的上下文是任务。软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。定时精度与系统时钟的周期有关。
    一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况(软件定时器回调函数的上下文环境是任务),比如 vTaskDelay() 以及其它能阻塞任务运行的函数,两次触发回调函数的时间间隔 xTimerPeriodInTicks 叫定时器的定时周期。

    FreeRTOS 操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务。 
    FreeRTOS 软件定时器功能上支持:
     裁剪:能通过宏关闭软件定时器功能。
     软件定时器创建。
     软件定时器启动。
     软件定时器停止。
     软件定时器复位。
     软件定时器删除。
    FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。

    单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
    周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

    FreeRTOS 通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软件定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。
    prvTimerTask 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h中的宏定义 configUSE_TIMERS 设置为 1 ,将相关代码编译进来,才能正常使用软件定时器相关功能。 

    应用场景

    在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器代替硬件定时器任务。 
    但需要注意的是软件定时器的精度是无法和硬件定时器相比的, 而且在软件定时器的定时过程中是极有可能被其它中断所打断,因为软件定时器的执行上下文环境是任务。
    所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助型的任务。

    软件定时器的精度

    在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000。
    那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是10ms, 20ms, 100ms 等,而不能取值为 15ms。
    由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。

    运作机制

    软件定时器是可选的系统资源,在创建定时器的时候会分配一块内存空间。
    当用户创建并启动一个软件定时器时, FreeRTOS 会根据当前系统时间及用户设置的定时确定该定时器唤醒时间,并将该定时器控制块挂入软件定时器列表, 
    FreeRTOS 中采用两个定时器列表维护软件定时器, pxCurrentTimerList 与 pxOverflowTimerList 是列表指针,在初始化的时候分别指向 xActiveTimerList1 与 xActiveTimerList2。

    1. PRIVILEGED_DATA static List_t xActiveTimerList1;
    2. PRIVILEGED_DATA static List_t xActiveTimerList2;
    3. PRIVILEGED_DATA static List_t *pxCurrentTimerList;
    4. PRIVILEGED_DATA static List_t *pxOverflowTimerList;

    pxCurrentTimerList:系统新创建并激活的定时器都会以超时时间升序的方式插入到 pxCurrentTimerList 列表中。
    系统在定时器任务中扫描 pxCurrentTimerList 中的第一个定时器,看是否已超时,若已经超时了则调用软件定时器回调函数。
    否则将定时器任务挂起,因为定时时间是升序插入软件定时器列表的,列表中第一个定时器的定时时间都还没到的话,那后面的定时器定时时间自然没到。
    pxOverflowTimerList 列表是在软件定时器溢出的时候使用, 作用与 pxCurrentTimerList 一致。

    同时,FreeRTOS 的软件定时器还有采用消息队列进行通信,利用“定时器命令队列”向软件定时器任务发送一些命令,任务在接收到命令就会去处理命令对应的程序,比如启动定时器,停止定时器等。
    假如定时器任务处于阻塞状态,我们又需要马上再添加一个软件定时器的话,就是采用这种消息队列命令的方式进行添加,才能唤醒处于等待状态的定时器任务,并且在任务中将新添加的软件定时器添加到软件定时器列表中,
    所以,在定时器启动函数中,FreeRTOS 是采用队列的方式发送一个消息给软件定时器任务,任务被唤醒从而执行接收到的命令。

    例如:系统当前时间 xTimeNow 值为 0,注意: xTimeNow 其实是一个局部变量, 是根据 xTaskGetTickCount()函数获取的,实际它的值就是全局变量 xTickCount 的值,下文都采用它表示当前系统时间。 
    在当前系统中已经创建并启动了 1 个定时器 Timer1;系统继续运行,当系统的时间 xTimeNow 为 20 的时候,用户创建并且启动一个定时时间为 100 的定时器 Timer2,
    此时 Timer2 的溢出时间 xTicksToWait 就为定时时间 + 系统当前时间(100+20=120),然后将 Timer2 按 xTicksToWait 升序插入软件定时器列表中;
    假设当前系统时间 xTimeNow 为 40 的时候,用户创建并且启动了一个定时时间为 50 的定时器Timer3 , 那么此时 Timer3 的溢出时间 xTicksToWait 就为 40+50=90 , 
    同样安装 xTicksToWait 的数值升序插入软件定时器列表中。同理创建并且启动在已有的两个定时器中间的定时器也是一样的。

    系统在不断运行,而 xTimeNow(xTickCount)随着 SysTick 的触发一直在增长(每一次硬件定时器中断来临,xTimeNow 变量会加 1),
    在软件定时器任务运行的时候会获取下一个要唤醒的定时器,比较当前系统时间xTimeNow 是否大于或等于下一个定时器唤醒时间 xTicksToWait。
    若大于则表示已经超时,定时器任务将会调用对应定时器的回调函数,否则将软件定时器任务挂起,直至下一个要唤醒的软件定时器时间到来或者接收到命令消息。

    在创建定时器 Timer1 并且启动后,假如系统经过了 50 个 tick, xTimeNow 从 0 增长到 50,与 Timer1 的 xTicksToWait 值相等,
    这时会触发与 Timer1 对应的回调函数,从而转到回调函数中执行用户代码,同时将 Timer1 从软件定时器列表删除。
    如果软件定时器是周期性的,那么系统会根据 Timer1 下一次唤醒时间重新将 Timer1 添加到软件定时器列表中,按照 xTicksToWait 的升序进行排列。
    同理,在 xTimeNow=40 的时候创建的 Timer3,在经过 130 个 tick 后(此时系统时间 xTimeNow 是 40, 130 个 tick 就是系统时间 xTimeNow 为 170 的时候),与 Timer3 定时器对应的回调函数会被触发,
    接着将 Timer3 从软件定时器列表中删除,如果是周期性的定时器,还会按照 xTicksToWait 升序重新添加到软件定时器列表中。

    使用软件定时器时候要注意以下几点:
     软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的 API 接口,在回调函数中也绝对不允许出现死循环。
     软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为 configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级。
     创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
     定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。

    软件定时器控制块

    1. typedef struct tmrTimerControl
    2. {
    3. const char *pcTimerName; //软件定时器名称,一般用于调试,RTOS 使用定时器是通过其句柄,并不是使用其名称
    4. ListItem_t xTimerListItem; //软件定时器列表项,用于插入定时器列表
    5. TickType_t xTimerPeriodInTicks; //软件定时器的周期,单位为系统节拍周期(即 tick),pdMS_TO_TICKS()可以把时间单位从 ms 转换为系统节拍周期
    6. UBaseType_t uxAutoReload; //软件定时器是否自动重置,pdFalse,单次模式。pdTRUE,周期模式。
    7. void *pvTimerID; //软件定时器 ID,数字形式。该 ID 典型的用法是当一个回调函数分配给一个或者多个软件定时器时,在回调函数里面根据 ID 号来处理不同的软件定时器
    8. TimerCallbackFunction_t pxCallbackFunction; //软件定时器的回调函数, 当定时时间到达的时候就会调用这个函数
    9. #if( configUSE_TRACE_FACILITY == 1 )
    10. UBaseType_t uxTimerNumber;
    11. #endif
    12. #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
    13. uint8_t ucStaticallyAllocated; //标记定时器使用的内存,删除时判断是否需要释放内存
    14. #endif
    15. } xTIMER;

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

    创建软件定时器

    1. #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    2. TimerHandle_t xTimerCreate( const char * const pcTimerName, //软件定时器名称
    3. const TickType_t xTimerPeriodInTicks, //软件定时器的周期,单位为系统节拍周期(即 tick),用法如pdMS_TO_TICKS(500)
    4. const UBaseType_t uxAutoReload, //pdFalse,单次模式。pdTRUE,周期模式。
    5. void * const pvTimerID, //软件定时器 ID,数字形式。
    6. TimerCallbackFunction_t pxCallbackFunction ) //软件定时器的回调函数
    7. {
    8. Timer_t *pxNewTimer;
    9. pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); //为软件定时器申请一块内存
    10. if( pxNewTimer != NULL )
    11. {
    12. prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer ); //初始化软件定时器
    13. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    14. {
    15. pxNewTimer->ucStaticallyAllocated = pdFALSE;
    16. }
    17. #endif /* configSUPPORT_STATIC_ALLOCATION */
    18. }
    19. return pxNewTimer;
    20. }
    21. #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
    1. static void prvInitialiseNewTimer( const char * const pcTimerName,
    2. const TickType_t xTimerPeriodInTicks,
    3. const UBaseType_t uxAutoReload,
    4. void * const pvTimerID,
    5. TimerCallbackFunction_t pxCallbackFunction,
    6. Timer_t *pxNewTimer )
    7. {
    8. /* 断言,判断定时器的周期是否大于 0 */
    9. configASSERT( ( xTimerPeriodInTicks > 0 ) );
    10. if ( pxNewTimer != NULL )
    11. {
    12. /* 初始化软件定时器列表与创建软件定时器消息队列 */
    13. prvCheckForValidListAndQueue();
    14. /* 初始化软件定时信息,这些信息保存在软件定时器控制块中 */
    15. pxNewTimer->pcTimerName = pcTimerName;
    16. pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
    17. pxNewTimer->uxAutoReload = uxAutoReload;
    18. pxNewTimer->pvTimerID = pvTimerID;
    19. pxNewTimer->pxCallbackFunction = pxCallbackFunction;
    20. vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ); //初始化定时器列表项
    21. traceTIMER_CREATE( pxNewTimer );
    22. }
    23. }

    xTimerCreate()实例

    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. }

    启动软件定时器

    在系统开始运行的时候,系统会帮我们自动创建一个软件定时器任务(prvTimerTask),在这个任务中,如果暂时没有运行中的定时器,任务会进入阻塞态等待命令, 
    而我们的启动函数就是通过“定时器命令队列” 向定时器任务发送一个启动命令,定时器任务获得命令就解除阻塞,然后执行启动软件定时器命令。 

    xTimer 软件定时器句柄
    tmrCOMMAND_START 软件定时器启动命令
    xTaskGetTickCount() 获取当前系统时间
    pxHigherPriorityTaskWoken 为 NULL,该参数在中断中发送命令才起作用
    xTicksToWait 用户指定超时阻塞时间, 单位为系统节拍周期(即 tick)。

    1. //在任务中启动软件定时器
    2. //在中断中启动软件定时器,xTimerStartFromISR()是函数 xTimerStart()的中断版本, 用于启动一个先前由函数 xTimerCreate() / xTimerCreateStatic()创建的软件定时器。
    3. #define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
    4. #define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )

    软件定时器启动命令选择

    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的扩展宏。

    xTimerGenericCommand()函数如下

    1. BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait )
    2. {
    3. BaseType_t xReturn = pdFAIL;
    4. DaemonTaskMessage_t xMessage;
    5. configASSERT( xTimer );
    6. //发送命令给定时器任务
    7. if( xTimerQueue != NULL )
    8. {
    9. xMessage.xMessageID = xCommandID;
    10. xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;
    11. xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;
    12. if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ) /* 命令是在任务中发出的 */
    13. {
    14. if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ) //调度器正在运行,则根据用户指定超时时间发送
    15. {
    16. xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );
    17. }
    18. else //调度器还未运行,发送就行了,不需要阻塞
    19. {
    20. xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );
    21. }
    22. }
    23. else /* 命令是在中断中发出的 */
    24. {
    25. xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ); //调用从中断向消息队列发送消息的函数
    26. }
    27. traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
    28. }
    29. else
    30. {
    31. mtCOVERAGE_TEST_MARKER();
    32. }
    33. return xReturn;
    34. }

    xTimerStartFromISR()函数实例

    这个方案假定软件定时器 xBacklightTimer 已经创建,定时周期为 5s,执行次数为一次,即定时时间到了之后就进入休眠态。
    程序说明:当按键按下,打开液晶背光,启动软件定时器,5s 时间到,关掉液晶背光

    1. /* 软件定时器回调函数 */
    2. void vBacklightTimerCallback( TimerHandle_t pxTimer )
    3. {
    4. /* 关掉液晶背光 */
    5. vSetBacklightState( BACKLIGHT_OFF );
    6. }
    7. /* 按键中断服务程序 */
    8. void vKeyPressEventInterruptHandler( void )
    9. {
    10. BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    11. /* 确保液晶背光已经打开 */
    12. vSetBacklightState( BACKLIGHT_ON );
    13. /* 启动软件定时器 */
    14. if ( xTimerStartFromISR( xBacklightTimer,&xHigherPriorityTaskWoken ) != pdPASS )
    15. {
    16. /* 软件定时器开启命令没有成功执行 */
    17. }
    18. /* ...执行其他的按键相关的功能代码 */
    19. if ( xHigherPriorityTaskWoken != pdFALSE )
    20. {
    21. /* 执行上下文切换 */
    22. }
    23. }

    停止软件定时器

    1. //在任务中停止软件定时器,进入休眠态
    2. //在中断中停止软件定时器,进入休眠态,xTimerStopFromISR()是函数 xTimerStop()的中断版本
    3. #define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )
    4. #define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U )

    xTimerStop()实例

    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. /* 用户自己实现任务代码 */
    22. xTimerStop(Swtmr1_Handle,0); //停止定时器
    23. }
    24. }

    xTimerStopFromISR()函数实例

    这个方案假定软件定时器 xTimer 已经创建且启动。当中断发生时,停止软件定时器

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

    软件定时器任务

    软件定时器回调函数运行的上下文环境是任务。系统调度器函数里自动创建了一个软件定时器任务。

    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. configTIMER_SERVICE_TASK_NAME,
    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. configTIMER_SERVICE_TASK_NAME,
    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. }

    xTimerCreateTimerTask()函数创建了个任务,任务入口是prvTimerTask,即prvTimerTask()函数。

    1. static void prvTimerTask( void *pvParameters )
    2. {
    3. TickType_t xNextExpireTime;
    4. BaseType_t xListWasEmpty;
    5. ( void ) pvParameters;
    6. #if( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
    7. {
    8. extern void vApplicationDaemonTaskStartupHook( void );
    9. vApplicationDaemonTaskStartupHook();
    10. }
    11. #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */
    12. for( ;; )
    13. {
    14. /* 获取下一个要到期的软件定时器的时间 */
    15. xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
    16. /* 处理定时器或者将任务阻塞到下一个到期的软件定时器时间 */
    17. prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
    18. /* 读取“定时器命令队列”,处理相应命令 */
    19. prvProcessReceivedCommands();
    20. }
    21. }

    prvTimerTask()函数里有prvProcessTimerOrBlockTask()。

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

    prvProcessTimerOrBlockTask()函数如下

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

    prvSampleTimeNow()获取当前系统时间节拍并判断系统节拍计数是否溢出。如果是,那么就处理当前列表上的定时器,并切换定时器列表。

    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;
    6. //获取当前系统时间节拍
    7. xTimeNow = xTaskGetTickCount();
    8. //判断是否溢出了,当前系统时间节拍比上一次调用时间节拍的值小,这种情况是溢出的情况
    9. if( xTimeNow < xLastTime )
    10. {
    11. prvSwitchTimerLists(); // 发生溢出, 处理当前定时器列表上所有定时器并切换定时器列表
    12. *pxTimerListsWereSwitched = pdTRUE;
    13. }
    14. else
    15. {
    16. *pxTimerListsWereSwitched = pdFALSE;
    17. }
    18. xLastTime = xTimeNow; // 更新本次系统时间节拍
    19. return xTimeNow;
    20. }

    回到prvTimerTask(),prvTimerTask()函数里有prvProcessReceivedCommands()。读取“定时器命令队列”,处理相应命令。
    prvProcessReceivedCommands()如下

    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 )
    8. {
    9. #if ( INCLUDE_xTimerPendFunctionCall == 1 )
    10. {
    11. if( xMessage.xMessageID < ( BaseType_t ) 0 )
    12. {
    13. const CallbackParameters_t * const pxCallback = &( xMessage.u.xCallbackParameters );
    14. configASSERT( pxCallback );
    15. pxCallback->pxCallbackFunction( pxCallback->pvParameter1, pxCallback->ulParameter2 );
    16. }
    17. else
    18. {
    19. mtCOVERAGE_TEST_MARKER();
    20. }
    21. }
    22. #endif /* INCLUDE_xTimerPendFunctionCall */
    23. if( xMessage.xMessageID >= ( BaseType_t ) 0 ) //判断软件定时器命令是否有效
    24. {
    25. pxTimer = xMessage.u.xTimerParameters.pxTimer;
    26. if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE )
    27. {
    28. ( void ) uxListRemove( &( pxTimer->xTimerListItem ) ); /* 如果定时器在列表中,将定时器移除 */
    29. }
    30. else
    31. {
    32. mtCOVERAGE_TEST_MARKER();
    33. }
    34. traceTIMER_COMMAND_RECEIVED( pxTimer, xMessage.xMessageID, xMessage.u.xTimerParameters.xMessageValue );
    35. // 判断节拍计数器是否溢出过,如果有就处理并切换定时器列表
    36. // 因为下面的操作可能有新定时器项插入确保定时器列表对应
    37. xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
    38. switch( xMessage.xMessageID )
    39. {
    40. case tmrCOMMAND_START :
    41. case tmrCOMMAND_START_FROM_ISR :
    42. case tmrCOMMAND_RESET :
    43. case tmrCOMMAND_RESET_FROM_ISR :
    44. case tmrCOMMAND_START_DONT_TRACE :
    45. // 以上的命令都是让定时器启动,求出定时器到期时间并插入到定时器列表中
    46. if( prvInsertTimerInActiveList( pxTimer, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, xTimeNow, xMessage.u.xTimerParameters.xMessageValue ) != pdFALSE )
    47. {
    48. // 该定时器已经溢出赶紧执行其回调函数
    49. pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
    50. traceTIMER_EXPIRED( pxTimer );
    51. // 如果定时器是重载定时器,就重新启动
    52. if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
    53. {
    54. xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, NULL, tmrNO_DELAY );
    55. configASSERT( xResult );
    56. ( void ) xResult;
    57. }
    58. else
    59. {
    60. mtCOVERAGE_TEST_MARKER();
    61. }
    62. }
    63. else
    64. {
    65. mtCOVERAGE_TEST_MARKER();
    66. }
    67. break;
    68. case tmrCOMMAND_STOP :
    69. case tmrCOMMAND_STOP_FROM_ISR :
    70. // 如果命令是停止定时器,那就将定时器移除,在开始的时候已经从定时器列表移除,此处就不需要做其他操作
    71. break;
    72. case tmrCOMMAND_CHANGE_PERIOD :
    73. case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
    74. pxTimer->xTimerPeriodInTicks = xMessage.u.xTimerParameters.xMessageValue; // 更新定时器配置
    75. configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );
    76. // 插入到定时器列表,也重新启动了定时器
    77. ( void ) prvInsertTimerInActiveList( pxTimer, ( xTimeNow + pxTimer->xTimerPeriodInTicks ), xTimeNow, xTimeNow );
    78. break;
    79. case tmrCOMMAND_DELETE : // 删除定时器,判断定时器内存是否需要释放(动态的释放)
    80. #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
    81. {
    82. vPortFree( pxTimer );
    83. }
    84. #elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
    85. {
    86. if( pxTimer->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
    87. {
    88. vPortFree( pxTimer );
    89. }
    90. else
    91. {
    92. mtCOVERAGE_TEST_MARKER();
    93. }
    94. }
    95. #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
    96. break;
    97. default :
    98. /* Don't expect to get here. */
    99. break;
    100. }
    101. }
    102. }
    103. }

    当任务获取到命令消息的时候,会先移除对应的定时器,无论是什么原因,然后就根据命令去处理对应定时器的操作。

    删除软件定时器

    xTimerDelete()用于删除一个已经被创建成功的软件定时器,删除之后就无法使用该定时器,并且定时器相应的资源也会被系统回收释放。 

    #define xTimerDelete( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) )

    删除一个软件定时器也是在软件定时器任务中删除,调用 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. /****************************************************************
    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. /* 用户自己实现任务代码 */
    22. xTimerDelete(Swtmr1_Handle,0); //删除软件定时器
    23. }
    24. }

    软件定时器实验

    在 FreeRTOS 中创建了两个软件定时器,其中一个软件定时器是单次模式,5000 个 tick 调用一次回调函数,另一个软件定时器是周期模式,1000 个 tick 调用一次回调函数,在回调函数中输出相关信息。

    1. #include "FreeRTOS.h"
    2. #include "task.h"
    3. #include "event_groups.h"
    4. #include "bsp_led.h"
    5. #include "bsp_usart.h"
    6. #include "bsp_key.h"
    7. static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
    8. static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
    9. static TimerHandle_t Swtmr2_Handle =NULL; /* 软件定时器句柄 */
    10. static uint32_t TmrCb_Count1 = 0; /* 记录软件定时器 1 回调函数执行次数 */
    11. static uint32_t TmrCb_Count2 = 0; /* 记录软件定时器 2 回调函数执行次数 */
    12. int main(void)
    13. {
    14. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
    15. BSP_Init();
    16. /* 创建 AppTaskCreate 任务 */
    17. xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
    18. (const char* )"AppTaskCreate", /* 任务名字 */
    19. (uint16_t )512, /* 任务栈大小 */
    20. (void* )NULL, /* 任务入口函数参数 */
    21. (UBaseType_t )1, /* 任务的优先级 */
    22. (TaskHandle_t*)&AppTaskCreate_Handle); /* 任务控制块指针 */
    23. if (pdPASS == xReturn)
    24. vTaskStartScheduler(); /* 启动任务,开启调度 */
    25. else
    26. return -1;
    27. while (1); /* 正常不会执行到这里 */
    28. }
    29. static void AppTaskCreate(void)
    30. {
    31. taskENTER_CRITICAL(); //进入临界区
    32. Swtmr1_Handle=xTimerCreate( (const char*)"AutoReloadTimer",
    33. (TickType_t)1000, /*定时器周期 1000(tick) */
    34. (UBaseType_t)pdTRUE, /* 周期模式 */
    35. (void*)1, /*为每个计时器分配一个索引的唯一 ID */
    36. (TimerCallbackFunction_t)Swtmr1_Callback);
    37. if (Swtmr1_Handle != NULL)
    38. {
    39. /***************************************************************
    40. * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
    41. * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
    42. * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0
    43. ***********************************************************/
    44. xTimerStart(Swtmr1_Handle,0); //开启周期定时器
    45. }
    46. Swtmr2_Handle=xTimerCreate( (const char* )"OneShotTimer",
    47. (TickType_t)5000, /*定时器周期 5000(tick) */
    48. (UBaseType_t )pdFALSE, /* 单次模式 */
    49. (void*)2, /*为每个计时器分配一个索引的唯一 ID */
    50. (TimerCallbackFunction_t)Swtmr2_Callback);
    51. if (Swtmr2_Handle != NULL)
    52. {
    53. /********************************************************
    54. * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
    55. * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
    56. * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
    57. *************************************************************/
    58. xTimerStart(Swtmr2_Handle,0); //开启周期定时器
    59. }
    60. vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
    61. taskEXIT_CRITICAL(); //退出临界区
    62. }
    63. static void Swtmr1_Callback(void* parameter)
    64. {
    65. TickType_t tick_num1;
    66. TmrCb_Count1++; /* 每回调一次加一 */
    67. tick_num1 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
    68. LED1_TOGGLE;
    69. printf("swtmr1_callback 函数执行 %d 次\n", TmrCb_Count1);
    70. printf("滴答定时器数值=%d\n", tick_num1);
    71. }
    72. static void Swtmr2_Callback(void* parameter)
    73. {
    74. TickType_t tick_num2;
    75. TmrCb_Count2++; /* 每回调一次加一 */
    76. tick_num2 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
    77. printf("swtmr2_callback 函数执行 %d 次\n", TmrCb_Count2);
    78. printf("滴答定时器数值=%d\n", tick_num2);
    79. }

    至此,事件内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
    创建软件定时器任务
    static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
    /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
    Swtmr1_Handle=xTimerCreate(    (const char*)"AutoReloadTimer",
                                (TickType_t)1000,                            /* 定时器周期 1000(tick) */
                                (UBaseType_t)pdTRUE,                        /* 周期模式 */
                                (void* )1,                                    /* 为每个计时器分配一个索引的唯一 ID */
                                (TimerCallbackFunction_t)Swtmr1_Callback);     /* 回调函数 */

    xTimerStart( xTimer, xTicksToWait )                            启动软件定时器
    xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken )        启动软件定时器,xTimerStart()的中断保护版本

    xTimerStop( xTimer, xTicksToWait )                            停止软件定时器
    xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken )      停止软件定时器,xTimerStop()的中断保护版本

    xTimerDelete( xTimer, xTicksToWait )                        删除软件定时器

  • 相关阅读:
    牛客刷题——前端面试【四】谈一谈async 函数、await表达式
    ES数据类型
    【2023秋招】杭州游卡开发岗笔试AK
    自动编码器(AE)生成Mnist手写数字集,基于tensorflow和keras实现
    软件定义汽车产业生态创新白皮书
    深眸科技基于AI机器视觉实现应用部署,构建铝箔异物检测解决方案
    Apache顶级项目Ranger和Alluxio的最佳实践(附教程)
    86.(cesium之家)cesium叠加面接收阴影效果(gltf模型)
    Spring中Bean的作用域和生命周期
    【linux命令讲解大全】045.网络数据分析利器:深度解读 tcpdump 抓包工具的使用方法
  • 原文地址:https://blog.csdn.net/weixin_47077788/article/details/125995176