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-11 14:52:23