Skip to content

Concepts First

  • WO (Wire Output): the chip's programmable single-wire output peripheral — configure the total count of one period and the high-level widths of code0/code1, and it outputs a custom timing waveform automatically, ideal for single-wire protocol devices (WS2812 strips, IR, remotes, etc.).
  • WS2812 bit encoding: the example divides the 40 MHz clock by 50 to get 800 kHz (1.25 µs period); code0 = 0.4 µs high / 0.85 µs low for "0", code1 = 0.85 µs high / 0.4 µs low for "1".
  • Not Wake On: WO is an "output protocol" peripheral, unrelated to low-power wake-up (the name is easy to confuse).
  • Two output modes: WO_MODE_WRITE (direct write I/O, used by WS2812) and WO_MODE_SET_CLR (set/clear mode, used by wo_uart).

Example Overview

The WO peripheral has 5 examples in the SDK (examples/peripherals/wo/), covering various single-wire protocol output use cases:

ExampleFunctionOutput pin
wo_consoleSimplest console check: prints test strings in a loop (no WO init)-
wo_dmaWO + DMA driving 6 WS2812 LEDs with flowing colorsGPIO23
wo_intWO interrupt mode: END / FIFO / FIFO-error interrupt handlingunspecified
wo_uartOutputs a UART waveform on a pin via SET_CLR mode (bytes encoded as 10-bit serial timing)GPIO23
wo_ws2812 (this page)WO + DMA driving 60 WS2812 LEDs with a rainbow gradientGPIO10

This page uses wo_ws2812 as the main walkthrough; the "Complete Code" section below contains all 5 examples.

Operation Steps

1
Prepare the Hardware

Connect the WS2812 strip’s DIN to GPIO10 (the example uses WS2812_PIN = 10 by default), VCC to 5V per the strip spec (3.3V for 3.3V strips), and GND shared with the board.

2
Enter the Example Directory

Open a terminal and enter the WO example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/peripherals/wo/wo_ws2812
3
Build the Project

Run the build command. 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

After power-up, the 60 LEDs show a rainbow gradient flowing effect (one frame every 5 ms); if nothing lights up, see the FAQ below.

Code Execution Flow

The complete execution flow from startup to running is shown below (loop arrows mean repeated execution):

Other examples

The other examples use the same build/flash/verify flow — just enter their directory: cd examples/peripherals/wo/wo_console (or wo_dma / wo_int / wo_uart). Note that wo_dma and wo_uart output on GPIO23, so wire accordingly.

APIs Used by the Example

bflb_wo_init(wo, config)

Initializes the WO timing. In struct bflb_wo_cfg_s:

  • code_total_cnt: total count of one period, 50 in the example (40 MHz / 50 = 800 kHz)
  • code0_first_cnt / code1_first_cnt: first-phase high widths of code0/code1, 16 (0.4 µs) and 34 (0.85 µs)
  • code0_first_level / code1_first_level: first-phase levels of the two codes
  • idle_level: idle level
  • fifo_threshold: FIFO threshold
  • mode: WO_MODE_WRITE or WO_MODE_SET_CLR

Parameters:

  • wo: WO device handle (bflb_device_get_by_name("wo"))
  • config: pointer to the config struct

Return: 0 on success; negative error code on failure

bflb_wo_pin_init(wo, pin, mode)

Binds a pin as the WO output. wo_ws2812 uses GPIO10; wo_dma / wo_uart use GPIO23.

Parameters:

  • wo: WO device handle
  • pin: pin number
  • mode: WO_MODE_WRITE / WO_MODE_SET_CLR

Return: none

bflb_wo_enable_dma(wo)

Enables the WO DMA channel so DMA feeds data into the WO FIFO automatically (used by wo_ws2812 and wo_dma).

Parameters:

  • wo: WO device handle

Return: 0 on success; negative error code on failure

bflb_wo_push_fifo / bflb_wo_push_fifo_force(wo, data, len)

Pushes 16-bit data into the WO FIFO: normal push (wo_console/wo_uart) and forced push (wo_int overflow scenario).

Parameters:

  • wo: WO device handle
  • data: uint16_t * data buffer (each bit drives one output of the pin)
  • len: number of data words

