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);
    }
}
相关推荐
LCG元2 小时前
STM32实战:基于STM32F103的LCD1602液晶屏(并口/模拟时序)驱动
stm32·单片机·嵌入式硬件
可乐鸡翅好好吃3 小时前
从四个 ble_evt_handler 看 Nordic BLE 架构:模块化解耦与优先级控制
单片机·嵌入式硬件
匿名了匿名了3 小时前
直流无刷与直流有刷电机
stm32·嵌入式硬件·mcu
水果里面有苹果4 小时前
26-MT41J64M16LA-187E 美光科技DDR3 SDRAM 1Gb
嵌入式硬件
三佛科技-187366133974 小时前
LPK8717省外围无需启动电阻,12W自供电PSR控制芯片恒压恒流方案
单片机·嵌入式硬件
陶瓷好烦4 小时前
智能编码助手:VSCode+Keil+Kilo Code打造自然语言编程环境
vscode·stm32·单片机
cmpxr_5 小时前
【单片机】51单片机的晶振选择
单片机·嵌入式硬件·51单片机
松小白song5 小时前
如何在定时器中断中实现PWM波形切换?
stm32·单片机·嵌入式硬件
asjodnobfy5 小时前
生产过程中的电容损坏分析
嵌入式硬件·硬件工程