概念先知道
- WO(Wire Output):芯片的可编程单线输出外设——配置一个周期的总计数和 code0/code1 两种码型的高低电平宽度,就能自动输出自定义时序波形,适合驱动单线协议设备(WS2812 灯带、红外、遥控等)。
- WS2812 位编码:例程 40MHz 时钟分频 50 得到 800kHz(周期 1.25µs);code0 = 高 0.4µs / 低 0.85µs 表示“0”,code1 = 高 0.85µs / 低 0.4µs 表示“1”。
- 不是 Wake On:WO 是“输出协议”外设,与低功耗唤醒无关(名称容易混淆,注意区分)。
- WO 有两种输出模式:
WO_MODE_WRITE(直接写 I/O,WS2812 用)与WO_MODE_SET_CLR(置位/清零模式,wo_uart 用)。
例程功能简介
WO 外设在 SDK 中共有 5 个例程(examples/peripherals/wo/),覆盖单线协议输出的各种用法:
| 例程 | 功能 | 输出引脚 |
|---|---|---|
wo_console | 最简控制台验证:循环打印测试字符串(不初始化 WO 外设) | - |
wo_dma | WO + DMA 驱动 6 颗 WS2812,颜色循环流动 | GPIO23 |
wo_int | WO 中断模式:演示 END / FIFO / FIFO 错误三类中断处理 | 未指定 |
wo_uart | 用 SET_CLR 模式在引脚输出 UART 串口波形(把字节编码成 10 位串口时序) | GPIO23 |
wo_ws2812(本页主线) | WO + DMA 驱动 60 颗 WS2812,彩虹渐变 | GPIO10 |
本页以 wo_ws2812 为主线讲解操作步骤与核心 API,文末「完整代码」给出全部 5 个例程源码。
操作步骤
把 WS2812 灯带的 DIN 接到开发板 GPIO10(例程默认 WS2812_PIN = 10),VCC 按灯带规格接 5V(部分 3.3V 灯带接 3.3V),GND 与开发板共地。
在终端进入 SDK 的 WO 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/peripherals/wo/wo_ws2812执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0上电后灯带的 60 颗灯珠呈现彩虹渐变流动效果(每 5ms 刷新一帧);如果只亮或完全不亮,见文末 FAQ。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
其他例程
其余例程的编译、烧录、验证步骤与主线相同,只需进入对应目录:cd examples/peripherals/wo/wo_console(或 wo_dma / wo_int / wo_uart)。其中 wo_dma 与 wo_uart 使用 GPIO23 输出,接线时注意区分。
例程调用的 API 介绍
bflb_wo_init(wo, config)
初始化 WO 外设的时序参数。struct bflb_wo_cfg_s 中:
code_total_cnt:一个周期的总计数,例程50(40MHz/50 = 800kHz)code0_first_cnt/code1_first_cnt:code0/code1 首段高电平宽度,例程16(0.4µs)与34(0.85µs)code0_first_level/code1_first_level:两种码型的首段电平idle_level:空闲电平fifo_threshold:FIFO 阈值mode:WO_MODE_WRITE(直接写 I/O)或WO_MODE_SET_CLR(置位/清零)
参数:
wo:WO 设备句柄(bflb_device_get_by_name("wo"))config:配置结构体指针
返回值:成功返回 0;失败返回负值错误码
bflb_wo_pin_init(wo, pin, mode)
把指定引脚绑定为 WO 输出。wo_ws2812 使用 GPIO10,wo_dma / wo_uart 使用 GPIO23。
参数:
wo:WO 设备句柄pin:引脚号mode:WO_MODE_WRITE/WO_MODE_SET_CLR
返回值:无
bflb_wo_enable_dma(wo)
使能 WO 的 DMA 通道,配合 DMA 自动把数据写入 WO FIFO(wo_ws2812、wo_dma 使用)。
参数:
wo:WO 设备句柄
返回值:成功返回 0;失败返回负值错误码
bflb_wo_push_fifo / bflb_wo_push_fifo_force(wo, data, len)
把 16 位数据写入 WO FIFO:普通写入(wo_console/wo_uart 使用)与强制写入(wo_int 溢出场景使用)。
参数:
wo:WO 设备句柄data:uint16_t *数据缓冲区(每位对应引脚一路输出)len:数据个数
返回值:成功返回 0;失败返回负值错误码
bflb_wo_enable / bflb_wo_disable(wo)
使能/关闭 WO 输出,wo_int 例程在推送数据前后切换。
参数:
wo:WO 设备句柄
返回值:成功返回 0;失败返回负值错误码
bflb_wo_get_int_status / bflb_wo_int_clear / bflb_wo_int_mask / bflb_wo_int_unmask(wo, ...)
WO 中断控制(wo_int 例程):查询中断状态(WO_INT_END 发送结束 / WO_INT_FIFO FIFO 就绪 / WO_INT_FER FIFO 错误)、清除与屏蔽/取消屏蔽中断。
参数:
wo:WO 设备句柄int_status/int_mask:中断标志/屏蔽位
返回值:get_int_status 返回状态位;其余成功返回 0
bflb_dma_channel_init / lli_reload / start(...)
内存→外设 DMA:目的地址 DMA_ADDR_WO_TDR、请求源 DMA_REQUEST_WO、16 位宽度,把灯带帧数据搬进 WO FIFO。
参数:同 DMA 例程(通道配置、LLI 链表、启动)
返回值:成功返回 0;失败返回负值错误码
完整代码
以下为 examples/peripherals/wo/ 全部 5 个例程的完整源码(与官方示例一致),默认折叠,点击展开:
📜 点击展开 wo_ws2812/main.c 完整代码(主线:60 颗 WS2812 彩虹,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);
}
}📜 点击展开 wo_dma/main.c 完整代码(WO+DMA 驱动 6 颗 WS2812,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);
}
}📜 点击展开 wo_int/main.c 完整代码(WO 中断模式)
#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);
}📜 点击展开 wo_uart/main.c 完整代码(WO 输出 UART 波形,SET_CLR 模式,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);
}📜 点击展开 wo_console/main.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
灯带完全不亮
检查接线:DIN 接 GPIO10、VCC/GND 正确供电并共地(多数灯带需 5V 供电);确认代码里 WS2812_PIN 与接线一致;确认烧录成功并复位运行。
颜色或顺序不对
例程按 GRB 格式输出;请确认灯带协议与 WS2812 一致(部分兼容灯珠时序略有差异)。code_total_cnt 等时序参数改动会直接导致解码错误,建议保持例程默认值。
只有第一颗灯亮或尾部闪烁
确认灯珠数量与 WS2812_NUM(例程 60)一致;灯带供电不足时后续灯珠电压跌落会表现异常,请使用足够功率的电源并缩短灯带。
wo_int 例程卡在等待标志位
WO_INT_END / WO_INT_FIFO 中断需要在中断回调里清除标志(bflb_wo_int_clear);确认中断已使能(bflb_irq_enable)且未把对应中断屏蔽。
想换到其他引脚输出
修改 WS2812_PIN(或 PIN_USE),并同步更新数据编码函数里 (pin % 16) / (pin % 8) 的位偏移,重新编译烧录即可。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