Return: 0 on success; negative error code on failure

bflb_wo_enable / bflb_wo_disable(wo)

Enables/disables WO output; wo_int toggles it around data pushes.

Parameters:

  • wo: WO device handle

Return: 0 on success; negative error code on failure

bflb_wo_get_int_status / bflb_wo_int_clear / bflb_wo_int_mask / bflb_wo_int_unmask(wo, ...)

WO interrupt control (from wo_int): read interrupt status (WO_INT_END transfer end / WO_INT_FIFO FIFO ready / WO_INT_FER FIFO error), clear, and mask/unmask interrupts.

Parameters:

  • wo: WO device handle
  • int_status / int_mask: interrupt status/mask bits

Return: get_int_status returns status bits; others return 0 on success

bflb_dma_channel_init / lli_reload / start(...)

Memory-to-peripheral DMA: destination DMA_ADDR_WO_TDR, request DMA_REQUEST_WO, 16-bit width, moving each strip frame into the WO FIFO.

Parameters: same as the DMA example

Return: 0 on success; negative error code on failure

Complete Code

The complete sources below match the effects described on this page, identical to the official examples under examples/peripherals/wo/. Collapsed by default, click to expand:

📜 Click to expand wo_ws2812/main.c full code (main walkthrough: 60 LEDs, GPIO10)
c
#include "bflb_mtimer.h"
#include "bflb_dma.h"
#include "board.h"
#include "bflb_wo.h"

#define DMA_ENABLE
#define WS2812_PIN     (10)
#define WS2812_NUM     (60)
#define WS2812_BUFFER  (WS2812_NUM * 24)

uint32_t buffer_rgb[WS2812_NUM];
uint16_t buffer_data[WS2812_BUFFER] __attribute__((aligned(32)));

struct bflb_device_s *wo;
#if defined(DMA_ENABLE)
struct bflb_device_s *dma0_ch0;
static ATTR_NOCACHE_RAM_SECTION struct bflb_dma_channel_lli_pool_s llipool[1];
static ATTR_NOCACHE_RAM_SECTION struct bflb_dma_channel_lli_transfer_s transfers[1];
#endif

struct bflb_wo_cfg_s cfg = {
    .code_total_cnt = 50,   /* 40MHz(XCLK) / 50 = 800KHz, period = 1.25us */
    .code0_first_cnt = 16,  /* high = 1.25us * 16 / 50 = 0.4us, low = 1.25us * (50 - 16) / 50 = 0.85us */
    .code1_first_cnt = 34,  /* high = 1.25us * 34 / 50 = 0.85us, low = 1.25us * (50 - 34) / 50 = 0.4us */
    .code0_first_level = 1,
    .code1_first_level = 1,
    .idle_level = 0,
    .fifo_threshold = 64,
    .mode = WO_MODE_WRITE,
};

#if defined(DMA_ENABLE)
struct bflb_dma_channel_config_s dma_cfg = {
    .direction = DMA_MEMORY_TO_PERIPH,
    .src_req = DMA_REQUEST_NONE,
    .dst_req = DMA_REQUEST_WO,
    .src_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
    .dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
    .src_burst_count = DMA_BURST_INCR8,
    .dst_burst_count = DMA_BURST_INCR8,
    .src_width = DMA_DATA_WIDTH_16BIT,
    .dst_width = DMA_DATA_WIDTH_16BIT,
};
#endif

