RGB灯带控制(WS2811)

芯片采用单线归零码的通讯方式,芯片在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个芯片提取后,送到芯片内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的芯片,每经过一个芯片的传输,信号减少24bit。芯片采用自动整形转发技术,使得该芯片的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。

芯片内部的数据锁存器根据接受到的24bit数据,在OUTR、OUTG、OUTB控制端产生不同的占空比控制信号, 等待DIN端输入RESET信号时,所有芯片同步将接收到的数据送到各个段,芯片将在该信号结束后重新接收的数据,在接收完开始的24bit数据后,通过DO口转发数据口,芯片在没有接收到RESET码前,OUTR、OUTG、OUTB管脚原输出保持不变,当接受到280μs以上低电平RESET码后,芯片将刚才接收到的24bit PWM数据脉宽输出到OUTR、OUTG、OUTB引脚上。

通过控制引脚拉高拉低的时间来生成0码和1码,传输RGB数据,需要注意的是由于时间周期小,如果单片机主频不高 ,单条指令的执行时间可能也会影响到通信时序,因此建议通过DMA搭配TIMER的PWM输出来实现。

下边是一份GD32F303CCT6的的参考代码,使用PA0作为控制接口,实测功能正常,供参考。

复制代码
#include "gd32f30x.h"
#include <stdint.h>
#include <string.h>

/* =========================================================
 * 基本配置
 * ========================================================= */
#define LED_COUNT               48

/* PA0 -> TIMER4_CH0 */
#define WS_GPIO_PORT            GPIOA
#define WS_GPIO_PIN             GPIO_PIN_0

/* DMA 映射 */
#define WS_DMA_PERIPH           DMA1
#define WS_DMA_CHANNEL          DMA_CH4

#define WS_TIMER                TIMER4
#define WS_TIMER_RCU            RCU_TIMER4
#define WS_TIMER_CH             TIMER_CH_0

/* 颜色顺序:1=GRB,0=RGB */
#define WS2811_ORDER_GRB        1

/* =========================================================
 * 时钟假设
 * =========================================================
 * 假设 TIMER4 内核时钟 = 120MHz
 * 1 tick = 8.333ns
 * 1.25us / 8.333ns = 150 tick
 */
#define WS_TIMER_CLK_HZ         120000000UL
#define WS_BIT_TICKS            150U
#define WS_TIMER_PERIOD         (WS_BIT_TICKS - 1U)

/* 已调通的一组占空值
 * 0码: 42 tick ≈ 350ns
 * 1码: 84 tick ≈ 700ns
 */
#define WS_0H_TICKS             42U
#define WS_1H_TICKS             84U

/* 只发有效 bit,不把 reset 放进 DMA buffer */
#define WS_BITS_PER_LED         24U
#define WS_DMA_BUF_LEN          (LED_COUNT * WS_BITS_PER_LED)

/* show() 超时保护 */
#define WS_DMA_TIMEOUT          1000000UL

/* =========================================================
 * 全局缓存
 * ========================================================= */
static uint8_t  g_led_buf[LED_COUNT][3];
static uint16_t g_dma_buf[WS_DMA_BUF_LEN];

static volatile uint8_t g_ws_tx_done  = 0;
static volatile uint8_t g_ws_tx_error = 0;

/* =========================================================
 * 简单延时(只用于 demo,不参与 WS2811 bit 时序)
 * ========================================================= */
static void delay_cycles(volatile uint32_t n)
{
    while (n--) {
        __NOP();
    }
}

static void delay_us(uint32_t us)
{
    /* 粗略延时,只用于 show() 后 reset 和 demo */
    uint32_t cycles = SystemCoreClock / 6000000U;
    while (us--) {
        delay_cycles(cycles);
    }
}

static void delay_ms(uint32_t ms)
{
    while (ms--) {
        delay_us(1000);
    }
}

/* =========================================================
 * GPIO 初始化
 * ========================================================= */
static void ws2811_gpio_init(void)
{
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_AF);

    /* PA0 -> TIMER4_CH0, 复用推挽输出 */
    gpio_init(WS_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, WS_GPIO_PIN);
    gpio_bit_reset(WS_GPIO_PORT, WS_GPIO_PIN);
}

