《Mastering the FreeRTOS Real Time Kernel》读书笔记(4)软定时器

发布时间 2023-10-13 14:58:34作者: BD4RTZ

5.软件定时器管理

软件定时器由FreeRTOS内核实现,并受其控制。它们不需要硬件支持,也与硬件计时器或硬件计数器无关。
软件定时器功能是可选的。包括软件定时器功能:1。作为项目的一部分,构建FreeRTOS源文件FreeRTOS/source/timers.c。2.在FreeRTOSConfig.h中将configUSE_TIMERS设置为1。

5.2 软件定时器回调函数

void ATimerCallback( TimerHandle_t xTimer );

返回值为void,并将软件计时器的句柄作为其唯一参数。
软件定时器回调函数是在启动FreeRTOS调度器时自动创建的任务的上下文中执行的。因此,重要的是,软件计时器回调函数永远不要调用FreeRTOS API函数,这将导致调用任务进入阻止状态。可以调用xQueueReceive()等函数,但前提是函数的xTicksToWait参数(指定函数的块时间)设置为0。

5.3 软件计时器的属性和状态

周期

软件定时器的“周期”是软件定时器启动和软件定时器回调功能执行之间的时间。

一次性与自动装载

1.一次性计时器一旦启动,一次性计时器将只执行一次回调功能。一次性计时器可以手动重新启动,但不会自行重新启动。
2.自动重新加载定时器一旦启动,自动重新加载计时器将在每次到期时重新启动,从而定期执行其回调功能。

两种状态

1.休眠状态:只有句柄,但是回调函数是没有反应的。
2.运行状态:正常状态,回调函数可以被触发。一次性计时器执行完一次回调函数后,回到休眠状态。

5.4 软件定时器的环境

OS的守护进程(Daemon Task)

守护进程,又有叫精灵进程。很多博文都介绍说守护进程是伴随创建其的那个进程(A进程)而存在的。如果A进程没了,其守护进程就没了。OS的守护进程是随着OS启动便一直运行,它是一个标准的FreeRTOS任务,也有优先级和堆栈,分别由configTIMER_TASK_priority和configTIMER_TASK_stack_DEPTH编译时配置常量设置。
软定时器的检测和运行都在RTOS的守护进程中。

定时器命令队列

通过向定时器命令队列发送命令以控制软件定时器,命令有启动定时器、停止定时器、重置定时器。
这个队列是一个标准的FreeRTOS队列,它是在调度程序启动时自动创建的。队列的长度由FreeRTOSConfig.h中的configTIMER_queue_length编译时配置常量设置。
守护进程中的软定时器功能,会接受定时器命令队列中的命令,来进行操作。

守护进程的优先级

守护进程的优先级可以小于包含软件定时器开始和停止命令的任务的优先级,也可以大于其优先级。这两种方式的程序有不同的用途。
通过改变configTIMER_TASK_PRIORITY设置会实现两种执行模式。

5.5 创建和启动一个软件定时器

主要包括创建函数和启动函数。

xTimerCreate()

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

1.pcTimerName:描述性命名。人类可读的命名比单纯的句柄名称好理解得多。
2.xTimerPeriodInTicks:以刻度为单位指定的计时器周期。可使用pdMS_TO_TICKS()宏。
3.uxAutoReload:设置为pdTRUE以创建自动重新加载计时器。设置为pdFALSE以创建一次性计时器。
4.pvTimerID:ID是一个空指针,应用程序编写器可以将其用于任何目的。当多个软件计时器使用相同的回调函数时,ID特别有用,因为它可以用于提供特定于计时器的存储。万用指针。
5.pxCallbackFunction:是指向回调函数名称的指针,用作正在创建的软件计时器的回调函数。
6.返回值:如果返回NULL,则无法创建软件计时器,因为没有足够的堆内存可用于FreeRTOS分配必要的数据结构。

xTimerStart() & xTimerStop()

xTimerStart() 用于启动处于休眠状态的软件定时器,或重置(重新启动)处于运行状态的软件计时器。相同的xTimerStop()用于停止处于运行状态的软件计时器。

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

1.xTimer:正在启动或重置的软件计时器的句柄。句柄将从用于创建软件计时器的xTimerCreate()调用中返回。
2.xTicksToWait:启动命令是需要发送到定时器命令队列的,这里的参数解释与队列发送的xTicksToWait解释相同。
3.返回值:与发送消息给队列的返回值解释相同。

例子13是分别创建一次性和自动装填软件定时器的例程。

5.6 定时器的ID

