FreeRTOS学习-软件定时器管理

2023-11-12

1. 简介

软件定时器用于在未来的某个时间执行某个预先指定的函数,或者以一个固定的频率周期性调度该函数。这个预先指定的函数称为软件定时器回调函数, 它是由软件定时器服务调用的.

软件定时器由FreeRTOS的内核控制,不需要硬件的支持,不与硬件定时器和硬件计数器关联。

2. 软件定时器回调函数

函数原型定义:

typedef void (*TimerCallbackFunction_t)( TimerHandle_t xTimer );

要求:执行时间尽可能短,不允许进入Block state.

3. 软件定时器的属性和状态

3.1. 软件定时器的间隔(period)

软件定时器的间隔指的是软件定时器被开启到回调函数被执行的时间间隔, 以Tick为单位。

获取period的周期的函数原型:

TickType_t xTimerGetPeriod( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;

3.2. One-shot和Auto-reload定时器

one-shot定时器:回调函数被执行一次便失效的定时器类型。可以被手动重启。

auto-reload定时器:每次超时后会自动重启的定时器类型。回调函数将会被多次执行。

3.3. 软件定时器状态

两种状态:

  • dormant(休眠的):可以被引用,但没有开始"计时", 所以回调函数不会被调用.
  • running:正常执行,超时后会调用回调函数。

状态转换:

  • One-shot定时器

    One-shot定时器状态机

  • Auto-reload定时器

    Auto-reload定时器状态机

获取定时器状态的函数原型:

BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;

返回pdFALSE则表示定时器处于dormant状态。否则处于active(running)。

该函数会进入临界区,然后判断该定时器是否在Active队列中。因为链表项和链表互联,查看该定时器的xTimerListItem的Container是否为NULL,如果不为NULL,则表明在当前Active或超时队列中,这个操作时间复杂度只有O(1)。

4. 软件定时器的上下文

在FreeROTS的定时器实现中,所有定时器服务都由一个Timer Task来提供, 并通过一个链表来管理Active的定时器。其他任务通过一个定时器命令队列与Timer Task进行交互。

4.1. RTOS Daemon(Timer Service) Task

职责:负责调用回调函数和处理命令队列的消息。

创建:在开启调度器时自动创建。详情可查看任务管理文章中的开启调度器部分.

优先级:configTIMER_TASK_PRIORITY

栈大小:configTIMER_TASK_STACK_DEPTH

4.1.1. 创建与获取RTOS Daemon(Timer Service) Task

创建Timer Task的函数原型如下:

BaseType_t xTimerCreateTimerTask( void ) PRIVILEGED_FUNCTION;

该函数主要负责初始化Active定时器队列和定时器命令队列, 并创建Timer task.

在介绍该函数的具体实现之前, 先来看看检查和创建Active定时器队列和定时器命令队列(static void prvCheckForValidListAndQueue( void ))的实现:

检查和创建Active定时器队列和定时器命令队列

可以看到, Active定时器队列是由两个队列组成的, 分别是当前激活的Active定时器队列(即pxCurrentTimerList指向的队列)和溢出的Active定时器队列(即pxOverflowTimerList指向的队列). 当系统Tick溢出后, 需要对这两个指针进行一次切换.

创建Timer service的具体实现过程如下:

创建Timer service

这里创建的timer task的入口为prvTimerTask()

获取Timer Daemon Task句柄的函数原型:

TaskHandle_t xTimerGetTimerDeamonTaskHandle( void ) PRIVILEGED_FUNCTION;

只有在开启了调度器之后才允许调用该函数。

4.1.2. RTOS Daemon(Timer Service) Task的设计与实现

创建与获取RTOS Daemon(Timer Service) Task中, 可以看到, RTOS Daemon(Timer Service) Task的任务函数是prvTimerTask(). 这个函数便是Timer Service的实现. 下面就来看看该函数的实现.

Timer Service的主体是一个无限循环的处理逻辑. 但在进入任务的无限循环之前,FreeRTOS会调用外部实现的钩子函数vApplicationDaemonTaskStartupHook(),调用代码如下所示:

#if( configUSE_DAEMON_TASK_STARTUP_HOOK == 1)
    extern void vApplicationDaemonTaskStartupHook( void );

    vApplicationDaemonTaskStartupHook();
#endif

该机制可使得上层应用的程序员编写在Timer Task上下文运行的代码。如果真的需要,甚至可以重载Timer Task的功能。

此后,该任务将进入类似于服务器的监听状态,代码如下:

for( ;; )
{
    xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
    prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
    prvProcessReceivedCommands();
}

监听状态的实现代码很简洁,但在详细说明这个流程之前,首先需要理解几个与时刻相关的概念,以及几个关键的行为。

关键概念:

  • 发送命令的时刻xCommandTime:定时器的所有控制都是通过将命令发送到定时器命令队列的方式来实现的,而命令的实际执行者便是Timer Task本身。因此发送命令的时刻指的就是命令入队的时刻。此时该命令并没有被处理。
  • 当前时刻xTimeNow:即通过xTaskGetTickCount()获取的系统Tick。
  • 处理命令的时刻:处理的时刻指的是执行命令的时刻。
  • 定时器超时时刻xNextExpiryTime:它等于发送命令的时刻加上定时器的周期,即xNextExpityTime = xCommandTime + pxTimer->xTimerPeriodInTicks

关键的行为:

  • 切换Active定时器队列(static void prvSwitchTimerLists( void )): 切换队列的原因创建与获取RTOS Daemon(Timer Service) Task所述. 该函数主要负责处理当前已经超时的定时器, 并切换Active定时器队列指针.
  • 获取当前的Tick并进行队列切换(static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListWereSwitched )): 该函数的主要职责是获取当前的Tick,并根据是否发生了Tick溢出,进而切换当前的Active定时器队列,通过pxTimerListWereSwitched将结果反馈给调用者。
  • 将定时器加入到Active定时队列(static BaseType_t prvInsertTimerInActiveList( Timer_t * const pxTimer, const TickType_t xNextExpiryTime, const TickType_t xTimeNow, const TickType_t xCommandTime )): 顾名思义,这个函数的主要任务就是将定时器插入到合适的Active队列。因此区分Tick overflow的情况。另外,它还需要处理一些特殊的情况,即如果被插入的定时器已经超时(pdTRUE表示已超时), 则不会进行入队. 最终将是否超时的标志反馈给调用者。
  • 处理超时的定时器(static void prvProcessExpiredTimer( const TickType_t xNextExpireTime, const TickType_t xTimeNow )).

