Skip to content

概述

定时器是嵌入式开发最常用的外设之一:LED 闪烁、按键扫描、超时判断、周期性数据采集等场景都离不开它。本教程通过 硬件定时器周期中断,每 100us 自动翻转一次 GPIO 输出,产生方波信号。

用大白话讲:定时器就像开发板里的一个「闹钟」。你设好时间(比如 100 微秒),闹钟一到点就自动「响铃」(触发中断),把正在做的事打断一下,去执行你安排好的小任务(翻转引脚电平),然后继续计时。本教程就是靠这个闹钟,每 100 微秒翻转一次引脚,输出方波。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/peripherals/demo_timer 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过硬件定时器每 100us 触发一次中断,在回调中翻转 IO3 输出方波,掌握定时器初始化、启动与回调编写。
🧰前置条件① Ai-WB2 开发板、逻辑分析仪(或示波器)② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [GPIO输出(点亮LED)](./gpio_led)。
🔗相关章节延时方案见 [系统控制-软件定时器](../system/soft_timer);GPIO 输出见 [GPIO输出(点亮LED)](./gpio_led)。

硬件接线

按官方示例接线(见 SDK applications/peripherals/demo_timer/README.md):

Ai-WB2 引脚 连接到
IO3 逻辑分析仪探针(Probe)

💡 示例通过 IO3 输出 100us 周期方波,用逻辑分析仪或示波器即可直观观察波形。没有仪器时,也可将 IO3 接 LED 观察闪烁节奏。

进入示例工程

打开终端,进入官方 demo_timer 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/peripherals/demo_timer

说明:cd 是「进入目录」命令,这里进入 demo_timer 示例工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

编写代码

打开 demo_timer/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/peripherals/demo_timer/demo_timer/main.c)完全一致。

代码要点:

代码 作用
period = 100 定时周期 100us(单位是微秒,不是毫秒),设错单位周期就相差 1000 倍
reload_mode = TIMER_RELOAD_PERIODIC 自动重载模式:响完一次自动接着计时、连续触发;选错就只响一次
timer_cb 定时器中断回调(到点自动执行的函数):每次触发翻转一次 IO3 电平,不翻转就没有波形
hosal_timer_init(&timer0) 初始化定时器,不初始化后面启动也无效
hosal_timer_start(&timer0) 启动定时器(闹钟开始走),不启动永远不触发
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/demo_timer.bin

烧录固件

开发板保持 USB 连接,确认串口设备号后执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。

运行验证

烧录完成后开发板自动重启运行,逻辑分析仪在 IO3 上可观察到周期 100us 的方波(每 50us 翻转一次,50% 占空比),与官方示例输出的波形一致。

img

💡 把 period 改为 1000000(1 秒)并接 LED,即可看到 LED 每 1 秒闪烁一次——定时器相比 vTaskDelay 阻塞延时,不占用 CPU,且定时精度由硬件保证。

看到逻辑分析仪上 IO3 输出周期 100us 的方波即为成功;如果看不到波形或频率不对,说明还没成功,对照文末「常见问题与踩坑提示」排查。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_timer_init(tim)

dev 结构体中的周期、重载模式、回调等配置硬件定时器(本教程 100us 周期触发)。

参数

  • timhosal_timer_dev_t 结构体指针,必填。关键字段:config.period(周期,单位微秒 us,本教程 100)、config.reload_mode(可选值:TIMER_RELOAD_PERIODIC 自动重载周期触发 / TIMER_RELOAD_ONCE 单次触发)、config.cb(到期回调函数指针,形如 void cb(void *arg))、config.arg(回调参数,无参传 NULL

返回值:成功返回 0;失败返回负值错误码

hosal_timer_start(tim)

开始计时,到期触发回调(周期模式下自动重复)。

参数

  • timhosal_timer_dev_t 结构体指针(需先 hosal_timer_init

返回值:成功返回 0;失败返回负值错误码

hosal_timer_stop(tim)

停止计时,不再触发回调(再次 start 可重新计时)。

参数

  • timhosal_timer_dev_t 结构体指针

返回值:成功返回 0;失败返回负值错误码

hosal_timer_finalize(tim)

注销定时器并释放占用的硬件资源(不再使用前调用)。

参数

  • timhosal_timer_dev_t 结构体指针

返回值:成功返回 0;失败返回负值错误码

bl_gpio_enable_output(pin, pullup, pulldown)

把指定引脚配置为推挽输出模式,可驱动 LED、蜂鸣器等负载(本教程用于输出方波)。

参数

  • pin:引脚号,可选值:0~22(GPIO0~GPIO22),本教程用 3
  • pullup:是否使能内部上拉,可选值:1 使能 / 0 关闭(推挽输出无需上拉,传 0
  • pulldown:是否使能内部下拉,可选值:1 使能 / 0 关闭

返回值:成功返回 0;失败返回负值错误码

bl_gpio_output_set(pin, value)

向已配置为输出的引脚输出高电平或低电平(本教程在定时器回调中翻转 IO3)。

参数

  • pin:引脚号,可选值同 bl_gpio_enable_output0~22
  • value:输出电平,可选值:1 高电平(3.3V)/ 0 低电平(0V)

返回值:成功返回 0;失败返回负值错误码


完整代码

以下为 demo_timer/main.c 完整源码,与官方示例(applications/peripherals/demo_timer/demo_timer/main.c)完全一致:

📜 点击展开 demo_timer/main.c 完整代码
c
#include <stdio.h>

#include <FreeRTOS.h>
#include <task.h>

#include <hosal_timer.h>
#include <bl_gpio.h>
#include <blog.h>

static void timer_cb(void *arg)
{
    static int i = 0;
    if (i % 2) {
        bl_gpio_output_set(3, 0);
    } else {
        bl_gpio_output_set(3, 1);
    }
    i++;
}

int main(void)
{
    bl_gpio_enable_output(3, 1, 0);

    static hosal_timer_dev_t timer0 = {
        .config = {
            .arg = NULL,
            .cb = timer_cb,
            .period = 100, /// 100us
            .reload_mode = TIMER_RELOAD_PERIODIC,
        },
        .port = 0,
    };

    hosal_timer_init(&timer0);

    hosal_timer_start(&timer0);

    return 0;
}

常见问题与踩坑提示

⚠️ 波形频率与预期不符
原因period 单位是 us,误按 ms 换算会导致周期相差 1000 倍
解决:1ms 定时应写 period = 1000;周期=2×period,方波周期 200us 时 period 应设 100

⚠️ 定时器只触发一次就停止
原因reload_mode 误配置为 TIMER_RELOAD_ONCE(单次模式)
解决:需要周期触发时使用 TIMER_RELOAD_PERIODIC

⚠️ 定时器回调中耗时操作导致波形失真
原因:回调内执行 printf 等耗时操作,占用时间超过周期
解决:回调中只做轻量操作(置标志位、翻转 IO),耗时处理放主循环;如需打印日志使用 blog_info(非阻塞 LOG)

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号

运行自检

逻辑分析仪在 IO3 上观察到周期 100us 的方波,即定时器功能验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23