static uint32_t get_rgb(uint32_t x, uint8_t a)
{
    uint8_t r, g, b;
    uint16_t i = 0, j = 0;
    x %= 1530;
    i = x / 256;

    j = x % 256;

    switch (i) {
        case 0: {
            r = 255;
            g = 0;
            b = j;
        } break;

        case 1: {
            r = 255 - j;
            g = 0;
            b = 255;
        } break;
        case 2: {
            r = 0;
            g = j;
            b = 255;
        } break;
        case 3: {
            r = 0;
            g = 255;
            b = 255 - j;
        } break;
        case 4: {
            r = j;
            g = 255;
            b = 0;
        } break;
        case 5: {
            r = 255;
            g = 255 - j;
            b = 0;
        } break;

        default:
            return 0;
            break;
    }

    return ((r >> a) << 16) | ((g >> a) << 8) | (b >> a);
}
static void data_update(void)
{
    static uint32_t rgb_base = 0;
    static uint32_t k = 51;
    uint32_t rgb_temp;

    rgb_temp = rgb_base;
    for (uint32_t i = 0; i < WS2812_NUM; i++) {
        buffer_rgb[i] = get_rgb((rgb_temp += k), 4);
    }
    rgb_base += 5;
    if (rgb_base >= 1530) {
        rgb_base = 0;
    }

    for (uint32_t i = 0; i < WS2812_NUM; i++) {
        for (uint8_t j = 0; j < 8; j++) {
            buffer_data[i * 24 + j] = ((buffer_rgb[i] >> (15 - j)) & 1) << (WS2812_PIN % 16);
            buffer_data[i * 24 + j + 8] = ((buffer_rgb[i] >> (23 - j)) & 1) << (WS2812_PIN % 16);
            buffer_data[i * 24 + j + 16] = ((buffer_rgb[i] >> (7 - j)) & 1) << (WS2812_PIN % 16);
        }
    }
    bflb_l1c_dcache_clean_range(buffer_data, sizeof(buffer_data));
}

int main(void)
{
    board_init();
    wo = bflb_device_get_by_name("wo");
    bflb_wo_init(wo, &cfg);
    bflb_wo_pin_init(wo, WS2812_PIN, WO_MODE_WRITE);
#if defined(DMA_ENABLE)
    bflb_wo_enable_dma(wo);
    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");
    transfers[0].src_addr = (uint32_t)buffer_data;
    transfers[0].dst_addr = DMA_ADDR_WO_TDR;
    transfers[0].nbytes = WS2812_BUFFER * sizeof(uint16_t);
    printf("wo_ws2812 example with dma\r\n");
#else
    printf("wo_ws2812 example by polling\r\n");
#endif

    while (1) {
        data_update();
#if defined(DMA_ENABLE)
        bflb_dma_channel_stop(dma0_ch0);
        bflb_dma_channel_init(dma0_ch0, &dma_cfg);
        bflb_dma_channel_lli_reload(dma0_ch0, llipool, 1, transfers, 1);
        bflb_dma_channel_start(dma0_ch0);
#else
        bflb_wo_push_fifo(wo, buffer_data, WS2812_BUFFER);
#endif
        bflb_mtimer_delay_ms(5);
    }
}
📜 Click to expand wo_dma/main.c full code (WO+DMA, 6 WS2812 LEDs, GPIO23)
c
#include "bflb_mtimer.h"
#include "bflb_dma.h"
#include "board.h"
#include "bflb_wo.h"

#define PIN_USE  (23)
#define LED_NUM  (6)
#define BUFF_LEN (LED_NUM * 24)
#define LUMI     (50)

struct bflb_device_s *wo;

uint32_t rgb[LED_NUM] = { LUMI, (LUMI << 8) | LUMI, LUMI << 8, (LUMI << 8) | (LUMI << 16), LUMI << 16, (LUMI << 16) | LUMI };
uint16_t buff[LED_NUM * 24] __attribute__((aligned(4)));

struct bflb_wo_cfg_s cfg = {
    .code_total_cnt = 50,
    .code0_first_cnt = 16,
    .code1_first_cnt = 34,
    .code0_first_level = 1,
    .code1_first_level = 1,
    .idle_level = 0,
    .fifo_threshold = 64,
    .mode = WO_MODE_WRITE,
};

struct bflb_dma_channel_config_s dma_cfg = {
    .direction = DMA_MEMORY_TO_PERIPH,
    .src_req = DMA_REQUEST_NONE,
    .dst_req = DMA_REQUEST_WO,
    .src_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
    .dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
    .src_burst_count = DMA_BURST_INCR8,
    .dst_burst_count = DMA_BURST_INCR8,
    .src_width = DMA_DATA_WIDTH_16BIT,
    .dst_width = DMA_DATA_WIDTH_16BIT,
};

struct bflb_device_s *dma0_ch0;
static ATTR_NOCACHE_RAM_SECTION struct bflb_dma_channel_lli_pool_s llipool[1];
static ATTR_NOCACHE_RAM_SECTION struct bflb_dma_channel_lli_transfer_s transfers[1];

