一、概述
在嵌入式系统开发中,定时器是实现任务调度、精确延时等功能的核心组件。Arch Timer 作为基于 Timer Driver 实现的间隔定时器,在系统调度中扮演着重要角色。本文将全面介绍 Arch Timer 驱动框架,从基本概念到实际应用,帮助开发者快速掌握其使用与开发技巧。
二、认识 Arch Timer
1. 什么是 Arch Timer
Arch Timer 是基于 Timer Driver 实现的间隔定时器,为操作系统的 sched 模块提供了丰富的 timer 接口。它支持两种工作模式,以满足不同场景的需求:
Tickless 模式
:允许更灵活高效的系统调度,无周期性时钟中断,系统在无任务执行时进入空闲模式,有效降低功耗。Tick 模式
:提供固定时间间隔的调度机制,按照配置的固定周期运行,保证周期性任务的稳定执行。
Arch timer 在系统中的位置框架如下图所示:
2. Arch Timer 的驱动框架
Arch Timer 的驱动框架分为 upper - half 和 lower - half 两部分:
upper - half
部分:由 openvela 提供,其中up_timer_initialize
接口需由芯片厂商实现。lower - half
部分:需芯片厂商进行适配,实现硬件级别的控制。
应用程序可通过标准的 POSIX API 接口或 ioctl 接口,调用这两部分的接口来完成相应功能。
三、Arch Timer 接口详解
sched
模块依赖于 arch
模块提供的定时器接口,这些接口定义在 /include/nuttx/arch.h
头文件中。在 Tickless 模式下,接口按时间单位分为基于微秒(us
)和基于计时单元(tick
)的两组,开发者可通过配置选项 CONFIG_SCHED_TICKLESS_TICK_ARGUMENT
选择启用哪一组。默认支持 tick
接口以确保运行效率,部分主要接口如下:
Arch Timer 接口说明
接口名称 | 功能描述 |
---|---|
up_timer_set_lowerhalf | 初始化 Arch Timer 定时器,设置一个 timer_lowerhalf_s 实例,并启动定时器 |
up_timer_tick_start | 启动定时器,仅在 Tickless 模式下使用,参数为超时时间(单位:tick) |
up_timer_tick_cancel | 停止 Arch Timer 定时器,仅在 Tickless 模式中使用,返回当前剩余 tick 数 |
up_timer_getmask | 获取定时器支持的时间掩码(mask)的值 |
up_timer_gettick | 获取定时器当前已经经过的 tick 数值 |
up_udelay | 实现以微秒(us)为单位的延时操作 |
up_mdelay | 实现以毫秒(ms)为单位的延时操作 |
四、Timer Driver 深入了解
1. 配置说明
启用和调整 Timer Driver 功能需配置三个关键选项:
CONFIG_TIMER
:启用 Timer Driver 功能。
CONFIG_TIMER_ARCH
:启用 arch timer 模块。
CONFIG_SCHED_TICKLESS
:启用 Tickless 模式。
相关配置文件路径如下:
sched/Kconfig
:包含 SCHED_TICKLESS 等相关配置。
# sched/Kconfig
config SCHED_TICKLESSdepends on ARCH_HAVE_TICKLESSconfig SCHED_TICKLESS_TICK_ARGUMENT
config SCHED_TICKLESS_LIMIT_MAX_SLEEP
drivers/timers/Kconfig
:包含 TIMER 和 TIMER_ARCH 等配置。
# drivers/timers/Kconfig
config TIMER
......
if TIMER
config TIMER_ARCHselect ARCH_HAVE_TICKLESSselect ARCH_HAVE_TIMEKEEPINGselect SCHED_TICKLESS_LIMIT_MAX_SLEEP if SCHED_TICKLESSselect SCHED_TICKLESS_TICK_ARGUMENT if SCHED_TICKLESS
#endif
可通过以下命令检查配置是否正确:
grep -rE "CONFIG_TIMER|CONFIG_TIMER_ARCH|CONFIG_ARCH_HAVE_TICKLESS|CONFIG_ARCH_HAVE_TIMEKEEPING|CONFIG_SCHED_TICKLESS_TICK_ARGUMENT|CONFIG_SCHED_TICKLESS_LIMIT_MAX_SLEEP" nuttx/.config
2. 初始化
在 board 初始化过程中,需调用具体厂商实现的 ***_timer_initialize
函数,完成分配并初始化 struct
timer_lowerhalf_s
结构实例、注册 Timer 驱动等操作,注册后会生成 /dev/timer
设备节点,并绑定相关结构实例。
/***************************************************************************** Name: timer_register** Description:* This function binds an instance of a "lower half" timer driver with the* "upper half" timer device and registers that device so that can be used* by application code.** When this function is called, the "lower half" driver should be in the* disabled state (as if the stop() method had already been called).** NOTE: Normally, this function would not be called by application code.* Rather it is called indirectly through the architecture-specific* initialization.** Input Parameters:* dev path - The full path to the driver to be registered in the NuttX* pseudo-filesystem. The recommended convention is to name all timer* drivers as "/dev/timer0", "/dev/timer1", etc. where the driver* path differs only in the "minor" number at the end of the device name.* lower - A pointer to an instance of lower half timer driver. This* instance is bound to the timer driver and must persists as long as* the driver persists.** Returned Value:* On success, a non-NULL handle is returned to the caller. In the event* of any failure, a NULL value is returned.*****************************************************************************/FAR void *timer_register(FAR const char *path,FAR struct timer_lowerhalf_s *lower);
3. 上下层接口
-
upper - half
接口:主要供 sched 调用,根据配置可选择以struct timespec
或 tick 为单位的接口,减少时间转换工作。 -
lower - half
接口:通过struct timer_ops_s
提供标准化接口,供 upper - half、ioctl 系统调用等使用,按时间单位分为两组,开发者可根据需求选择实现,未实现的接口有默认实现。
struct timer_ops_s
{/* Required methods *******************************************************/CODE int (*start)(FAR struct timer_lowerhalf_s *lower);CODE int (*stop)(FAR struct timer_lowerhalf_s *lower);CODE int (*getstatus)(FAR struct timer_lowerhalf_s *lower,FAR struct timer_status_s *status);CODE int (*settimeout)(FAR struct timer_lowerhalf_s *lower,uint32_t timeout);CODE void (*setcallback)(FAR struct timer_lowerhalf_s *lower,CODE tccb_t callback, FAR void *arg);CODE int (*maxtimeout)(FAR struct timer_lowerhalf_s *lower,FAR uint32_t *maxtimeout);CODE int (*ioctl)(FAR struct timer_lowerhalf_s *lower, int cmd,unsigned long arg);CODE int (*tick_getstatus)(FAR struct timer_lowerhalf_s *lower,FAR struct timer_status_s *status);CODE int (*tick_setttimeout)(FAR struct timer_lowerhalf_s *lower,uint32_t timeout);CODE int (*tick_maxtimeout)(FAR struct timer_lowerhalf_s *lower,FAR uint32_t *maxtimeout);
};
五、调用流程解析
- Tickless 模式
- 模式概述:sched 动态管理软件定时器,选择最短时长的定时器作为下一次超时时间,动态调用启动或停止函数。
- 调用流程:初始化定时器后,sched 计算超时时间,配置并启动定时器,超时后触发回调函数通知 sched,随后重新分配时长并启动下一个定时器或任务。
以下为 Tickless 模式下的调用流程图
:
- Tick 模式
- 模式概述:sched 启用固定时间间隔的周期性定时器,间隔由 CONFIG_USEC_PER_TICK 配置,系统初始化时启动,无需频繁调用启动和停止接口。
- 流程描述:初始化时启动周期性定时器,按固定周期触发事件,触发调度器调度,保证周期性任务执行。
六、驱动适配实例
以 nrf52(基于 ARMv7 - M 架构)为例,驱动适配主要包括两部分:
1. 实现 up_timer_initialize 接口
在平台特定代码中实现该接口,调用 timer_register
函数注册定时器驱动,生成设备节点。初始化调用流程如下:
nx_start
-> clock_initialize-> up_timer_initialize #开发者实现-> systick_initialize #开发者实现-> timer_register-> up_timer_set_lowerhalf
以下为 systick_initialize
的实现参考:
struct timer_lowerhalf_s *systick_initialize(bool coreclk,unsigned int freq, int minor)
{struct systick_lowerhalf_s *lower =(struct systick_lowerhalf_s *)&g_systick_lower;.../* Register the timer driver if need */if (minor >= 0){char devname[32];sprintf(devname, "/dev/timer%d", minor);timer_register(devname, (struct timer_lowerhalf_s *)lower);}return (struct timer_lowerhalf_s *)lower;
}
2. 实现 lower - half 接口
通过定义 struct timer_ops_s
结构的实例,实现其中的方法,如 start
、stop
等,以控制硬件运行。
在 ARMv7-M 的 Arch Timer 适配中,lower-half
方法的出现形式如下:
文件路径: arch/arm/src/armv7-m/arm_systick.c
/* "Lower half" driver methods */
static const struct timer_ops_s g_systick_ops =
{.start = systick_start,.stop = systick_stop,.getstatus = systick_getstatus,.settimeout = systick_settimeout,.setcallback = systick_setcallback,.maxtimeout = systick_maxtimeout,
};
七、POSIX API 与测试实例
1. POSIX API
包括 timer_create
、timer_delete
、timer_settime
等接口,用于创建、删除、配置定时器等操作。
timer_create
/*
* 函数:timer_create
* 参数:clockid,定时类型;evp,sigevent结构体,用来指定定时器到期时如何相应
* timerid,返回一个timerid
* 返回:0 success | -1 error
* 说明:创建一个定时器
*/
int timer_create(clockid_t clockid, FAR struct sigevent *evp,FAR timer_t *timerid);
timer_delete
/*
* 函数:timer_delete
* 参数:timerid,执行timer_create返回的timerid
* 返回:0 success | -1 error
* 说明:删除一个定时器
*/
int timer_delete(timer_t timerid);
timer_settime
/* 设置定时器
* 函数:timer_settime
* 参数:timerid:id
* flags:相对时间/绝对时间
* value:定时时间和间隔
* ovalue:若不为NULL,则返回上次定时的剩余到期时间
* 返回:0 success | -1 error
* 说明:设置定时
*/
int timer_settime(timer_t timerid, int flags,FAR const struct itimerspec *value,FAR struct itimerspec *ovalue);
2. IOCTL 接口
应用级别的程序可以通过 ioctl 函数直接操作定时器(前提是在 bringup 过程中已注册 /dev/timer 设备节点)。
支持的 IOCTL 命令
IOCTL 命令 | 功能描述 | 参数类型 | 参数说明 |
---|---|---|---|
TCIOC_START | 启动定时器 | 无 | 无 |
TCIOC_STOP | 停止定时器 | 无 | 无 |
TCIOC_GETSTATUS | 获取当前定时器的状态 | struct timer_status_s* | 用于存储定时器状态信息的结构体指针 |
TCIOC_SETTIMEOUT | 设置定时器间隔时间(单位:微秒) | 32位无符号整数 | 定时器的间隔时间值 |
TCIOC_NOTIFICATION | 设置定时器超时消息 | struct timer_notify_s* | 包含超时通知配置的结构体指针 |
TCIOC_MAXTIMEOUT | 获取定时器支持的最大时延(单位:微秒) | uint32_t* | 用于存储最大时延值的指针 |
3. 测试实例
- TIMER API 测试:通过
cmocka
测试框架验证 POSIX API 的工作,包括创建、配置、以及删除定时器的全过程。
此部分介绍如何测试定时器相关功能,通过 cmocka 测试框架(可参考文档:OpenVela之开发自测试框架cmocka进行cmocka相关配置)验证 POSIX API 的工作,包括创建、配置、以及删除定时器的全过程。
- 代码位置:
apps/testing/drivertest/drivertest_posix_timer.c
- 测试框架:
cmocka
- 依赖配置:
TESTING_CMOCKA
TESTING_DRIVER_TEST
CONFIG_SIG_EVTHREAD
- 运行步骤:
- 启用上述配置,并构建固件。
- 在 NuttShell(NSH)中运行以下命令:
nsh> cmocka_posix_timer
- IOCTL 测试:通过 IOCTL 命令控制定时器设备,测试启动、停止、设置间隔等功能。
- 代码路径:
apps/testing/drivertest/drivertest_timer.c
- 测试框架:
cmocka
- 依赖配置:
TESTING_CMOCKA
TESTING_DRIVER_TEST
- 测试步骤:
- 启用依赖配置,并构建功能完整的固件。
- 在 NuttShell(NSH)中运行以下命令:
nsh> cmocka_driver_timer
总结
Arch Timer 驱动框架为嵌入式系统提供了灵活高效的定时器解决方案,支持两种工作模式,满足不同调度需求。通过本文的介绍,相信开发者对其原理、接口、配置和应用有了全面的了解,能够根据实际需求进行开发和适配,充分发挥其在系统调度中的作用。