概念先知道
- 软件定时器:程序里的“闹钟”,到点自动执行你安排的回调函数;与硬件定时器不同,它不占用芯片定时器外设,由 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/helloworld 的 FreeRTOSConfig.h,再把本页 main.c 替换进去。
操作步骤
SDK 没有独立的软件定时器例程,本页示例为自编示例。在 SDK 的 examples 下新建工程目录(把目录名换成你喜欢的名字),并创建 main.c 与 Makefile:
cd examples
mkdir soft_timer_demo
cd soft_timer_demo用编辑器把本页「完整代码」中的 main.c 保存到 soft_timer_demo/main.c,再创建 Makefile(内容见下方「编译工程」提示):
vim main.c自编工程需要仿照其他例程提供 Makefile 与 FreeRTOSConfig.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用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 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)把毫秒转成 tickauto_reload:pdTRUE周期模式 /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 完整代码
#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

