1 前言
计时器是一个内核对象,它使用内核的系统时钟来度量时间的流逝。 当达到定时器的指定时间限制时,它可以执行应用程序定义的操作,或者它可以简单地记录到期并等待应用程序读取其状态。
我正在学习 Zephyr,一个很可能会用到很多物联网设备上的操作系统,如果你也感兴趣,可点此查看帖子zephyr学习笔记汇总。
2 概念
可以定义任意数量的定时器。每个定时器都由地址引用。
计时器具有以下关键属性:
- duration,指定定时器到期前的持续时间,以毫秒为单位。它必须大于零。
- period ,指定定时器到期后的时间间隔(以毫秒为单位)。它必须是非负的。零周期意味着定时器是一次性定时器,在一次到期后停止。 (例如,如果一个定时器的启动持续时间为200,周期为75,它将首先持续200ms,然后再75ms后重复。)
- expiry function,每当计时器到期时执行一次到期函数。该功能由系统时钟中断处理程序执行。如果不需要到期函数,则可以指定NULL函数。
- stop function,如果定时器在运行时过早停止,则执行停止功能。该函数由停止定时器的线程执行。如果不需要停止功能,则可以指定NULL功能。
- status,状态值,指示自从状态值上次读取以来定时器已经过期的次数。
定时器必须在使用前初始化。这指定了其到期函数和停止函数值,将定时器的状态设置为零,并使定时器进入停止状态。
定时器通过指定持续时间和周期来启动。定时器的状态被重置为零,然后定时器进入运行状态并开始到期的倒计时。
当一个正在运行的定时器到期时,它的状态会增加,如果存在到期函数的话会执行到期函数; 如果一个线程正在等待定时器,它将被解除阻塞。 如果定时器的周期为零,则定时器进入停止状态; 否则定时器会以等于其周期的新持续时间重新启动。
如果需要,正在运行的计时器可以在倒计时期间中止。定时器的状态保持不变,然后定时器进入停止状态并执行其停止函数(如果存在)。如果一个线程正在等待定时器,它将被解除阻塞。试图停止不运行的计时器是允许的,但它对定时器没有影响,因为它已经停止。
如果需要,正在运行的定时器可以在倒数计时器中重新启动。定时器的状态重置为零,然后定时器使用调用者指定的新的持续时间和周期值开始倒计时。如果一个线程正在等待定时器,它将继续等待。
可以随时直接读取定时器的状态,以确定定时器自上次读取状态以来已经过了多少次。读定时器的状态会将其值重置为零。定时器到期之前剩余的时间量也可以读取;值为零表示定时器已停止。
线程可以通过与定时器同步来间接读取定时器的状态。这会阻塞线程,直到定时器的状态为非零(表示它至少已经过期)或定时器停止;如果定时器状态已经非零或定时器已经停止,则线程继续而不等待。同步操作返回定时器的状态并将其重置为零。
代码语言:javascript复制注意:由于读取状态(直接或间接)会改变其值,因此只有一个用户应该检查任何给定定时器的状态。 同样,一次只有一个线程应该与给定的定时器同步。ISR不允许与定时器同步,因为ISR不允许被阻塞。
3 定时器限制
由于定时器基于系统时钟,因此使用定时器时指定的延迟值为最小值。 (请参阅zephyr笔记 2.2.1 内核时钟中的时钟限制章节。)
4 操作
4.1 定义一个计时器
定时器使用 struct k_timer 类型的变量来定义。 它必须通过调用 k_timer_init() 来初始化。
以下代码定义并初始化一个计时器。
代码语言:javascript复制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在编译时定义和初始化计时器。
以下代码与上面的代码段具有相同的效果。
代码语言:javascript复制K_TIMER_DEFINE(my_timer, my_expiry_function, NULL);
4.2 使用定时器到期函数
以下代码使用计时器定期执行重要操作。由于所需工作不能在中断级完成,因此计时器的到期函数将工作项提交给系统工作队列,该工作队列的线程执行工作。
代码语言:javascript复制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 periodic timer that expires once every second */
k_timer_start(&my_timer, K_SECONDS(1), K_SECONDS(1));
4.3 读取计时器状态
以下代码直接读取计时器的状态以确定计时器是否已过期。
代码语言:javascript复制K_TIMER_DEFINE(my_status_timer, NULL, NULL);
...
/* start one shot timer that expires after 200 ms */
k_timer_start(&my_status_timer, K_MSEC(200), 0);
/* 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 */
}
4.4 使用定时器状态同步
以下代码执行定时器状态同步以允许线程执行有用的工作,同时确保一对协议操作按指定的时间间隔分隔。
代码语言:javascript复制K_TIMER_DEFINE(my_sync_timer, NULL, NULL);
...
/* do first protocol operation */
...
/* start one shot timer that expires after 500 ms */
k_timer_start(&my_sync_timer, K_MSEC(500), 0);
/* do other work */
...
/* ensure timer has expired (waiting for expiry, if necessary) */
k_timer_status_sync(&my_sync_timer);
/* do second protocol operation */
...
代码语言:javascript复制注意:如果线程没有其他工作要做,它可以简单地在两个协议操作之间休眠,而不使用定时器。
5 建议用法
使用定时器在指定的时间后启动异步操作。
使用计时器确定是否已经过了指定的时间量。
使用计时器执行其他工作,同时执行涉及时间限制的操作。
代码语言:javascript复制注意:如果一个线程在等待时间通过时没有其他工作要执行,它应该调用k_sleep() 。 如果线程需要测量执行操作所需的时间,它可以直接读取系统时钟或硬件时钟,而不是使用定时器。
6 配置选项
无
7 APIs
下列定时器API,都在 kernel.h 中提供了:
代码语言:javascript复制K_TIMER_DEFINE
k_timer_init()
k_timer_start()
k_timer_stop()
k_timer_status_get()
k_timer_status_sync()
k_timer_remaining_get()