Skip to content

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 (xTimerCreate and 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:

  • xTimerCreate creates a 2-second one-shot timer: its callback prints [one-shot] fired once;
  • xTimerCreate creates a 2-second periodic timer: it prints [periodic] tick every 2 seconds and stops itself with xTimerStop after 3 ticks;
  • pvTimerGetTimerID distinguishes which timer fired a shared callback (IDs 1 and 2 in the example).
  • Place it under examples/soft_timer_demo and build like an official example with make 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

1
Create a Project Directory

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_demo
2
Write the Code

Save the main.c from the Complete Code section into soft_timer_demo/main.c, then create a Makefile (see the Build step):

vim main.c
3
Build the Project

A 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=bl616dk
4
Flash the Firmware

Connect 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/ttyUSB0
5
Run and Verify

Open 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; use pdMS_TO_TICKS(2000) to convert milliseconds
  • auto_reload: pdTRUE for periodic / pdFALSE for one-shot
  • id: timer ID, retrieved in the callback with pvTimerGetTimerID to distinguish timers
  • callback: 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 handle
  • ticks_to_wait: wait time in ticks, 0 means 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
c
#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

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