在以上说明的基础上,下面将介绍Timer Task的实现,活动图如下:

Timer Task的实现

总体上分为3步:

  1. 获取最近要超时的时刻(xNextExpireTime = prvGetNextExpireTime(&xListWasEmpty)):查询Active定时器队列,如果不为空,则获取最近超时的定时器的超时时刻xNextExpireTime;否则,返回0。时刻记住xNextExpireTime是有可能小于当前Tick count的。xListWasEmpty记录了当前Active定时器队列是否为空。

  2. 处理超时的定时器,又或是进入等待状态(prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty )):

    处理超时的定时器

  3. 处理定时器命令队列(static void prvProcessReceivedCommands( void )):

    处理定时器命令队列

4.2. Timer Command Queue

定时器命令队列对外部是不可见的。

职责:连接用户task和rtos daemon task。用户task每次调用timer相关的函数,自动入队相关的命令。

长度:configTIMER_QUEUE_LENGTH

向该队列发送命令的函数原型:

BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

xTimerGenericCommand()是定时器操作的核心API。从名字可以知道,它就是向定时器命令队列发送控制命令和参数以实现相应的功能。它没有中断上下文的版本,因为它在内部通过命令类型来判断调用时的上下文。如果是在任务上下文,在给队列发送之前,会判断当前调度器是否挂起,如果被挂起了,xTicksToWait会被忽略,而强制设置为0,这是因为调度器挂起时,不允许当前任务进入Blocked状态。此时给命令队列的参数类型为xTimerParameter_t

具体实现如下:

{
    校验入参(configASSERT( xTimer ));

    如果定时器命令队列不为空(xTimerQueue != NULL):
    {
        设置命令的ID(xMessage.xMessageID = xCommandID);
        设置命令的定时器消息参数(xMessage.u.xTimerParameters.xMessageValue = xOptionalValue);
        设置命令的目标定时器句柄(xMessage.u.xTimerParamters.pxTimer = (Timer_t *) xTimer);

        如果命令来自任务上下文(xCommandID < tmrFIRST_FROM_ISR_COMMAND):
        {
            如果当前调度器未被挂起(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING):
            {
                将命令入队到定时器命令队列, 允许等待(xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait ));
            }
            否则, 即调度器被挂起:
            {
                将命令入队到定时器命令队列, 不允许等待(xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY ));
            }
        }
        否则, 即命令来自ISR:
        {
            将命令入队到定时器命令队列(xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ));
        }
    }

    返回xReturn;
}

