Skip to content

定时器

  • 超时时间
  • 函数
  • 单次触发还是多次触发

实际实现

会使用两个链表, 这两个链表一个记录溢出的时钟, 一个记录没有溢出的时钟

使用指针进行切换

c
PRIVILEGED_DATA static List_t xActiveTimerList1 = {0};
PRIVILEGED_DATA static List_t xActiveTimerList2 = {0};
PRIVILEGED_DATA static List_t *pxCurrentTimerList = NULL;
PRIVILEGED_DATA static List_t *pxOverflowTimerList = NULL;

守护任务

一般来说定时器的时间到了处理函数会放在Tick中断里面, 但是在中断里面执行长度未知的代码是不确定的, 所以FreeRTOS使用了一个线程来执行定时器函数

这个任务是"RTOS Damemon Task"守护函数, 使用宏定义configUSE_TIMERS的时候会自动创建这一个任务

守护任务的优先级为:configTIMER_TASK_PRIORITY;定时器命令队列的长度为 configTIMER_QUEUE_LENGTH, 还有一个栈的深度configTIMER_TASK_STACK_DEPTH

这个队列是在调用定时器任务的时候其他的函数相当于在给任务发送命令, 命令保存在一个队列里面

c
vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );

使用这一个获取任务的参数

c
static portTASK_FUNCTION( prvTimerTask, pvParameters )
{
    TickType_t xNextExpireTime;
    BaseType_t xListWasEmpty;

    /* Just to avoid compiler warnings. */
    ( void ) pvParameters;

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

        /* Allow the application writer to execute some code in the context of
             * this task at the point the task starts executing.  This is useful if the
             * application includes initialisation code that would benefit from
             * executing after the scheduler has been started. */
        vApplicationDaemonTaskStartupHook();
    }
    #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */

    for( ; ; )
    {
        /* Query the timers list to see if it contains any timers, and if so,
             * obtain the time at which the next timer will expire. */
        xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );

        /* If a timer has expired, process it.  Otherwise, block this task
             * until either a timer does expire, or a command is received. */
        prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

        /* Empty the command queue. */
        prvProcessReceivedCommands();
    }
}
c
#define portTASK_FUNCTION( vFunction, pvParameters )          void vFunction( void * pvParameters )

相当于定义了一个void prvTimerTask( void * pvParameters )

xTimerCreateTimerTask里面会创建这个函数的优先级

定时器的状态

有两个状态: 运行和冬眠

image-20231121225742533

实际使用

  • 创建
c
/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
                const TickType_t xTimerPeriodInTicks,
                const UBaseType_t uxAutoReload,
                void * const pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction );
/* 使用静态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
* 返回值: 成功则返回TimerHandle_t, 
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                    TickType_t xTimerPeriodInTicks,
                    UBaseType_t uxAutoReload,
                    void * pvTimerID,
                    TimerCallbackFunction_t pxCallbackFunction,
                    StaticTimer_t *pxTimerBuffer );

在使用的时候可以使用pvTimerID传递一个参数

回调函数

c
void ATimerCallback( TimerHandle_t xTimer );
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );
c
    typedef struct tmrTimerControl                  /* The old naming convention is used to prevent breaking kernel aware debuggers. */
    {
        const char * pcTimerName;                /* 计时器的名字 */
        ListItem_t xTimerListItem;               /* 计时器使用的列表项 */
        TickType_t xTimerPeriodInTicks;             /*计时器的时间 */
        void * pvTimerID;                           /* 一个计时器的ID, 用于在一个回调函数里面区分不同的时钟 */
        TimerCallbackFunction_t pxCallbackFunction; /* 回调函数 */
        #if ( configUSE_TRACE_FACILITY == 1 )
            UBaseType_t uxTimerNumber;              /*<< An ID assigned by trace tools such as FreeRTOS+Trace */
        #endif
        uint8_t ucStatus;                         /* 记录是不是Static的 */
    } xTIMER;
  • 删除
