单次定时器
超时保护 — OSAL 单次定时器、GPIO 驱动
学习目标
- 理解单次定时器与周期定时器的区别——触发一次后自动停止,不需要手动
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) |
入口 | 初始化定时器(需先设 handler 和 data) |
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 秒无操作 → 自动恢复闪烁 |
程序运行流程:
- 上电 → LED 周期定时器启动 → LED 正常闪烁(500ms 间隔)
- 按键按下 → 停 LED 周期定时器 → LED 常亮 → 启动 3 秒单次超时
- 分支 A:3 秒内再按键 →
osal_timer_stop取消超时 → 恢复 LED 闪烁 - 分支 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
案例操作指导
第一步:编译
第二步:烧录
第三步:验证
| 操作 | 预期结果 |
|---|---|
| 上电 | 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 */
}
超时回调中直接
startLED 定时器——这是非阻塞操作(和sem_up一样安全)。如果超时回调需要做更复杂的事(如 printf),应该用"回调sem_up+ 任务"的模式——见上一篇「周期定时器」的进阶模式。
选择指南:单次 vs 周期
| 需求 | 选哪个 |
|---|---|
| 持续周期性驱动(心跳、LED 闪烁、传感器上报) | 周期定时器 |
| N 秒后做一件事(超时、延时、兜底) | 单次定时器 |
| N 秒后做一件事,但如果提前满足条件就取消 | 单次定时器 + stop |