void rgb_water(void)
{
    uint32_t temp = rgb[0];
    for (uint32_t i = 0; i < LED_NUM - 1; i++) {
        rgb[i] = rgb[i + 1];
    }
    rgb[LED_NUM - 1] = temp;
}

void rgb_to_buff(uint32_t *rgb, uint16_t *buff, uint32_t len, uint8_t pin)
{
    pin = pin % 16;
    uint16_t val1 = (1 << pin);

    for (uint32_t i = 0; i < len; i++) {
        uint32_t rgb_val = rgb[i];
        uint32_t buff_val;
        for (uint32_t j = 0; j < 24; j++) {
            buff_val = rgb_val >> (23 - j);
            buff_val &= 1;
            if (buff_val) {
                buff[i * 24 + j] = val1;
            } else {
                buff[i * 24 + j] = 0;
            }
        }
    }
    bflb_l1c_dcache_clean_invalidate_range(buff, sizeof(uint16_t) * BUFF_LEN);
}

int main(void)
{
    board_init();
    wo = bflb_device_get_by_name("wo");
    bflb_wo_init(wo, &cfg);
    bflb_wo_pin_init(wo, PIN_USE, WO_MODE_WRITE);
    bflb_wo_enable_dma(wo);
    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");
    bflb_dma_channel_tcint_mask(dma0_ch0, 1);
    transfers[0].src_addr = (uint32_t)buff;
    transfers[0].dst_addr = DMA_ADDR_WO_TDR;
    transfers[0].nbytes = BUFF_LEN * sizeof(uint16_t);

    while (1) {
        rgb_water();
        rgb_to_buff(rgb, buff, LED_NUM, PIN_USE);
        // bflb_wo_push_fifo(buff, BUFF_LEN);
        bflb_dma_channel_init(dma0_ch0, &dma_cfg);
        bflb_dma_channel_lli_reload(dma0_ch0, llipool, 1, transfers, 1);
        bflb_dma_channel_start(dma0_ch0);
        arch_delay_ms(500);
        bflb_dma_channel_stop(dma0_ch0);
    }
}
📜 Click to expand wo_int/main.c full code (WO interrupt mode)
c
#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_wo.h"

static uint16_t data_write_arr[256];

struct bflb_device_s *wo;

volatile int flag_end = 0, flag_fifo = 0;

void wo_isr(int irq, void *arg)
{
    uint32_t int_status = bflb_wo_get_int_status(wo);

    if (int_status & WO_INT_END) {
        printf("interrupt end!\n");
        bflb_wo_int_clear(wo, WO_INT_END);
        flag_end++;
    }
    if (int_status & WO_INT_FIFO) {
        printf("interrupt fifo!\n");
        bflb_wo_int_clear(wo, WO_INT_FIFO | WO_INT_FER);
        bflb_wo_disable(wo);
        bflb_wo_int_mask(wo, WO_INT_FIFO);
        flag_fifo++;
    }
    if (int_status & WO_INT_FER) {
        printf("interrupt fer!\n");
        bflb_wo_int_clear(wo, WO_INT_FER);
    }
}

struct bflb_wo_cfg_s cfg = {
    .code_total_cnt = 10,
    .code0_first_cnt = 6,
    .code1_first_cnt = 2,
    .code0_first_level = 1,
    .code1_first_level = 1,
    .idle_level = 0,
    .fifo_threshold = 16,
    .mode = WO_MODE_WRITE,
};

int main(void)
{
    board_init();
    wo = bflb_device_get_by_name("wo");
    bflb_wo_init(wo, &cfg);
    printf("fifo available cnt: %d\n", bflb_wo_get_fifo_available_cnt(wo));
    bflb_wo_int_unmask(wo, WO_INT_END);
    bflb_wo_int_mask(wo, WO_INT_FIFO | WO_INT_FER);
    bflb_irq_attach(wo->irq_num, wo_isr, NULL);
    bflb_irq_enable(wo->irq_num);

    bflb_wo_disable(wo);
    bflb_wo_push_fifo(wo, data_write_arr, 5);
    bflb_wo_enable(wo);
    while (flag_end == 0);

    bflb_wo_disable(wo);
#if defined(BL616CL)
    bflb_wo_push_fifo(wo, data_write_arr, 16);
#else
    bflb_wo_push_fifo(wo, data_write_arr, 128);
#endif
    bflb_wo_int_mask(wo, WO_INT_END);
    bflb_wo_int_unmask(wo, WO_INT_FIFO | WO_INT_FER);
    bflb_wo_enable(wo);
    while (flag_fifo == 0);

    bflb_wo_push_fifo_force(wo, data_write_arr, 130);
}
📜 Click to expand wo_uart/main.c full code (UART waveform via WO, SET_CLR mode, GPIO23)
c
#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_wo.h"

