杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

定时器与系统任务

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 采用经典的**"单一定时中断 + 计数分频轮询"**架构:

  1. 一个硬件定时器产生周期约 2ms 的中断(tick_timer_init() 初始化;common.h 中 loop_detect_handler.time 注释明确"timer 周期默认一般是 2ms");
  2. 中断服务程序中调用 tick_timer_loop(),该函数维护一个递增的 g_tick_cnt 计数器;
  3. 各层任务通过判断 cnt % N == 0 来获得不同的执行周期(例如 cnt % 5 即 10ms、cnt % 250 即 500ms);
  4. 系统维护全局节拍 jiffies(每 10ms 递增一次),作为软件延时的时钟基准;
  5. 低功耗时可通过 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 == 010msdac_fade_api() 音量淡入淡出;jiffies++ 推进全局节拍
cnt % 50 == 0100ms仅当 FLASH_CACHE_ENABLE 且 EXT_FLASH_EN 且 flash 缓存定时器开启时,投递事件 B_EVENT_100MS
cnt % 250 == 0500ms向系统消息队列投递 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

执行顺序与设计考虑:

  1. 先递增 g_tick_cnt:g_tick_cnt 从 1 开始累计,避免 0 时刻所有 cnt % N == 0 的任务同时爆发执行;
  2. 系统任务 → 应用任务 → 模块任务:系统任务先推进 jiffies 并投递周期消息,随后应用任务与模块任务才能读到一致的节拍状态;
  3. 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 == 04msadc_scan(NULL)ADC 采样扫描,周期最短,因为 ADC 需要高频采样滤波(按键电压、电量检测等)
cnt % 5 == 010mskey_scan()、ir_timeout()(KEY_IR_EN 时)按键扫描与红外接收超时检测;10ms 是人手按键抖动的典型去抖采样间隔
cnt % 100 == 0200mssd0_dev_detect(NULL)(TFG_SD_EN 时)SD 卡在位检测,机械插拔动作在 200ms 量级即可可靠感知
cnt % 125 == 0250msusb_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

流程要点:

  1. 硬件定时器以 2ms 周期触发中断,中断服务程序调用 tick_timer_loop();
  2. g_tick_cnt 递增后,依次执行系统、应用、模块三级任务,每级内部通过 cnt % N 分频决定本拍是否动作;
  3. sys_tick_timer 是节拍源头:10ms 推进一次 jiffies,并负责 100ms 事件与 500ms 消息的投递;
  4. 模块任务完成各外设的轻量轮询;重逻辑通过消息/事件队列异步化;
  5. 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/1SD 卡功能使能;为 1 时每 200ms 调用 sd0_dev_detect()
TCFG_PC_ENABLE编译期宏0/1USB PC 模式使能;参与 250ms usb_hotplug_detect() 的编译条件
TCFG_UDISK_ENABLE编译期宏0/1U 盘模式使能;参与 250ms usb_hotplug_detect() 的编译条件
g_tick_cnt运行时变量u16tick 计数,500ms(cnt=250)后归零
jiffies运行时变量volatile u32全局节拍,每 10ms 递增一次
tick 中断周期硬件配置约 2ms所有取模分频的基准;由 tick_timer_init() 与时钟配置决定
cnt % 2分频常数4msADC 扫描周期
cnt % 5分频常数10msjiffies 推进、DAC 淡入淡出、按键扫描、红外超时周期
cnt % 50分频常数100msflash 缓存模式下 B_EVENT_100MS 事件周期
cnt % 100分频常数200msSD 卡检测周期
cnt % 125分频常数250msUSB 热插拔检测周期
cnt % 250分频常数500msMSG_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 符号与消息机制提供三个层次的扩展:

  1. 应用层周期任务:覆盖 weak 的 app_timer_loop(),按需自行分频。适合 UI 刷新、播放状态轮询等应用级周期逻辑;
  2. 模块层周期任务:覆盖/扩展 modules_tick_timer()(当前由 tick_timer_modules.c 强实现)。新增外设模块时,可仿照 adc_scan/key_scan 的写法,在 cnt % N 分频点挂入自己的扫描函数,并遵循"短小、非阻塞、条件编译裁剪"约定;
  3. 周期消息/事件通道:通过 post_msg(MSG_500MS) 与 bsp_post_event(B_EVENT_100MS) 把周期事件投递到异步处理端,业务逻辑监听消息即可,无需直接接触中断上下文;
  4. 硬件定时器资源:需要独立 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"目录页)
Prev
电源管理与低功耗控制