【FreeRTOS定时器】
软件定时器用于在未来的某个设定时间,进行调度函数的执行,或者按照固定的频率周期性执行。将软件定时器执行的函数称为软件定时器的回调函数。软件定时器由FreeRTOS内核实现并受其控制。它软件定时器的回调函数是通过C函数来实现的。唯一特别的地方是回调函数的原型必须返回void,并且仅将软件定时器的句柄作为唯一的参数。软件定时器回调函数的原型如下所示。们不需要硬件支持,也与硬件定时器或硬件计数器无关。示
软件定时器的回调函数
软件定时器用于在未来的某个设定时间,进行调度函数的执行,或者按照固定的频率周期性执行。将软件定时器执行的函数称为软件定时器的回调函数。软件定时器由FreeRTOS内核实现并受其控制。
它软件定时器的回调函数是通过C函数来实现的。唯一特别的地方是回调函数的原型必须返回void,并且仅将软件定时器的句柄作为唯一的参数。软件定时器回调函数的原型如下所示。们不需要硬件支持,也与硬件定时器或硬件计数器无关。
函数的原型如下所示:
void ATimerCallback( TimerHandle_t xTimer );
示例
// 回调函数的具体实现(函数体)
void ATimerCallback( TimerHandle_t xTimer )
{
// 判断触发当前回调的是哪个定时器
if( xTimer == 定时器1的句柄 ){
执行“刷新页面数据”; // 定时器1触发时做的事
}
else if( xTimer == 定时器2的句柄 ){
执行“检查网络连接”; // 定时器2触发时做的事
}
}
软件定时器回调函数从开始执行到结束,以及退出都以正常方式进行。软件定时器回调函数应该保持简短,并且绝对不能进入阻塞状态。
软件定时器的两种类型
一次性定时器:一旦启动,一次性定时器只执行一次回调函数。一次性定时器可以手动重新启动,但不会自动重启。
自动重载定时器:一旦启动,自动重装定时器将在每次到期时自动重新启动,从而导致回调函数的周期性执行。
软件定时器可能处于以下两种状态中的一种。
休眠状态:休眠状态的软件定时器是存在的,可通过其句柄引用,但没有运行,因此其回调函数不会执行。
运行状态:软件定时器进入运行状态或最后一次重置后,将在与其周期相等的时间过去后执行其回调函数。
软件定时器的上下文
RTOS守护(定时器服务)任务
所有软件定时器的回调函数都在同一个RTOS守护任务(该任务过去被称为“定时器服务任务” )的上下文中执行。RTOS 的定时器服务(守护)任务是内核自带的系统级后台任务,专门负责统一管理所有软件定时器的计时和超时事件处理,相当于整个系统的 “定时器管家”,避免每个应用任务自己轮询计时,既节省 CPU 资源,又保证定时器的精准性。
守护任务是标准的FreeRTOS任务,当调度器启动时会自动创建。其优先级和堆栈大小分别由编译时配置常量需先配置configUSE_TIMERS = 1,内核会自动创建定时器服务任务(优先级由configTIMER_TASK_PRIORITY配置,通常设为高优先级)。
软件定时器回调函数不得调用会导致调用任务进入阻塞状态的FreeRTOS API函数,因为这样做会导致守护任务进入阻塞状态。例如,调用如vTaskDelay()这样的函数则是不可以的,因为调用vTaskDelay()总会将调用任务置于阻塞状态。
定时器命令队列
软件定时器API函数通过名为“定时器命令队列”的队列,将命令从调用任务发送到守护进程任务。命令包括“启动定时器”、“停止定时器”和“重置定时器”。
定时器命令队列是标准的FreeRTOS队列,在调度器启动时会自动创建。定时器命令队列的长度由FreeRTOSConfig.h文件中的configTIMER_QUEUE_LENGTH编译时配置常量设置。
守护任务的调度
守护任务像其他FreeRTOS任务一样进行调度;只有当守护任务是能够运行的最高优先级任务时,它才会处理命令或执行定时器回调函数。configTIMER_TASK_PRIORITY的设置将影响执行模式。
发送到定时器命令队列的命令包含时间戳。时间戳用于计算从应用程序任务发送命令到守护任务处理同一命令之间所经过的时间。
定时器相关函数
函数xTimerDelete()
xTimerDelete() API函数用于删除定时器。可以在任何时候删除定时器。其函数的原型如下所示:
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xBlockTime);
xTimerDelete () API函数的参数与返回值说明如下所示:
| 参数名称 | 参数说明 |
|---|---|
| xTimer | 正在删除的软件定时器的句柄。 |
| xBlockTime或xTicksToWait | 在调用xTimerDelete() 时队列已满的情况下,指定调用任务应保持阻塞状态以等待删除命令成功发送到定时器命令队列的时间 (以滴答为单位)。 如果启动 RTOS 调度器之前调用 xTimerStop(),则会忽略 xBlockTime。 |
| 返回值 | pdPASS:软件定时器删除成功。pdFAIL:如果在经过xBlockTime个滴答时间后,删除命令仍无法发送到定时器命令队列,则返回pdFAIL。 |
函数xTimerCreate()
FreeRTOS还包含xTimerCreateStatic()函数,该函数在编译时静态分配创建定时器所需的内存。在使用软件定时器之前,必须显式地创建它。
软件定时器由类型为TimerHandle_t的变量引用。使用xTimerCreate() API函数用于创建软件定时器,该函数返回TimerHandle_t类型的变量来引用它创建的软件定时器。软件定时器在创建时处于休眠状态。
可以在调度器运行之前创建软件定时器,也可以在调度器启动后在任务中创建软件定时器。
xTimerCreate() API 函数的原型如下所示:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
xTimerCreate() API函数的参数与返回值说明如下:
| 参数名称 | 参数说明 |
|---|---|
| pcTimerName | 定时器的描述性名称。FreeRTOS 并不会以任何方式使用它。纯粹作为辅助调试工具。 |
| xTimerPeriodInTicks | 定时器的周期,以滴答为单位。可以使用pdMS_TO_TICKS()宏将毫秒为单位的时间转换为以滴答为单位的时间。不能为0。 |
| uxAutoReload | 设置xAutoReload为 pdTRUE,创建自动重载定时器。设置 xAutoReload 为 pdFALSE,创建一次性定时器。 |
| pvTimerID | 每个软件定时器都有ID值。该ID是一个空指针,可以由编程人员随意使用。当相同回调函数被多个软件定时器使用时,可以使用ID来提供与定时器相关的存储。 |
| pxCallbackFunction | 软件定时器的回调函数(void ATimerCallback( TimerHandle_t xTimer ))是C语言函数。pxCallbackFunction参数是指向函数的指针(实际上只是函数名),用来作为正在创建的软件定时器的回调函数。 |
| 返回值 | 如果返回NULL,则表示无法创建软件定时器,因为没有足够的堆内存供FreeRTOS来分配必要的数据结构。如果返回非NULL值,则表示软件定时器已成功创建。返回的值是已创建定时器的句柄。 |
函数xTimerStart()
xTimerStart() API函数用于启动处于休眠状态的软件定时器,或重置(重新启动)处于运行状态的软件定时器。xTimerStop() API函数用于停止处于运行状态的软件定时器。停止软件定时器和将定时器转换到休眠状态,实现的功能是相同的。
xTimerStart() API函数可以在调度器启动之前调用,但是,这样做的结果是直到调度器启动时,软件定时器才会真正启动。
注意:不要在中断服务程序中调用xTimerStart() API函数。应该使用中断安全版本的xTimerStartFromISR() API函数来替代。
xTimerStart () API 函数的原型如下所示:
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
xTimerStart() API函数的参数与返回值说明如下:
| 参数名称 | 参数说明 |
|---|---|
| xTimer | 正在启动的软件定时器的句柄。 |
| xTicksToWait | xTimerStart()函数使用定时器命令队列将“启动定时器”命令发送给守护任务。xTicksToWait指定了在定时器命令队列已满时,调用任务在阻塞状态下等待定时器命令队列空间可用的最大时间。如果xTicksToWait为0且定时器命令队列已满,xTimerStart() 函数将立即返回。如果在调度器启动之前调用xTimerStart(),则忽略xTicksToWait的值,并且xTimerStart()的行为就像xTicksToWait被设置为0一样。 |
| 返回值 | pdPASS:软件定时器启动成功。pdFAIL:如果在经过xTicksToWait个滴答时间后,启动命令仍无法发送到定时器命令队列,则返回pdFAIL。 |
函数vTimerSetTimerID()
使用vTimerSetTimerID() API函数更新定时器ID,软件定时器周期更改会直接影响回调函数的执行频率,周期变长,执行频率降低,反之则升高;定时器周期更改可能会影响任务调度,比如原本定时触发的任务因周期改变,其执行时间和顺序可能发生变化。软件定时器周期更改与系统功耗没有直接关系,也不会影响定时器基于系统滴答定时器的精度。,vTimerSetTimerID() API 函数的原型如下所示:
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
vTimerSetTimerID () API函数的参数说明如下所示:
| 参数名称 | 参数说明 |
|---|---|
| xTimer | 被更新为新ID值的软件定时器的句柄。这个句柄是由用于创建软件定时器的xTimerCreate()函数调用中返回的。 |
| pvNewID | 软件定时器的ID会被更新为该值。 |
该函数的使用示例如下所示:
/* 分配给定时器的回调函数。 */
void TimerCallbackFunction( TimerHandle_t pxExpiredTimer )
{
uint32_t ulCallCount;
/* 此定时器过期并执行其回调函数的次数存储在定时器的ID中。
检索计数,将其递增,然后将其保存回定时器的ID中。 */
ulCallCount =( uint32_t ) pvTimerGetTimerID( pxExpiredTimer );
ulCallCount++;
vTimerSetTimerID( pxExpiredTimer, ( void * ) ulCallCount );
}
上述代码描述了一个定时器回调函数TimerCallbackFunction,该函数会在定时器到期时被调用。在回调函数中,它会检索定时器的当前到期次数(存储在定时器的ID中),将其递增,然后将新的到期次数保存回定时器的ID中。通过这种方式,可以跟踪定时器回调函数的执行次数。
函数pvTimerGetTimerID()
pvTimerGetTimerID() API函数查询ID,pvTimerGetTimerID() API 函数的原型如下所示:
void *pvTimerGetTimerID( const TimerHandle_t xTimer );
pvTimerGetTimerID() API函数的参数与返回值说明如下所示:
| 参数名称 | 参数说明 |
|---|---|
| xTimer | 被查询的软件定时器的句柄。该句柄将通过调用xTimerCreate()函数创建软件定时器时返回。 |
| 返回值 | 被查询的软件定时器的ID。 |
函数xTimerReset()
定时器通过xTimerReset() API函数进行重置。
xTimerReset() API函数也可以用于启动处于休眠状态的定时器。
注意:千万不要在中断服务程序中调用xTimerReset() API函数。应该使用中断安全版本xTimerResetFromISR() API函数来替代。
xTimerReset() API 函数的原型如下所示:
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
xTimerReset() API函数的参数与返回值说明如下所示:
| 参数名称 | 参数说明 |
|---|---|
| xTimer | 被重置或启动的软件定时器的句柄。该句柄将通过调用xTimerCreate()函数返回,该函数用于创建软件定时器。 |
| xTicksToWait | xTimerReset () 函数使用定时器命令队列向守护任务发送“重置”命令。如果定时器命令队列已满,xTicksToWait 指定了调用任务在定时器命令队列空间可用之前应保持在阻塞状态的最大时间。如果 xTicksToWait 为0且定时器命令队列已满,xTimerReset()将立即返回。 |
| 返回值 | pdPASS:重置或启动定时器成功。pdFAIL:重置或启动定时器失败。 |
软件定时器与硬件定时器的区别
软件定时器由 FreeRTOS 内核实现并控制,不需要硬件支持,与硬件定时器或硬件计数器无关。它在不执行回调函数时不会占用处理时间,通过软件方式模拟定时功能,依赖于 FreeRTOS 的调度机制和定时器命令队列来管理。而硬件定时器是基于硬件电路实现的定时功能,通常由专门的硬件定时器模块提供精确的定时中断,与 CPU 的执行状态相对独立,能提供更高精度和更稳定的定时,常用于对时间精度要求极高的场景,如实时时钟、PWM 信号生成等。软件定时器更灵活,可在任务中动态创建和管理,适用于对定时精度要求不那么严格的一般性定时任务;硬件定时器则侧重于满足高精度定时需求,但配置和使用相对复杂。
详细阐述在一个基于FreeRTOS的智能环境监测系统中,如何利用软件定时器实现周期性的数据采集和处理,并分析其优势和可能存在的问题。
在基于 FreeRTOS 的智能环境监测系统中,利用软件定时器实现周期性数据采集和处理步骤如下:首先,创建一个周期性软件定时器。使用xTimerCreate()函数,设置uxAutoReload参数为pdTRUE,表示周期性定时器。根据实际需求设置合适的定时周期,例如每 5 秒采集一次数据。然后,编写定时器的回调函数。在回调函数中,实现数据采集和处理的逻辑。比如,读取温度传感器、湿度传感器等环境监测设备的数据,并进行数据校验、滤波等处理。处理完成后,可以将数据通过队列发送给其他任务进行进一步分析和存储。接着,启动软件定时器。使用xTimerStart()函数,传入创建好的定时器句柄,使定时器开始工作。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)