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) andWO_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:
| Example | Function | Output pin |
|---|---|---|
wo_console | Simplest console check: prints test strings in a loop (no WO init) | - |
wo_dma | WO + DMA driving 6 WS2812 LEDs with flowing colors | GPIO23 |
wo_int | WO interrupt mode: END / FIFO / FIFO-error interrupt handling | unspecified |
wo_uart | Outputs 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 gradient | GPIO10 |
This page uses wo_ws2812 as the main walkthrough; the "Complete Code" section below contains all 5 examples.
Operation Steps
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.
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_ws2812Run the build command. 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/ttyUSB0After 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,50in the example (40 MHz / 50 = 800 kHz)code0_first_cnt/code1_first_cnt: first-phase high widths of code0/code1,16(0.4 µs) and34(0.85 µs)code0_first_level/code1_first_level: first-phase levels of the two codesidle_level: idle levelfifo_threshold: FIFO thresholdmode:WO_MODE_WRITEorWO_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 handlepin: pin numbermode: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 handledata: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 handleint_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)
#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)
#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)
#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)
#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)
#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