5. 创建并开启软件定时器

创建定时器的函数原型:需要设置configSUPPORT_DYNAMIC_ALLOCATION = 1

TimerHandle_t xTimerCreate( const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction ) PRIVILEGED_FUNCTION;

参数中,pcTimerName仅用于调试。

xTimerPeriodInTicks是定时器的周期,单位是Ticks,因此可通过portTICK_PERIOD_MS转换成现实时间。传入的值必须大于零

uxAutoReloadpdTURE,定时器将会变成一个周期性的定时器(Auto-Reload),以xTimerPeriodInTicks为周期;否则为One-Shot定时器,即超时一次后就进入休眠状态。

pvTimerID是Timer的ID。通常而言,它会作为参数传给定时器的回调函数,用于区分是哪个定时器发生了超时。

pxCallbackFunction便是定时器回调函数了。

如果调用失败则返回NULL,否者返回这个定时器的句柄。

该函数创建一个软件定时器实例,并返回其句柄。类似于Task,它需要创建一个内部的数据结构(Timer_t)来记录该软件定时器的信息。

在介绍具体实现之前, 先来看看如何初始化一个新定时器的原信息(static void prvInitialiseNewTimer( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, Timer_t *pxNewTimer )):

{
    校验定时器的周期(configASSERT( ( xTimerPeriodInTicks > 0 ) ));

    如果定时器指针不为空(pxNewTimer != NULL):
    {
        检查Active定时器队列和定时器命令队列是否已经创建, 如果未创建则创建(prvCheckForValidListAndQueue());

        设置定时器的名字(pxNewTimer->pcTimerName = pcTimerName);
        设置定时器的周期(pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks);
        设置定时器的Reload类型(pxNewTimer->uxAutoReload = uxAutoReload);
        设置定时器的ID(pxNewTimer->pvTimerID = pvTimerID);
        设置定时器的回调函数(pxNewTimer->pxCallbackFunction = pxCallbackFunction);

        初始化定时器的链表项(vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ));
    }
}

xTimerCreate的具体实现如下:

{
    申请定时器原信息结构体所需的内存pxNewTimer(pxNewTimer = (Timer_t *) pvPortMalloc( sizeof( Timer_t ) ));

    如果申请内存成功(pxNewTimer != NULL):
    {
        初始化定时器原信息(prvInitialseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer ));

        (仅开启configSUPPORT_STATIC_ALLOCATION):
        {
            清空定时器静态创建标志(pxNewTimer->ucStaticallyAllocated = pdFALSE);
        }
    }

    返回pxNewTimer;
}

另外,与Task类似,还提供了静态创建的版本:

TimerHandle_t xTimerCreateStatic( const char * const pcTimerName,
                                  const TickType_t xTimerPeriodInTicks,
                                  const UBaseType_t uxAutoReload,
                                  void * const pvTimerID,
                                  TimerCallbackFunction_t pxCallbackFunction,
                                  StaticTimer_t *pxTimerBuffer ) PRIVILEGED_FUNCTION;

与动态创建不同的是,静态创建的接口需要使用pxTimerBuffer来记录该定时器的内部信息。

与任务创建不一样的是,定时器在创建后,处于休眠状态,并不会开始计时。可以通过如下几个接口开启:

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

先看看开启定时器的函数原型:需要设置configUSE_TIMERS = 1

#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )

可以看到,它是通过向定时器命令队列发送命令来实现开启定时器的效果的。如果一个定时器已经处于Active状态,那xTimerStart()的功能等同于复位定时器xTimerReset()。该函数可以在调度器开启前被调用,但实际上定时器是在开启任务调度器之后才开始运行。

xTicksToWait是调用者的等待时间。

如果在xTicksToWait时间内未成功将命令发送到定时器命令队列,则返回pdFAIL;否则返回pdPASS

该函数还提供了中断上下文的版本:

#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )

由于该操作有可能导致Timer Task被唤醒,所以需要pxHigherPriorityTaskWoken来记录是否需要进行调度。

6. 定时器ID

每个定时器有一个ID,在创建Timer时由外部赋予(也可后期更改),并且换传递到定时器回调函数中。当多个定时器使用相同的定时器回调函数时,可以通过ID来判断是哪个定时器发生了超时,也可用作内部私有信息。