/* =========================================================
 * TIMER4 初始化
 * 关键点:
 *   1) 用 PWM 输出 bit 波形
 *   2) 用 CH0 DMA 请求
 *   3) 开启 CH0 compare shadow/preload
 *   4) reset 不放在 DMA buffer 里,发送结束后软件拉低 300us
 * ========================================================= */
static void ws2811_timer_init(void)
{
    timer_parameter_struct timer_initpara;
    timer_oc_parameter_struct timer_ocpara;

    rcu_periph_clock_enable(WS_TIMER_RCU);

    timer_deinit(WS_TIMER);

    memset(&timer_initpara,0,sizeof(timer_initpara));
    timer_initpara.prescaler         = 0;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = WS_TIMER_PERIOD;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(WS_TIMER, &timer_initpara);

    memset(&timer_ocpara,0,sizeof(timer_ocpara));
    timer_ocpara.outputstate  = TIMER_CCX_ENABLE;
    timer_ocpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;
    timer_channel_output_config(WS_TIMER, WS_TIMER_CH, &timer_ocpara);

    /* PWM0:计数器从 0 开始先高,计数到比较值后拉低 */
    timer_channel_output_mode_config(WS_TIMER, WS_TIMER_CH, TIMER_OC_MODE_PWM0);
    timer_channel_output_pulse_value_config(WS_TIMER, WS_TIMER_CH, 0);

    /* 已调通方案:开启 compare preload/shadow */
    timer_channel_output_shadow_config(WS_TIMER, WS_TIMER_CH, TIMER_OC_SHADOW_ENABLE);

    /* ARR shadow 保留 */
    timer_auto_reload_shadow_enable(WS_TIMER);

    timer_counter_value_config(WS_TIMER, 0);
    timer_disable(WS_TIMER);
}

/* =========================================================
 * DMA 初始化
 * ========================================================= */
static void ws2811_dma_init_once(void)
{
    rcu_periph_clock_enable(RCU_DMA1);
    dma_deinit(WS_DMA_PERIPH, WS_DMA_CHANNEL);
}

/* =========================================================
 * 颜色接口
 * ========================================================= */
static void ws2811_set_rgb(uint32_t index, uint8_t r, uint8_t g, uint8_t b)
{
    if (index >= LED_COUNT) return;

    g_led_buf[index][0] = r;
    g_led_buf[index][1] = g;
    g_led_buf[index][2] = b;
}

static void ws2811_fill_rgb(uint8_t r, uint8_t g, uint8_t b)
{
    uint32_t i;
    for (i = 0; i < LED_COUNT; i++) {
        g_led_buf[i][0] = r;
        g_led_buf[i][1] = g;
        g_led_buf[i][2] = b;
    }
}

static void ws2811_clear(void)
{
    memset(g_led_buf, 0, sizeof(g_led_buf));
}

/* =========================================================
 * 效果辅助函数
 * ========================================================= */
static uint8_t ws2811_scale8(uint8_t value, uint8_t brightness)
{
    return (uint8_t)(((uint16_t)value * (uint16_t)brightness) / 255U);
}

static void ws2811_color_wheel(uint8_t pos, uint8_t *r, uint8_t *g, uint8_t *b)
{
    if (pos < 85U) {
        *r = 255U - pos * 3U;
        *g = pos * 3U;
        *b = 0U;
    } else if (pos < 170U) {
        pos -= 85U;
        *r = 0U;
        *g = 255U - pos * 3U;
        *b = pos * 3U;
    } else {
        pos -= 170U;
        *r = pos * 3U;
        *g = 0U;
        *b = 255U - pos * 3U;
    }
}

/* =========================================================
 * 构建 DMA 占空比缓冲
 * ========================================================= */
static void ws2811_build_dma_buf(void)
{
    uint32_t i, j, k = 0;

    for (i = 0; i < LED_COUNT; i++) {
        uint8_t bytes[3];

#if WS2811_ORDER_GRB
        bytes[0] = g_led_buf[i][1];   /* G */
        bytes[1] = g_led_buf[i][0];   /* R */
        bytes[2] = g_led_buf[i][2];   /* B */
#else
        bytes[0] = g_led_buf[i][0];   /* R */
        bytes[1] = g_led_buf[i][1];   /* G */
        bytes[2] = g_led_buf[i][2];   /* B */
#endif

        for (j = 0; j < 3; j++) {
            uint8_t data = bytes[j];
            uint8_t bit;

            for (bit = 0; bit < 8; bit++) {
                g_dma_buf[k++] = (data & 0x80U) ? WS_1H_TICKS : WS_0H_TICKS;
                data <<= 1;
            }
        }
    }
}

