一、概述

在嵌入式系统开发中,定时器是实现任务调度、精确延时等功能的核心组件。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_TICKLESS
    depends on ARCH_HAVE_TICKLESS

config 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_ARCH
        select ARCH_HAVE_TICKLESS
        select ARCH_HAVE_TIMEKEEPING
        select SCHED_TICKLESS_LIMIT_MAX_SLEEP  if SCHED_TICKLESS
        select 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);
};

五、调用流程解析

  1. Tickless 模式
  • 模式概述:sched 动态管理软件定时器,选择最短时长的定时器作为下一次超时时间,动态调用启动或停止函数。
  • 调用流程:初始化定时器后,sched 计算超时时间,配置并启动定时器,超时后触发回调函数通知 sched,随后重新分配时长并启动下一个定时器或任务。

以下为 Tickless 模式下的调用流程图

在这里插入图片描述

  1. 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 结构的实例,实现其中的方法,如 startstop 等,以控制硬件运行。

在 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_createtimer_deletetimer_settime 等接口,用于创建、删除、配置定时器等操作。

  1. 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);
  1. timer_delete
/*
* 函数:timer_delete
* 参数:timerid,执行timer_create返回的timerid
* 返回:0 success | -1 error
* 说明:删除一个定时器
*/
int timer_delete(timer_t timerid);
  1. 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. 测试实例

  1. 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

在这里插入图片描述

在这里插入图片描述

  • 运行步骤:
  1. 启用上述配置,并构建固件。
  2. 在 NuttShell(NSH)中运行以下命令:
nsh> cmocka_posix_timer

在这里插入图片描述

  1. IOCTL 测试:通过 IOCTL 命令控制定时器设备,测试启动、停止、设置间隔等功能。
  • 代码路径:apps/testing/drivertest/drivertest_timer.c
  • 测试框架:cmocka
  • 依赖配置:
    • TESTING_CMOCKA
    • TESTING_DRIVER_TEST

在这里插入图片描述

在这里插入图片描述

  • 测试步骤:
  1. 启用依赖配置,并构建功能完整的固件。
  2. 在 NuttShell(NSH)中运行以下命令:
nsh> cmocka_driver_timer

在这里插入图片描述

总结

Arch Timer 驱动框架为嵌入式系统提供了灵活高效的定时器解决方案,支持两种工作模式,满足不同调度需求。通过本文的介绍,相信开发者对其原理、接口、配置和应用有了全面的了解,能够根据实际需求进行开发和适配,充分发挥其在系统调度中的作用。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