Skip to content

Overview ​

A software timer (in plain words: an "alarm clock" inside the program — at the set time it automatically runs the task you arranged) is built on the system clock (FreeRTOS software timer; FreeRTOS is the embedded operating system the board uses, in plain words: the chip's built-in "heartbeat" that beats a fixed number of times per second, and programs count time with it), the most commonly used delay method in task scheduling — suited to periodic polling (in plain words: checking a state at fixed intervals), timeout judgment, heartbeat detection and more. Unlike hardware timers (HWT, real timing circuits), software timers don't occupy hardware peripherals — you can create many and start/stop them anytime. This tutorial demonstrates creating 3 software timers, periodic triggering, stopping, deleting and period changing.

In plain words: a software timer is like the alarm clock in your phone — you can set "ring every 5 minutes" (periodic), or "ring only once" (one-shot), and cancel or reschedule anytime. "Timing" in a program works the same: set the time, and when it's reached the system automatically runs the code you prepared (the callback, in plain words: a function you wrote in advance — when the alarm rings, the system calls it automatically, just like an alarm ringing on its own).

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/system/soft_timer; the code can be found directly in your local SDK.

🎯Page GoalThrough creating and linking 3 timers (100ms periodic print → 1s stop → 3s period change), master software timer create, start, stop and delete.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② Environment set up per [SDK Installation](../sdk/sdk_intro).
🔗RelatedHardware timer (µs precision): [Timer (Hardware)](../basic/timer); task delay basics: [GPIO Output (Blink LED)](../basic/gpio_led).

①
Enter the Example Project

Open a terminal and enter the official soft_timer example project directory:

cd ~/Ai-Thinker-WB2/applications/system/soft_timer

Note: cd is the “change directory” command and ~ means your user home directory. This enters the soft_timer example project; all subsequent make commands must run in this directory. If it says No such file or directory, the path is wrong — see the FAQ at the end.

②
Write the Code

Open soft_timer/main.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/system/soft_timer/soft_timer/main.c).

Code highlights:

Code Purpose
bl_os_timer_create(cb, NULL) Create a timer and bind the callback; without creating there’s no “alarm” available
bl_os_timer_start_periodic(timer, sec, nsec) Periodic start: fires repeatedly at the interval; without it the timer is just a “decoration” that never rings
bl_os_timer_start_once(timer, sec, nsec) One-shot start: rings once then stops; for reminding only once
bl_os_timer_stop(timer, BL_OS_NO_WAITING) Stop the timer; without stopping it keeps ringing — the demo’s chained timing needs it stopped
bl_os_timer_delete(timer, BL_OS_NO_WAITING) Delete the timer and free resources; not deleting a finished timer wastes memory

The chained timing of the three timers:

Timer Period Action
timer1 100ms Prints timer1_cb:n at every trigger (n accumulates)
timer2 1000ms After one trigger: delete timer2 + stop timer1
timer3 3000ms One-shot trigger: restart timer1 periodically at 1000ms

💡 The timeout is split into seconds + nanoseconds: delay_ms / 1000 is the seconds, (delay_ms % 1000) * 1e6 is the nanoseconds. BL_OS_NO_WAITING means don’t block waiting for the internal lock.

③
Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, translating the source code into machine code the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/soft_timer.bin is generated (firmware: the program burned into the board after compilation, like the board’s “operating system + your program”).

④
Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the chip (flashing: the process of writing a program into the chip); p=/dev/ttyUSB0 is the serial device — change it to your computer’s actual port (like COM3 on Windows), b=921600 is the flash baud rate (transfer speed).

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded. If it keeps waiting or reports the serial port can’t open, see the FAQ at the end.

⑤
Run and Verify

After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600) and observe the logs:

timer1_cb:1
timer1_cb:2
...      (prints every 100ms, lasting 1 second)
timer1_cb:9
timer2_cb:delete timer2 and stop timer1
timer3_cb:start timer1 again and change timer cycle
timer1_cb:10
timer1_cb:11
...      (now prints every 1000ms)

Log pattern verification:

Phase Observation
0~1s timer1 prints every 100ms (~9 times)
1s timer2 fires: deletes itself, stops timer1
3s timer3 one-shot fires: restarts timer1 at 1000ms
after 3s timer1 prints every 1000ms, visibly slower

💡 timer2 doesn’t fire after deletion and timer3 doesn’t fire after its one-shot — verifying the behavior difference of bl_os_timer_stop / bl_os_timer_delete / start_once.

✅ Expected result: first timer1_cb:1~timer1_cb:9 (~every 100ms), timer2_cb at 1s, timer3_cb at 3s after which timer1_cb becomes ~every 1s — verified. If only a few timer1_cb lines print and the log never changes, the timer chaining didn’t work — see the FAQ at the end.


API Summary for This Tutorial ​

bl_os_timer_create(cb, arg) ​

Creates a timer, binds the callback function and argument, and returns a timer handle.

Parameters:

  • cb: callback function pointer, shaped void cb(void *arg), called on expiry, required
  • arg: callback argument pointer (passed through to the callback), pass NULL if none

Return: timer handle (bl_os_timer_t) on success; NULL on failure

bl_os_timer_start_periodic(timer, sec, nsec) ​

Starts the timer with repeating periodic triggers (period = seconds + nanoseconds).

Parameters:

  • timer: handle returned by bl_os_timer_create
  • sec: seconds part of the period, values: any non-negative integer, e.g. 1 (1 second)
  • nsec: nanoseconds part of the period, values: 0~999999999 (0 is fine when whole seconds suffice)