/* =========================================================
 * 启动 DMA 发送
 * 当前已验证思路:
 *   - 第1个 bit 手动写入 CH0CV
 *   - 后续 bit 由 CH0 DMA 请求依次搬运
 *   - reset 不进 DMA buffer,show() 末尾软件拉低
 * ========================================================= */
static void ws2811_dma_start(void)
{
    dma_parameter_struct dma_init_struct;

    g_ws_tx_done  = 0;
    g_ws_tx_error = 0;

    timer_disable(WS_TIMER);
    timer_dma_disable(WS_TIMER, TIMER_DMA_CH0D);
    dma_channel_disable(WS_DMA_PERIPH, WS_DMA_CHANNEL);
    dma_deinit(WS_DMA_PERIPH, WS_DMA_CHANNEL);

    memset(&dma_init_struct,0,sizeof(dma_init_struct));
    dma_init_struct.direction    = DMA_MEMORY_TO_PERIPHERAL;
    dma_init_struct.memory_addr  = (uint32_t)&g_dma_buf[1];              /* 从第2个 bit 开始 */
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;
    dma_init_struct.number       = WS_DMA_BUF_LEN - 1U;
    dma_init_struct.periph_addr  = (uint32_t)(&TIMER_CH0CV(WS_TIMER));
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    dma_init_struct.priority     = DMA_PRIORITY_ULTRA_HIGH;

    /* 你本地库这里是传结构体本体 */
    dma_init(WS_DMA_PERIPH, WS_DMA_CHANNEL, dma_init_struct);
    dma_circulation_disable(WS_DMA_PERIPH, WS_DMA_CHANNEL);
    dma_memory_to_memory_disable(WS_DMA_PERIPH, WS_DMA_CHANNEL);

    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_G);
    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_FTF);
    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_ERR);

    /* 第1个 bit 先手动装入 */
    timer_counter_value_config(WS_TIMER, 0);
    timer_channel_output_pulse_value_config(WS_TIMER, WS_TIMER_CH, g_dma_buf[0]);

    /* 触发一次更新,把第1个 bit 装到活动寄存器 */
    timer_event_software_generate(WS_TIMER, TIMER_EVENT_SRC_UPG);

    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_G);
    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_FTF);
    dma_flag_clear(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_ERR);

    /* 开 CH0 DMA 请求 */
    timer_dma_enable(WS_TIMER, TIMER_DMA_CH0D);
    dma_channel_enable(WS_DMA_PERIPH, WS_DMA_CHANNEL);
    timer_enable(WS_TIMER);
}

/* =========================================================
 * 阻塞式刷新
 * ========================================================= */
static void ws2811_show(void)
{
    uint32_t timeout = WS_DMA_TIMEOUT;

    ws2811_build_dma_buf();
    ws2811_dma_start();

    while (RESET == dma_flag_get(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_FTF)) {
        if (SET == dma_flag_get(WS_DMA_PERIPH, WS_DMA_CHANNEL, DMA_FLAG_ERR)) {
            g_ws_tx_error = 1U;
            break;
        }

        if (--timeout == 0U) {
            g_ws_tx_error = 2U;
            break;
        }
    }

    if (!g_ws_tx_error) {
        g_ws_tx_done = 1U;
    }

    timer_dma_disable(WS_TIMER, TIMER_DMA_CH0D);
    dma_channel_disable(WS_DMA_PERIPH, WS_DMA_CHANNEL);
    timer_disable(WS_TIMER);

    /* 软件 reset:强制拉低 */
    timer_channel_output_pulse_value_config(WS_TIMER, WS_TIMER_CH, 0);
    gpio_bit_reset(WS_GPIO_PORT, WS_GPIO_PIN);

    delay_us(300);
}

/* =========================================================
 * 特效:单点流水灯
 * ========================================================= */