获取ID的函数原型:

void *pvTimerGetTimerID( const TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;

设置ID的函数原型:

void vTimerSetTimerID( const TimerHandler_t xTimer, void *pvNewID ) PRIVILEGED_FUNCTION;

7. 修改Timer的Period

FreeRTOS的软件定时器支持在其活动的状态下,修改其超时周期。函数原型如下:需要设置configUSE_TIMERS = 1

#define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, ( xNewPeriod ), NULL, ( xTicksToWait ) )

该函数还可以在定时器处于dormant状态时使用。需要注意的是,在修改完周期后它将开启该定时器。参数和返回值与其他类似,不在赘述。

FreeRTOS还提供了中断上下文的版本:

#define xTimerChangePeriodFromISR( xTimer, xNewPeriod, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer), tmrCOMMAND_CHANGE_PERIOD_FROM_ISR, ( xNewPeriod ), ( pxHigherPriorityTaskWoken ), 0U )

8. 复位软件Timer

复位Timer指的是重启Timer。函数原型如下:需要设置configUSE_TIMERS = 1

#define xTimerReset( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )

如果此时Timer处于Active,那Timer的计时将会重新开始。如果Timer处于Dormant状态,那其功能类似于xTimerStart()

FreeRTOS还提供了中断上下文的版本:

#define xTimerResetFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken), 0U )

9. PendFunction

PendFunction可以理解为被延后的函数调用。它是一种特殊的定时器回调,与其他定时器回调不同的是,它是将回调函数指针直接发送到Timer命令队列,由RTOS Daemon Task执行调用。

发送设置PendFunction的函数原型:

BaseType_t xTimerPendFunctionCall( PendedFunction_t xFunctionToPend, void * pvParameter1, uint32_t ulParameter2, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

pvParameter1ulParameter2xFunctionToPend的参数。

由于该函数实际上还是通过Timer命令队列实现,所以需要给出等待时间xTicksToWait

如果成功发送到Timer命令队列,则返回pdPASS;否则返回pdFALSE

此时给任务命令队列发送的参数类型为xCallbackParameters_t

具体实现如下所示:

{
    校验定时器命令队列(configASSERT( xTimerQueue ));

    设置命令的类型为PendFunction(xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK);
    设置回调函数(xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend);
    设置回调函数的参数1(xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1);
    设置回调函数的参数2(xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2);

    将命令入队到定时器命令队列(xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait ));

    返回xReturn;
}

该函数还有中断上下文的版本:

BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

10. 停止和删除定时器

停止定时器的函数原型:需要设置configUSE_TIMERS = 1

#define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )

它将会把指定的定时器设置为dormant状态。其参数和返回值与xTimerStart()相似,这里就不赘述了。

该函数还提供了中断上下文的版本:

#define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U )

删除定时器的函数原型:需要设置configUSE_TIMERS = 1

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

11. 其他定时器操作

获取定时器的名字的函数原型:

const char * pcTimerGetName( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;

获取定时器的超时时刻的函数原型:

TickType_t xTimerGetExpiryTime( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;

如果此时Timer处于dormant状态,则返回无意义的值。

12. 定时器的实现细节

本节将从定时器的对外接口和内涵分别阐述定时器的实现细节。

12.1. 定时器的接口

12.1.1. 定时器的数据结构接口
12.1.1.1. 定时器句柄

句柄定义:

typedef void * TimerHandle_t;

指代某个具体的定时器,所有定时器操作的对象。在创建定时器时创建。

12.1.1.2. 定时器回调函数指针类型

定义如下:

typedef void (*TimerCallbackFunction_t)( TimerHandle_t xTimer );

定义了定时器回调函数必须遵循的模板。

12.1.1.3. PendFunction函数指针类型

定义如下:

typedef void (*PendedFunction_t)( void *, uint32_t );

定义了xTimerPendFunctionCallFromISR()所示用的函数必须遵循的模板。

12.1.2. 定时器的接口宏
12.1.2.1. 定时器的常量

关于Timer命令的定义:

#define tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR    ( ( BaseType_t ) -2 )
#define tmrCOMMAND_EXECUTE_CALLBACK             ( ( BaseType_t ) -1 )
#define tmrCOMMAND_START_DONT_TRACE             ( ( BaseType_t ) 0 )
#define tmrCOMMAND_START                        ( ( BaseType_t ) 1 )
#define tmrCOMMAND_RESET                        ( ( BaseType_t ) 2 )
#define tmrCOMMAND_STOP                         ( ( BaseType_t ) 3 )
#define tmrCOMMAND_CHANGE_PERIOD                ( ( BaseType_t ) 4 )
#define tmrCOMMAND_DELETE                       ( ( BaseType_t ) 5 )

#define tmrFIRST_FROM_ISR_COMMAND               ( ( BaseType_t ) 6 )
#define tmrCOMMAND_START_FROM_ISR               ( ( BaseType_t ) 7 )
#define tmrCOMMAND_RESET_FROM_ISR               ( ( BaseType_t ) 8 )
#define tmrCOMMAND_CHANGE_PERIOD_FROM_ISR       ( ( BaseType_t ) 9 )

12.2. 定时器的内涵

12.2.1. 定时器依赖的头文件

标准C库:

#include <stdlib.h>

FreeRTOS头文件:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
12.2.2. 定时器的私有数据结构
12.2.2.1. 定时器控制信息

软件定时器的实现中,最重要的数据结构为定时器控制信息,定义如下:

typedef struct tmrTimerControl
{
    const char          *pcTimerName;
    ListItem_t          xTimerListItem;
    TickType_t          xTimerPeriodInTicks;
    UBaseType_t         uxAutoReload;
    void                *pvTimerID;
    TimerCallbackFunction_t pxCallbackFunction;
    #if( configUSE_TRACE_FACILITY == 1)
        UBaseType_t     uxTimerNumber;
    #endif

    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t         ucStaticallyAllocated;
    #endif
} xTIMER;

typedef xTIMER Timer_t;

pcTimerName是Timer的名字,仅用于调试。

xTimerListItem是Active定时器队列的链表项。其xItemValue记录了定时器的超时时刻

xTimerPeriodInTicks记录了该定时器的超时周期。

uxAutoReload用于标识该Timer为One-shot或Auto-Reload定时器。

pvTimerID记录定时器的私有ID。由外部传入。

pxCallbackFunction定时器回调函数。

uxTimerNumber只在使能了profiling功能时使用。

ucStaticallyAllocated用于标识该定时器的数据是否为静态创建,如果为pdTRUE则不执行Free动作。

12.2.2.2. Timer命令队列参数结构体

定义如下:

typedef struct tmrTimerParameters
{
    TickType_t      xMessageValue;
    Timer_t *       pxTimer;
} TimerParameter_t;

typedef struct tmrCallbackParameters
{
    PendedFunction_t pxCallbackFunction;
    void *pvParameter1;
    uint32_t ulParameter2;
} CallbackParameters_t;

typedef struct tmrTimerQueueMessage
{
    BaseType_t      xMessageID;
    union
    {
        TimerParameter_t xTimerParameters;

        #if( INCLUDE_xTimerPendFunctionCall == 1 )
            CallbackParameters_t xCallbackParameters;
        #endif
    } u;
} DaemonTaskMessage_t;

DaemonTaskMessage_t是Timer命令队列中的命令结构体. xMessageID是命令的类型.

而从联合体u的定义可以看出,一共有两种消息可以被入队:

  • 用于实现软件定时的TimerParameter_t:其中的xMessageValue可用作命令的参数;pxTimer则是目标定时器。
  • 用于实现延迟调用的CallbackParameters_tpxCallbackFunction被延迟调度的函数指针;pvParameter1ulParameter2是函数的参数。
12.2.2.3. Active Timer队列

定义如下:

PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;

与Task的Delayed任务队列类似,FreeRTOS存在两个Active Timer队列和两个指针,其中pxCurrentTimerList指向正常运行的Timer,而pxOverflowTimerList指向溢出于当前Tick的队列。

在队列中,队列项按照剩余超时时间升序排列,即最快超时的项排在最前面。

12.2.2.4. Timer命令队列

定义如下:

PRIVILEGED_DATA static QueueHandle_t xTimerQueue = NULL;

命令队列是基于FreeRTOS的队列实现的。

12.2.3. 定时器的私有宏
12.2.3.1. 定时器的私有常量

杂项:

#define tmrNO_DELAY     ( TickType_t ) 0U

Timer Task的任务名:可在FreeRTOSConfig.h中修改。

#define configTIMER_SERVICE_TASK_NAME "Tmr Svc"
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS学习-软件定时器管理 的相关文章

  • Jmeter 性能压测-常遇问题与解决技巧

    2024软件测试面试刷题 这个小程序 永久刷题 靠它快速找到工作了 刷题APP的天花板 CSDN博客 文章浏览阅读2 2k次 点赞85次 收藏11次 你知不知道有这么一个软件测试面试的刷题小程序 里面包含了面试常问的软件测试基础题 web自
  • 小学二三年级入门信奥赛,如何从Scratch进入C++的学习

    小学生几年级适宜开始学习C 这是讨论的比较热烈 也是比较热门的话题 小学生适宜几年级开始学C 小学生适宜几年级开始学C CSDN博客 simple happiness 信息学规划 北京二年级学生图形化过二级想往信奥靠拢如何准备 信息学规划
  • 我当年自学黑客(网络安全)的一些心得!(内附学习笔记)

    前 言 写这篇教程的初衷是很多朋友都想了解如何入门 转行网络安全 实现自己的 黑客梦 文章的宗旨是 1 指出一些自学的误区 2 提供客观可行的学习表 3 推荐我认为适合小白学习的资源 大佬绕道哈 文末有福利 一 自学网络安全学习的误区和陷阱
  • 网络安全从入门到精通(超详细)学习路线

    首先看一下学网络安全有什么好处 1 可以学习计算机方面的知识 在正式学习网络安全之前是一定要学习计算机基础知识的 只要把网络安全认真的学透了 那么计算机基础知识是没有任何问题的 操作系统 网络架构 网站容器 数据库 前端后端等等 可以说不想
  • 【OpenCV学习笔记02】- 图像入门

    内容 这里介绍了图像处理的入门操作 你将学习如何读取图像 如何显示图像以及如何将其保存回去 你将学习以下功能 cv imread cv imshow cv imwrite 简单使用OpenCV 读取图像 使用 cv imread 函数读取图
  • Azure IoT 中心证书

    我正在尝试使用 Mqtt 在 Azure IoT 中心发布一些数据 我已使用 SAS 令牌成功发布了一些数据 但我的客户想要一个 x509 自生成和自签名证书 Azure 支持这一点 但没有提供太多相关信息 https learn micr
  • 牛客字符串

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • 【CTF必看】从零开始的CTF学习路线(超详细),让你从小白进阶成大神!

    最近很多朋友在后台私信我 问应该怎么入门CTF 个人认为入门CTF之前大家应该先了解到底 什么是CTF 而你 学CTF的目的又到底是什么 其次便是最好具备相应的编程能力 若是完全不具备这些能力极有可能直接被劝退 毕竟比赛的时候动不动写个脚本
  • 华为OD机试真题-整数对最小和-Java-OD统一考试(C卷)

    题目描述 给定两个整数数组array1 array2 数组元素按升序排列 假设从array1 array2中分别取出一个元素可构成一对元素 现在需要取出k对元素 并对取出的所有元素求和 计算和的最小值 注意 两对元素如果对应于array1
  • 用CHAT写一份标题为职业教育教师教学能力提升培训总结

    CHAT回复 标题 职业教育教师教学能力提升培训总结 一 活动概述 本次由学校组织的职业教育教师教学能力提升培训于8月15日至8月20日顺利进行 来自全校的60位职业教育教师参与了此次培训 主讲人为享有盛名的教育专家马丁先生 二 培训内容与
  • 利用CHAT写实验结论

    问CHAT 通过观察放置在玻璃表面上的单个水滴 人们可以观察到水滴充当成像系统 探究这样一个透镜的放大倍数和分辨率 CHAT回复 实验报告标题 利用玻璃表面的单一水滴观察成像系统的放大倍数和分辨率 一 实验目的 通过对比和测量 研究和探索玻
  • 网络安全(黑客)自学

    1 网络安全是什么 网络安全可以基于攻击和防御视角来分类 我们经常听到的 红队 渗透测试 等就是研究攻击技术 而 蓝队 安全运营 安全运维 则研究防御技术 2 网络安全市场 一 是市场需求量高 二 则是发展相对成熟入门比较容易 3 所需要的
  • 跨平台UI自动化框架:Airtest,游戏开发和应用测试的利器

    2024软件测试面试刷题 这个小程序 永久刷题 靠它快速找到工作了 刷题APP的天花板 CSDN博客 文章浏览阅读2 3k次 点赞85次 收藏11次 你知不知道有这么一个软件测试面试的刷题小程序 里面包含了面试常问的软件测试基础题 web自
  • 用栈实现队列(OJ中报错的处理)

    用栈实现队列 ERROR AddressSanitizer myQueueFree函数中栈的释放处现了问题 没有调用StackDestory而是直接free了 这个是栈初始化时 capacity与malloc申请的空间大小没有匹配 请你仅使
  • 项目文章 | IF=8.4&转录因子Egr-1是脑膜炎型大肠杆菌引起的血脑屏障损伤的关键调节因子

    2024年1月17日华中农业大学动科动医学院陈焕春院士 王湘如教授团队在期刊 Cell Communication and Signaling IF 8 4 发表了题为 Egr 1 is a key regulator of the blo
  • 将 Reactjs 连接到 Myqtthub

    您好 我对所有物联网事物都很陌生 我希望能够使用 mqtt 从 Arduino 发送和接收数据https myqtthub com https myqtthub com作为我们的经纪人 我使用以下代码进行连接 import React Co
  • 了解 Azure 事件中心分区使用者模式

    Azure 事件中心使用分区使用者模式中描述的docs https learn microsoft com en us azure event hubs event hubs features 当涉及到现实世界场景时 我在理解该模型的消费者
  • CoAP 和 DTLS 集成

    我实现了 CoAP libcoap 和 DTLS tinyDTLS 的实现 如何将 CoAP libcoap 与 DTLS tinyDTLS 集成 我将不胜感激任何建议 libcoap 现在完全支持这一点 当您使用其子模块构建它时 git
  • Android Ble GATT_ERROR 133 经常使用三星设备

    我正在研究 BLE 应用程序 我已经使用 Nexus Moto Samsung LG 等不同设备进行了测试 我仅在三星设备中收到 GATT 错误 133 三星 A5 2016 尝试连接 10 次 但只连接了 2 或 3 次 请帮助我 Non
  • 即使 Android M 上的移动数据已打开(有连接),也可以通过 WiFi(无连接)发送请求

    我必须在没有互联网连接的情况下将 UDP 数据包发送到 WiFi 模块 配有自己的 AP 但是当我将手机连接到 AP 时 Android 会在移动数据接口上重定向我的数据包 因为它有互联网连接 我使用下面的代码来完成我的工作 但它似乎不适用

随机推荐

  • 实现DNS主从复制、子域、转发、智能DNS

    主从复制 前提准备 关闭SElinux 关闭防火墙 时间同步 环境说明 Centos7 ip地址 dns master 10 0 0 100 dns slave 10 0 0 103 web 10 0 0 101 目的 搭建DNS主从服务器
  • 计算机原理-结构组成

    cpu 中央处理器 程序控制 操作控制 时间控制 数据处理 运算器 算数逻辑单元ALU 逻辑运行 累加计算器AC 为alu提供工作区 数据缓存寄存器 DR 暂存指令和数据 状态条件寄存器PSW 保存指令条件码 控制器 程序计数器PC 指令计
  • python华为OD机试-进制转换-(5)

    题目5 题目描述 写出一个程序 接受一个十六进制的数 输出该数值的十进制表示 输入描述 输入一个十六进制的数值字符串 注意 一个用例会同时有多组输入数据 请参考帖子https www nowcoder com discuss 276处理多组
  • Matlab中的画图函数

    目录 一 二维曲线和图形 1 二维图像基本命令plot 1 曲线线型 颜色和标记点类型 2 设置曲线线宽 标记点大小 标记点边框颜色和标记点填充颜色等 3 坐标轴设置 4 坐标轴刻度设置 5 图例 6 更多的设置 二 图形的控制与表现 1
  • spring-MVC__spring__hibernate整合值hibernate的配置文件 (hibernate.cfg.xml)

  • QT-样式表

    Qt样式表是一个可以自定义部件外观的十分强大的机制 除了那些能够通过子类化QStyle更改的外观 其余的都可以使用Qt样式表来美化 Qt样式表的概念 术语和语法都受到了HTML的层叠样式表 Cascading Style Sheets CS
  • Ubuntu使用darknet实现YOLOv4-tiny预训练模型测试+训练自己的数据集+评估自己的模型

    文章目录 1 使用YOLOv4 tiny预训练模型 2 训练自己的数据集 2 1 建立yolov4 tiny数据格式 2 2 开始训练 2 3 多GPU训练 3 评估自己的模型 参考博客 YOLOv4 tiny的原理本文不做讲解 只有应用方
  • 黑马程序员SSM-Spring学习笔记

    学完Spring之后是SpringMVC 文章目录 前言 一 注解开发 1 1 注解开发定义bean 1 2纯注解开发 1 3bean作用范围 1 4依赖注入 自动装配 1 5 第三方bean管理 1 6 总结 二 Spring整合MyBa
  • qt槽函数如何传递多个参数_Qt 信号槽如何传递参数(或带参数的信号槽)

    标签 信号槽如何传递参数 或带参数的信号槽 利用Qt进行程序开发时 有时需要信号槽来完成参数传递 带参数的信号槽在使用时 有几点需要注意的地方 下面结合实例进行介绍 第一点 当信号与槽函数的参数数量相同时 它们参数类型要完全一致 signa
  • LeetCode 27.移除元素

    文章目录 题目分析 解题思路 思路1 暴力求解 遍历 接口源码 思路2 空间换时间 接口源码 思路3 双指针 快慢指针 接口源码 题目链接 LeetCode 27 移除元素 题目分析 给你一个数组 nums 和一个值 val 你需要 原地
  • 推荐算法实战项目:FNN 原理以及案例实战(附完整 Python 代码)

    本文要介绍的是FNN模型 出自于张伟楠老师于2016年发表的论文 Deep Learning over Multi field Categorical Data 论文提出了两种深度学习模型 分别叫做FNN Factorisation Mac
  • JMeter接口压测和性能监测

    引言 今天我来和大家分享一篇关于JMeter接口压测和性能监测的文章 在现代互联网时代 应用程序的性能已经成为了一个非常重要的问题 并且对于许多公司的生存和发展都起着至关重要的作用 而在这其中 JMeter是一个非常实用的工具 可以帮助我们
  • html5养树游戏源码,奥日小屋:寻找精灵之树

    大家好我是Receiver 又到了一年一度的夏促时间啦 不知道如何剁手的盒友可以考虑入坑 奥日与黑暗森林 哟 作为一款15年的游戏放在现在来看仍然有惊艳的画风 音乐与游戏性 夏促期间五折史低 今天给大家带来的是寻找银之树的流程攻略 另外奥日
  • 【JavaSE】jdk8新特性

    尚硅谷JavaSE笔记合集 文章名 链接 JavaSE 异常 文章地址 JavaSE 常用类 String LocalDateTime 文章地址 JavaSE 枚举 文章地址 JavaSE 注解 文章地址 JavaSE 集合框架 文章地址
  • 【设计相关】UML类图和时序图介绍

    文章目录 一 什么是UML UML的定义 UML的应用场景 类图 Class Diagrams 类关系 继承关系 记忆技巧 案例 汽车关系 购票机 类说明 方法说明 时序图 Sequence Diagrams 一 什么是UML UML的定义
  • CGAL的简介及安装设置

    一 CGAL库的介绍 CGAL Computational Geometry Algorithms Library 库 计算几何算法库 是一个大型的C 几何数据结构和算法库 包含Delaunay三角网 网格生成 布尔运算的多边形 各种几何处
  • [苹果开发者账号]05 换收款的银行账号

    问题场景 新公司申请的苹果开发者账号 收费APP有收入 苹果打款进入了公司银行账号 但银行反馈说 账号不能结算这笔钱 根因是 财务搞错账号业务了 解决方法 要换苹果的收款账号 提醒 涉及苹果这种境外业务 但又是可以人民币结算的 一定要和当地
  • IAR error: a declaration cannot have a label

    在使用switch时 在case 后面申请变量会出现 error a declaration cannot have a label错误 原因 Case statements are only labels This means the c
  • 【安全技术】Java 实现加密数据库连接

    一 前言 在很多项目中 数据库相关的配置文件内容都是以明文的形式展示的 这存在一定的安全隐患 在开发和维护项目时 不仅要关注项目的性能 同时也要注重其安全性 二 实现思路 我们都知道项目启动时 Spring 容器会加载配置文件并读取文件中的
  • FreeRTOS学习-软件定时器管理

    1 简介 软件定时器用于在未来的某个时间执行某个预先指定的函数 或者以一个固定的频率周期性调度该函数 这个预先指定的函数称为软件定时器回调函数 它是由软件定时器服务调用的 软件定时器由FreeRTOS的内核控制 不需要硬件的支持 不与硬件定