跳转至

单次定时器

超时保护 — OSAL 单次定时器、GPIO 驱动

前置阅读:周期定时器 — 先理解 osal_timer 结构体和基本用法;信号量同步

学习目标

  • 理解单次定时器与周期定时器的区别——触发一次后自动停止,不需要手动 stop
  • 掌握"启动操作 → 启动超时定时器 → 操作完成取消 / 超时触发兜底"的超时保护模式
  • 掌握同一 osal_timer 对象的重复使用——每次 start 都是新一轮单次触发
  • 能够在 WS63 上实现超时保护和延时启动功能

基本概念

单次 vs 周期定时器

单次定时器和周期定时器用的是同一个 osal_timer 对象,区别只在于你什么时候调 stop

sequenceDiagram
    participant TM as 定时器
    participant CB as 回调

    rect rgb(255,250,240)
        Note over TM,CB: 周期模式 — 无限循环
        TM->>CB: 到期触发
        Note over TM: 自动重新装载,继续计时
        TM->>CB: 到期触发
        Note over TM: 自动重新装载...
    end

    rect rgb(240,250,255)
        Note over TM,CB: 单次模式 — 触发即停
        TM->>CB: 到期触发
        Note over TM: 自动停止,不再触发
    end

同一 osal_timer 可以在周期/单次间切换——区别只是你在回调中或外部是否调 stop

超时取消模式

嵌入式开发中最常用的超时保护模式——"如果在 X 秒内没完成,就认为失败":

sequenceDiagram
    participant OP as 操作
    participant TM as 超时定时器
    participant CB as 超时回调

    OP->>OP: 启动操作
    OP->>TM: osal_timer_start (单次)

    alt 操作提前完成
        OP->>TM: osal_timer_stop 取消超时
        Note over CB: 回调不会触发
        OP->>OP: 正常完成流程
    else 超时触发
        TM->>CB: 超时到期
        CB->>CB: 执行兜底处理
        Note over CB: 重试 / 复位 / 告警
    end

这个模式在通信协议中无处不在——连接超时、应答超时、握手超时。核心逻辑:给操作一个时间窗口,窗口内完成则取消定时器,超时则走兜底分支。

典型使用场景

场景 超时间隔 提前取消条件 超时兜底动作
SLE 连接超时 5s 收到连接成功回调 重试连接或报错
按键超时恢复 3s 用户再次按键 自动恢复默认模式
应答超时 500ms 收到对端应答 重发请求
延时启动 2s 通常不取消 初始化外设

同一 timer 重复使用

单次定时器触发后自动停止,可以直接再次 start——不需要重新 init。每次 start 前可以改 interval 改变超时时间。

/* 第一次: 5 秒超时 */
timer.interval = 5000;
osal_timer_start(&timer);
/* ... 超时触发或 cancel ... */

/* 第二次: 换个超时时间,直接改 interval 再 start */
timer.interval = 3000;
osal_timer_start(&timer);  // 不需要重新 init

涉及 API

