- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
Clock HRTimer 是 RT-Thread clock_time 子系统在时间源(clock source)之上提供的高精度超时调度框架。它以统一的计数单位维护按到期时间排序的定时器队列,自动将下一次到期换算为事件设备(clock event)单位进行编程,并在到期时分发回调;当硬件事件缺失时还能自动退化为软件定时器触发。读完本文,你将掌握rt_clock_hrtimer_*全部 API 的语义与返回值、阻塞延时辅助函数的使用方法,以及 hrtimer 在 clock_hrtimer.c 中的队列排序、回绕比较与退化编程实现原理。
定位与设计:从 clock_time 到高精度超时
RT-Thread 的 clock_time 子系统由 clock_time_core.c、clock_hrtimer.c、clock_timer.c 与 clock_time_arm_arch.c 组成,对外接口集中在 clock_time.h。
子系统中存在两类角色:
- 时钟源(clock source):负责提供高频计数(
get_freq/get_counter),是 hrtimer 延时计数的基准。若没有注册外部源,系统默认使用基于rt_tick_get()的 tick 源(频率即RT_TICK_PER_SECOND)。 - 事件设备(clock event):负责"编程下一次事件"(
set_timeout),即按 delta 计数值设置下一次硬件超时。若没有注册事件设备,则rt_clock_time_set_timeout()返回-RT_ENOSYS,hrtimer 会自动退化。
hrtimer 正是架在两者之上的调度层:它维护按到期时间排序的队列,把队首事件换算成事件设备单位编程,到期时在中断里分发回调。rt_clock_time_device_register()(clock_time_core.c)在注册设备时会通过caps(RT_CLOCK_TIME_CAP_SOURCE/RT_CLOCK_TIME_CAP_EVENT,定义于 clock_time.h)自动填充默认源与默认事件;而rt_clock_timer_register()(clock_timer.c)则把传统硬件定时器封装成 clock time 设备,并挂到默认事件上。
hrtimer 对象模型
struct rt_clock_hrtimer定义于 clock_time.h:
struct rt_clock_hrtimer { rt_uint8_t flag; /**< 与 tick 定时器 flag 兼容 */ char name[RT_NAME_MAX]; rt_list_t node; void *parameter; unsigned long delay_cnt; /* 相对延时(默认时钟源计数) */ unsigned long timeout_cnt; /* 绝对到期计数值 */ rt_err_t error; struct rt_completion completion; void (*timeout_func)(void *parameter); }; typedef struct rt_clock_hrtimer *rt_clock_hrtimer_t;要点:
delay_cnt是启动时传入的相对计数,timeout_cnt = delay_cnt + 当前计数是绝对到期点,队列按timeout_cnt排序。flag直接复用RT_TIMER_FLAG_*系列宏(单次/周期/硬定时器),RT_TIMER_FLAG_ACTIVATED表示激活态,与 RT-Thread 传统定时器语义一致。- 每个 hrtimer 内置一个
completion,供rt_clock_hrtimer_sleep()阻塞等待使用。
API 详解
完整原型见 clock_time.h,实现见 clock_hrtimer.c。
rt_clock_hrtimer_init
void rt_clock_hrtimer_init(rt_clock_hrtimer_t timer, const char *name, rt_uint8_t flag, void (*timeout)(void *parameter), void *parameter);- 作用:初始化高精度定时器对象。
- 参数:
timer为待初始化对象;name为名称,内部通过rt_strncpy(timer->name, name, RT_NAME_MAX - 1)截断;flag复用RT_TIMER_FLAG_*;timeout为超时回调;parameter为回调参数。 - 行为:
rt_memset清空内部状态、初始化链表节点node与completion,并清除RT_TIMER_FLAG_ACTIVATED标志(clock_hrtimer.c)。仅初始化,不会启动,需另行调用rt_clock_hrtimer_start()。 - 上下文:线程上下文。
timer与timeout均不能为空(RT_ASSERT)。
rt_clock_hrtimer_start
rt_err_t rt_clock_hrtimer_start(rt_clock_hrtimer_t timer, unsigned long cnt);- 作用:启动定时器,在
cnt个计数后到期。 - 参数:
cnt为相对延时,单位是默认时钟源的计数(不是 tick、不是纳秒)。 - 返回值:
RT_EOK启动成功;-RT_ERROR表示定时器已处于激活态(重复 start)。 - 说明:
- 源码通过
RT_ASSERT(delay_cnt < (_HRTIMER_MAX_CNT / 2))约束cnt必须小于计数器最大值的一半,以避免计数器回绕造成到期判断歧义。_HRTIMER_MAX_CNT在 64 位架构上为UINT64_MAX,否则为UINT32_MAX(clock_hrtimer.c)。 - 启动时计算
timeout_cnt = delay_cnt + 当前计数,插入排序队列后调用_set_next_timeout_locked()重新编程下一次硬件事件(clock_hrtimer.c)。这意味着启动新定时器可能改写硬件比较寄存器。 - 在中断上下文调用会产生并发风险,应与
rt_clock_hrtimer_stop/control一样遵守调用上下文约束。
- 源码通过
rt_clock_hrtimer_stop
rt_err_t rt_clock_hrtimer_stop(rt_clock_hrtimer_t timer);- 作用:停止正在运行的定时器。
- 返回值:
RT_EOK成功;-RT_ERROR表示定时器未激活。 - 说明:从队列移除节点、清除激活标志,并触发
_set_next_timeout_locked()重新编程下一次事件(clock_hrtimer.c)。
rt_clock_hrtimer_control
rt_err_t rt_clock_hrtimer_control(rt_clock_hrtimer_t timer, int cmd, void *arg);- 作用:查询或修改定时器属性,命令与 RT-Thread 传统定时器保持一致(clock_hrtimer.c)。
- 常用命令:
| 命令 | 行为 |
|---|---|
RT_TIMER_CTRL_GET_TIME | 读取delay_cnt到*(unsigned long *)arg |
RT_TIMER_CTRL_SET_TIME | 用*(unsigned long *)arg设置delay_cnt,并重算timeout_cnt = 新值 + 当前计数(同样要求小于最大值一半) |
RT_TIMER_CTRL_SET_ONESHOT | 清除RT_TIMER_FLAG_PERIODIC,切换为单次模式 |
RT_TIMER_CTRL_SET_PERIODIC | 置位RT_TIMER_FLAG_PERIODIC,切换为周期模式 |
RT_TIMER_CTRL_GET_STATE | 查询激活状态,写入RT_TIMER_FLAG_ACTIVATED或RT_TIMER_FLAG_DEACTIVATED到*(rt_uint32_t *)arg |
RT_TIMER_CTRL_GET_REMAIN_TIME | 获取绝对到期计数值timeout_cnt到*(unsigned long *)arg |
RT_TIMER_CTRL_GET_FUNC/SET_FUNC | 获取/设置超时回调 |
RT_TIMER_CTRL_GET_PARM/SET_PARM | 获取/设置回调参数 |
- 说明:修改时间或模式不会自动启动已停止的定时器;所有操作在自旋锁保护下进行。
rt_clock_hrtimer_detach
rt_err_t rt_clock_hrtimer_detach(rt_clock_hrtimer_t timer);- 作用:分离定时器,并唤醒等待者。
- 行为(clock_hrtimer.c):
- 通过
rt_completion_wakeup_by_errno(&timer->completion, RT_ERROR)唤醒rt_clock_hrtimer_sleep()中的等待线程; - 标记定时器为未激活;若之前因
-RT_EINTR被中断,则同时从队列移除并重新编程下一次事件。
- 通过
- 适用场景:定时器资源释放或任务退出前清理。
rt_clock_hrtimer_delay_init / delay_detach
void rt_clock_hrtimer_delay_init(struct rt_clock_hrtimer *timer); void rt_clock_hrtimer_delay_detach(struct rt_clock_hrtimer *timer);delay_init:初始化用于阻塞延时的 one-shot hrtimer。内部调用rt_clock_hrtimer_init(timer, "hrtimer_sleep", RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_HARD_TIMER, _sleep_timeout, timer),回调_sleep_timeout负责rt_completion_done()触发 completion([clock_hrtimer.c](https://link.gitcode.com/i/2efd354b4c5057a23ca927ed5bb90de8#L152-L156, L400-L404))。delay_detach:释放上述定时器,等价于rt_clock_hrtimer_detach();即使已经超时也可安全调用(completion唤醒与队列操作均有防御处理)。
rt_clock_hrtimer_sleep / ndelay / udelay / mdelay
rt_err_t rt_clock_hrtimer_sleep(struct rt_clock_hrtimer *timer, unsigned long cnt); rt_err_t rt_clock_hrtimer_ndelay(struct rt_clock_hrtimer *timer, unsigned long ns); rt_err_t rt_clock_hrtimer_udelay(struct rt_clock_hrtimer *timer, unsigned long us); rt_err_t rt_clock_hrtimer_mdelay(struct rt_clock_hrtimer *timer, unsigned long ms);sleep:阻塞当前线程直到超时。实现为rt_clock_hrtimer_start()后调用rt_completion_wait_flags(..., RT_WAITING_FOREVER, RT_INTERRUPTIBLE)(clock_hrtimer.c)。返回值:RT_EOK:正常到期;-RT_EINTR:被信号打断或 detach 唤醒;-RT_EINVAL:cnt为 0;- 若 start 失败则直接返回其错误码。
- 仅线程上下文可用(依赖 completion 阻塞机制)。
ndelay/udelay/mdelay:按纳秒/微秒/毫秒换算为计数后调用sleep。换算基准为rt_clock_time_get_res_scaled():cnt = (ns * RT_CLOCK_TIME_RESMUL) / res,其中RT_CLOCK_TIME_RESMUL = 1000000ULL(clock_time.h)。udelay即ndelay(us * 1000),mdelay即ndelay(ms * 1000000)(clock_hrtimer.c)。- 说明:实际延时精度受时钟源计数频率与事件编程粒度限制;当
res为 0(未注册有效源)时ndelay返回-RT_ERROR。
内部机制:队列、回绕与事件编程
按到期时间排序的队列
所有 hrtimer 挂在静态链表_timer_list上(clock_hrtimer.c)。_insert_timer_to_list_locked()使用_cnt_before比较timeout_cnt,把新定时器插到正确位置,并置位RT_TIMER_FLAG_ACTIVATED(clock_hrtimer.c)。
计数比较采用无符号减法再转有符号的技巧避免回绕歧义:
rt_inline rt_bool_t _cnt_before(unsigned long a, unsigned long b) { return ((rt_base_t)(a - b)) < 0; }这正是start要求cnt < 最大值的一半的根本原因——保证任意两个活跃定时器的绝对到期点之差不会超过半个计数空间,使上述比较在回绕场景下依然正确。
事件编程与退化机制
_set_next_timeout_locked()(clock_hrtimer.c)是每次 start/stop/到期后的统一收口:取队首定时器,用_cnt_convert把其绝对到期计数减去当前计数,再按"源分辨率/事件分辨率"换算成事件单位,调用rt_clock_hrtimer_settimeout()。
rt_clock_hrtimer_settimeout()是rt_weak弱函数(clock_hrtimer.c),其退化逻辑正是文档强调的"缺硬件事件自动用软件定时器":
- 先尝试
rt_clock_time_set_timeout(cnt),即编程真实的事件设备;成功则直接返回; - 若返回非
RT_EOK(典型是未注册事件设备时的-RT_ENOSYS),则通过_hrtimer_cnt_to_tick()把计数换算为 tick,驱动一个名为"shrtimer"的静态 one-shot 软件定时器(rt_timer_init/start),其回调为rt_clock_hrtimer_process,实现等价的到期处理。
_hrtimer_cnt_to_tick内部依次按事件分辨率(rt_clock_hrtimer_getres(),默认取rt_clock_time_get_event_res_scaled())换算纳秒、再向上取整到 tick,保证最小延时至少 1 tick(clock_hrtimer.c)。
到期处理与中断调用链
到期后事件中断调用rt_clock_time_event_isr()(clock_hrtimer.c),它只做一件事:调用rt_clock_hrtimer_process()。该函数在自旋锁保护下:
_hrtimer_process_locked():循环取出队首,若now >= timeout_cnt则出队;周期定时器(RT_TIMER_FLAG_PERIODIC)按timeout_cnt = delay_cnt + now重新插入;单次定时器清除激活标志;最后执行timeout_func(parameter)(clock_hrtimer.c)。_set_next_timeout_locked():重新编程下一次事件;若队首已到期(换算结果为 0),则先就地处理再继续找下一个。
在硬件事件设备场景下,该路径运行于中断上下文,因此回调可能在中断上下文执行(文档注意事项第一条)。具体中断如何到达rt_clock_time_event_isr,可参见 clock_timer.c 中rt_clock_timer_isr():硬件定时器 ISR 计数溢出后调用rt_clock_time_event_isr()。
典型流程
- 初始化 hrtimer 并设置回调:
rt_clock_hrtimer_init(&timer, name, flag, timeout, param)。 - 将时间转换为计数值:直接使用
rt_clock_time_ns_to_counter(ns)(clock_time_core.c),或借助ndelay/udelay/mdelay辅助函数自动换算。 - 启动定时器:
rt_clock_hrtimer_start(&timer, cnt),系统自动排序并编程下一次事件。 - 到期后事件中断调用
rt_clock_time_event_isr(),触发 hrtimer 处理与回调分发;周期模式自动重挂,单次模式自动停用。
完整示例
示例一:单次超时
#include <drivers/clock_time.h> static struct rt_clock_hrtimer demo_timer; static void demo_timeout(void *parameter) { RT_UNUSED(parameter); rt_kprintf("hrtimer timeout\n"); } static void demo_hrtimer_start(void) { rt_uint64_t ns = 5ULL * 1000 * 1000; /* 5 ms */ unsigned long cnt = (unsigned long)rt_clock_time_ns_to_counter(ns); rt_clock_hrtimer_init(&demo_timer, "demo", RT_TIMER_FLAG_ONE_SHOT, demo_timeout, RT_NULL); rt_clock_hrtimer_start(&demo_timer, cnt); }要点:rt_clock_time_ns_to_counter()依据默认时钟源的分辨率把纳秒换算为计数,这是把"人类时间单位"翻译成"计数单位"的标准入口;若要在运行时查询当前事件分辨率与频率,可用rt_clock_hrtimer_getres()/rt_clock_hrtimer_getfrq()(均为rt_weak,可被平台覆盖)。
示例二:阻塞延时辅助
static void demo_hrtimer_sleep(void) { struct rt_clock_hrtimer timer; rt_clock_hrtimer_delay_init(&timer); rt_clock_hrtimer_mdelay(&timer, 10); rt_clock_hrtimer_delay_detach(&timer); }要点:delay_init建立的 one-shot 定时器内部回调会触发 completion;mdelay阻塞当前线程 10 ms;delay_detach负责清理,即使已经超时也安全。
启用与配置
hrtimer 随 clock_time 子系统一起编译。配置入口为 components/drivers/clock_time/Kconfig:
RT_USING_CLOCK_TIME:启用 clock_time 子系统(hrtimer 随之包含);CLOCK_TIMER_FREQ:RISC-V64 平台上时钟计数器的基准频率(Hz);RT_CLOCK_TIME_ARM_ARCH:ARM ARCH Timer(Cortex-A / ARMV8,依赖RT_USING_DM);RT_USING_CLOCK_TIMER_TRIGGER:可选,启用时钟定时器硬件触发输出(供 ADC 等使用,选中定时器需专用)。
构建层面,components/drivers/clock_time/SConscript 在未定义RT_USING_CLOCK_TIME时直接跳过编译;启用后编译clock_time_core.c、clock_hrtimer.c、clock_boottime.c、clock_timer.c,并视平台加入clock_time_arm_arch.c或arch/<ARCH>下的平台实现。
注意事项
- 使用硬件事件时,超时回调可能在中断上下文执行,回调内应避免阻塞操作与调度敏感调用。
rt_clock_hrtimer_sleep()依赖 completion 阻塞等待,可能返回-RT_EINTR(信号打断或 detach),调用方需按返回值处理重试或退出逻辑;它只能在线程上下文中使用。start/control(SET_TIME)要求计数值小于计数器最大值的一半,这是保证回绕比较正确性的硬性约束。cnt的单位始终是默认时钟源的计数,而非纳秒/微秒;换算请统一走rt_clock_time_ns_to_counter()或ndelay/udelay/mdelay辅助函数。- 未注册事件设备(无硬件定时器)时 hrtimer 会自动退化为软件定时器触发,此时精度受 tick 粒度限制——退化是"可用性兜底",并非高精度保证。
- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
相关推荐
RT-Thread 高精度定时器(Clock HRTimer)完全指南:API 详解、实现原理与实战示例
RT Thread 高精度定时器(Clock HRTimer)完全指南:API 详解、实现原理与实战示例 高精度定时器(Clock HRTimer)是 RT T
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread Clock Time 子系统深度解析:统一时间基准与高精度定时事件调度
RT Thread Clock Time 子系统深度解析:统一时间基准与高精度定时事件调度 RT Thread 的 Clock Time 子系统(位于 comp
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread Clock Time Core 子系统解析:时钟设备模型、定点换算与高精度事件调度
RT Thread Clock Time Core 子系统解析:时钟设备模型、定点换算与高精度事件调度 Clock Time Core 是 RT Thread
操作系统嵌入式物联网嵌入式OSRTOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考