Skip to content

概念先知道

  • 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_dmaWO + DMA 驱动 6 颗 WS2812,颜色循环流动GPIO23
wo_intWO 中断模式:演示 END / FIFO / FIFO 错误三类中断处理未指定
wo_uartSET_CLR 模式在引脚输出 UART 串口波形(把字节编码成 10 位串口时序)GPIO23
wo_ws2812(本页主线)WO + DMA 驱动 60 颗 WS2812,彩虹渐变GPIO10

本页以 wo_ws2812 为主线讲解操作步骤与核心 API,文末「完整代码」给出全部 5 个例程源码。

操作步骤

1
硬件准备

把 WS2812 灯带的 DIN 接到开发板 GPIO10(例程默认 WS2812_PIN = 10),VCC 按灯带规格接 5V(部分 3.3V 灯带接 3.3V),GND 与开发板共地。

2
进入例程目录

在终端进入 SDK 的 WO 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/wo/wo_ws2812
3
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

上电后灯带的 60 颗灯珠呈现彩虹渐变流动效果(每 5ms 刷新一帧);如果只亮或完全不亮,见文末 FAQ。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

其他例程

其余例程的编译、烧录、验证步骤与主线相同,只需进入对应目录:cd examples/peripherals/wo/wo_console(或 wo_dma / wo_int / wo_uart)。其中 wo_dmawo_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 阈值
  • modeWO_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:引脚号
  • modeWO_MODE_WRITE / WO_MODE_SET_CLR

返回值:无

bflb_wo_enable_dma(wo)

使能 WO 的 DMA 通道,配合 DMA 自动把数据写入 WO FIFO(wo_ws2812wo_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 设备句柄
  • datauint16_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)
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);
    }
}
📜 点击展开 wo_dma/main.c 完整代码(WO+DMA 驱动 6 颗 WS2812,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);
    }
}
📜 点击展开 wo_int/main.c 完整代码(WO 中断模式)
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);
}
📜 点击展开 wo_uart/main.c 完整代码(WO 输出 UART 波形,SET_CLR 模式,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);
}
📜 点击展开 wo_console/main.c 完整代码(最简控制台验证)
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

Released under the MIT License. Build Time 2026-09-11 14:52:23