static void ws2811_effect_running_light(uint8_t r, uint8_t g, uint8_t b,
                                        uint16_t step_delay_ms,
                                        uint16_t rounds)
{
    uint16_t round;
    uint32_t i;

    for (round = 0; round < rounds; round++) {
        for (i = 0; i < LED_COUNT; i++) {
            ws2811_clear();
            ws2811_set_rgb(i, r, g, b);
            ws2811_show();
            delay_ms(step_delay_ms);
        }
    }
}

/* =========================================================
 * 特效:带尾巴流水灯
 * ========================================================= */
static void ws2811_effect_running_tail(uint8_t r, uint8_t g, uint8_t b,
                                       uint8_t tail_len,
                                       uint16_t step_delay_ms,
                                       uint16_t rounds)
{
    uint16_t round;
    int32_t head, j;

    if (tail_len == 0U) {
        tail_len = 1U;
    }

    for (round = 0; round < rounds; round++) {
        for (head = 0; head < (int32_t)LED_COUNT; head++) {
            ws2811_clear();

            for (j = 0; j < (int32_t)tail_len; j++) {
                int32_t idx = head - j;
                if (idx >= 0 && idx < (int32_t)LED_COUNT) {
                    uint8_t brightness = (uint8_t)(255U - (uint32_t)j * 255U / tail_len);
                    ws2811_set_rgb((uint32_t)idx,
                                   ws2811_scale8(r, brightness),
                                   ws2811_scale8(g, brightness),
                                   ws2811_scale8(b, brightness));
                }
            }

            ws2811_show();
            delay_ms(step_delay_ms);
        }
    }
}

/* =========================================================
 * 特效:单色呼吸灯
 * ========================================================= */
static void ws2811_effect_breathe(uint8_t r, uint8_t g, uint8_t b,
                                  uint8_t step,
                                  uint16_t step_delay_ms,
                                  uint16_t rounds,
                                  uint8_t min_bright,
                                  uint8_t max_bright)
{
    uint16_t round;
    uint16_t bright;
    uint32_t i;
    uint8_t real_bright;

    if (step == 0U) {
        step = 5U;
    }

    /* 防呆:保证 min <= max */
    if (min_bright > max_bright) {
        uint8_t tmp = min_bright;
        min_bright = max_bright;
        max_bright = tmp;
    }

    for (round = 0; round < rounds; round++) {

        /* 渐亮 */
        for (bright = 0; bright <= max_bright; bright += step) {
            real_bright = min_bright +
                          (uint8_t)(((uint16_t)(max_bright - min_bright) * bright) / max_bright);

            for (i = 0; i < LED_COUNT; i++) {
                ws2811_set_rgb(i,
                               ws2811_scale8(r, real_bright),
                               ws2811_scale8(g, real_bright),
                               ws2811_scale8(b, real_bright));
            }

            ws2811_show();
            delay_ms(step_delay_ms);

            if (bright + step > max_bright) {
                break;
            }
        }

        /* 渐暗 */
        for (bright = max_bright; bright > 0U; bright = (bright > step) ? (bright - step) : 0U) {
            real_bright = min_bright +
                          (uint8_t)(((uint16_t)(max_bright - min_bright) * bright) / max_bright);

            for (i = 0; i < LED_COUNT; i++) {
                ws2811_set_rgb(i,
                               ws2811_scale8(r, real_bright),
                               ws2811_scale8(g, real_bright),
                               ws2811_scale8(b, real_bright));
            }

            ws2811_show();
            delay_ms(step_delay_ms);

            if (bright <= step) {
                break;
            }
        }
    }
}

/* =========================================================
 * 特效:呼吸变色灯
 * ========================================================= */
