Concepts First
- Software timer: a program-level "alarm clock" that runs your callback when it expires; unlike a hardware timer it does not occupy a chip timer peripheral — the RTOS timer task manages it.
- FreeRTOS: the real-time OS used by the BL616/BL618 SDK by default; software timers are provided by it (
xTimerCreateand friends). - One-shot vs. periodic: a one-shot timer fires once; an auto-reload timer restarts itself and keeps firing.
- Timer callback: the function executed on expiry, running in the timer service task context — keep it short.
Example Overview
This page is a self-authored example (the SDK has no dedicated software-timer example), written with the FreeRTOS software-timer API:
xTimerCreatecreates a 2-second one-shot timer: its callback prints[one-shot] firedonce;xTimerCreatecreates a 2-second periodic timer: it prints[periodic] tickevery 2 seconds and stops itself withxTimerStopafter 3 ticks;pvTimerGetTimerIDdistinguishes which timer fired a shared callback (IDs 1 and 2 in the example).- Place it under
examples/soft_timer_demoand build like an official example withmake CHIP=bl616 BOARD=bl616dk.
Note
This self-authored example is for learning only: the API names and usage match the FreeRTOS built into the SDK, but there is no same-named example in the official SDK repository. To follow the official project structure, copy FreeRTOSConfig.h from examples/helloworld and replace its main.c with the code below.
Operation Steps
The SDK has no dedicated software-timer example, so this page is a self-authored example. Create a project directory under the SDK’s examples folder and enter it:
cd examples
mkdir soft_timer_demo
cd soft_timer_demoSave the main.c from the Complete Code section into soft_timer_demo/main.c, then create a Makefile (see the Build step):
vim main.cA custom project needs a Makefile and a FreeRTOSConfig.h (copy examples/helloworld/FreeRTOSConfig.h). A minimal Makefile looks like:
# Makefile (place in soft_timer_demo, one level under examples)
SDK_DEMO_PATH ?= $(abspath .)
BL_SDK_BASE ?= $(abspath ./../..)
export BL_SDK_BASE
include $(BL_SDK_BASE)/project.build
Then build. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):
make flash CHIP=bl616 COMX=/dev/ttyUSB0Open a serial tool (baud rate 2000000). 2 seconds after startup, [one-shot] fired prints (the one-shot timer expired); then every 2 seconds [periodic] tick prints; after the 3rd tick it stops and prints [periodic] stopped, with no further output.
Code Execution Flow
The complete flow from startup to timer expiry and stop is shown below (loop arrows mean repeated execution):
APIs Used by the Example
xTimerCreate(name, period, auto_reload, id, callback)
Creates a software timer (dormant until started with xTimerStart).
Parameters:
name: timer name (debug only)period: period in ticks; usepdMS_TO_TICKS(2000)to convert millisecondsauto_reload:pdTRUEfor periodic /pdFALSEfor one-shotid: timer ID, retrieved in the callback withpvTimerGetTimerIDto distinguish timerscallback: expiry callback
Return: TimerHandle_t; NULL on failure
xTimerStart / xTimerStop(timer, ticks_to_wait)
Starts/stops a software timer. The command is queued to the timer service task; ticks_to_wait is the maximum ticks to wait for the command to enter the queue (the example passes 0).
Parameters:
timer: timer handleticks_to_wait: wait time in ticks,0means don't wait
Return: pdPASS on success / pdFAIL on failure
pvTimerGetTimerID(timer)
Retrieves the ID set at creation time from inside the callback, to identify which timer fired.
Parameters:
timer: timer handle passed to the callback
Return: the ID pointer (void *) set at creation
pdMS_TO_TICKS(ms)
Converts milliseconds to ticks: ms × configTICK_RATE_HZ / 1000. With the SDK's default configTICK_RATE_HZ of 1000, pdMS_TO_TICKS(2000) is 2000 ticks.
Parameters:
ms: milliseconds
Return: the tick count
Complete Code
The complete source below is the self-authored soft_timer_demo/main.c (there is no same-named official SDK example; APIs match the SDK's built-in FreeRTOS). Collapsed by default, click to expand:
📜 Click to expand soft_timer_demo/main.c full code
#include "board.h"
#include "bflb_mtimer.h"
#include "FreeRTOS.h"
#include "timers.h"
/* Both timers share one callback; distinguish them with the timer 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();
/* One-shot timer: fires once after 2 seconds */
one_shot_timer = xTimerCreate(
"one_shot",
pdMS_TO_TICKS(2000),
pdFALSE,
(void *)1,
timer_callback);
/* Periodic timer: fires every 2 seconds, stopped after 3 ticks */
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
The timer never fires
Confirm the scheduler is running (vTaskStartScheduler), since software timers depend on the timer service task; check configUSE_TIMERS is 1 (default in the SDK) and that xTimerStart returned pdPASS.
The callback timing is off or stutters
Callbacks run in the timer service task: avoid blocking inside them (delays, waiting on locks, heavy printing). In real projects, set a flag in the callback and let a worker task do the work.
Why did my one-shot timer fire again
Check the auto_reload argument: pdTRUE is periodic, pdFALSE is one-shot. In the example, the one-shot uses pdFALSE and the periodic timer uses pdTRUE.
Have questions?
For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