API 谁调用 用途 头文件
osal_timer_init(osal_timer *timer) 入口 初始化定时器(需先设 handlerdata osal_timer.h
osal_timer_start(osal_timer *timer) 入口/任务 启动定时器(使用当前 interval,触发一次自动停) osal_timer.h
osal_timer_stop(osal_timer *timer) 入口/任务 提前取消定时器(操作已提前完成) osal_timer.h

同一个 osal_timer 对象每次 start 都是新一轮单次触发——不需要重新 init

案例说明

做什么

按下按键 → LED 停止闪烁进入常亮模式 → 启动 3 秒超时定时器。如果 3 秒内无操作,自动恢复 LED 闪烁。如果 3 秒内再次按键,取消超时立即恢复。

规格与功能

规格项 说明
按键检测 GPIO 中断,下降沿触发
LED 驱动 周期定时器 500ms 翻转
超时定时器 单次模式 3000ms
按键行为 常亮中按键 → 立即恢复闪烁 + 取消超时
超时行为 3 秒无操作 → 自动恢复闪烁

程序运行流程:

  1. 上电 → LED 周期定时器启动 → LED 正常闪烁(500ms 间隔)
  2. 按键按下 → 停 LED 周期定时器 → LED 常亮 → 启动 3 秒单次超时
  3. 分支 A:3 秒内再按键 → osal_timer_stop 取消超时 → 恢复 LED 闪烁
  4. 分支 B:3 秒超时触发 → 回调中恢复 LED 闪烁

案例流程

sequenceDiagram
    participant KEY as 按键
    participant TM as 超时定时器
    participant LED as LED 定时器
    participant G as GPIO

    Note over LED: LED 正常闪烁 500ms

    KEY->>KEY: 按键中断
    KEY->>LED: osal_timer_stop — 停闪烁
    Note over G: LED 常亮
    KEY->>TM: osal_timer_start — 启动 3s 超时

    alt 3 秒内再次按键
        KEY->>TM: osal_timer_stop — 取消超时
        Note over TM: 超时回调不触发
        KEY->>LED: osal_timer_start — 恢复闪烁
    else 3 秒超时
        TM->>TM: 超时回调触发
        TM->>LED: osal_timer_start — 恢复闪烁
        Note over TM: 定时器自动停止
    end

案例操作指导

第一步:编译

fbb build ws63-liteos-app

第二步:烧录

第三步:验证

操作 预期结果
上电 LED 以 500ms 间隔正常闪烁
按一下按键 LED 停止闪烁,保持当前亮灭状态(常亮或常灭)
等 3 秒 LED 自动恢复闪烁
按键后 1 秒内再按 LED 立即恢复闪烁(不等 3 秒)

关键配置

参数 说明
超时 interval 3000ms 太短反应不及,太长感觉迟钝
LED 周期 500ms 用周期定时器驱动,非 osal_msleep 循环
stop 时机 操作提前完成时 超时已触发后 stop 是空操作(无害)
handler 设置 init 之前 和周期定时器规则相同
重复使用 每次 start 前可改 interval 不需要重新 init

代码详解

定时器与全局变量

#include "osal_timer.h"
#include "osal_semaphore.h"
#include "driver/gpio.h"

static osal_timer g_led_timer;       // 周期定时器——驱动 LED
static osal_timer g_timeout_timer;   // 单次定时器——超时保护
static bool g_manual_mode = false;   // 当前模式:自动闪烁 / 手动常亮

初始化

static void app_entry(void)
{
    /* LED 周期定时器 */
    g_led_timer.handler = led_timer_callback;
    g_led_timer.data = 0;
    osal_timer_init(&g_led_timer);
    g_led_timer.interval = 500;
    osal_timer_start(&g_led_timer);  // 开始闪烁

    /* 超时单次定时器 */
    g_timeout_timer.handler = timeout_callback;
    g_timeout_timer.data = 0;
    osal_timer_init(&g_timeout_timer);
    /* 不启动——需要时才 start */

    /* GPIO 和按键中断初始化(省略) */
}
app_run(app_entry);

LED 回调——正常闪烁

static void led_timer_callback(unsigned long data)
{
    (void)data;
    uapi_gpio_toggle(LED_PIN);  // 正常模式:每次回调翻转一次
}

按键处理——进入手动模式

static void on_key_pressed(void)
{
    if (g_manual_mode) {
        /* 已在手动模式——再次按键:恢复自动 */
        osal_timer_stop(&g_timeout_timer);   // 取消超时
        osal_timer_start(&g_led_timer);      // 恢复 LED 闪烁
        g_manual_mode = false;
    } else {
        /* 自动模式——按键进入手动 */
        osal_timer_stop(&g_led_timer);       // 停 LED 闪烁(常亮)
        g_timeout_timer.interval = 3000;      // 3 秒超时
        osal_timer_start(&g_timeout_timer);   // 启动单次超时
        g_manual_mode = true;
    }
}

超时回调——自动恢复

static void timeout_callback(unsigned long data)
{
    (void)data;
    /* 3 秒到了,自动恢复 LED 闪烁 */
    osal_timer_start(&g_led_timer);   // 恢复闪烁
    g_manual_mode = false;
    /* 单次定时器自动停止,不需要调 stop */
}

超时回调中直接 start LED 定时器——这是非阻塞操作(和 sem_up 一样安全)。如果超时回调需要做更复杂的事(如 printf),应该用"回调 sem_up + 任务"的模式——见上一篇「周期定时器」的进阶模式。

选择指南:单次 vs 周期

需求 选哪个
持续周期性驱动(心跳、LED 闪烁、传感器上报) 周期定时器
N 秒后做一件事(超时、延时、兜底) 单次定时器
N 秒后做一件事,但如果提前满足条件就取消 单次定时器 + stop