OpenVela之 Arch Timer 驱动框架使用指南
一、概述
在嵌入式系统开发中,定时器是实现任务调度、精确延时等功能的核心组件。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 驱动框架为嵌入式系统提供了灵活高效的定时器解决方案,支持两种工作模式,满足不同调度需求。通过本文的介绍,相信开发者对其原理、接口、配置和应用有了全面的了解,能够根据实际需求进行开发和适配,充分发挥其在系统调度中的作用。