news 2026/10/5 1:38:56

RT-Thread Clock HRTimer 高精度定时器完全指南:API、事件编程与退化机制解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RT-Thread Clock HRTimer 高精度定时器完全指南:API、事件编程与退化机制解析
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/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):
    1. 通过rt_completion_wakeup_by_errno(&timer->completion, RT_ERROR)唤醒rt_clock_hrtimer_sleep()中的等待线程;
    2. 标记定时器为未激活;若之前因-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),其退化逻辑正是文档强调的"缺硬件事件自动用软件定时器":

  1. 先尝试rt_clock_time_set_timeout(cnt),即编程真实的事件设备;成功则直接返回;
  2. 若返回非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()。该函数在自旋锁保护下:

  1. _hrtimer_process_locked():循环取出队首,若now >= timeout_cnt则出队;周期定时器(RT_TIMER_FLAG_PERIODIC)按timeout_cnt = delay_cnt + now重新插入;单次定时器清除激活标志;最后执行timeout_func(parameter)(clock_hrtimer.c)。
  2. _set_next_timeout_locked():重新编程下一次事件;若队首已到期(换算结果为 0),则先就地处理再继续找下一个。

在硬件事件设备场景下,该路径运行于中断上下文,因此回调可能在中断上下文执行(文档注意事项第一条)。具体中断如何到达rt_clock_time_event_isr,可参见 clock_timer.c 中rt_clock_timer_isr():硬件定时器 ISR 计数溢出后调用rt_clock_time_event_isr()。

典型流程

  1. 初始化 hrtimer 并设置回调:rt_clock_hrtimer_init(&timer, name, flag, timeout, param)。
  2. 将时间转换为计数值:直接使用rt_clock_time_ns_to_counter(ns)(clock_time_core.c),或借助ndelay/udelay/mdelay辅助函数自动换算。
  3. 启动定时器:rt_clock_hrtimer_start(&timer, cnt),系统自动排序并编程下一次事件。
  4. 到期后事件中断调用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/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

相关推荐

上一篇:5步搭建个人AI助手:闻达平台零基础部署指南
下一篇:WPF UI 表单验证实战:用 INotifyDataErrorInfo 3 步搞定输入校验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 1:38:19

支付宝授权扫脸认证跳转页面全过程

背景:H5 支付宝小程序 (支付宝嵌套H5小程序) 场景:用户扫码进入小程序进行授权,人脸认证 。人脸认证完成后跳转H5对应页面 进行操作 思考: 如何将H5小程序内嵌支付宝进行开发https://opendocs.alipay.com/mini/component/web-view <!--axml--> <!--网址后面…

作者头像 李华
网站建设 2026/10/5 1:37:22

ESP32-P4跑LLM:从0.61到4.31 tok/s的7倍优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:36:15

工业级SPI MRAM与PIC24FJ128GA310的嵌入式存储实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:35:54

微信小程序Echarts中国地图加载指南:GeoJSON处理与性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:35:39

基于STM32F746VG与MR25H40CDF的MRAM工业存储方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华