Return: 0 on success; negative error code on failure

bl_os_timer_start_once(timer, sec, nsec) ​

Starts the timer; after expiry it fires once then stops automatically.

Parameters:

  • timer: timer handle
  • sec: seconds part of the delay
  • nsec: nanoseconds part of the delay

Return: 0 on success; negative error code on failure

bl_os_timer_stop(timer, wait) ​

Stops the timer, no more triggers (a later start_* can restart it).

Parameters:

  • timer: timer handle
  • wait: whether to wait for the timer task to finish processing the stop command, values: BL_OS_NO_WAITING (no wait, returns immediately) / BL_OS_WAITING (wait)

Return: 0 on success; negative error code on failure

bl_os_timer_delete(timer, wait) ​

Deletes the timer and frees the occupied resources (call when no longer used).

Parameters:

  • timer: timer handle
  • wait: whether to wait, values: BL_OS_NO_WAITING / BL_OS_WAITING

Return: 0 on success; negative error code on failure

blog_info(fmt, ...) ​

Outputs an INFO-level log (UART0, filtered by level).

Parameters:

  • fmt: format string, same usage as printf, required
  • ...: variadic args matching fmt placeholders, optional

Return: none


Full Code ​

Below is the complete soft_timer/main.c source, identical to the official example (applications/system/soft_timer/soft_timer/main.c):

📜 Click to expand the full soft_timer/main.c code
c

#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <blog.h>
#include "bl_sys.h"
#include <bl_os_hal.h>
#include "timers.h"
#include <blog.h>

struct timer_adpt *timer1;
struct timer_adpt *timer2;
struct timer_adpt *timer3;
uint32_t delay_ms;

static void timer1_cb(void *data)
{
    static int cnt;
    cnt++;
    blog_info("timer1_cb:%d\r\n",cnt);
}

static void timer2_cb(void *data)
{
    blog_info("timer2_cb:delete timer2 and stop timer1\r\n");
    bl_os_timer_delete(timer2, BL_OS_NO_WAITING);      //delete timer2
    bl_os_timer_stop(timer1,BL_OS_NO_WAITING);         //stop timer1
}

static void timer3_cb(void *data)
{
    blog_info("timer3_cb:start timer1 again and change timer cycle\r\n");
    delay_ms=1000;           //cycle =1000ms
    bl_os_timer_start_periodic(timer1,(delay_ms / 1000),((delay_ms % 1000) * 1e6));
}

void main(void)
{
    blog_set_level_log_component(BLOG_LEVEL_INFO, "soft_timer");
    delay_ms=100;
    timer1=bl_os_timer_create(timer1_cb,NULL);
    bl_os_timer_start_periodic(timer1,(delay_ms / 1000),((delay_ms % 1000) * 1e6));     // cycle =100ms,start periodic

    timer2=bl_os_timer_create(timer2_cb,NULL);
    delay_ms=1000;
    bl_os_timer_start_periodic(timer2,(delay_ms / 1000),((delay_ms % 1000) * 1e6));     //cycle =1000ms,start periodic

    timer3=bl_os_timer_create(timer3_cb,NULL);
    delay_ms=3000;
    bl_os_timer_start_once(timer3,(delay_ms / 1000),((delay_ms % 1000) * 1e6));         //cycle =3000ms,start once
}

FAQ & Troubleshooting ​

⚠️ Timer callback doesn't fire
Cause: software timers are scheduled by the system timer task; a high-priority task blocks it too long, or no blog_set_level_log_component level was set so logs get filtered out
Fix: confirm the component level for the callback's prints is set correctly (soft_timer in this example); avoid business tasks occupying the CPU too long

⚠️ Period doesn't match real time (visibly slow)
Cause: software timer precision depends on the system tick (default tick) and jitters when tasks are busy
Fix: for µs-level precise timing use the hardware timer (see Timer (Hardware)); software timers suit ms-level applications

⚠️ Crashes after calling bl_os_timer_delete inside a timer callback
Cause: the timer whose callback is currently running gets deleted directly, conflicting with the FreeRTOS timer task context
Fix: follow the official approach — delete other timers in the callback (e.g. timer2's callback deleting timer2 itself is officially verified to work); for cross-task deletion, defer via a queue

⚠️ Multiple timers sharing one callback argument corrupts data
Cause: the callback's second parameter (void *data) points to a shared variable
Fix: allocate a separate argument struct for each timer, avoiding shared global data

⚠️ Serial port won't open / /dev/ttyUSB0 not found
Cause: USB-to-serial driver not installed, port occupied, or (on Linux) no access permission
Fix: on Linux confirm the device is recognized with lsusb, run sudo chmod 666 /dev/ttyUSB0 or add your user to the dialout group and retry; on Windows check the COM port in Device Manager and install the CH340/CP210x driver

⚠️ Flashing stuck waiting / chip not found
Cause: download mode wasn't entered, the cable only charges and can't transfer data, or the baud rate is wrong
Fix: press and hold EN during flashing until the progress bar appears; try a data cable; confirm p= port and b=921600 are correct

⚠️ cd reports No such file or directory / no Makefile found
Cause: make ran outside the example project directory, or the SDK install path differs from the tutorial
Fix: cd ~/Ai-Thinker-WB2/applications/system/soft_timer first, then run make; if ~/Ai-Thinker-WB2 doesn't exist, find the SDK with find ~ -name "Ai-Thinker-WB2"

Self-Check

The serial prints per the pattern "100ms × 9 → stop → 1000ms after 3s" — the software timer is verified.

Released under the MIT License. Build Time 2026-09-30 17:31:25