#define PIN_USE     (23)
#define DATA_LENGTH (256)
#define BUFF_LENGTH (DATA_LENGTH * 10)
uint8_t byte_arr[DATA_LENGTH];
uint16_t buff[BUFF_LENGTH];
struct bflb_device_s *wo;

struct bflb_wo_cfg_s cfg = {
    .code_total_cnt = 20,
    .code0_first_cnt = 0,
    .code1_first_cnt = 0,
    .code0_first_level = 0,
    .code1_first_level = 0,
    .idle_level = 1,
    .fifo_threshold = 0,
    .mode = WO_MODE_SET_CLR,
};

void wo_byte_to_buff(uint8_t *c, uint16_t *buff, uint32_t len, uint8_t pin)
{
    pin = pin % 8;
    uint16_t val1 = (1 << pin);
    uint16_t val0 = (1 << (pin + 8));
    for (uint32_t i = 0; i < len; i++) {
        buff[i * 10] = val0;
        for (uint32_t j = 0; j < 8; j++) {
            if ((c[i] >> j) & 1) {
                buff[i * 10 + 1 + j] = val1;
            } else {
                buff[i * 10 + 1 + j] = val0;
            }
        }
        buff[i * 10 + 9] = val1;
    }
}

void data_init(void)
{
    for (uint32_t i = 0; i < DATA_LENGTH; i++) {
        byte_arr[i] = i;
    }
    wo_byte_to_buff(byte_arr, buff, DATA_LENGTH, PIN_USE);
}

int main(void)
{
    board_init();
    wo = bflb_device_get_by_name("wo");
    bflb_wo_init(wo, &cfg);
    bflb_wo_pin_init(wo, PIN_USE, WO_MODE_SET_CLR);
    data_init();
    bflb_wo_push_fifo(wo, buff, BUFF_LENGTH);
}
📜 Click to expand wo_console/main.c full code (simplest console check)
c
#include "bflb_mtimer.h"
#include "board.h"

int main(void)
{
    board_init();
    while (1) {
        static uint32_t i = 0;
        printf("i = %d\n", i++);
        if ((i & 3) == 0) {
            printf("abcdefghijklmnopqrstuvwxyz\n");
        } else if ((i & 3) == 1) {
            printf("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
        } else if ((i & 3) == 2) {
            printf("0123456789\n");
        } else {
            printf("~!@#$%%^&*()_+-={}[]:\";'<>?,./'\n");
        }
        bflb_mtimer_delay_ms(500);
    }
}

FAQ

The strip does not light up at all

Check the wiring: DIN to GPIO10, VCC/GND powered correctly with shared ground (most strips need 5V); confirm WS2812_PIN matches your wiring; make sure flashing succeeded and the board restarted.

Wrong colors or order

The example outputs GRB format; make sure the strip uses the WS2812 protocol (some compatible LEDs differ slightly). Changing timing parameters such as code_total_cnt breaks decoding, so keep the defaults.

Only the first LED lights or the tail flickers

Confirm the LED count matches WS2812_NUM (60 in the example); with insufficient power, later LEDs drop voltage and behave abnormally — use a properly sized power supply and a shorter strip.

wo_int hangs waiting for a flag

WO_INT_END / WO_INT_FIFO must be cleared in the ISR (bflb_wo_int_clear); make sure the interrupt is enabled (bflb_irq_enable) and not masked.

I want to use another output pin

Change WS2812_PIN (or PIN_USE) and update the (pin % 16) / (pin % 8) bit offset in the encoding function, then rebuild and flash.

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