定时器与系统任务
AD23N SDK 的定时器与系统任务机制:基于 2ms 周期硬件定时中断驱动的统一节拍(tick)框架,通过 tick_timer_loop() 在中断上下文中轮询调度系统任务(sys_tick_timer)、应用任务(app_timer_loop)与模块任务(modules_tick_timer),并以全局节拍计数 jiffies 提供软件延时与周期消息能力。
Purpose and Scope
本页面系统性地介绍 AD23N SDK 中定时器与系统任务这一完整能力,覆盖:
- 系统滴答定时器(tick timer)的初始化与全局节拍计数
jiffies; - 系统任务轮询框架
tick_timer_loop()及其内部调度逻辑(sys_tick_timer/app_timer_loop/modules_tick_timer); - 模块级周期任务的分频机制(ADC 扫描、按键扫描、红外超时、SD 卡检测、USB 热插拔检测等);
- 基于
jiffies的软件延时原语os_time_dly/delay_10ms; - 低功耗场景下 tick 中断的开关控制(
tick_timer_set/tick_timer_close); - 通用硬件定时器 gptimer 的接口概览(完整 PWM/捕获功能属于外设文档范畴)。
以下主题有意留给其他目录页面,本页只做必要的交叉引用:gptimer 的 PWM/捕获/信号测量详细用法(参见外设 gptimer 文档)、电源管理与低功耗状态机(app_power_mg)、消息与事件机制(post_msg / bsp_post_event 所属的消息服务页面)。
Overview
在 AD23N 这类 MCU SDK 中,绝大多数周期性任务(按键扫描、ADC 采样、外设热插拔检测、音量淡入淡出、系统消息投递)不需要各自占用一个硬件定时器或 RTOS 任务。SDK 采用经典的**"单一定时中断 + 计数分频轮询"**架构:
- 一个硬件定时器产生周期约 2ms 的中断(
tick_timer_init()初始化;common.h中loop_detect_handler.time注释明确"timer 周期默认一般是 2ms"); - 中断服务程序中调用
tick_timer_loop(),该函数维护一个递增的g_tick_cnt计数器; - 各层任务通过判断
cnt % N == 0来获得不同的执行周期(例如cnt % 5即 10ms、cnt % 250即 500ms); - 系统维护全局节拍
jiffies(每 10ms 递增一次),作为软件延时的时钟基准; - 低功耗时可通过
tick_timer_set(true)关闭 tick 中断,进入休眠前/唤醒后由电源管理模块控制。
这种设计的意图非常明确:在资源受限的 MCU 上,用单个中断源 + 整数分频,低成本地满足几十个不同周期的轮询需求,同时避免引入重量级 RTOS 调度器。代价是:所有回调都在中断上下文中执行,因此每个回调必须短小、快速返回、不可阻塞。
Architecture
flowchart TD
subgraph sg_HW["硬件层"]
HW_TMR["硬件定时器<br/>(2ms 周期中断)"]
end
subgraph sg_CORE["滴答定时器核心 (tick_timer_sys.c)"]
INIT["tick_timer_init()"]
LOOP["tick_timer_loop()<br/>g_tick_cnt++"]
SYS["sys_tick_timer(cnt)"]
APP["app_timer_loop()<br/>(weak, 应用可覆盖)"]
MOD["modules_tick_timer(cnt)<br/>(weak, 模块强实现)"]
JIFFIES["jiffies (volatile u32)<br/>每 10ms +1"]
DELAY["os_time_dly / delay_10ms<br/>(基于 jiffies 忙等)"]
PM["tick_timer_set / tick_timer_close<br/>(低功耗开关)"]
end
subgraph sg_MOD["模块周期任务 (tick_timer_modules.c)"]
KEY["key_scan()<br/>cnt%5 → 10ms"]
IR["ir_timeout()<br/>cnt%5 → 10ms (KEY_IR_EN)"]
ADC["adc_scan()<br/>cnt%2 → 4ms"]
SD["sd0_dev_detect()<br/>cnt%100 → 200ms (TFG_SD_EN)"]
USB["usb_hotplug_detect()<br/>cnt%125 → 250ms"]
end
subgraph sg_MSG["消息/事件通道"]
MSG500["post_msg(1, MSG_500MS)<br/>cnt%250 → 500ms"]
EVT100["bsp_post_event(B_EVENT_100MS)<br/>cnt%50 → 100ms (FLASH_CACHE)"]
end
HW_TMR --> INIT
INIT --> LOOP
LOOP --> SYS
LOOP --> APP
LOOP --> MOD
SYS --> JIFFIES
SYS --> MSG500
SYS --> EVT100
SYS --> DAC["dac_fade_api()<br/>音量淡入淡出 (cnt%5)"]
JIFFIES --> DELAY
PM -.->|"控制中断开关"| HW_TMR
MOD --> KEY
MOD --> IR
MOD --> ADC
MOD --> SD
MOD --> USB
架构说明:
tick_timer_init()完成硬件定时器初始化(由系统启动流程调用,接口声明见 tick_timer_driver.h);tick_timer_loop()是中断上下文中唯一的系统任务入口,依次调度三个层级的任务:系统任务 → 应用任务 → 模块任务;sys_tick_timer负责推进jiffies、驱动 DAC 淡入淡出、投递 100ms/500ms 周期事件与消息;app_timer_loop与modules_tick_timer均为 weak 符号:前者供应用工程覆盖,后者由tick_timer_modules.c提供强实现;- 模块任务通过取模分频获得各自周期,互不阻塞地顺序执行;
jiffies只被sys_tick_timer递增、只被延时原语读取,构成轻量级的软件时钟;tick_timer_set/tick_timer_close供电源管理模块在低功耗模式下停掉 tick 中断,避免唤醒抖动。
核心实现:系统滴答定时器与任务调度
全局节拍与中断开关
tick_timer_sys.c 定义了核心全局状态:jiffies(节拍计数)与 isr_tick_timer_close(tick 中断开关标志):
volatile u32 jiffies = 0;
volatile u8 isr_tick_timer_close = 0;
/*
* 警告!!!!!!!!!!!!
* 以下两个函数一定要内联
* */
__attribute__((always_inline))
void tick_timer_set(bool on_off)
{
isr_tick_timer_close = on_off;
}
__attribute__((always_inline))
bool tick_timer_close(void)
{
return isr_tick_timer_close;
}
Source: tick_timer_sys.c
设计意图:tick_timer_set / tick_timer_close 被强制内联(__attribute__((always_inline))),因为它们会被低功耗关键路径(关中断、进入休眠、唤醒)频繁调用,内联可以消除函数调用开销并保证时序确定。isr_tick_timer_close 使用 volatile,因为它在中断上下文与主循环/电源管理上下文之间共享。
系统任务 sys_tick_timer
系统级周期任务按 g_tick_cnt 取模分频:
u16 g_tick_cnt = 0;
void sys_tick_timer(u32 cnt)
{
if (0 == (cnt % 5)) {
dac_fade_api();
jiffies ++;
}
#if (FLASH_CACHE_ENABLE==1)
#if EXT_FLASH_EN
if (get_flash_cache_timer()) {
if (0 == (cnt % 50)) {//100ms
bsp_post_event(B_EVENT_100MS);
/* putchar('z'); */
}
}
#endif
#endif
if (0 == (cnt % 250)) { //500ms
post_msg(1, MSG_500MS);
/* cnt = 0; */
}
}
Source: tick_timer_sys.c
周期换算(基准 2ms/次):
| 取模条件 | 实际周期 | 动作 |
|---|---|---|
cnt % 5 == 0 | 10ms | dac_fade_api() 音量淡入淡出;jiffies++ 推进全局节拍 |
cnt % 50 == 0 | 100ms | 仅当 FLASH_CACHE_ENABLE 且 EXT_FLASH_EN 且 flash 缓存定时器开启时,投递事件 B_EVENT_100MS |
cnt % 250 == 0 | 500ms | 向系统消息队列投递 MSG_500MS 消息 |
注意 jiffies 的递增与 DAC 淡入淡出绑定在同一个 10ms 分频点上,保证系统节拍与音频效果同步;而 500ms 消息通过 post_msg(1, ...) 投递到消息队列,由消息循环异步处理,避免在中断中执行重逻辑。
任务调度入口 tick_timer_loop
void tick_timer_loop()
{
g_tick_cnt ++;
sys_tick_timer(g_tick_cnt);
app_timer_loop();
modules_tick_timer(g_tick_cnt);
if (0 == (g_tick_cnt % 250)) { //500ms
g_tick_cnt = 0;
}
}
Source: tick_timer_sys.c
执行顺序与设计考虑:
- 先递增
g_tick_cnt:g_tick_cnt从 1 开始累计,避免 0 时刻所有cnt % N == 0的任务同时爆发执行; - 系统任务 → 应用任务 → 模块任务:系统任务先推进
jiffies并投递周期消息,随后应用任务与模块任务才能读到一致的节拍状态; - 500ms 后清零
g_tick_cnt:由于u16计数器容量有限,且所有分频周期(2/5/50/100/125/250)都是 250 的约数,在 500ms 时归零既避免了回绕问题,又不破坏任何取模节奏。
弱符号扩展点
app_timer_loop 与 modules_tick_timer 都声明为 weak 函数,这是 SDK 提供给应用层的标准扩展机制:
__attribute__((weak))
void app_timer_loop(void)
{
}
__attribute__((weak))
void modules_tick_timer(u32 cnt)
{
}
Sources: tick_timer_sys.c 中的 weak 定义 、tick_timer_sys.c 中的 weak 定义
设计意图:app_timer_loop 让应用层可以注入自己的周期任务而无需改动 SDK 内核;modules_tick_timer 则允许模块层(tick_timer_modules.c)提供强实现,把各个外设模块的周期扫描统一挂接到 tick 中断上。链接器规则保证:若链接时存在强定义,weak 定义自动被覆盖。
模块级周期任务:modules_tick_timer
tick_timer_modules.c 为 weak 的 modules_tick_timer 提供强实现,把各外设模块的周期轮询统一挂接到 tick 中断:
void modules_tick_timer(u32 cnt)
{
if (0 == (cnt % 5)) {
#if KEY_IR_EN
ir_timeout();
#endif
key_scan();
}
if (0 == (cnt % 100)) { //200ms
#if defined(TFG_SD_EN) && (TFG_SD_EN)
sd0_dev_detect(NULL);
#endif
}
if (0 == (cnt % 125)) { //250ms
#if TCFG_PC_ENABLE || TCFG_UDISK_ENABLE
void usb_hotplug_detect(void *argv);
usb_hotplug_detect(NULL);
#endif
}
if (0 == (cnt % 2)) { //4ms
adc_scan(NULL);
}
}
Source: tick_timer_modules.c
各模块任务的周期与设计意图:
| 取模条件 | 实际周期 | 任务 | 说明 |
|---|---|---|---|
cnt % 2 == 0 | 4ms | adc_scan(NULL) | ADC 采样扫描,周期最短,因为 ADC 需要高频采样滤波(按键电压、电量检测等) |
cnt % 5 == 0 | 10ms | key_scan()、ir_timeout()(KEY_IR_EN 时) | 按键扫描与红外接收超时检测;10ms 是人手按键抖动的典型去抖采样间隔 |
cnt % 100 == 0 | 200ms | sd0_dev_detect(NULL)(TFG_SD_EN 时) | SD 卡在位检测,机械插拔动作在 200ms 量级即可可靠感知 |
cnt % 125 == 0 | 250ms | usb_hotplug_detect(NULL)(TCFG_PC_ENABLE/TCFG_UDISK_ENABLE 时) | USB 热插拔检测,USB 枚举/断开事件无需过快轮询 |
设计意图:
- 周期选择与物理现象匹配:按键去抖、ADC 采样、SD/USB 插拔分别对应毫秒、十毫秒、百毫秒量级的物理过程,分频值的选择避免了过度轮询浪费功耗,也避免了采样不足漏检;
- 编译期裁剪:每个任务都用条件编译宏(
KEY_IR_EN、TFG_SD_EN、TCFG_PC_ENABLE、TCFG_UDISK_ENABLE)包裹,未使能的外设不会产生任何代码与运行时开销; - 中断上下文约束:所有回调必须是"非阻塞、快速返回"的扫描函数——它们只负责采样/检测并置标志或投递事件,真正的重处理(文件系统、USB 协议栈)在消息循环或主循环中完成。
软件延时原语:os_time_dly / delay_10ms
基于 jiffies 的忙等延时,二者实现相同,仅语义命名不同:
void os_time_dly(u32 tick)
{
tick = tick + jiffies;
while (1) {
if (time_after(jiffies, tick)) {
break;
}
}
}
void delay_10ms(u32 tick)
{
tick = tick + jiffies;
while (1) {
if (time_after(jiffies, tick)) {
break;
}
}
}
Source: tick_timer_sys.c
要点:
jiffies每 10ms 递增一次(见sys_tick_timer),因此os_time_dly(1)表示约 10ms、delay_10ms(1)语义上表示 1 个 10ms 单位;- 使用
time_after()宏比较节拍,天然处理了u32计数器的回绕问题(当jiffies从 0xFFFFFFFF 回绕到 0 时仍能正确判断); - 这是忙等实现:延时期间 CPU 空转,适合短延时或中断被关闭的临界场景;不能用于长延时(会阻塞整个系统,且低功耗模式下 tick 中断被关闭时
jiffies停止递增,忙等将无法退出)。
通用硬件定时器 gptimer 概览
除系统 tick 定时器外,SDK 还提供通用硬件定时器(gptimer),用于 PWM、输入捕获、定时计数等外设级功能。其接口定义于 gptimer.h,核心内容包括:
typedef void (*timer_irq_callback)(u32 tid, void *private_data); //回调函数
struct gptimer_timer { //计时(计数)器配置
Source: gptimer.h
关键接口(完整签名见头文件):
timer_hw_start(GPTIMER *GPTIMERx, enum gptimer_mode mode)/timer_hw_pause(GPTIMER *GPTIMERx):启动/暂停硬件定时器;timer_hw_timer_deinit/timer_hw_pwm_deinit/timer_hw_capture_deinit:释放定时器资源;timer_hw_capture_init(...):输入捕获初始化(含 GPIO、边沿、滤波、最大周期、中断优先级、回调);timer_hw_pwm_flip/timer_hw_pwm_enable:PWM 翻转与使能;timer_hw_get_cur_para(GPTIMER *GPTIMERx, enum gptimer_para para_type):读取当前计数值/周期等参数;gptimer_tick2us(u32 tid, u32 tick):tick 到微秒换算;gptimer_get_private_data/gptimer_set_private_data:回调私有数据存取;gptimer_dump():调试输出定时器状态。
gptimer 的中断回调 timer_irq_callback(u32 tid, void *private_data) 同样运行在中断上下文,回调内不可执行阻塞操作。gptimer 的 PWM/捕获/信号测量详细用法属于外设 gptimer 文档范畴,本页不展开;可参考 demo:gptimer_demo.c。
核心流程
sequenceDiagram
participant HW as 硬件定时器
participant ISR as 定时中断入口
participant LOOP as tick_timer_loop()
participant SYS as sys_tick_timer()
participant APP as app_timer_loop() (weak)
participant MOD as modules_tick_timer()
participant J as jiffies
participant Q as 消息/事件队列
participant KEY as key_scan/adc_scan 等
HW->>ISR: 2ms 周期中断
ISR->>LOOP: 调用 tick_timer_loop()
LOOP->>LOOP: g_tick_cnt++
LOOP->>SYS: sys_tick_timer(g_tick_cnt)
alt cnt % 5 == 0 (每 10ms)
SYS->>J: jiffies++
SYS->>SYS: dac_fade_api()
end
alt cnt % 50 == 0 且 FLASH_CACHE (每 100ms)
SYS->>Q: bsp_post_event(B_EVENT_100MS)
end
alt cnt % 250 == 0 (每 500ms)
SYS->>Q: post_msg(1, MSG_500MS)
LOOP->>LOOP: g_tick_cnt = 0
end
LOOP->>APP: app_timer_loop()
APP-->>APP: 应用自定义周期逻辑
LOOP->>MOD: modules_tick_timer(g_tick_cnt)
alt cnt % 2 == 0 (每 4ms)
MOD->>KEY: adc_scan(NULL)
end
alt cnt % 5 == 0 (每 10ms)
MOD->>KEY: key_scan() / ir_timeout()
end
alt cnt % 100 == 0 (每 200ms)
MOD->>KEY: sd0_dev_detect(NULL)
end
alt cnt % 125 == 0 (每 250ms)
MOD->>KEY: usb_hotplug_detect(NULL)
end
ISR-->>HW: 中断返回,等待下一 tick
流程要点:
- 硬件定时器以 2ms 周期触发中断,中断服务程序调用
tick_timer_loop(); g_tick_cnt递增后,依次执行系统、应用、模块三级任务,每级内部通过cnt % N分频决定本拍是否动作;sys_tick_timer是节拍源头:10ms 推进一次jiffies,并负责 100ms 事件与 500ms 消息的投递;- 模块任务完成各外设的轻量轮询;重逻辑通过消息/事件队列异步化;
- 500ms 时
g_tick_cnt归零,进入下一个 500ms 周期,各分频相位自动对齐。
使用示例
以下示例全部提取自仓库实际源码。
示例 1:应用层注入周期任务(覆盖 weak 符号)
应用工程中直接定义同名函数即可覆盖 SDK 提供的 weak app_timer_loop,在 2ms tick 基础上自行分频:
__attribute__((weak))
void app_timer_loop(void)
{
}
Source: tick_timer_sys.c
覆盖时遵循同样的分频约定(cnt 参数通过全局 g_tick_cnt 或自行计数获得)。由于 app_timer_loop 无参数,应用通常在内部维护自己的计数器,或直接依赖 jiffies。
示例 2:在 tick 中断中做 10ms 节拍推进与 500ms 消息投递
系统任务展示"分频 + 消息异步化"的标准写法:
void sys_tick_timer(u32 cnt)
{
if (0 == (cnt % 5)) {
dac_fade_api();
jiffies ++;
}
#if (FLASH_CACHE_ENABLE==1)
#if EXT_FLASH_EN
if (get_flash_cache_timer()) {
if (0 == (cnt % 50)) {//100ms
bsp_post_event(B_EVENT_100MS);
/* putchar('z'); */
}
}
#endif
#endif
if (0 == (cnt % 250)) { //500ms
post_msg(1, MSG_500MS);
/* cnt = 0; */
}
}
Source: tick_timer_sys.c
示例 3:模块层周期任务(强实现覆盖 weak)
tick_timer_modules.c 通过强定义覆盖 weak 符号,将外设扫描挂接到 tick:
void modules_tick_timer(u32 cnt)
{
if (0 == (cnt % 5)) {
#if KEY_IR_EN
ir_timeout();
#endif
key_scan();
}
if (0 == (cnt % 100)) { //200ms
#if defined(TFG_SD_EN) && (TFG_SD_EN)
sd0_dev_detect(NULL);
#endif
}
if (0 == (cnt % 125)) { //250ms
#if TCFG_PC_ENABLE || TCFG_UDISK_ENABLE
void usb_hotplug_detect(void *argv);
usb_hotplug_detect(NULL);
#endif
}
if (0 == (cnt % 2)) { //4ms
adc_scan(NULL);
}
}
Source: tick_timer_modules.c
示例 4:基于 jiffies 的忙等延时
void os_time_dly(u32 tick)
{
tick = tick + jiffies;
while (1) {
if (time_after(jiffies, tick)) {
break;
}
}
}
Source: tick_timer_sys.c
os_time_dly(1) ≈ 10ms 延时;time_after 宏保证回绕安全。注意该延时依赖 tick 中断持续推进 jiffies,在 tick 被关闭(低功耗)或中断被屏蔽期间不可用。
示例 5:低功耗场景下关闭 tick 中断
__attribute__((always_inline))
void tick_timer_set(bool on_off)
{
isr_tick_timer_close = on_off;
}
__attribute__((always_inline))
bool tick_timer_close(void)
{
return isr_tick_timer_close;
}
Source: tick_timer_sys.c
电源管理模块(app_power_mg)在进入低功耗前调用 tick_timer_set(true),唤醒后调用 tick_timer_set(false);其他代码可通过 tick_timer_close() 查询 tick 是否处于关闭状态,避免在休眠期间依赖 jiffies。
配置选项
定时器与系统任务的行为主要由编译期宏与取模常数控制,汇总如下:
| 配置项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
FLASH_CACHE_ENABLE | 编译期宏 | 0/1 | 使能 flash 缓存;为 1 时 sys_tick_timer 才可能投递 100ms 事件 |
EXT_FLASH_EN | 编译期宏 | 0/1 | 外部 flash 使能;与 FLASH_CACHE_ENABLE 共同决定 B_EVENT_100MS 投递路径 |
KEY_IR_EN | 编译期宏 | 0/1 | 红外遥控使能;为 1 时每 10ms 调用 ir_timeout() |
TFG_SD_EN | 编译期宏 | 0/1 | SD 卡功能使能;为 1 时每 200ms 调用 sd0_dev_detect() |
TCFG_PC_ENABLE | 编译期宏 | 0/1 | USB PC 模式使能;参与 250ms usb_hotplug_detect() 的编译条件 |
TCFG_UDISK_ENABLE | 编译期宏 | 0/1 | U 盘模式使能;参与 250ms usb_hotplug_detect() 的编译条件 |
g_tick_cnt | 运行时变量 | u16 | tick 计数,500ms(cnt=250)后归零 |
jiffies | 运行时变量 | volatile u32 | 全局节拍,每 10ms 递增一次 |
| tick 中断周期 | 硬件配置 | 约 2ms | 所有取模分频的基准;由 tick_timer_init() 与时钟配置决定 |
cnt % 2 | 分频常数 | 4ms | ADC 扫描周期 |
cnt % 5 | 分频常数 | 10ms | jiffies 推进、DAC 淡入淡出、按键扫描、红外超时周期 |
cnt % 50 | 分频常数 | 100ms | flash 缓存模式下 B_EVENT_100MS 事件周期 |
cnt % 100 | 分频常数 | 200ms | SD 卡检测周期 |
cnt % 125 | 分频常数 | 250ms | USB 热插拔检测周期 |
cnt % 250 | 分频常数 | 500ms | MSG_500MS 系统消息周期与 g_tick_cnt 归零点 |
分频常数直接来源于 tick_timer_sys.c 与 tick_timer_modules.c 的源码注释。
API 参考
以下 API 均来自 tick_timer_driver.h 与 tick_timer_sys.c 的实际实现。
extern volatile u32 jiffies
全局节拍计数变量。由 sys_tick_timer 在每 10ms(cnt % 5 == 0)递增一次,作为 os_time_dly / delay_10ms 的时钟基准。
void tick_timer_init(void)
初始化系统滴答定时器(硬件定时器 + 相关状态)。由系统启动流程调用,声明于 tick_timer_driver.h。
void tick_timer_set(bool on_off)
控制 tick 中断的开关状态。
- 参数:
on_off—true表示关闭 tick 中断(进入低功耗),false表示恢复; - 说明:
__attribute__((always_inline))强制内联,供电源管理关键路径调用; - 注意:关闭 tick 后
jiffies停止递增,依赖节拍的延时与周期任务全部暂停。
bool tick_timer_close(void)
查询 tick 中断当前是否被关闭。
- 返回:
true表示 tick 中断已关闭(休眠中),false表示正常运行。
void sys_tick_timer(u32 cnt)
系统级周期任务(每 2ms 被调用一次)。
- 参数:
cnt— 递增的 tick 计数; - 行为:10ms 推进
jiffies并调用dac_fade_api();flash 缓存模式下 100ms 投递B_EVENT_100MS;500ms 投递MSG_500MS消息; - 运行上下文:定时中断上下文。
void tick_timer_loop(void)
系统任务轮询总入口,由 tick 中断调用。
- 行为:递增
g_tick_cnt,依次调用sys_tick_timer、app_timer_loop、modules_tick_timer,500ms 时清零g_tick_cnt。
void app_timer_loop(void)(weak)
应用层周期任务扩展点。
- 默认实现:空函数(weak 符号);
- 覆盖方式:应用工程定义同名强函数即可;无参数,自行管理分频。
void modules_tick_timer(u32 cnt)(weak)
模块层周期任务。
- 参数:
cnt— 递增的 tick 计数; - 默认实现:weak 空函数;
- 强实现:
tick_timer_modules.c提供,内含 ADC/按键/红外/SD/USB 的周期扫描。
void os_time_dly(u32 tick)
基于 jiffies 的忙等延时(单位:10ms)。
- 参数:
tick— 延时长度,1表示约 10ms; - 行为:忙等直到
time_after(jiffies, tick + 起始jiffies); - 限制:阻塞调用;tick 中断被关闭时无法退出。
void delay_10ms(u32 tick)
与 os_time_dly 实现相同的忙等延时,命名强调单位为 10ms。
gptimer 硬件定时器接口(节选)
完整定义见 gptimer.h:
| 函数 | 说明 |
|---|---|
typedef void (*timer_irq_callback)(u32 tid, void *private_data) | 定时器中断回调类型 |
timer_hw_start(GPTIMER *x, enum gptimer_mode mode) | 启动硬件定时器 |
u32 timer_hw_pause(GPTIMER *x) | 暂停并返回当前计数值 |
timer_hw_capture_init(...) | 输入捕获初始化(GPIO/边沿/滤波/优先级/回调) |
timer_hw_pwm_flip(GPTIMER *x) / timer_hw_pwm_enable(GPTIMER *x) | PWM 翻转/使能 |
u32 timer_hw_get_cur_para(GPTIMER *x, enum gptimer_para t) | 读取当前周期/计数参数 |
u32 gptimer_tick2us(u32 tid, u32 tick) | tick 转微秒 |
void gptimer_dump() | 打印定时器状态(调试用) |
失败模式、边界情况与并发
中断上下文约束(最关键的失败模式)
tick_timer_loop() 及其调用的所有回调都运行在定时中断上下文。任何回调中出现以下情况都会导致系统故障:
- 阻塞/长耗时操作(如等待 flash 写入、串口同步发送):会拉长中断响应,导致其他中断(如 I/O、USB)丢失事件;
- 调用
os_time_dly/delay_10ms:忙等直接卡死中断,系统完全停摆; - 重入风险:回调中再次触发同优先级中断或使能嵌套,可能造成
g_tick_cnt/jiffies状态不一致。
正确做法:回调中只做采样、置标志、投递消息(post_msg / bsp_post_event),重逻辑放入消息循环。
低功耗下的节拍停摆
tick_timer_set(true) 关闭 tick 中断后:
jiffies不再递增,os_time_dly/delay_10ms会死循环(time_after永不满足);- 所有
cnt % N周期任务暂停,唤醒后g_tick_cnt继续累计,周期任务自动恢复节奏; - 休眠期间依赖节拍的看门狗/超时逻辑必须改用唤醒源(RTC、IO 中断)计数。
计数器回绕
jiffies为u32,约 497 天回绕一次(10ms × 2³²);延时判断使用time_after()宏,回绕安全;g_tick_cnt为u16,但每 500ms 归零一次,永不回绕;归零同时保证所有分频相位对齐(250 是 2/5/10/25/50/125 的倍数)。
首个 tick 的相位问题
tick_timer_loop() 先 g_tick_cnt++ 再分派,使 g_tick_cnt 从 1 开始。若从 0 开始,第一拍所有 cnt % N == 0 任务会同时爆发执行,造成启动瞬间中断负载尖峰;从 1 开始则各任务错峰启动。
并发与共享状态
jiffies、isr_tick_timer_close、g_tick_cnt均为volatile,在中断与主循环/电源管理上下文间共享;- 主循环读取
jiffies做超时判断时,无需关中断即可读到一致值(u32在 32 位 MCU 上是原子读); - 若应用在主循环中修改与中断共享的变量,必须使用关中断临界区或原子操作,避免与
tick_timer_loop的读写竞争。
条件编译缺失导致的行为差异
各模块任务由宏裁剪(KEY_IR_EN、TFG_SD_EN、TCFG_PC_ENABLE 等)。若配置了相关外设但未开启对应宏,则该外设永远不会被轮询(如 SD 卡检测不执行);反之若宏开启但外设未初始化,回调可能访问无效句柄。配置宏必须与外设驱动使能保持一致。
性能与运维考虑
中断负载预算
- tick 中断周期约 2ms,即中断频率 500Hz;每拍执行
tick_timer_loop()的全部三级分派,加上命中分频点的任务体; - 每 10ms 的
jiffies++/dac_fade_api、每 4ms 的adc_scan、每 10ms 的key_scan是主要负载;各任务必须把单次执行时间控制在亚毫秒级,否则将挤占 2ms 中断周期; - 若需要评估负载,可在
tick_timer_loop()入口/出口翻转 GPIO,用示波器测量中断驻留时间。
内联与调用开销
tick_timer_set / tick_timer_close 被强制内联(always_inline),源码注释明确警告"以下两个函数一定要内联"。原因:低功耗进出路径上函数调用开销与缓存/栈副作用不可接受。修改这些函数时必须保留内联属性。
唤醒抖动与功耗
- 低功耗模式下必须
tick_timer_set(true),否则 500Hz 的中断会持续唤醒 CPU,休眠功耗无法达标; - 唤醒后
g_tick_cnt从休眠前的值继续累计(若休眠中 tick 关闭则冻结),周期任务恢复后需注意:若应用在唤醒瞬间执行了阻塞操作,可能错过多个 tick,cnt % N的取模判断仍能保证不连续补执行(跳过漏拍),避免任务堆积。
调试手段
log_info/log_info_hexdump(tt_printf/tt_printf_buf宏)可打印 tick 相关信息;- gptimer 提供
gptimer_dump()用于输出定时器寄存器状态,排查硬件定时器配置问题。
扩展点
该框架围绕 weak 符号与消息机制提供三个层次的扩展:
- 应用层周期任务:覆盖 weak 的
app_timer_loop(),按需自行分频。适合 UI 刷新、播放状态轮询等应用级周期逻辑; - 模块层周期任务:覆盖/扩展
modules_tick_timer()(当前由tick_timer_modules.c强实现)。新增外设模块时,可仿照adc_scan/key_scan的写法,在cnt % N分频点挂入自己的扫描函数,并遵循"短小、非阻塞、条件编译裁剪"约定; - 周期消息/事件通道:通过
post_msg(MSG_500MS)与bsp_post_event(B_EVENT_100MS)把周期事件投递到异步处理端,业务逻辑监听消息即可,无需直接接触中断上下文; - 硬件定时器资源:需要独立 PWM/捕获/计时的功能使用 gptimer 接口(
timer_hw_start/timer_hw_pause/timer_hw_capture_init等),回调同样运行于中断上下文。
测试与验证
仓库提供了 gptimer 外设演示程序:gptimer_demo.c,可用于验证硬件定时器的 PWM/捕获/计时功能,间接验证 tick 框架所依赖的硬件定时器驱动。tick 定时器框架本身的验证主要依赖:
- 系统运行后观察 500ms 消息(
MSG_500MS)是否按预期触发(如呼吸灯/状态机是否正常推进); - 按键扫描、ADC 采样、SD/USB 检测等功能是否周期工作;
- 低功耗测试:进入休眠后测量电流,确认 tick 中断已关闭(无 2ms 周期唤醒);
- 长时间运行(跨越
jiffies回绕边界不现实,但可验证time_after宏在接近回绕时的正确性)。
相关链接
- 系统滴答定时器驱动头文件:tick_timer_driver.h
- 系统滴答定时器与系统任务实现:tick_timer_sys.c
- 模块级周期任务实现:tick_timer_modules.c
- 通用硬件定时器接口:gptimer.h
- gptimer 演示程序:gptimer_demo.c
- 消息/事件机制相关页面:
post_msg、bsp_post_event、MSG_500MS/B_EVENT_100MS的定义与消费(参见"消息机制与事件系统"目录页) - 电源管理页面:
tick_timer_set/tick_timer_close的调用方app_power_mg(参见"电源管理"目录页) - 外设 gptimer 页面:PWM、输入捕获、信号测量的完整用法(参见"外设-gptimer"目录页)