定时器
定时器 是利用内核系统时钟测量经过时间的内核对象。到达指定时限时,定时器可以执行应用定义的操作,也可以仅记录到期事件,等待应用读取其状态。
概念
可以定义任意数量的定时器(仅受可用 RAM 的限制)。每个定时器通过其内存地址引用。
定时器具有以下主要属性:
持续时间,指定定时器首次到期前的时间间隔。这是一个
k_timeout_t值,可使用不同单位进行初始化。周期,指定首次到期后各次到期之间的时间间隔,同样是
k_timeout_t值,且必须非负。周期为K_NO_WAIT(即零)或K_FOREVER表示该定时器为单次定时器,到期一次后即停止。(例如,若启动定时器时持续时间设为 200、周期设为 75,则会在 200 ms 后首次到期,此后每隔 75 ms 到期一次。)到期函数,每次定时器到期时执行。该函数由系统时钟中断处理程序执行。如果不需要到期函数,可以将函数指定为
NULL。停止函数,当运行中的定时器被提前停止时执行。该函数由停止定时器的线程执行。如果不需要停止函数,可以将函数指定为
NULL。状态 值,表示自上次读取状态值以来,定时器已到期的次数。
定时器使用前必须初始化。初始化会指定到期函数和停止函数,将定时器状态置零,并将定时器置于 停止 状态。
通过指定持续时间和周期来 启动 定时器。其状态会重置为零,随后定时器进入 运行 状态,开始倒计时直至到期。
备注
定时器的持续时间是相对于启动时刻的 最小 延迟。定时器的周期是相对于上次“应当”到期时刻的 最小 延迟。这意味着周期定时器不会相对于系统定时器产生漂移,实际的周期间隔可能短于或长于指定周期。
若要保证定时器到期前的最小延迟,应在 到期函数 内或定时器到期后重新启动定时器。
延迟的变化来自系统中的不确定因素,例如中断处理延迟和先前定时器处理程序的执行时间。
运行中的定时器到期时,其状态值递增,并执行到期函数(如果存在);如果有线程正在等待该定时器,则解除其阻塞。如果定时器周期为零,定时器进入停止状态;否则,定时器重新启动,新的持续时间根据“应当”到期的时刻和周期计算,使下次到期对齐到预定周期。
如有需要,可以在倒计时期间停止运行中的定时器。定时器状态值保持不变,然后进入停止状态并执行停止函数(如果存在)。如果有线程正在等待该定时器,则解除其阻塞。允许尝试停止未运行的定时器,但由于它已经停止,因此不会产生任何影响。
如有需要,可以在倒计时期间重新启动运行中的定时器。定时器状态重置为零,然后使用调用者指定的新持续时间和周期开始倒计时。如果有线程正在等待该定时器,则继续等待。
可以随时直接读取定时器状态,以确定自上次读取状态以来定时器到期的次数。读取状态会将其值重置为零。还可以读取定时器到期前的剩余时间;值为零表示定时器已停止。
线程可以通过与定时器 同步 来间接读取其状态。这会阻塞线程,直到定时器状态非零(表示至少到期过一次)或定时器被停止;如果状态已非零或定时器已停止,线程无需等待即可继续。同步操作返回定时器状态,并将其重置为零。
备注
对于任意一个定时器,应当只有一个使用者检查其状态,因为无论直接还是间接读取状态都会改变其值。同样,同一时刻应当只有一个线程与给定定时器同步。ISR 不允许与定时器同步,因为 ISR 不允许阻塞。
定时器观察者
启用 CONFIG_TIMER_OBSERVER 后,代码可以注册 定时器观察者,接收系统中所有定时器的生命周期事件通知。观察者由一组可选回调组成,在定时器初始化、启动、停止或到期时调用。这使外部模块(例如跟踪、性能分析或电源管理代码)能够响应定时器活动,而无需修改内核内部实现或各个定时器。
通过 K_TIMER_OBSERVER_DEFINE 静态定义观察者,该宏接受指向 on_init、on_start、on_stop 和 on_expiry 回调的指针;不需要的回调可传入 NULL。由于到期回调在中断上下文中运行,因此必须保持简短且不能阻塞。
实现
定义定时器
使用 k_timer 类型的变量定义定时器,然后必须调用 k_timer_init() 将其初始化。
以下代码定义并初始化一个定时器。
struct k_timer my_timer;
extern void my_expiry_function(struct k_timer *timer_id);
k_timer_init(&my_timer, my_expiry_function, NULL);
也可以通过调用 K_TIMER_DEFINE,在编译时定义并初始化定时器。
以下代码与上面的代码片段具有相同效果。
K_TIMER_DEFINE(my_timer, my_expiry_function, NULL);
使用定时器到期函数
以下代码使用定时器定期执行一项较复杂的操作。由于所需工作无法在中断上下文中完成,定时器的到期函数会向 系统工作队列 提交工作项,由工作队列线程执行该工作。
void my_work_handler(struct k_work *work)
{
/* do the processing that needs to be done periodically */
...
}
K_WORK_DEFINE(my_work, my_work_handler);
void my_timer_handler(struct k_timer *dummy)
{
k_work_submit(&my_work);
}
K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);
...
/* start a periodic timer that expires once every second */
k_timer_start(&my_timer, K_SECONDS(1), K_SECONDS(1));
读取定时器状态
以下代码直接读取定时器状态,以确定定时器是否已到期。
K_TIMER_DEFINE(my_status_timer, NULL, NULL);
...
/* start a one-shot timer that expires after 200 ms */
k_timer_start(&my_status_timer, K_MSEC(200), K_NO_WAIT);
/* do work */
...
/* check timer status */
if (k_timer_status_get(&my_status_timer) > 0) {
/* timer has expired */
} else if (k_timer_remaining_get(&my_status_timer) == 0) {
/* timer was stopped (by someone else) before expiring */
} else {
/* timer is still running */
}
使用定时器状态同步
以下代码通过定时器状态同步,使线程能够执行其他有效工作,同时确保两次协议操作之间具有指定的时间间隔。
K_TIMER_DEFINE(my_sync_timer, NULL, NULL);
...
/* do first protocol operation */
...
/* start a one-shot timer that expires after 500 ms */
k_timer_start(&my_sync_timer, K_MSEC(500), K_NO_WAIT);
/* do other work */
...
/* ensure timer has expired (waiting for expiry, if necessary) */
k_timer_status_sync(&my_sync_timer);
/* do second protocol operation */
...
备注
如果线程没有其他工作要做,可以直接在两次协议操作之间休眠,而无需使用定时器。
使用建议
使用定时器在指定时间后发起异步操作。
使用定时器判断指定时间是否已经过去。尤其是当所需精度和/或时间单位控制能力超过较简单的 k_sleep() 和 k_usleep() 调用所能提供的范围时,应使用定时器。
使用定时器,在执行有时间限制的操作期间开展其他工作。
备注
如果线程需要测量某项操作的耗时,可以直接读取 系统时钟或硬件时钟,无需使用定时器。
配置选项
相关配置选项:
无