软件定时器用于在将来的设定时间或以固定频率定期计划功能的执行。软件定时器执行的功能称为软件定时器的回调函数。软件定时器由FreeRTOS内核实现并受其控制。它们不需要硬件支持,并且与硬件计时器或硬件计数器无关。
软件计时器功能是可选的。包括软件计时器功能:
1. 作为项目的一部分,构建FreeRTOS源文件FreeRTOS / Source / timers.c。
2. 在FreeRTOSConfig.h中将configUSE_TIMERS设置为1。
1. 软件定时器回调函数
软件定时器回调函数原型必须返回void,并将软件定时器的句柄作为其唯一参数。其原型如下所示:
void ATimerCallback(TimerHandle_t xTimer);
软件定时器回调函数从头到尾执行,然后以正常方式退出。它们应保持简短,并且不得进入“阻塞”状态。
注意:可以看到,软件定时器回调函数在启动FreeRTOS调度程序时自动创建的任务的上下文中执行。因此,至关重要的是,软件定时器回调函数切勿调用FreeRTOS API函数,这将导致调用任务进入“阻止”状态。可以调用xQueueReceive()之类的函数,但前提是该函数的xTicksToWait参数(指定该函数的阻止时间)设置为0。调用vTaskDelay()之类的函数不能像调用vTaskDelay()那样进行将始终将调用任务置于“阻止”状态。
软件定时器的属性和状态
软件定时器的“周期”是软件计时器启动到执行软件定时器的回调函数之间的时间。
1. 单次定时器
一旦启动,单次定时器将仅执行一次其回调函数。单次定时器可以手动重启,但不会自动重启。
2. 自动重装定时器
一旦启动,自动重载定时器将在每次到期时自动重新启动,从而导致其回调函数的定期执行。
下图显示了单触发定时器和自动重载定时器之间的行为差异。垂直虚线表示滴答中断发生的时间。
定时器1是单拍计时器,周期为6个滴答。它在时间t1开始,因此其回调函数在时间t7之后执行6个滴答。由于计时器1是单触发计时器,因此其回调函数不会再次执行。
定时器2是一个自动重载定时器,周期为5个滴答。它在时间t1开始,因此其回调函数在时间t1之后每5个滴答执行一次。再上图中,这是在时间t6,t11和t16。
- 软件定时器的状态
软件定时器可以处于以下两种状态之一:
1.休眠
休眠软件计时器存在,并且可以由其句柄引用,但未运行,因此其回调函数将不会执行。
2.运行
从软件定时器进入“运行”状态以或从最后一次重置软件定时器,运行中的软件定时器将在定时器的周期时间后执行其回调函数。
下面两图分别显示了自动重载定时器和单次定时器在休眠和运行状态之间的可能转换。这两个图之间的主要区别是计时器到期后进入的状态;自动重载定时器执行其回调函数,然后重新进入运行状态,单次定时器执行其回调函数,然后进入休眠状态。自动重载定时器在休眠和运行状态之间的转换如下图所示:
单次定时器在休眠和运行状态之间的转换如下图所示:
所有软件计时器回调函数都在同一RTOS守护程序(或“定时器服务”)任务的上下文中执行。
守护程序任务是标准的FreeRTOS任务,它在调度程序启动时自动创建。它的优先级和堆栈大小分别由configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH编译时间配置常量设置。这两个常量都在FreeRTOSConfig.h中定义。
软件计时器回调函数不得调用FreeRTOS API函数,否则将导致调用任务进入“阻止”状态,否则将导致守护程序任务进入“阻止”状态。
软件计时器API函数在称为“计时器命令队列”的队列上将命令从调用任务发送到守护程序任务。如下图所示。命令示例包括“启动定时器”,“停止定时器”和“重置定时器”,定时器命令队列是标准的FreeRTOS队列,在调度程序启动时会自动创建。定时器命令队列的长度由FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH编译时间配置常量设置。
守护程序任务的安排与其他FreeRTOS任务一样;当它是能够运行的最高优先级任务时,它将仅处理命令或执行计时器回调函数。下两图演示了configTIMER_TASK_PRIORITY设置如何影响执行模式。
下图显示了守护程序任务的优先级低于调用xTimerStart()API函数的任务的优先级时的执行模式。
1. 在t1时刻
任务1处于“正在运行”状态,守护程序任务处于“已阻止”状态。如果将命令发送到计时器命令队列(在这种情况下它将处理该命令),或者如果软件计时器到期(在这种情况下它将执行软件计时器的回调功能),则守护程序任务将退出“已阻塞”状态。
2. 在t2时刻
任务1调用xTimerStart(),xTimerStart()将命令发送到计时器命令队列,导致守护程序任务退出“阻塞”状态。任务1的优先级高于守护程序任务的优先级,因此守护程序任务不会抢占任务1。任务1仍处于“运行”状态,并且守护程序任务已离开“已阻塞”状态并进入“就绪”状态。
3. 在t3时刻
任务1完成了xTimerStart()函数的执行。任务1从函数开始到函数结束执行了xTimerStart(),而没有离开“运行”状态。
4. 在t4时刻
任务1调用一个API函数,导致该函数进入“已阻止”状态。现在,守护程序任务是处于“就绪”状态的优先级最高的任务,因此调度程序选择守护程序任务作为进入“运行”状态的任务。然后,守护程序任务开始处理任务1发送到计时器命令队列的命令。
注意:启动软件计时器将到期的时间是从“启动计时器”命令发送到计时器命令队列的时间开始计算的-它不是从守护程序任务收到“启动计时器”的时间开始计算的计时器命令队列中的命令。
5. 在t5时刻
守护程序任务已完成对任务1发送给它的命令的处理,并尝试从定时器命令队列中接收更多数据。timer命令队列为空,因此守护程序任务重新进入Blocked状态。如果将命令发送到计时器命令队列,或者软件定时器到期,则守护程序任务将再次离开“已阻止”状态。现在,“空闲”任务是处于“就绪”状态的优先级最高的任务,因此调度程序选择“空闲”任务作为进入“运行”状态的任务
下图显示了调用xTimerStart()的任务的优先级为高于守护程序任务的优先级
1.t1时刻
和以前一样,任务1处于“运行”状态,守护程序任务处于“已阻塞”状态。
2. t2时刻
任务1调用xTimerStart(),xTimerStart()将命令发送到定时器命令队列,导致守护程序任务退出“阻塞”状态。守护程序任务的优先级高于任务1的优先级,因此调度程序选择守护程序任务作为进入运行状态的任务。任务1在完成执行xTimerStart()函数之前被守护程序任务抢占,现在处于就绪状态。守护程序任务开始处理任务1发送到定时器命令队列的命令。
3. t3时刻
守护程序任务已完成对任务1发送给它的命令的处理,并尝试从定时器命令队列中接收更多数据。定时器命令队列为空,因此守护程序任务重新进入阻塞状态。现在,任务1是处于“就绪”状态的优先级最高的任务,因此调度程序选择任务1作为进入“运行”状态的任务。
4. t4时刻
任务1在完成执行xTimerStart()函数之前被守护程序任务抢占,并且只有在其重新进入运行状态后才退出xTimerStart()(从中返回)。
5. t5时刻
任务1调用一个API函数,导致该函数进入“已阻止”状态。现在,“空闲”任务是处于“就绪”状态的优先级最高的任务,因此调度程序选择“空闲”任务作为进入“运行”状态的任务。
在上图1所示的场景中,任务1向计时器命令队列发送命令与守护程序任务接收和处理命令之间的时间间隔。在上图 2所示的场景中,守护程序任务在任务1从发送命令的函数返回之前,已经接收并处理了任务1发送给它的命令。
发送到定时器命令队列的命令包含时间戳。时间戳记用于说明应用程序任务发送的命令与守护程序任务处理的同一命令之间经过的任何时间。例如,如果发送了“启动定时器”命令来启动一个周期为10滴答的定时器,则时间戳将用于确保正在启动的定时器在发送命令后10滴答应到期,而不是在发送命令后10滴答应到期。该命令已由守护程序任务处理。
FreeRTOS V9.0.0还包含xTimerCreateStatic()函数,该函数在编译时静态分配创建计时器所需的内存:必须明确创建一个软件计时器,然后才能使用它。
软件定时器由类型为TimerHandle_t的变量引用。xTimerCreate()用于创建软件定时器,并返回TimerHandle_t引用其创建的软件定时器。软件定时器在休眠状态下创建。可以在调度程序运行之前创建软件定时器,也可以在调度程序启动之后根据任务创建软件定时器。
TimerHandle_t xTimerCreate(const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void* pvTimerID,
TimerCallbackFunction_t pxCallbackFunction);
参数名称/返回值 | 描述 |
pcTimerName | 定时器的描述性名称。FreeRTOS不会以任何方式使用它。它纯粹是作为调试辅助工具包含在内的。通过人可读的名称来标识定时器比通过其句柄来标识定时器要简单得多。 |
xTimerPeriodInTicks | 定时器的周期,以刻度为单位。pdMS_TO_TICKS()宏可用于将以毫秒为单位的时间转换为以滴答数为单位的时间。 |
uxAutoReload | 将uxAutoReload设置为pdTRUE以创建自动重装定时器。将uxAutoReload设置为pdFALSE可以创建一个单次定时器 |
pvTimerID | 每个软件定时器都有一个ID值。该ID是一个空指针,可由应用程序编写者出于任何目的使用。当多个软件定时器使用相同的回调函数时,该ID特别有用,因为它可用于提供特定于定时器的存储。 pvTimerID为正在创建的任务的ID设置初始值。 |
pxCallbackFunction | 软件定时器回调函数,pxCallbackFunction参数是指向该函数的指针(实际上,只是函数名称),用作创建的软件计时器的回调函数。 |
return | 如果返回NULL,则不能创建软件定时器,因为FreeRTOS没有足够的堆内存来分配必要的数据结构。
返回的非NULL值表示已成功创建软件定时器。返回的值是创建的定时器的句柄。 |
xTimerStart()用于启动处于休眠状态的软件定时器,或重置(重新启动)处于运行状态的软件定时器。xTimerStop()用于停止处于“运行”状态的软件定时器。停止软件定时器与将计时器转换为休眠状态相同。可以在启动调度程序之前调用xTimerStart(),但完成此操作后,软件定时器将不会真正启动,直到调度程序启动时为止。
BaseType_t xTimerStart(TimerHandle_t xTimer,TickType_t xTicksToWait);
参数名称/返回值 | 描述 |
xTimer | 正在启动或重置软件定时器的句柄。该句柄将从用于创建软件定时器xTimerCreate()的调用中返回。 |
xTicksToWait | xTimerStart()使用计时器命令队列将“启动计时器”命令发送到守护程序任务。xTicksToWait指定在队列已满的情况下,调用任务应保持在阻塞状态以等待计时器命令队列上的空间可用的最长时间。 1. 如果xTicksToWait为零并且计时器命令队列已满,则xTimerStart()将立即返回。 块时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位的时间转换为以刻度为单位的时间。 2. 如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地保持在阻塞状态(无超时),以等待计时器命令队列中的可用空间。 3.如果在启动调度程序之前调用了xTimerStart(),则xTicksToWait的值将被忽略,并且xTimerStart()的行为就像 xTicksToWait已设置为零。 |
返回值 | 有两个可能的返回值: 1. pdPASS 仅当“启动计时器”命令成功发送到定时器命令队列时,才会返回pdPASS。 如果守护程序任务的优先级高于名为xTimerStart()的任务的优先级,则调度程序将确保在xTimerStart()返回之前处理启动命令。这是因为一旦定时器命令队列中有数据,守护程序任务就会抢占名为xTimerStart()的任务。 如果指定了阻止时间(xTicksToWait不为零),则有可能将调用任务置于“阻塞”状态,以等待定时器命令队列中的空间在函数返回之前可用,但数据在阻塞时间到期之前已成功写入定时器命令队列。 2. pdFALSE 如果由于队列已满而无法将“启动定时器”命令写入定时器命令队列,则将返回pdFALSE。 如果指定了阻塞时间(xTicksToWait不为零),则调用任务将被置于“阻塞”状态,以等待守护程序任务在定时器命令队列中腾出空间,但是指定的阻塞时间在此之前已过期。 |
每个软件定时器都有一个ID,它是标签值,应用程序编写者可以出于任何目的使用它。该ID存储在一个void指针(void *)中,因此可以直接存储一个整数值,指向任何其他对象或用作函数指针。
创建软件定时器时,会为ID分配一个初始值,之后可以使用vTimerSetTimerID()API函数更新ID,然后使pvTimerGetTimerID()API函数查询ID。与其他软件定时器API函数不同,vTimerSetTimerID()和pvTimerGetTimerID()直接访问软件定时器-它们不会将命令发送到计时器命令队列。
void vTimerSetTimerID(const TimerHandle_t xTimer,void * pvNewID);
参数名称/返回值 |
描述 |
xTimer | 用新的ID值更新软件定时器的句柄。该句柄将从用于创建软件定时器的xTimerCreate()的调用中返回。 |
pvNewID |
将设置软件计时器ID的值。 |
void* pvTimerGetTimerID(TimerHandle_t xTimer);
参数名称/返回值 |
描述 |
xTimer |
查询软件计时器的句柄。该句柄将从用于创建软件计时器的xTimerCreate()的调用中返回。 |
返回值 |
查询的软件定时器的ID。 |
使用xTimerChangePeriod()函数可以更改软件定时器的周期。
如果使用xTimerChangePeriod()来更改已经运行的定时器的周期,则该定时器将使用新的周期值重新计算其到期时间。重新计算的到期时间与调用xTimerChangePeriod()的时间有关,而不与最初启动计时器的时间有关。如果使用xTimerChangePeriod()更改处于休眠状态的计时器(未运行的计时器)的时间段,则该计时器将计算到期时间,并转换为正在运行状态(计时器将开始运行) 。
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait);
参数名称/返回值 | 描述 |
xTimer | 用新的周期值更新软件定时器的句柄。该句柄将从用于创建软件定时器的xTimerCreate()的调用中返回。 |
xTimerPeriodInTicks | 软件定时器的新时间段,以刻度为单位。pdMS_TO_TICKS()宏可用于将以毫秒为单位的时间转换为以滴答数为单位的时间。 |
xTicksToWait | xTimerChangePeriod()使用定时器命令队列将“更改周期”命令发送到守护程序任务。xTicksToWait指定在队列已满的情况下,调用任务应保持在阻塞状态以等待定时器命令队列上的空间可用的最长时间。
1. 如果xTicksToWait为零并且计时器命令队列已满,则xTimerChangePeriod()将立即返回。宏pdMS_TO_TICKS()可用于将以毫秒为单位的时间转换为以刻度为单位的时间。 2.如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地保持在Blocked状态(没有超时),以等待计时器命令队列中的可用空间。 3.如果在启动调度程序之前调用了xTimerChangePeriod(),则xTicksToWait的值将被忽略,并且xTimerChangePeriod()的行为就像xTicksToWait被设置为零一样。 |
返回值 | 有两个可能的返回值: 仅当数据成功发送到计时器命令队列时,才会返回pdPASS。
如果指定了阻止时间(xTicksToWait不为零),则有可能将调用任务置于“阻止”状态,以等待计时器命令队列中的空间在函数返回之前可用,但数据在阻塞时间到期之前已成功写入定时器命令队列。
如果由于队列已满而无法将'change period'命令写入计时器命令队列,将返回pdFALSE。
如果指定了阻塞时间(xTicksToWait不为零),则调用任务将被置于“阻塞”状态,以等待守护程序任务在队列中腾出空间,但是指定的阻塞时间在此之前已过期。 |
重置软件计时器意味着要重新启动定时器。定时器的到期时间重新计算是相对于定时器重置的时间,而不是相对于定时器最初启动的时间。下图显示了这一点,该图显示了一个计时器,该计时器的周期为6,开始计时,然后复位两次,最后终止并执行其回调函数。
1. 定时器1在时间t1启动。它的周期为6,因此执行回调函数的时间最初计算为t7,即启动后的6个滴答。
2. 定时器1在到达时间t7之前复位,因此在它到期之前并执行其回调函数。 定时器1在时间t5重置,因此它将执行其回调函数的时间重新计算为t11,即重置后的6个滴答。
3. 定时器1在时间t11之前再次复位,因此在其到期并执行其回调函数之前再次复位。 定时器1在时间t9重置,因此它将执行其回调函数的时间重新计算为t15,即上一次重置后的6个滴答。
4. 定时器1不会再次复位,因此它将在时间t15到期,并相应执行其回调函数。
使用xTimerReset()API函数重置定时器。xTimerReset()也可用于启动处于休眠状态的计时器。
BaseType_t xTimerReset(imerHandle_t xTimer,TickType_t xTicksToWait);
参数名称/返回值 |
描述 |
xTimer | 重置或启动软件定时器的句柄。该句柄将从用于创建软件定时器的xTimerCreate()的调用中返回。 |
xTicksToWait | xTimerReset()使用定时器命令队列将“重置”命令发送到守护程序任务。xTicksToWait指定在队列已满的情况下,调用任务应保持在阻塞状态以等待计时器命令队列上的空间可用的最长时间。 1.如果xTicksToWait为零并且计时器命令队列已满,则xTimerReset()将立即返回。 2.如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地保持在阻塞状态(无超时),以等待 定时器命令队列中的可用空间。 |
返回值 | 有两个可能的返回值: 1.pdPASS 仅当数据成功发送到 定时器命令队列时,才会返回pdPASS。
如果指定了阻止时间(xTicksToWait不为零),则有可能将调用任务置于“阻塞”状态,以等待 定时器命令队列中的空间在函数返回之前可用,但数据已成功写入阻止时间到期之前的 定时器命令队列。 2.pdFALSE
如果由于队列已满而无法将“重置”命令写入定时器命令队列,则将返回pdFALSE。
如果指定了阻塞时间(xTicksToWait不为零),则调用任务将被置于“阻塞”状态,以等待守护程序任务在队列中腾出空间,但是指定的阻塞时间在此之前已过期。 |