每个软件定时器都有一个ID,这是一个标签值,应用程序编写器可以将其用于任何目的。ID存储在一个void指针(void*)中,因此可以直接存储一个整数值,指向任何其他对象,或用作函数指针。

创建软件计时器时,会为ID分配一个初始值,之后可以使用vTimerSetTimerID()API函数更新ID,并使用pvTimerGetTimerID()API函数查询ID。与其他软件计时器API函数不同的是,vTimerSetTimerID()和pvTimerGetTimerID()直接访问软件计时器—它们不向计时器命令队列发送命令。

vTimerSetTimerID()

void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );

pvTimerGetTimerID()

void *pvTimerGetTimerID( TimerHandle_t xTimer );

例子14:定时器ID的使用场景

示例14创建了与示例13创建的功能类似的功能,但为两个软件定时器分配了相同的回调功能。

/* Create the one shot timer software timer, storing the handle in xOneShotTimer. */
xOneShotTimer = xTimerCreate( "OneShot",
                                mainONE_SHOT_TIMER_PERIOD,
                                pdFALSE,
                                /* The timer’s ID is initialized to 0. */
                                0,
                                /* prvTimerCallback() is used by both timers. */
                                prvTimerCallback );

/* Create the auto-reload software timer, storing the handle in xAutoReloadTimer */
xAutoReloadTimer = xTimerCreate( "AutoReload",
                                  mainAUTO_RELOAD_TIMER_PERIOD,
                                  pdTRUE,
                                  /* The timer’s ID is initialized to 0. */
                                  0,
                                  /* prvTimerCallback() is used by both timers. 
                                  这里使用了相同的回调函数 */
                                  prvTimerCallback );

static void prvTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
uint32_t ulExecutionCount;
  /* A count of the number of times this software timer has expired is stored in the         timer's
  ID. Obtain the ID, increment it, then save it as the new ID value. The ID is a void
  pointer, so is cast to a uint32_t. */
  ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
  ulExecutionCount++;
  vTimerSetTimerID( xTimer, ( void * ) ulExecutionCount );
  /* Obtain the current tick count. */
  xTimeNow = xTaskGetTickCount();
  /* The handle of the one-shot timer was stored in xOneShotTimer when the timer was   created.
  Compare the handle passed into this function with xOneShotTimer to determine if it was the
  one-shot or auto-reload timer that expired, then output a string to show the time at which
  the callback was executed. */
  if( xTimer == xOneShotTimer )
  {
    vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
  }
  else
  {
    /* xTimer did not equal xOneShotTimer, so it must have been the auto-reload timer that
    expired. */
    vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
    if( ulExecutionCount == 5 )
    {
      /* Stop the auto-reload timer after it has executed 5 times. This callback function
      executes in the context of the RTOS daemon task so must not call any functions that
      might place the daemon task into the Blocked state. Therefore a block time of 0 is
      used. */
      xTimerStop( xTimer, 0 );
    }
  }
}

在同一个回调函数中,判断是哪个定时器触发的,并且执行该定时器相应的打印任务。

5.7 更改定时器的值

可以实现类似呼吸灯等需要改变“重装填值”的效果。

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                                TickType_t xNewTimerPeriodInTicks,
                                TickType_t xTicksToWait );

1.xTimer:定时器的句柄,由create返回。
2.xNewTimerPeriodInTicks:新的重装填值。
3.xTicksToWait:由于这个函数也算是一个命令,所以也需要发送到命令队列,也必须有一个最长等待时间。

5.8 重置一个软件定时器

官方解释:重置软件计时器意味着重新启动计时器;计时器的到期时间将重新计算为相对于计时器重置的时间,而不是计时器最初启动的时间。

也就是将定时器内部已经积累的计数值清零。

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

这个函数也是一个命令,需要发送到定时器命令队列。

例子15讲述了如何使用重置软件定时器功能,来阻止一些定时事件的发生。

总结

其实软件定时器,在裸机程序中,就应该是已经很好用的了。
使用一个1ms的硬件定时器,通过每次进硬件中断后进行++的计数。并在每次都判断计数值是否达到阈值,以达到比如10ms软定时器中断、100ms软定时器中断、1s的软定时器中断。但是这些中断不能处理很多信息,一定不能超过1ms,不然会影响下一次进硬中断,导致程序卡死。

本章节就是讲了一下如何使用FreeRTOS的框架实现软件定时器的,利用前面讲的任务和队列,将软件定时器的实现规范化,使得在FreeRTOS的心跳时钟下,还能分出很多软件定时器和它们的回调函数,来处理不同的信息。

具体的使用和自己的案例就不在读书笔记中分享了。