5.7 Timer 设备
5.7 Timer 设备
📚 本节导读
学习时长:约 35 分钟
难度级别:⭐⭐⭐⭐☆
前置知识:C 语言基础、链表基础、5.1 设备驱动框架、4.5 时间管理(内核定时器)
本节讲解 OneOS 驱动层的 Timer 高精度定时器(HRTIMER)。它位于 drivers/timer/,是内核时钟子系统的驱动层实现,提供纳秒级精度的高精度定时触发能力,精度远高于第 4.5 节介绍的内核软件定时器(基于 OS Tick,毫秒级)。
本节会带你:
- 理解 Timer 设备的三层结构:时钟源(clocksource)→ 时钟事件(clockevent)→ 高精度定时器(hrtimer);
- 看懂
os_hrtimer_t结构体与OS_HRTIMER_STATE_*状态机的含义; - 逐个拆解
os_hrtimer_start/os_hrtimer_stop/os_hrtimer_enqueue/os_hrtimer_trig_hwtimer/os_hrtimer_callback/os_hrtimer_init/os_hrtimer_stoped的完整源码(含按超时时间排序的链表、触发硬件定时器与到点回调处理周期/单次逻辑); - 掌握高精度定时器的定义与启停流程;
- 理解更换 MCU 时如何适配时钟源(clocksource)与时钟事件(clockevent)的移植方法。
说明:本节所有源码均取自项目
drivers/timer/hrtimer.h、drivers/timer/hrtimer.c、drivers/timer/clocksource.h、drivers/timer/clockevent.h、drivers/timer/clocksource_cortexm.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解 clocksource / clockevent / hrtimer 三层时钟抽象
- 掌握
os_hrtimer_t结构体与状态机 - 逐个拆解
os_hrtimer_start/os_hrtimer_stop/os_hrtimer_callback/os_hrtimer_init/os_hrtimer_stoped的完整源码 - 理解 hrtimer 链表按到期时间升序的排序调度机制
- 掌握高精度定时器的定义与使用流程
- 理解时钟源 / 时钟事件的 BSP 接口契约,掌握更换 MCU 时的适配流程
一、概述
1.1 Timer 设备解决什么问题
一句话核心:Timer 高精度定时器解决"需要比内核软件定时器更精细(纳秒级)的定时触发"的场景。它让你"指定一个纳秒级超时时间,到点后回调函数被精确执行"。
为什么需要 HRTIMER:
- 内核软件定时器(第 4.5 节)基于 OS Tick(通常 1ms 或 10ms),精度有限。
- 驱动层需要更高精度的定时(如协议超时、波形周期、DMA 传输调度),这需要直接操作硬件定时器。
- HRTIMER 在驱动层封装了"时钟源(读当前时间)+ 时钟事件(设置硬件定时器中断)+ 排序链表(管理多个定时器)",提供统一的纳秒级定时接口。
1.2 三层时钟抽象(为什么这样设计)
OneOS 的 Timer 时钟子系统分三层:
应用层:os_hrtimer_start / os_hrtimer_stop(高精度定时器)
│
▼
hrtimer 层(hrtimer.c):排序链表 + 触发调度
│
▼
clockevent 层(clockevent.c):设置硬件定时器单次中断
│
▼
clocksource 层(clocksource.c):读取单调递增的纳秒时间关键理解 —— 三层的职责分工:
| 层 | 作用 | 类比 |
|---|---|---|
| clocksource(时钟源) | 提供一个不断递增的时间基准(os_clocksource_gettime()),只管"读时间" | 一块表 |
| clockevent(时钟事件) | 在某个时间点产生一次硬件中断(os_clockevent_start_oneshot),只管"到点触发" | 一个闹钟 |
| hrtimer(高精度定时器) | 管理多个定时器,到点调用各自的回调函数,统筹"闹钟"的编排 | 闹钟管理器 |
为什么分层:把"读时间"(clocksource)和"定时触发"(clockevent)分离,可以让系统用不同硬件做这两件事(如用 SysTick 走时间、用通用定时器做触发),灵活性更高。HRTIMER 在最上层统一调度。
1.3 hrtimer 的核心结构体与状态机
来源:drivers/timer/hrtimer.h
struct os_hrtimer
{
void (*timeout_func)(void *parameter); /* callback function when hrtimer timeout */
void *parameter; /* parameter for callback function */
uint64_t timeout_nsec; /* first timeout time */
uint64_t period_nsec; /* period timeout time */
uint64_t next_nsec;
enum os_hrtimer_state state;
os_list_node_t list;
};
typedef struct os_hrtimer os_hrtimer_t;关键字段说明:
| 字段 | 含义 |
|---|---|
timeout_func | 超时回调函数 |
parameter | 回调参数 |
timeout_nsec | 首次超时时间(相对启动时刻的相对纳秒数) |
period_nsec | 周期超时时间(0 表示单次,非 0 表示周期) |
next_nsec | 下次超时的绝对纳秒时间戳 |
state | 定时器状态 |
状态机(enum os_hrtimer_state):
来源:drivers/timer/hrtimer.h
enum os_hrtimer_state
{
OS_HRTIMER_STATE_NONE,
OS_HRTIMER_STATE_WAIT,
OS_HRTIMER_STATE_RUN,
};关键理解 —— 三个状态的语义(注意:项目源码中枚举没有注释,下列中文说明是文档为便于理解补充的,不属于源码):
| 状态 | 含义 | 何时进入 | 定时器此刻在哪里 |
|---|---|---|---|
OS_HRTIMER_STATE_NONE | 未启动 / 已停止 | 刚定义、stop 后、单次定时器执行完 | 不在 gs_hrtimer_list 链表中 |
OS_HRTIMER_STATE_WAIT | 已入队等待 | os_hrtimer_start / os_hrtimer_enqueue 之后 | 挂在 gs_hrtimer_list 链表中 |
OS_HRTIMER_STATE_RUN | 正在执行回调 | 到点从链表删除后、执行 timeout_func 期间 | 已从链表删除,正被回调占用 |
关键理解 —— 状态流转:
NONE ──os_hrtimer_start──▶ WAIT(入队)
WAIT ──到点,从链表删除──▶ RUN(回调执行中)
RUN ──回调内再调 start──▶ WAIT(重新入队,周期模式)
RUN ──回调内调 stop 或单次──▶ NONE(停止/结束)
WAIT ──os_hrtimer_stop──▶ NONE(提前停止)状态如何被代码读取:几乎所有判断都围绕 state 这个字段:
os_hrtimer_enqueue末尾把状态置WAIT(入队即等待)。os_hrtimer_callback到点后置RUN再执行回调;回调返回后若状态仍是RUN(说明回调没调stop)才继续处理周期/单次。os_hrtimer_stop先清周期,若检测到state == RUN(正在执行回调)则直接返回,避免从链表删除一个已经不在链表里的定时器。os_hrtimer_stoped就是读状态判断是否等于NONE(见 2.6 节)。
1.4 Timer 使用场景
1.4.1 一句话核心
Timer 高精度定时器适用于驱动层需要纳秒级精度的定时触发场景,是内核软件定时器(第 4.5 节)的高精度补充。它让你"指定一个纳秒级超时时间,到点后回调函数被精确执行"。
1.4.2 与内核软件定时器的对比(为什么用它而不是别的)
在 OneOS 中实现"延时/定时"有两条路:内核软件定时器(os_timer,第 4.5 节)和 Timer 高精度定时器(HRTIMER)。它们的核心差异在于时间基准:
| 维度 | 内核软件定时器(os_timer) | HRTIMER(Timer 设备) |
|---|---|---|
| 所在层 | 内核层(第 4.5 节) | 驱动层(drivers/timer/) |
| 时间基准 | 基于 OS Tick(通常 1ms / 10ms) | 基于硬件定时器 / clocksource,纳秒级 |
| 精度 | 毫秒级(受 Tick 粒度限制) | 纳秒级(next_nsec 为 uint64_t 纳秒) |
| 触发方式 | 软件在 Tick 中断里扫描定时器链表 | 硬件定时器单次中断,到点硬件触发 |
| 时间单位 | Tick(tick_t) | 纳秒(nsec),可用 NSEC_PER_MSEC 等宏 |
| 典型用途 | 任务级周期/延时,毫秒即可 | 协议超时、波形周期、DMA 调度等高精度场景 |
| 能否读当前时间 | 不能 | clocksource 可读单调纳秒时间 |
结论:毫秒级、不要求精度的场景用软件定时器(简单、开销低);需要纳秒级精度的场景才用 HRTIMER。
1.4.3 典型应用场景
场景一:周期驱动系统时钟节拍
项目源码 hrtimer.c 中定义了 g_hrtimer_systick(在 OS_USING_HRTIMER_FOR_KERNEL_TICK 配置下),用 HRTIMER 周期触发 os_tick_handler 来驱动内核 Tick:
static os_hrtimer_t g_hrtimer_systick = {
.timeout_func = (void (*)(void *parameter))os_tick_handler,
.parameter = "hrtimer_systick",
.timeout_nsec = NSEC_PER_SEC / OS_TICK_PER_SECOND,
.period_nsec = NSEC_PER_SEC / OS_TICK_PER_SECOND,
};为什么用 HRTIMER:g_hrtimer_systick 是周期模式(period_nsec 非 0),首次超时 timeout_nsec 后触发回调,之后每隔 period_nsec 触发一次 os_tick_handler,从而用高精度硬件定时器取代默认的软件 Tick 源,提供更精确的 Tick 基准。
实际工作情况:假设 OS_TICK_PER_SECOND = 1000(1ms 一个 Tick),则 NSEC_PER_SEC / OS_TICK_PER_SECOND = 1000000000 / 1000 = 1000000 ns,即 g_hrtimer_systick 每 1ms 触发一次 os_tick_handler,驱动系统时钟推进 1 个 Tick。
场景二:协议超时的高精度检测
struct os_hrtimer timeout_timer;
timeout_timer.timeout_func = timeout_callback;
timeout_timer.timeout_nsec = 5 * NSEC_PER_MSEC; /* 5ms 超时 */
os_hrtimer_start(&timeout_timer);为什么用 HRTIMER:毫秒/微秒级的协议超时用软件定时器(Tick 粒度)不够精确。例如 5ms 超时,若 Tick 是 1ms 还能覆盖;若 Tick 是 10ms,软件定时器就完全无法表达 5ms 这种小于 Tick 的精度。HRTIMER 直接操作硬件定时器到点触发,精度可达纳秒级。
实际工作情况:时刻 T1 任务调用 os_hrtimer_start(&timeout_timer),硬件定时器被设置为 T1 + 5ms 时刻触发中断;到点后中断进入 os_hrtimer_callback,从链表删除该定时器、置 RUN 状态、调用 timeout_callback,在回调里做超时处理(如关闭连接、返回错误)。这是驱动层处理网络/串口协议超时的典型写法。
1.4.4 选型边界表(何时不该用它)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 毫秒级、不要求精度的周期任务 | 内核软件定时器(os_timer,第 4.5 节) | 软件定时器基于 Tick,简单且开销低 |
| 需要 MCU 硬件定时器的 PWM/编码器 | PWM 设备(5.12) | HRTIMER 只负责"到点回调",不产生 PWM 波形 |
| 需要高精度当前时间读取 | clocksource(os_clocksource_gettime) | 读时间不需要创建定时器 |
| 需要绝对时刻对齐(如 UTC 时间) | RTC 设备(5.8) | HRTIMER 是单调递增的相对时间,不处理日历/时区 |
| 任务需要阻塞等待超时(延时) | os_task_tsleep / os_task_msleep、信号量带超时 | HRTIMER 回调在中断上下文,不能阻塞任务 |
结合源码的使用注意点:
- 回调在中断上下文执行:
os_hrtimer_callback由硬件定时器中断触发(os_hrtimer_init里os_clockevent_register_isr注册),所以timeout_func不能阻塞、不能做耗时操作、不能调用会触发调度的阻塞 API。 - 有界性:所有定时器挂在全局链表
gs_hrtimer_list上,定时器数量没有上限限制,但回调执行时间会累加影响中断响应,应避免在回调里做重活。 - 单次/周期:
period_nsec = 0单次、非 0 周期。停止前周期定时器会一直重新入队。 - 停止时机:周期定时器若不再需要,必须
os_hrtimer_stop,否则会一直触发;在回调里调stop可用于"一次性回调"(见 2.2 节)。
二、核心 API 详解
说明:以下源码均取自
drivers/timer/hrtimer.c,与项目源码逐字一致(含原始英文注释)。
2.1 os_hrtimer_start —— 启动高精度定时器
函数签名:
void os_hrtimer_start(os_hrtimer_t *hrtimer);| 参数 | 说明 |
|---|---|
hrtimer | 要启动的 hrtimer 对象 |
无返回值(void)。
完整源码(drivers/timer/hrtimer.c,与项目一致):
void os_hrtimer_start(os_hrtimer_t *hrtimer)
{
os_ubase_t level;
hrtimer->next_nsec = os_clocksource_gettime() + hrtimer->timeout_nsec;
os_spin_lock_irq(&gs_device_lock, &level);
os_hrtimer_enqueue(hrtimer);
if (os_clockevent_best() == OS_NULL)
{
os_spin_unlock_irq(&gs_device_lock, level);
return;
}
if (os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list) == hrtimer)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
os_spin_unlock_irq(&gs_device_lock, level);
}分步详解:
全文统一基准数据:本节所有数值演算(
os_hrtimer_start、os_hrtimer_enqueue、os_hrtimer_stop)统一使用同一套数据,便于衔接理解——
- 当前单调时间
now = 10000 ns;- 三个定时器 A、B、C 的
timeout_nsec分别为1000、5000、3000ns,os_hrtimer_start后各自的next_nsec = now + timeout_nsec(见下)。
第 1 步:计算下次超时的绝对时间(无条件执行)
hrtimer->next_nsec = os_clocksource_gettime() + hrtimer->timeout_nsec;详细解释:关键计算。os_clocksource_gettime() 读当前单调纳秒时间,加上 timeout_nsec(相对延时)得到 next_nsec(绝对超时时间戳)。后续链表排序、硬件触发全都基于这个绝对时间戳比较。
为什么先算再上锁:os_clocksource_gettime() 读取单调计数器本身是一个原子操作,提前读取可以在持锁临界区之外完成计算,缩短持锁时间,降低对中断/其他任务的阻塞。
数值演算(假设当前时间 now = 10000 ns,分别用定时器 A、B、C):
| 定时器 | now + timeout_nsec | next_nsec 结果 |
|---|---|---|
| A | 10000 + 1000 | 11000(ns) |
| B | 10000 + 5000 | 15000(ns) |
| C | 10000 + 3000 | 13000(ns) |
下节
os_hrtimer_enqueue的链表插入示例将沿用 A/B/C 这三个定时器(到期时刻 11000 / 15000 / 13000)。
第 2 步:加锁并入队(无条件执行)
os_spin_lock_irq(&gs_device_lock, &level);
os_hrtimer_enqueue(hrtimer);详细解释:关中断加自旋锁 gs_device_lock,保护全局 hrtimer 链表(可能被其他任务/中断访问)。os_hrtimer_enqueue 把定时器按 next_nsec 排序插入 gs_hrtimer_list(见 2.3 节)。
第 3 步:无时钟事件则直接返回(条件 os_clockevent_best() == OS_NULL)
if (os_clockevent_best() == OS_NULL)
{
os_spin_unlock_irq(&gs_device_lock, level);
return;
}详细解释:os_clockevent_best() 返回当前选中的最佳时钟事件。若为空(没有可用的硬件定时器),无法触发,解锁直接返回。
第 4 步:若本定时器是链表头则触发硬件(条件 链表首 == hrtimer)
if (os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list) == hrtimer)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
os_spin_unlock_irq(&gs_device_lock, level);
}详细解释:关键点:若这个定时器排到了链表最前面(最早到期),说明现在需要硬件在它的 next_nsec 时刻触发中断,调用 os_hrtimer_trig_hwtimer(见 2.4 节)。否则说明已有更早的定时器在驱动硬件,无需重复设置。最后解锁。
链表示意(沿用统一基准,展示两种情况):
情况一:链表原本为空,现 start 定时器 C(13000)
C 入队后成为链表首 → 触发硬件 os_hrtimer_trig_hwtimer(13000)
情况二:链表已有 A(11000),现 start 定时器 C(13000)
C 入队后链表:A(11000) → C(13000);链表首是 A 不是 C
→ 条件 (链表首 == C) 不成立,不触发硬件(硬件仍触发 A 的 11000)关键理解:硬件定时器任何时候只为一个最早到期者服务。只有新加入的定时器恰好成为链表首(最早到期)时,才需要重新设置硬件触发时刻;否则它会在更早的定时器触发后,由 os_hrtimer_callback 的"触发下一个最早者"逻辑(见 2.5 第 3 步)接管。
os_hrtimer_start() 条件走向总结:
os_hrtimer_start() 条件走向总结:
├── hrtimer->next_nsec = 当前时间 + timeout_nsec
├── 加锁 gs_device_lock
├── os_hrtimer_enqueue(hrtimer)(按 next_nsec 排序入链表)
├── os_clockevent_best() == OS_NULL(无时钟事件)→ 解锁 + return
├── 链表首 == hrtimer(最早到期)→ os_hrtimer_trig_hwtimer(next_nsec)
└── 解锁2.2 os_hrtimer_stop —— 停止高精度定时器
函数签名:
void os_hrtimer_stop(os_hrtimer_t *hrtimer);| 参数 | 说明 |
|---|---|
hrtimer | 要停止的 hrtimer 对象 |
无返回值(void)。
完整源码(drivers/timer/hrtimer.c,与项目一致):
void os_hrtimer_stop(os_hrtimer_t *hrtimer)
{
os_ubase_t level;
os_hrtimer_t *tmp;
os_spin_lock_irq(&gs_device_lock, &level);
hrtimer->period_nsec = 0;
if (hrtimer->state == OS_HRTIMER_STATE_RUN)
{
os_spin_unlock_irq(&gs_device_lock, level);
return;
}
tmp = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
os_list_del(&hrtimer->list);
hrtimer->state = OS_HRTIMER_STATE_NONE;
if (tmp == hrtimer)
{
hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
}
os_spin_unlock_irq(&gs_device_lock, level);
}分步详解:
第 1 步:加锁并清除周期(无条件执行)
os_spin_lock_irq(&gs_device_lock, &level);
hrtimer->period_nsec = 0;详细解释:关中断加锁。先把 period_nsec 清零,确保停止后不会周期性重新入队。
第 2 步:若正在执行回调则返回(条件 state == OS_HRTIMER_STATE_RUN)
if (hrtimer->state == OS_HRTIMER_STATE_RUN)
{
os_spin_unlock_irq(&gs_device_lock, level);
return;
}详细解释:关键点:若回调正在执行(RUN 状态),不能从链表删除(它已不在链表,正在被 os_hrtimer_callback 处理),直接解锁返回。回调内部可通过再次判断状态决定是否重新入队。
第 3 步:从链表删除并重置状态(无条件执行)
tmp = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
os_list_del(&hrtimer->list);
hrtimer->state = OS_HRTIMER_STATE_NONE;详细解释:记录当前链表首 tmp。把定时器从链表删除,状态置 NONE(停止)。
第 4 步:若停的是链表首则触发新的最早定时器(条件 原链表首 == 被停定时器)
if (tmp == hrtimer)
{
hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
}
os_spin_unlock_irq(&gs_device_lock, level);
}详细解释:若被停的是链表首(最早到期),说明硬件当前设置的触发时刻已经作废,需要取出新的链表首并 os_hrtimer_trig_hwtimer 重新设置硬件触发。若链表已空则不再触发。解锁。
链表示意(沿用统一基准:升序链表 A(11000) → C(13000) → B(15000),现停止链表首 A):
停止前:gs_hrtimer_list ──▶ A(11000) ──▶ C(13000) ──▶ B(15000) (硬件触发时刻 = 11000)
停止 A:os_list_del(A);新链表首 = C(13000)
停止后:gs_hrtimer_list ──▶ C(13000) ──▶ B(15000) (硬件触发时刻改为 13000)
▲ 重新 os_hrtimer_trig_hwtimer(13000)关键理解:只有"被停的是链表首"时才需要重新设置硬件——因为此时原来触发硬件的那次设置(11000)已经因 A 被删而作废,硬件必须改去触发新的最早到期者 C(13000)。若停的不是链表首(如停 B),A 仍是链表首且其到期时刻未变,硬件触发设置无需改动,所以外层 if (tmp == hrtimer) 不成立,直接跳过。
os_hrtimer_stop() 条件走向总结:
os_hrtimer_stop() 条件走向总结:
├── 加锁;period_nsec = 0
├── state == RUN(回调执行中)→ 解锁 + return
├── tmp = 链表首;os_list_del(hrtimer);state = NONE
├── tmp == hrtimer(停的是链表首)
│ ├── 新链表首存在 → os_hrtimer_trig_hwtimer(新首)
│ └── 链表空 → 不触发
└── 解锁2.3 入队 os_hrtimer_enqueue(排序链表)
一句话定位:把定时器按 next_nsec 升序插入全局链表,保证链表首始终是最早到期的定时器。
完整源码(drivers/timer/hrtimer.c,与项目一致):
static void os_hrtimer_enqueue(os_hrtimer_t *hrtimer)
{
os_list_node_t *entry = &gs_hrtimer_list;
os_hrtimer_t *tmp;
os_list_for_each_entry(tmp, &gs_hrtimer_list, os_hrtimer_t, list)
{
OS_ASSERT(tmp != hrtimer);
if (tmp->next_nsec > hrtimer->next_nsec)
break;
entry = &tmp->list;
}
os_list_add(entry, &hrtimer->list);
hrtimer->state = OS_HRTIMER_STATE_WAIT;
}分步详解:
第 1 步:遍历链表找插入位置(无条件执行)
os_list_for_each_entry(tmp, &gs_hrtimer_list, os_hrtimer_t, list)
{
OS_ASSERT(tmp != hrtimer);
if (tmp->next_nsec > hrtimer->next_nsec)
break;
entry = &tmp->list;
}
os_list_add(entry, &hrtimer->list);详细解释:从链表头开始遍历,OS_ASSERT(tmp != hrtimer) 防止重复插入。当发现 tmp->next_nsec > hrtimer->next_nsec(当前定时器到期更早)时 break,否则继续并更新 entry。os_list_add(entry, ...) 把定时器插到 entry 之后。这样链表按到期时间升序排列。
数值演算(沿用 2.1 基准数据:当前 now = 10000 ns,定时器 A/B/C 的 next_nsec 分别为 11000 / 15000 / 13000。假设 A、B 已在链表,现插入 C):
链表现状:头→A(11000)→B(15000)
插入 C(13000):从链表头开始遍历
第1轮 tmp=A,A->next_nsec(11000) > C->next_nsec(13000)? 否 → entry=&A->list
第2轮 tmp=B,B->next_nsec(15000) > C->next_nsec(13000)? 是 → break
os_list_add(entry=&A->list 之后) → 头→A(11000)→C(13000)→B(15000) ✓(升序)链表移动前后示意(os_list_add(entry, &hrtimer->list) 把 C 插到 A 之后、B 之前):
插入前:gs_hrtimer_list ──▶ A(11000) ──▶ B(15000) ──▶ (回环到头)
插入后:gs_hrtimer_list ──▶ A(11000) ──▶ C(13000) ──▶ B(15000) ──▶ (回环到头)
▲
└── C 被插到 A 之后数值演算表(遍历各轮判断):
| 轮次 | tmp | tmp->next_nsec | hrtimer(C)->next_nsec | tmp > C? | 动作 |
|---|---|---|---|---|---|
| 1 | A | 11000 | 13000 | 否 | entry = &A->list |
| 2 | B | 15000 | 13000 | 是 | break |
| — | — | — | — | — | os_list_add(entry, ...) → 插到 A 后 |
第 2 步:置为等待状态(无条件执行)
hrtimer->state = OS_HRTIMER_STATE_WAIT;
}详细解释:入队成功后状态置为 WAIT(已入队等待)。
os_hrtimer_enqueue() 条件走向总结:
os_hrtimer_enqueue() 条件走向总结:
├── 遍历链表:
│ ├── tmp == hrtimer(重复插入)→ OS_ASSERT
│ ├── tmp->next_nsec > hrtimer->next_nsec → break
│ └── 否则 → entry = &tmp->list
├── os_list_add(entry, &hrtimer->list)(插入到 entry 之后,保持升序)
└── state = WAIT2.4 触发硬件定时器 os_hrtimer_trig_hwtimer
一句话定位:让时钟事件在指定时间点产生单次中断。
完整源码(drivers/timer/hrtimer.c,与项目一致):
static void os_hrtimer_trig_hwtimer(uint64_t next_nsec)
{
os_clockevent_t *ce;
ce = os_clockevent_best();
os_clockevent_stop(ce);
os_clockevent_start_oneshot(ce, next_nsec - os_clocksource_gettime());
}分步详解:
第 1 步:取最佳时钟事件(无条件执行)
ce = os_clockevent_best();详细解释:os_clockevent_best() 返回选中的最佳时钟事件设备(精度高、可靠)。
第 2 步:停止旧触发并设置单次触发(无条件执行)
os_clockevent_stop(ce);
os_clockevent_start_oneshot(ce, next_nsec - os_clocksource_gettime());
}详细解释:先 os_clockevent_stop 停止旧的触发。然后 os_clockevent_start_oneshot(ce, delta),其中 delta = next_nsec - 当前时间 是从现在到触发时刻的相对纳秒间隔。硬件定时器会在 delta 后产生一次中断,中断服务函数由 os_hrtimer_init 注册为 os_hrtimer_callback(见 2.5 节)。
数值演算(沿用统一基准:当前 now = 10000 ns,要触发定时器 A 的到期时刻 next_nsec = 11000 ns):
| 计算项 | 代入计算 | 结果 |
|---|---|---|
next_nsec | 11000(定时器 A 的绝对到期时刻) | 11000 ns |
当前时间 now | os_clocksource_gettime() | 10000 ns |
delta(传给 oneshot 的间隔) | 11000 - 10000 | 1000 ns |
硬件定时器被设置为"从现在起 1000ns 后产生一次中断"。
注意一个关键点:os_clockevent_stop(ce) 与 os_clockevent_start_oneshot(ce, ...) 在 os_hrtimer_trig_hwtimer 内部是顺序、原子地(处于 os_hrtimer_start/stop/callback 的自旋锁临界区内)执行的,避免"先 stop 后 start"之间窗口期错过触发时刻。
2.5 超时回调 os_hrtimer_callback 与初始化 os_hrtimer_init
一句话定位:硬件中断触发后,遍历链表执行所有到期的定时器回调,处理周期重排,并触发下一个最早定时器。
完整源码(drivers/timer/hrtimer.c,与项目一致):
static void os_hrtimer_callback(os_clockevent_t *ce)
{
os_hrtimer_t *hrtimer;
os_hrtimer_t *tmp;
os_list_for_each_entry_safe(hrtimer, tmp, &gs_hrtimer_list, os_hrtimer_t, list)
{
uint64_t now = os_clocksource_gettime();
if (hrtimer->next_nsec > now)
break;
os_list_del(&hrtimer->list);
hrtimer->state = OS_HRTIMER_STATE_RUN;
hrtimer->timeout_func(hrtimer->parameter);
if (hrtimer->state != OS_HRTIMER_STATE_RUN)
continue;
if (hrtimer->period_nsec != 0)
{
hrtimer->next_nsec = period_calc_next_nsec(hrtimer->next_nsec, now, hrtimer->period_nsec);
os_hrtimer_enqueue(hrtimer);
}
else
{
hrtimer->state = OS_HRTIMER_STATE_NONE;
}
}
hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
}分步详解:
第 1 步:遍历链表执行所有到期定时器(条件 next_nsec > now 则停)
os_list_for_each_entry_safe(hrtimer, tmp, &gs_hrtimer_list, os_hrtimer_t, list)
{
uint64_t now = os_clocksource_gettime();
if (hrtimer->next_nsec > now)
break;
os_list_del(&hrtimer->list);
hrtimer->state = OS_HRTIMER_STATE_RUN;
hrtimer->timeout_func(hrtimer->parameter);详细解释:关键理解:链表按到期时间升序,所以一旦遇到 next_nsec > now(还没到期)就 break(后面的更晚)。对每个到期的定时器:从链表删除、置 RUN 状态、执行回调 timeout_func(parameter)。
为什么用 os_list_for_each_entry_safe(safe 遍历):这是一个核心细节。普通的 os_list_for_each_entry 遍历时如果在循环体内删除或移动当前节点(这里正是如此——先 os_list_del(&hrtimer->list) 删除当前节点,后面周期模式还会 os_hrtimer_enqueue 重新入队),就会破坏遍历用的"下一个节点"指针,导致遍历跳过节点或死循环。_safe 版本的宏会先用 tmp 预先保存下一个节点指针,这样即使当前节点在回调里被删/被移,循环仍能按 tmp 继续正确遍历。这正是遍历过程中要修改链表时使用 safe 版本的根本原因。
第 2 步:处理周期或单次(条件 state 仍为 RUN / period_nsec 非 0)
if (hrtimer->state != OS_HRTIMER_STATE_RUN)
continue;
if (hrtimer->period_nsec != 0)
{
hrtimer->next_nsec = period_calc_next_nsec(hrtimer->next_nsec, now, hrtimer->period_nsec);
os_hrtimer_enqueue(hrtimer);
}
else
{
hrtimer->state = OS_HRTIMER_STATE_NONE;
}
}详细解释:回调执行后,根据回调是否改动了 state 以及 period_nsec 的值,分三条路走:
- 若回调里调用了
os_hrtimer_stop(state被置为NONE),则state != RUN成立,continue跳过后续,不再重新入队。这实现了"一次性回调":回调内部决定停掉定时器。 - 若回调返回后
state仍是RUN(回调没调stop)且period_nsec != 0(周期模式),用period_calc_next_nsec计算下次到期时间并重新入队,实现周期触发。 - 若
period_nsec == 0(单次),状态置NONE(结束,不再入队)。
周期重排数值演算(沿用统一基准:假设定时器 B 首次 next_nsec = 15000,period_nsec = 5000):
情形 A —— 准点触发(now = 16000,只落后 1000ns,不足一个周期):
| 计算项 | 说明 | 数值 |
|---|---|---|
last_nsec | 本次到期时刻(传给 period_calc_next_nsec) | 15000 |
now | 当前触发时刻 | 16000 |
period_nsec | 周期 | 5000 |
| 下次到期时刻 | last_nsec + n*period_nsec > now 的最小 n | 15000 + 5000 = 20000 |
period_calc_next_nsec(定义于 clockevent.h,内联函数)的核心思路:从 last_nsec 出发,按 period_nsec 步进到第一个大于 now 的到期时刻,即 last_nsec + n*period_nsec > now 的最小 n。本例 15000 + 5000 = 20000 > 16000,所以下次到期时刻为 20000。这个定时器将以 20000、25000、30000… 周期触发。
情形 B —— 中断被推迟多个周期(边界演示):假设硬件中断被更高优先级打断,等它真正执行时,当前时间已到 now = 23000(而 last_nsec = 15000 早就该在 20000 处触发,已落后 1.6 个周期)。此时:
| 计算项 | 说明 | 数值 |
|---|---|---|
last_nsec | 本次到期时刻 | 15000 |
now | 当前触发时刻(已落后 1.6 个周期) | 23000 |
period_nsec | 周期 | 5000 |
错误:next = last + period | 15000 + 5000 | 20000(还在过去,< now) |
正确:period_calc_next_nsec | last + k*period > now 的最小 k | 15000 + 2*5000 = 25000 |
为什么不能简单 next_nsec + period_nsec(回调风暴的根因):
用情形 B 解释最清楚。若中断被推迟到 now = 23000,此时简单的 next = last_nsec + period_nsec = 15000 + 5000 = 20000,算出的是一个已经过去的时刻(20000 < 23000)。硬件定时器设一个"过去的时刻"几乎立即触发,回调里又按 next + period 算出 20000 + 5000 = 25000……虽然 25000 正确了,但先被 20000 那次过期触发了一次。如果 now 落后 3 个周期,就会在瞬间连环补触发多次,形成"回调风暴"——每次回调如果做重活(打印、协议处理),系统会卡在这几次连环触发上。
而 period_calc_next_nsec(15000, 23000, 5000) 不关心 now 落后了几个周期,直接用二进制倍增法一次算出"未来最近、且落在固定周期网格 15000 + k*5000 上"的点:
时间轴(单位 ns):
15000 20000 25000 30000 35000
───●───────●───────●───────●───────●───────▶
last (已过期) 现在(now=23000)
↑
简单 next+period = 20000 → 还在过去,会补触发一次
正确 period_calc_next_nsec = 25000 → 一步跳到未来最近网格点,只触发一次period_calc_next_nsec(15000, 23000, 5000) 逐行演算(核心是"二进制倍增":把 _period_nsec 左移后逐步右移减半,累加若干个大块快速逼近,避免逐个周期慢加):
| 阶段 | 关键变量 | 计算 | 结果 |
|---|---|---|---|
| 求 shift | os_fls(5000 >> 32) | os_fls(0) | 0 → 因 shift==0,置 shift = 32 |
| 初始化 | nsec = now + 5000 - last | 23000 + 5000 - 15000 | 13000 |
| 初始化 | _period_nsec = 5000 << 32 | 左移 32 位 | 5000 * 2^32 |
循环末尾(_period_nsec 已右移回 5000) | nsec 经大块累加后剩多少 | 倍增累加若干大块 | 剩余 nsec 使 last 累加到 ≥ now |
| 收尾 | _period_nsec == period_nsec? | 断言成立 | 5000 == 5000 |
| 收尾 | nsec > 0? | 是(last 已超过 now) | 不再加周期,返回 25000 |
为什么必须用这种"倍增"而不是逐个
last += period循环:若now落后几百个周期,逐个加要循环几百次,在中断里不可接受;倍增法只需约shift(≤64)轮,复杂度 O(1)。这也是"为什么不能简单next + period"的第三层原因——不仅要"跳过积压",还要高效地跳过。
一句话总结:next + period 假设"本次是准点触发",一旦 now 落后多个周期,它算出的下次时刻可能还在过去,导致连环补触发(回调风暴);period_calc_next_nsec 则一次性跳到"未来最近且落在固定周期网格上"的点,既跳过积压、又保证后续周期不漂移、且计算开销极小。
第 3 步:触发下一个最早定时器(无条件执行)
hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
}详细解释:遍历完所有到期定时器后,取出新的链表首(最早到期的剩余定时器),触发硬件在它的 next_nsec 时刻中断。注意:若回调里有人 start 了新的定时器、或周期定时器重新入队,它们的到期时间早已在遍历过程中正确插入,这里取链表首即可保证硬件被设置为"当前最早到期时刻"。
os_hrtimer_callback() 条件走向总结:
os_hrtimer_callback() 条件走向总结(对链表中每个定时器 hrtimer,safe 遍历):
├── 取当前时间 now
├── hrtimer->next_nsec > now(还没到期)→ break(跳出整个遍历)
├── 到期:os_list_del(hrtimer);state = RUN;执行回调 timeout_func(parameter)
│ ├── 回调内调用 os_hrtimer_stop(state 变为 NONE)
│ │ └── state != RUN → continue(跳过本定时器,不入队)
│ └── 回调后 state 仍为 RUN
│ ├── period_nsec != 0(周期模式)
│ │ └── next_nsec = period_calc_next_nsec(...);os_hrtimer_enqueue(重新入队)
│ └── period_nsec == 0(单次)→ state = NONE(结束)
└── 遍历完:取链表首 hrtimer
├── 链表非空 → os_hrtimer_trig_hwtimer(hrtimer->next_nsec)(触发下一个最早者)
└── 链表为空 → 不触发(硬件停止)os_hrtimer_init()(初始化,OS_INIT_LEVEL_PRE_DEVICE 级别自动执行):
os_err_t os_hrtimer_init(void)
{
os_clockevent_select_best();
OS_ASSERT((os_clocksource_best() != OS_NULL && os_clockevent_best() != OS_NULL));
os_clockevent_register_isr(os_clockevent_best(), os_hrtimer_callback);
os_hrtimer_t *hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}
#ifdef OS_USING_HRTIMER_FOR_KERNEL_TICK
os_hrtimer_start(&g_hrtimer_systick);
#endif
return OS_SUCCESS;
}
OS_INIT_CALL(os_hrtimer_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_LOW);分步详解:
第 1 步:选择最佳时钟事件并注册中断(无条件执行)
os_clockevent_select_best();
OS_ASSERT((os_clocksource_best() != OS_NULL && os_clockevent_best() != OS_NULL));
os_clockevent_register_isr(os_clockevent_best(), os_hrtimer_callback);详细解释:先 os_clockevent_select_best() 选择最佳时钟事件。断言时钟源和时钟事件都存在。然后 os_clockevent_register_isr 把 os_hrtimer_callback 注册为时钟事件的中断回调。
第 2 步:触发已入队的定时器(条件 链表非空)
os_hrtimer_t *hrtimer = os_list_first_entry_or_null(&gs_hrtimer_list, os_hrtimer_t, list);
if (hrtimer != OS_NULL)
{
os_hrtimer_trig_hwtimer(hrtimer->next_nsec);
}详细解释:若已有定时器入队(OS_INIT_LEVEL_PRE_DEVICE 之前的早期初始化),触发链表首。
第 3 步:可选用 HRTIMER 驱动内核 Tick(条件 OS_USING_HRTIMER_FOR_KERNEL_TICK)
#ifdef OS_USING_HRTIMER_FOR_KERNEL_TICK
os_hrtimer_start(&g_hrtimer_systick);
#endif
return OS_SUCCESS;
}
OS_INIT_CALL(os_hrtimer_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_LOW);详细解释:若编译配置了 OS_USING_HRTIMER_FOR_KERNEL_TICK,启动 g_hrtimer_systick(一个周期型 hrtimer,周期 = 1 Tick),让内核 Tick 由高精度定时器驱动。OS_INIT_CALL(..., OS_INIT_LEVEL_PRE_DEVICE, ...) 让它在设备初始化前自动执行。
os_hrtimer_init() 条件走向总结:
os_hrtimer_init() 条件走向总结:
├── os_clockevent_select_best()(选择最佳时钟事件)
├── OS_ASSERT(clocksource 与 clockevent 均存在)(不存在则断言崩溃)
├── os_clockevent_register_isr(best, os_hrtimer_callback)(注册回调为中断 ISR)
├── 取链表首 hrtimer
│ ├── hrtimer != OS_NULL(有已入队定时器)→ os_hrtimer_trig_hwtimer(链表首)
│ └── hrtimer == OS_NULL(链表空)→ 不触发
├── #ifdef OS_USING_HRTIMER_FOR_KERNEL_TICK(编译配置)
│ └── 已定义 → os_hrtimer_start(&g_hrtimer_systick)(驱动内核 Tick)
└── 返回 OS_SUCCESS2.6 os_hrtimer_stoped —— 查询定时器是否已停止
一句话定位:查询一个 hrtimer 当前是否处于"未启动/已停止"(OS_HRTIMER_STATE_NONE)状态,常用于轮询判断定时器是否已触发/已停止。
函数签名:
os_bool_t os_hrtimer_stoped(os_hrtimer_t *hrtimer);| 参数 | 说明 |
|---|---|
hrtimer | 要查询的 hrtimer 对象 |
| 返回值 | 说明 |
|---|---|
OS_TRUE | 定时器处于 OS_HRTIMER_STATE_NONE(未启动 / 已停止 / 单次执行完) |
OS_FALSE | 定时器不处于 NONE(在等待 WAIT 或正在执行回调 RUN) |
完整源码(drivers/timer/hrtimer.c,与项目一致):
os_bool_t os_hrtimer_stoped(os_hrtimer_t *hrtimer)
{
return (hrtimer->state == OS_HRTIMER_STATE_NONE);
}分步详解:
第 1 步:准备与参数校验(无条件执行,源码未做判空)
return (hrtimer->state == OS_HRTIMER_STATE_NONE);详细解释:注意:该函数没有对 hrtimer 做空指针校验,直接解引用 hrtimer->state。因此调用方必须保证传入的 hrtimer 是有效且已初始化的对象(state 字段已初始化——通常 static 对象默认值为 0,即 OS_HRTIMER_STATE_NONE)。若传入野指针,将直接访问非法内存。
第 2 步:比较状态并返回结果(无条件执行)
return (hrtimer->state == OS_HRTIMER_STATE_NONE);详细解释:这是一个纯查询、无副作用的 getter。它把 hrtimer->state 与 OS_HRTIMER_STATE_NONE 比较:相等返回 OS_TRUE,否则返回 OS_FALSE。该函数不修改任何字段、不加锁——因为在中断上下文(回调里)也可能调用它,加锁反而可能死锁。
状态对应速查表:
当前 state | os_hrtimer_stoped() 返回值 | 含义 |
|---|---|---|
OS_HRTIMER_STATE_NONE | OS_TRUE | 未启动 / 已停止 / 单次执行完 |
OS_HRTIMER_STATE_WAIT | OS_FALSE | 正在链表中等待到期 |
OS_HRTIMER_STATE_RUN | OS_FALSE | 正在执行回调 |
典型应用场景:在任务上下文轮询判断一个"一次性"定时器是否已经触发完毕(单次执行完会自动回到 NONE);或在回调里先 os_hrtimer_stoped(&timer) 判断是否已经被外部 stop 过,再决定是否重新 start。
三、完整使用示例
示例:在任务中定义并启停高精度定时器
下面的示例演示一个完整可运行的 HRTIMER 用法:创建两个 os_hrtimer_t(一个单次、一个周期),在一个任务里启动它们,再通过轮询 os_hrtimer_stoped 观察单次定时器触发完毕,最后停止周期定时器。
#include <timer/hrtimer.h>
#include <timer/timer.h>
#include <os_clock.h>
#include <os_task.h>
#include <os_util.h>
#include <os_stddef.h>
#include <os_assert.h>
/* 超时回调:在中断上下文执行,只做轻量打印 */
static void timeout_callback(void *parameter)
{
os_kprintf("hrtimer timeout! param=%s\r\n", (const char *)parameter);
}
/* 定时器对象需定义成 static/全局,保证生命周期贯穿整个使用期 */
static os_hrtimer_t once_timer;
static os_hrtimer_t period_timer;
static void hrtimer_demo(void *parameter)
{
/* 1. 配置单次定时器:100ms 后触发一次(period_nsec = 0 表示单次) */
once_timer.timeout_func = timeout_callback;
once_timer.parameter = "once";
once_timer.timeout_nsec = 100 * NSEC_PER_MSEC; /* 100ms = 100000000 ns */
once_timer.period_nsec = 0; /* 单次 */
os_hrtimer_start(&once_timer);
/* 2. 配置周期定时器:首次 1ms 触发,之后每 1ms 触发一次 */
period_timer.timeout_func = timeout_callback;
period_timer.parameter = "period";
period_timer.timeout_nsec = 1 * NSEC_PER_MSEC; /* 首次 1ms */
period_timer.period_nsec = 1 * NSEC_PER_MSEC; /* 周期 1ms */
os_hrtimer_start(&period_timer);
/* 3. 轮询等待单次定时器触发完毕(单次执行完会自动回到 NONE 状态) */
while (os_hrtimer_stoped(&once_timer) == OS_FALSE)
{
os_task_msleep(10); /* 让出 CPU,延时 10ms 后再查 */
}
os_kprintf("once_timer stopped, single-shot done\r\n");
/* 4. 停止周期定时器(否则它会一直触发) */
os_hrtimer_stop(&period_timer);
os_kprintf("period_timer stopped\r\n");
while (1)
{
os_task_msleep(1000);
}
}
/* 定义任务控制块与任务栈(静态创建,不占堆) */
OS_TASK_DEFINE(hrtimer_demo_cb);
OS_TASK_STACK_DEFINE(hrtimer_demo_stack, 1024);
/* 创建任务:创建 → 检查返回值 → 启动 */
void hrtimer_demo_task(void)
{
os_task_id tid;
os_err_t ret;
tid = os_task_create(&hrtimer_demo_cb, /* 静态 TCB */
OS_TASK_STACK_BEGIN_ADDR(hrtimer_demo_stack), /* 栈起始地址 */
OS_TASK_STACK_SIZE(hrtimer_demo_stack), /* 栈大小 */
"hrtimer_demo", /* 任务名称 */
hrtimer_demo, /* 任务入口 */
OS_NULL, /* 入口参数 */
20); /* 优先级 20 */
/* 创建失败(TCB/栈内存不足)时返回 OS_NULL */
OS_ASSERT(tid != OS_NULL);
ret = os_task_startup(tid);
if (ret != OS_SUCCESS)
{
os_kprintf("task startup failed\r\n");
return;
}
}说明:示例展示 HRTIMER 的完整标准用法:定义
os_hrtimer_t→ 填充timeout_func/parameter/timeout_nsec/period_nsec→os_hrtimer_start。实际效果:单次定时器在 100ms 后触发一次回调;周期定时器每 1ms 触发一次回调;任务通过os_hrtimer_stoped检测单次定时器已触发完毕(回到NONE),再os_hrtimer_stop停止周期定时器。
注意:
timeout_nsec/period_nsec单位是纳秒,可用NSEC_PER_MSEC(=1000000)、NSEC_PER_SEC(=1000000000)等宏换算。period_nsec = 0表示单次,非 0 表示周期。- 超时回调在中断上下文执行,不能阻塞、不能做耗时操作,因此示例回调里只做
os_kprintf打印。os_hrtimer_t必须定义成static/全局(不能是任务栈上的局部变量),否则任务结束后定时器仍被链表引用,回调会访问已失效的内存。
四、时钟源与时钟事件的移植/适配(更换 MCU)
4.1 先认识 BSP 层:谁在提供"读时间"和"产生中断"
回顾 1.2 节的三层抽象,HRTIMER 能工作,依赖它脚下的两层时钟硬件:
- clocksource(时钟源):
os_hrtimer_start里调用os_clocksource_gettime()读当前时间; - clockevent(时钟事件):
os_hrtimer_trig_hwtimer里调用os_clockevent_best()+os_clockevent_start_oneshot()让硬件到点触发中断。
关键认知:hrtimer.c(以及 clocksource.c、clockevent.c)是跨平台统一的框架代码,换 MCU 不用改。真正跟具体芯片相关的是 BSP 层——它要为这个 MCU 提供一份"时钟源实现"和一份"时钟事件实现"。在 Cortex-M 平台上,这一层就是 drivers/timer/clocksource_cortexm.c(配合 clocksource_cortexm.h),它用 SysTick 或 DWT 来实现两者。
clocksource_cortexm.c 的内部结构(drivers/timer/clocksource_cortexm.c,通过三个编译开关分别控制三份实现):
| 编译开关 | 定义内容 | 用途 |
|---|---|---|
OS_USING_DWT_FOR_CLOCKSOURCE | cortexm_dwt_clocksource + cortexm_dwt_read + cortexm_dwt_init | 用 DWT 周期计数器(DWT->CYCCNT)做时钟源 |
OS_USING_SYSTICK_FOR_CLOCKSOURCE | cortexm_systick_clocksource + cortexm_systick_read + cortexm_systick_clocksource_init | 用 SysTick 计数器做时钟源(SysTick->LOAD/VAL) |
OS_USING_SYSTICK_FOR_CLOCKEVENT | cortexm_systick_clockevent + 三个 ops 函数 + cortexm_systick_clockevent_init + cortexm_systick_clockevent_isr | 用 SysTick 做时钟事件(到点中断,SysTick->CTRL/LOAD/VAL) |
说明:
clocksource_cortexm.c的三份实现由三个#ifdef编译开关分别包住,完整源码见 4.3 节的接口契约小节。BSP 层三份实现各自对应的硬件资源如下:
| BSP 实现 | 编译开关 | 用途 | 对应寄存器 |
|---|---|---|---|
cortexm_dwt_clocksource | OS_USING_DWT_FOR_CLOCKSOURCE | 用 DWT 周期计数器(CYCCNT)做时钟源 | DWT->CYCCNT |
cortexm_systick_clocksource | OS_USING_SYSTICK_FOR_CLOCKSOURCE | 用 SysTick 计数器做时钟源 | SysTick->LOAD/VAL |
cortexm_systick_clockevent | OS_USING_SYSTICK_FOR_CLOCKEVENT | 用 SysTick 做时钟事件(到点中断) | SysTick->CTRL/LOAD/VAL |
4.2 移植的全景图:只有 BSP 层与 MCU 相关
┌──────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ os_hrtimer_start / stop / init / stoped │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ HRTIMER 框架层(drivers/timer/hrtimer.c) │
│ 排序链表 + 触发调度(换 MCU 不用改) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ clockevent 层(drivers/timer/clockevent.c) │
│ 设置单次/周期中断(换 MCU 不用改) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ clocksource 层(drivers/timer/clocksource.c) │
│ 读单调纳秒时间(换 MCU 不用改) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ BSP 层(drivers/timer/clocksource_cortexm.c) ★需适配★ │
│ os_clocksource_t(read/freq/mask/rating) │
│ os_clockevent_t + struct os_clockevent_ops │
│ 操作具体 MCU 的 DWT / SysTick 寄存器 │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(MCU 的 DWT / SysTick / 定时器外设) │
└──────────────────────────────────────────────────────────┘移植的关键洞察:换 MCU,上面四层框架(应用 / hrtimer / clockevent / clocksource)一行都不用改,只需在 BSP 层提供"能读时间"的时钟源和"能产生中断"的时钟事件,并通过注册函数挂到系统里。这就是时钟子系统分层设计的最大价值——上层与硬件解耦。
4.3 移植需要提供的"接口契约"
4.3.1 时钟源契约:struct os_clocksource
一个时钟源要能用,必须提供 read(读计数器)、freq(频率)、mask(掩码)、rating(评级) 四个关键要素。
来源:drivers/timer/clocksource.h
struct os_clocksource
{
os_device_t parent;
char name[OS_CLOCKSOURCE_NAME_LENGTH];
uint32_t rating;
uint32_t freq;
uint64_t mask;
uint64_t min_nsec;
uint64_t max_nsec;
uint32_t mult; /* time(ns) = count * mult >> shift */
uint32_t shift;
uint32_t mult_t; /* count = time(ns) * mult_t >> shift_t */
uint32_t shift_t;
uint64_t last_update_count;
uint64_t last_update_nsec;
uint64_t (*read)(void *clock);
os_list_node_t list;
};移植时 BSP 只需填的字段(其余 mult/shift/last_update_* 由 os_clocksource_register 自动计算/维护):
| 字段 | 必须提供 | 含义 |
|---|---|---|
rating | ✅ | 评级(数字越大越优先,OS_CLOCKSOURCE_BEST 会被置为最大) |
freq | ✅ | 计数器频率(Hz),如 SystemCoreClock |
mask | ✅ | 计数器位宽掩码(如 24 位 SysTick 用 0x00ffffff) |
read | ✅ | 读计数器当前值的回调,返回 uint64_t 递增计数值 |
parent | 由框架填 | 设备基类,register 时自动填 |
mult/shift 等 | 自动 | 由 calc_mult_shift 计算,实现"计数值 ⇄ 纳秒"换算 |
源码参考——SysTick 时钟源的实现(drivers/timer/clocksource_cortexm.c,与项目逐字一致):
#ifdef OS_USING_SYSTICK_FOR_CLOCKSOURCE
static os_clocksource_t cortexm_systick_clocksource;
void cortexm_systick_clocksource_init(void)
{
systick->LOAD = 0x00fffffful; /* set reload register */
systick->VAL = 0UL; /* Load the systick Counter Value */
systick->CTRL &= ~SYSTICK_CTRL_TICKINT_MASK;
systick->CTRL |= SYSTICK_CTRL_ENABLE_MASK;
cortexm_systick_clocksource.rating = 240;
cortexm_systick_clocksource.freq = SystemCoreClock;
cortexm_systick_clocksource.mask = 0x00ffffffull;
cortexm_systick_clocksource.read = cortexm_systick_read;
os_clocksource_register("systick", &cortexm_systick_clocksource);
}
#endif /* OS_USING_SYSTICK_FOR_CLOCKSOURCE */其中 read 回调 cortexm_systick_read(用 LOAD - VAL 得到一个递增计数):
static uint64_t cortexm_systick_read(void *clock)
{
return systick->LOAD - systick->VAL;
}关键理解:
read必须返回一个单调递增的计数(随真实时间流逝而增大),框架才能算出"过去了多少时间"。SysTick 是递减计数器,所以用LOAD - VAL转成递增。freq决定每计数一个单位对应多少纳秒;mask决定计数回绕(溢出)的周期。两者都错了,os_clocksource_gettime()返回的时间就是错的。
4.3.2 时钟事件契约:struct os_clockevent + struct os_clockevent_ops
一个时钟事件要能用,BSP 必须实现三个底层操作并填到 ops 里,同时填写硬件能力字段。
来源:drivers/timer/clockevent.h
struct os_clockevent_ops
{
void (*start)(os_clockevent_t *ce, uint32_t prescaler, uint64_t count);
void (*stop)(os_clockevent_t *ce);
uint64_t (*read)(void *clock);
};移植时 BSP 需提供的 ops 三个函数:
| ops 函数 | 职责 |
|---|---|
start | 启动硬件定时器:写入装载值 count、使能计数与中断(在 prescaler * count 个计数后产生中断) |
stop | 停止硬件定时器:关闭计数与中断 |
read | 读当前计数(可复用时钟源的 read) |
源码参考——SysTick 时钟事件的实现(drivers/timer/clocksource_cortexm.c,与项目逐字一致):
#ifdef OS_USING_SYSTICK_FOR_CLOCKEVENT
static os_clockevent_t cortexm_systick_clockevent;
void cortexm_systick_clockevent_isr(void)
{
os_clockevent_isr(&cortexm_systick_clockevent);
}
static void cortexm_systick_start(os_clockevent_t *ce, uint32_t prescaler, uint64_t count)
{
systick->LOAD = count; /* set reload register */
systick->VAL = 0UL; /* Load the systick Counter Value */
systick->CTRL |= SYSTICK_CTRL_TICKINT_MASK | SYSTICK_CTRL_ENABLE_MASK;
}
static void cortexm_systick_stop(os_clockevent_t *ce)
{
systick->CTRL &= ~(SYSTICK_CTRL_TICKINT_MASK | SYSTICK_CTRL_ENABLE_MASK);
}
static const struct os_clockevent_ops cortexm_systick_clockevent_ops = {
.start = cortexm_systick_start,
.stop = cortexm_systick_stop,
.read = cortexm_systick_read,
};
void cortexm_systick_clockevent_init(void)
{
cortexm_systick_clockevent.rating = 240;
cortexm_systick_clockevent.freq = SystemCoreClock;
cortexm_systick_clockevent.mask = 0x00ffffffull;
cortexm_systick_clockevent.prescaler_mask = 1;
cortexm_systick_clockevent.prescaler_bits = 0;
cortexm_systick_clockevent.count_mask = 0x00ffffffull;
cortexm_systick_clockevent.count_bits = 24;
cortexm_systick_clockevent.feature = OS_CLOCKEVENT_FEATURE_PERIOD;
cortexm_systick_clockevent.min_nsec = NSEC_PER_SEC / SystemCoreClock;
cortexm_systick_clockevent.ops = &cortexm_systick_clockevent_ops;
os_clockevent_register("systick", &cortexm_systick_clockevent);
}
#endif /* OS_USING_SYSTICK_FOR_CLOCKEVENT */移植时需填的关键字段(os_clockevent_register 会断言这些字段必须正确):
| 字段 | 含义 |
|---|---|
ops->start/stop/read | 三个硬件操作(必填,register 断言非空) |
feature | OS_CLOCKEVENT_FEATURE_ONESHOT(单次)或 PERIOD(周期) |
freq / mask | 计数器频率与位宽掩码 |
prescaler_mask / prescaler_bits | 预分频器的掩码与位宽(无预分频用 1 / 0) |
count_mask / count_bits | 计数器的掩码与位宽 |
min_nsec | 能表示的最小间隔(一般 = 1 计数周期 NSEC_PER_SEC / freq) |
rating | 评级(数字越大越优先) |
4.4 移植的标准流程(以"给新 MCU 提供时钟事件"为例)
下面是把一个时钟事件从"想法"变成"可用"的通用流程。时钟源的移植同理,只是字段更少。
第 1 步:确认硬件能力——新 MCU 上选一个可产生周期性/单次中断的定时器外设(如通用定时器 TIMx),查它的位宽(如 16/32 位)、是否有预分频器、主频。
第 2 步:定义 os_clockevent_t 静态实例:
static os_clockevent_t my_clockevent;第 3 步:实现 start / stop / read 三个底层操作(操作具体寄存器):
static void my_ce_start(os_clockevent_t *ce, uint32_t prescaler, uint64_t count)
{
/* 写预分频器、装载值 count,使能计数与中断 */
}
static void my_ce_stop(os_clockevent_t *ce)
{
/* 关闭计数与中断 */
}
static uint64_t my_ce_read(void *clock)
{
return /* 读当前计数值 */;
}第 4 步:组装 ops 并填写硬件字段:
static const struct os_clockevent_ops my_ce_ops = {
.start = my_ce_start,
.stop = my_ce_stop,
.read = my_ce_read,
};第 5 步:初始化函数里填字段并注册(在系统时钟子系统初始化前调用):
void my_clockevent_init(void)
{
my_clockevent.rating = 200;
my_clockevent.freq = /* 定时器主频 */;
my_clockevent.mask = /* 计数器位宽掩码 */;
my_clockevent.feature = OS_CLOCKEVENT_FEATURE_PERIOD;
my_clockevent.ops = &my_ce_ops;
os_clockevent_register("my_ce", &my_clockevent);
}第 6 步:提供 ISR 并注册给框架——定时器中断服务函数里调用 os_clockevent_isr(&my_clockevent),让框架在到点时执行 HRTIMER 注册的回调(os_hrtimer_callback):
void my_ce_isr(void)
{
os_clockevent_isr(&my_clockevent);
}4.5 芯片未适配怎么办(补实现 vs 换外设)
如果新 MCU 上 SysTick 或 DWT 不可用/精度不足,有两条路:
| 维度 | 复用 SysTick/DWT | 改用通用定时器(TIMx) |
|---|---|---|
| 判断 | SysTick/DWT 可用,只是换型号 | SysTick/DWT 不可用,或需要更高精度 |
| 是否改框架 | 否 | 否 |
| 改动 | 在 clocksource_cortexm.c 补齐型号的外设初始化 | 新写一份类似 clocksource_cortexm.c 的 BSP(用 TIMx 做时钟事件) |
| 工作量 | 小 | 中 |
注意:无论走哪条路,框架四层(hrtimer / clockevent / clocksource)都不改,只新增/修改 BSP 层的
os_clocksource_t/os_clockevent_t实现,走 4.4 的标准流程注册即可。
4.6 移植检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
read 返回单调递增计数? | 递减计数器要转成递增(如 LOAD - VAL) |
freq / mask 与硬件一致? | 否则 gettime() 时间错乱、计数回绕判断错误 |
ops 三个函数齐全? | os_clockevent_register 会 OS_ASSERT 校验 |
feature 选对单次/周期? | 选错会导致定时触发行为异常 |
中断服务函数调用了 os_clockevent_isr? | 不调则到点不会执行 HRTIMER 回调 |
min_nsec 是否正确? | 一般设为 1 计数周期,太小/太大影响可表示范围 |
📝 本节小结
核心要点回顾
本节围绕 Timer 高精度定时器展开,核心要点如下:
1. 三层时钟抽象
| 层 | 作用 |
|---|---|
| clocksource | 读取单调递增的纳秒时间 |
| clockevent | 在指定时间点产生硬件中断 |
| hrtimer | 管理多个定时器,到点调用回调 |
2. 核心结构体与状态机
os_hrtimer_t:timeout_nsec(相对)/period_nsec(0=单次)/next_nsec(绝对)。- 状态机:
NONE → WAIT(入队)→ RUN(回调中)→ WAIT/NONE。
3. 排序链表调度
gs_hrtimer_list按next_nsec升序排列,链表首最早到期。- 只有链表首触发硬件定时器,到点后
os_hrtimer_callback遍历执行所有到期定时器并触发下一个最早者。
4. 核心函数与查询函数
| 函数 | 作用 |
|---|---|
os_hrtimer_start | 计算 next_nsec、入队、若为链表首则触发硬件 |
os_hrtimer_stop | 清周期、删链表、若停链表首则重新触发 |
os_hrtimer_callback | 到点遍历执行所有到期定时器,处理周期/单次,触发下一个最早者 |
os_hrtimer_init | 选时钟事件、注册 ISR、可选驱动内核 Tick |
os_hrtimer_stoped | 查询定时器是否处于 NONE(未启动/已停止)状态 |
5. 更换 MCU 的移植要点
hrtimer.c/clockevent.c/clocksource.c是跨平台统一框架,换 MCU 不用改。- 只有 BSP 层(如
clocksource_cortexm.c)与芯片相关,需提供:时钟源os_clocksource_t(read/freq/mask/rating)+ 时钟事件os_clockevent_t(struct os_clockevent_ops的start/stop/read+ 硬件字段)。 - 通过
os_clocksource_register/os_clockevent_register挂入系统,ISR 里调os_clockevent_isr通知框架。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 混淆相对/绝对时间 | timeout_nsec 是相对延时,next_nsec 是绝对时间戳(start 时计算) |
忘记 period_nsec 语义 | 0 表示单次,非 0 表示周期;停止前它会一直重新入队 |
| 在回调里做耗时/阻塞操作 | 回调在中断上下文执行,不能阻塞、不能调 os_kprintf 等耗时操作 |
| 未初始化字段就 start | start 前必须设置 timeout_func/timeout_nsec,否则未定义行为 |
| 回调里调 stop 的时序 | 回调中调 os_hrtimer_stop 会跳过重新入队(state != RUN 检查),用于"一次性回调"场景 |
回调里调 os_hrtimer_stoped 误判 | RUN(回调中)/WAIT(等待中)时返回 OS_FALSE,只有 NONE 才返回 OS_TRUE |
| 定时器定义在任务栈上 | os_hrtimer_t 必须定义成 static/全局,否则任务结束后链表仍引用,回调访问已失效内存 |
周期重排用 next + period | 应理解 period_calc_next_nsec 会跳到"未来最近到期点",避免中断积压导致回调风暴 |
| 遍历时误改链表 | 回调里删除/移动当前节点依赖 os_list_for_each_entry_safe 的 tmp 预存下一节点 |
| 与软件定时器混淆 | 内核软件定时器(os_timer)基于 Tick、毫秒级;HRTIMER 纳秒级、驱动层使用 |
| 换 MCU 时改框架四层 | 只需改 BSP 层(clocksource_cortexm.c 或新增),hrtimer/clockevent/clocksource 框架层不动 |
时钟源 read 返回递减计数 | 递减计数器要用 LOAD - VAL 转成递增,否则算出的时间倒退 |
时钟事件 freq/mask 填错 | freq 与 mask 必须与硬件一致,否则 gettime() 时间错乱、回绕判断错误 |
ops 未填全 / ISR 不调 os_clockevent_isr | ops 三函数齐全(register 断言),ISR 必须调 os_clockevent_isr 否则到点不执行回调 |
下一步
掌握了 Timer 高精度定时器后,接下来学习 RTC 实时时钟设备:
- 5.8 RTC设备:了解 OneOS 独立供电的实时时钟管理、时间读写与闹钟设置。