static void ws2811_effect_breathe_rainbow(uint8_t step,
                                          uint16_t step_delay_ms,
                                          uint16_t color_step,
                                          uint16_t rounds)
{
    uint16_t round;
    uint16_t bright;
    uint16_t color_pos = 0;
    uint32_t i;
    uint8_t r, g, b;

    if (step == 0U) {
        step = 5U;
    }
    if (color_step == 0U) {
        color_step = 1U;
    }

    for (round = 0; round < rounds; round++) {

        for (bright = 0; bright <= 255U; bright += step) {
            ws2811_color_wheel((uint8_t)(color_pos & 0xFFU), &r, &g, &b);

            for (i = 0; i < LED_COUNT; i++) {
                ws2811_set_rgb(i,
                               ws2811_scale8(r, (uint8_t)bright),
                               ws2811_scale8(g, (uint8_t)bright),
                               ws2811_scale8(b, (uint8_t)bright));
            }

            ws2811_show();
            delay_ms(step_delay_ms);
            color_pos += color_step;

            if (bright + step > 255U) {
                break;
            }
        }

        for (bright = 255U; bright > 0U; bright = (bright > step) ? (bright - step) : 0U) {
            ws2811_color_wheel((uint8_t)(color_pos & 0xFFU), &r, &g, &b);

            for (i = 0; i < LED_COUNT; i++) {
                ws2811_set_rgb(i,
                               ws2811_scale8(r, (uint8_t)bright),
                               ws2811_scale8(g, (uint8_t)bright),
                               ws2811_scale8(b, (uint8_t)bright));
            }

            ws2811_show();
            delay_ms(step_delay_ms);
            color_pos += color_step;

            if (bright <= step) {
                break;
            }
        }
    }

    ws2811_clear();
    ws2811_show();
}

/* =========================================================
 * 特效:彩虹流水灯
 * ========================================================= */
static void ws2811_effect_rainbow_running(uint16_t step_delay_ms, uint16_t rounds)
{
    uint16_t round;
    uint32_t offset, i;
    uint8_t r, g, b;

    for (round = 0; round < rounds; round++) {
        for (offset = 0; offset < 256U; offset++) {
            for (i = 0; i < LED_COUNT; i++) {
                uint8_t pos = (uint8_t)((i * 256U / LED_COUNT + offset) & 0xFFU);
                ws2811_color_wheel(pos, &r, &g, &b);
                ws2811_set_rgb(i, r, g, b);
            }

            ws2811_show();
            delay_ms(step_delay_ms);
        }
    }
}

/* =========================================================
 * 初始化
 * ========================================================= */
static void ws2811_init(void)
{
    SystemCoreClockUpdate();

    ws2811_gpio_init();
    ws2811_timer_init();
    ws2811_dma_init_once();

    ws2811_clear();
    delay_us(300);
}

/* =========================================================
 * demo
 * ========================================================= */
int main(void)
{
    ws2811_init();

    while (1) {
        /* 单点流水 */
        ws2811_effect_running_light(25, 25, 0, 80, 3);

        /* 带尾巴流水 */
        ws2811_effect_running_tail(32, 8, 8, 4, 60, 3);

        /* 单色呼吸 */
        ws2811_effect_breathe(102, 0, 0, 1, 20, 3,1,60);

        /* 呼吸变色 */
        ws2811_effect_breathe_rainbow(1, 20, 1, 3);

        /* 彩虹流水 */
        ws2811_effect_rainbow_running(20, 2);

        ws2811_clear();
        ws2811_show();
        delay_ms(500);
    }
}
相关推荐
清风6666661 小时前
基于单片机与DAC0832的双路波形信号发生系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
azwsm2 小时前
电路元器件和GPIO控制器
单片机·嵌入式硬件
kebidaixu6 小时前
FreeRTOS 移植到 STM32F407VETX 记录(一)
stm32·单片机·嵌入式硬件
CSDN官方博客6 小时前
「谁说嵌入式只是调包和焊板子?」—— 2026嵌入式全栈技术征锋令
嵌入式硬件·物联网·embedding
点灯小铭7 小时前
基于单片机的数码管定时插座设计与定时开关功能实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
云栖梦泽7 小时前
玩转RK3506SDK
linux·嵌入式硬件
数智工坊9 小时前
机器人四大主控板系统分层选型指南:树莓派、ESP32、STM32与Arduino的能力边界与实战定位
stm32·嵌入式硬件·机器人
进击的小头9 小时前
第8篇:IGBT 从零到精通:核心原理、关键参数、选型指南与工业级应用要点
经验分享·嵌入式硬件·学习
点灯小铭9 小时前
基于单片机的多模式智能洗衣机设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
项目題供诗9 小时前
STM32-AD单通道&AD多通道(十九)
stm32·单片机·嵌入式硬件