Skip to content

概念先知道

  • 软件定时器:程序里的“闹钟”,到点自动执行你安排的回调函数;与硬件定时器不同,它不占用芯片定时器外设,由 RTOS 的定时器任务统一管理。
  • FreeRTOS:BL616/BL618 SDK 默认使用的实时操作系统,软件定时器由它提供(xTimerCreate 等 API)。
  • 一次性 / 周期定时器:一次性(one-shot)到期只触发一次;周期(auto-reload)到期后自动重新计时,一直循环触发。
  • 定时器回调:到点后执行的函数,运行在定时器服务任务(Timer Service)上下文中,回调里不要做耗时操作。

例程功能简介

本页为自编示例(SDK 无独立软件定时器例程),基于 FreeRTOS 软件定时器 API 编写,演示:

  • xTimerCreate 创建 2 秒一次性定时器:启动 2 秒后回调打印一次 [one-shot] fired
  • xTimerCreate 创建 2 秒周期定时器:每隔 2 秒回调打印 [periodic] tick,累计触发 3 次后调用 xTimerStop 停止;
  • 通过 pvTimerGetTimerID 区分同一回调服务多个定时器的场景(示例用定时器 ID 区分 one-shot 与 periodic)。
  • 本示例放在 examples/soft_timer_demo 下即可像官方例程一样 make CHIP=bl616 BOARD=bl616dk 编译。

提示

自编示例仅作为学习参考:API 名称与用法与 SDK 内置 FreeRTOS 完全一致,但官方 SDK 仓库中没有同名例程。若希望照搬官方结构,可先复制 examples/helloworldFreeRTOSConfig.h,再把本页 main.c 替换进去。

操作步骤

1
新建工程目录

SDK 没有独立的软件定时器例程,本页示例为自编示例。在 SDK 的 examples 下新建工程目录(把目录名换成你喜欢的名字),并创建 main.cMakefile

cd examples
mkdir soft_timer_demo
cd soft_timer_demo
2
编写代码

用编辑器把本页「完整代码」中的 main.c 保存到 soft_timer_demo/main.c,再创建 Makefile(内容见下方「编译工程」提示):

vim main.c
3
编译工程

自编工程需要仿照其他例程提供 MakefileFreeRTOSConfig.h(可直接复制 examples/helloworld/FreeRTOSConfig.h)。最小 Makefile 可参考:

# Makefile(放在 soft_timer_demo 目录,位于 examples 下一层)
SDK_DEMO_PATH ?= $(abspath .)
BL_SDK_BASE ?= $(abspath ./../..)
export BL_SDK_BASE

include $(BL_SDK_BASE)/project.build

然后执行编译。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

打开串口助手(波特率 2000000)。启动后 2 秒时打印 [one-shot] fired(一次性定时器到期),之后每 2 秒打印 [periodic] tick,第 3 次 tick 后停止并打印 [periodic] stopped,不再输出。

代码执行流程

示例从启动到定时器触发、停止的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

xTimerCreate(name, period, auto_reload, id, callback)

创建软件定时器(创建后处于休眠状态,需要 xTimerStart 启动)。

参数

  • name:定时器名称(仅调试用)
  • period:周期,单位 tick,用 pdMS_TO_TICKS(2000) 把毫秒转成 tick
  • auto_reloadpdTRUE 周期模式 / pdFALSE 一次性模式
  • id:定时器 ID,回调里用 pvTimerGetTimerID 取出,用于区分多个定时器
  • callback:到期回调函数

返回值TimerHandle_t 句柄;创建失败为 NULL

xTimerStart / xTimerStop(timer, ticks_to_wait)

启动 / 停止软件定时器。命令通过队列发给定时器服务任务,ticks_to_wait 是等待命令入队的最大 tick 数(示例传 0)。

参数

  • timer:定时器句柄
  • ticks_to_wait:等待时间(tick),0 表示不等待

返回值pdPASS 成功 / pdFAIL 失败

pvTimerGetTimerID(timer)

在回调中取出创建时设置的定时器 ID,用于区分是哪个定时器触发了回调。

参数

  • timer:回调传入的定时器句柄

返回值:创建时传入的 ID 指针(void *

pdMS_TO_TICKS(ms)

把毫秒换算成 tick:ms × configTICK_RATE_HZ / 1000。SDK 默认 configTICK_RATE_HZ 为 1000 时,pdMS_TO_TICKS(2000) 就是 2000 tick。

参数

  • ms:毫秒数

返回值:对应的 tick 数

完整代码

以下为自编示例 soft_timer_demo/main.c 完整代码(SDK 无同名官方例程,API 与 SDK 内置 FreeRTOS 一致),默认折叠,点击展开:

📜 点击展开 soft_timer_demo/main.c 完整代码
c
#include "board.h"
#include "bflb_mtimer.h"
#include "FreeRTOS.h"
#include "timers.h"

/* 两个定时器共用同一个回调,用定时器 ID 区分 */
static TimerHandle_t one_shot_timer;
static TimerHandle_t periodic_timer;

static void timer_callback(TimerHandle_t xTimer)
{
    if (pvTimerGetTimerID(xTimer) == (void *)1) {
        printf("[one-shot] fired\r\n");
    } else if (pvTimerGetTimerID(xTimer) == (void *)2) {
        static uint32_t tick_count = 0;

        tick_count++;
        printf("[periodic] tick %lu\r\n", tick_count);
        if (tick_count >= 3) {
            xTimerStop(xTimer, 0);
            printf("[periodic] stopped\r\n");
        }
    }
}

int main(void)
{
    board_init();

    /* 一次性定时器:2 秒后触发一次 */
    one_shot_timer = xTimerCreate(
        "one_shot",
        pdMS_TO_TICKS(2000),
        pdFALSE,
        (void *)1,
        timer_callback);

    /* 周期定时器:每 2 秒触发一次,触发 3 次后由回调停止 */
    periodic_timer = xTimerCreate(
        "periodic",
        pdMS_TO_TICKS(2000),
        pdTRUE,
        (void *)2,
        timer_callback);

    if (one_shot_timer != NULL && periodic_timer != NULL) {
        xTimerStart(one_shot_timer, 0);
        xTimerStart(periodic_timer, 0);
        printf("software timer demo start\r\n");
    } else {
        printf("xTimerCreate failed\r\n");
    }

    vTaskStartScheduler();

    while (1) {
        bflb_mtimer_delay_ms(1000);
    }
}

FAQ

定时器没有触发

先确认启动了调度器(vTaskStartScheduler),软件定时器依赖定时器服务任务运行;再确认 configUSE_TIMERS 为 1(SDK 默认开启),以及 xTimerStart 返回 pdPASS

回调执行时间不准或偶发卡顿

回调运行在定时器服务任务中,回调里避免阻塞(延时、等待锁、长时间打印)。打印较多时串口本身也会占用时间,实际项目回调里建议只置标志位,由业务任务处理。

一次性定时器为什么又触发了

检查 auto_reload 参数:pdTRUE 是周期模式,pdFALSE 才是一次性模式。示例中 one-shot 用 pdFALSE,periodic 用 pdTRUE

遇到问题?

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

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