c
/* 删除定时器
* xTimer: 要删除哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。 如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间 xTicksToWait ,等待一会。

  • 启动停止
c
/* 启动定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, 
                       TickType_t xTicksToWait );
/* 启动定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"启动命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
				BaseType_t *pxHigherPriorityTaskWoken );
/* 停止定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStop( TimerHandle_t xTimer, 
                      TickType_t 	xTicksToWait );
/* 停止定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"停止命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
				BaseType_t *pxHigherPriorityTaskWoken );

这些函数的 xTicksToWait 表示的是,把命令写入命令队列的超时时间。命令队列可能已经满 了,无法马上把命令写入队列里,可以等待一会。

xTicksToWait 不是定时器本身的超时时间,不是定时器本身的"周期"。

创建定时器时,设置了它的周期(period)。 xTimerStart() 函数是用来启动定时器。假设调用 xTimerStart() 的时刻是tX,定时器的周期是n,那么在 tX+n 时刻定时器的回调函数被调用。

如果定时器已经被启动,但是它的函数尚未被执行,再次执行 xTimerStart() 函数相当于执行 xTimerReset() ,重新设定它的启动时间。

  • 复位

使用 xTimerReset() 函数可以让定时器的状态从冬眠态转换为运行 态,相当于使用 xTimerStart() 函数。

如果定时器已经处于运行态,使用 xTimerReset() 函数就相当于重新确定超时时间。假设调用 xTimerReset() 的时刻是tX,定时器的周期是n,那么 tX+n 就是重新确定的超时时间。

c
/* 复位定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerReset( TimerHandle_t xTimer, 
                       TickType_t xTicksToWait );
/* 复位定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"停止命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
					BaseType_t *pxHigherPriorityTaskWoken );
  • 修改周期
c
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
  • 使用ID

ID可以作为标志也可以作为参数

c
/* 获得定时器的ID
* xTimer: 哪个定时器
* 返回值: 定时器的ID
*/
void *pvTimerGetTimerID( TimerHandle_t xTimer );
/* 设置定时器的ID
* xTimer: 哪个定时器
* pvNewID: 新ID
* 返回值: 无
*/
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

示例

使用定时器进行硬件消抖

image-20231122083653147

在引脚中断的时候打开一个时钟

image-20231122084311943

image-20231122084335093

使用这一个模拟按键

FreeRTOS实现

c
static void prvTimerTask( void *pvParameters )
{
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;

	for( ;; )
	{
		//获取先下一个时钟的时间, 没有时钟返回0, 以及获取列表是不是空的
        xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
		
		prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

		/* 处理收到的命令 */
		prvProcessReceivedCommands();
	}
}
c
static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty )
{
TickType_t xNextExpireTime;

	*pxListWasEmpty = listLIST_IS_EMPTY( pxCurrentTimerList );
	if( *pxListWasEmpty == pdFALSE )
	{
		xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
	}
	else
	{
		/* Ensure the task unblocks when the tick count rolls over. */
		xNextExpireTime = ( TickType_t ) 0U;
	}

	return xNextExpireTime;
}
c
static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime, BaseType_t xListWasEmpty )
{
TickType_t xTimeNow;
BaseType_t xTimerListsWereSwitched;

	vTaskSuspendAll();
	{
		/* 检测一下时钟有没有溢出, 有的话进行切换 */
		xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
		if( xTimerListsWereSwitched == pdFALSE )
		{
			/* 没有溢出 */
			if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
			{
                //时钟已经到点了
				( void ) xTaskResumeAll();
				prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
			}
			else
			{
				/* 下一个时钟的时间没有到 */
				if( xListWasEmpty != pdFALSE )
				{
					/* 记录一下移溢出时钟是不是也是空的 */
					xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
				}
				//进入阻塞, 等待下一个任务或者一个命令
				vQueueWaitForMessageRestricted( xTimerQueue, ( xNextExpireTime - xTimeNow ), xListWasEmpty );

				if( xTaskResumeAll() == pdFALSE )
				{
					/* Yield to wait for either a command to arrive, or the
					block time to expire.  If a command arrived between the
					critical section being exited and this yield then the yield
					will not cause the task to block. */
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			( void ) xTaskResumeAll();
		}
	}
}
c
void vQueueWaitForMessageRestricted( QueueHandle_t xQueue, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely )
{
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	
    prvLockQueue( pxQueue );
    if( pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0U )
    {
      /* 记录一下需要等待的时间, 以及把这一个事件的链表放在事件以及Delay链表里面 */
        vTaskPlaceOnEventListRestricted( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait, xWaitIndefinitely );
    }
    prvUnlockQueue( pxQueue );
}
c
static void prvProcessExpiredTimer( const TickType_t xNextExpireTime, const TickType_t xTimeNow )
{
BaseType_t xResult;
Timer_t * const pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );

	( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
	traceTIMER_EXPIRED( pxTimer );
    
	if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
	{
		/* 则一个任务需要周期性执行. */
		if( prvInsertTimerInActiveList( pxTimer, ( xNextExpireTime + pxTimer->xTimerPeriodInTicks ), xTimeNow, xNextExpireTime ) != pdFALSE )
		{
			/* 发送一个命令使能下一个时钟 */
			xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
			configASSERT( xResult );
			( void ) xResult;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
}