本文以CH592测试,使用TMR的PWM功能驱动WS2812,这种方法相对于SPI DMA驱动的方式有点时节省IO资源,但是RAM消耗会比SPI方式大四倍。

下面贴出程序:

1.pwm_ws2812.c文件:

#include "pwm_ws2812.h"

/**
 * @brief  初始化WS2812缓冲区(填充前后复位信号)
 */
void ws2812_buf_init(uint32_t *buf)
{
    uint16_t i;
    // 前导复位信号(低电平)
    for(i = 0; i < WS2812_RESET_CYCLES; i++)
    {
        buf[i] = 0; // 占空比0%(低电平)
    }
    // 后导复位信号(确保数据发送完成后复位)
    uint16_t data_end = WS2812_RESET_CYCLES + WS2812_LED_NUM * 24;
    for(i = data_end; i < WS2812_BUF_LEN; i++)
    {
        buf[i] = 0;
    }
}

/**
 * @brief  内部函数:填充单个LED的24bit数据
 */
static void ws2812_set_rgb_bits(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b)
{
    uint8_t i;
    // 绿色分量(8bit,先低后高)
    for(i = 0; i < 8; i++)
    {
        buf[i] = (g & 0x01) ? WS2812_T1H : WS2812_T0H;
        g >>= 1;
    }
    // 红色分量(8bit,先低后高)
    for(i = 0; i < 8; i++)
    {
        buf[i + 8] = (r & 0x01) ? WS2812_T1H : WS2812_T0H;
        r >>= 1;
    }
    // 蓝色分量(8bit,先低后高)
    for(i = 0; i < 8; i++)
    {
        buf[i + 16] = (b & 0x01) ? WS2812_T1H : WS2812_T0H;
        b >>= 1;
    }
}

/**
 * @brief  设置单个LED的颜色
 */
void ws2812_set_single(uint32_t *buf, uint8_t led_idx, uint8_t g, uint8_t r, uint8_t b)
{
    if(led_idx >= WS2812_LED_NUM) return; // 索引越界检查
    // 计算当前LED在缓冲区中的起始位置
    uint32_t *led_buf = buf + WS2812_RESET_CYCLES + led_idx * 24;
    ws2812_set_rgb_bits(led_buf, g, r, b);
}

/**
 * @brief  填充所有LED为同一颜色
 */
void ws2812_fill_all(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b)
{
    for(uint8_t i = 0; i < WS2812_LED_NUM; i++)
    {
        ws2812_set_single(buf, i, g, r, b);
    }
}

/**
 * @brief  发送WS2812数据
 */
void ws2812_send(uint32_t *buf)
{
    // 配置DMA传输范围
    TMR2_DMACfg(ENABLE,
               (uint16_t)(uint32_t)buf,
               (uint16_t)(uint32_t)(buf + WS2812_BUF_LEN ),
               Mode_Single);
    // 启动PWM(低电平起始,单次传输)
    //TMR2_PWMInit(Low_Level, PWM_Times_1);
    TMR2_PWMEnable();
    TMR2_Enable();

}

/**
 * @brief  初始化WS2812硬件(定时器2+GPIO)
 */
void ws2812_hw_init(void)
{
    // 配置GPIO(PB11,推挽输出)
    GPIOA_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA);

//    GPIOB_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA);
//    GPIOPinRemap(ENABLE, RB_PIN_TMR2); // 映射定时器2到PB11

    // 配置定时器2 PWM周期
    TMR2_PWMCycleCfg(WS2812_BIT_CYCLE); // 单bit周期
    TMR2_PWMInit(High_Level, PWM_Times_1);

}

2.pwm_ws2812.h文件:

#ifndef __PWM_WS2812_H
#define __PWM_WS2812_H

#include "CH59x_common.h"

// WS2812 配置参数(根据实际需求修改)
#define WS2812_LED_NUM      10      // LED数量
#define WS2812_CLK_FREQ     60      // 系统时钟频率(MHz),需与实际时钟一致
#define WS2812_RESET_US     50      // 复位信号最小时间(us)

// 时序参数(单位:系统时钟周期,自动计算)
#define WS2812_T0H          (uint32_t)(0.4 * WS2812_CLK_FREQ)  // 0码高电平(≈0.4us)
#define WS2812_T1H          (uint32_t)(0.8 * WS2812_CLK_FREQ)  // 1码高电平(≈0.8us)
#define WS2812_BIT_CYCLE    (uint32_t)(1.25 * WS2812_CLK_FREQ) // 单bit总周期(≈1.25us)
#define WS2812_T0L          (WS2812_BIT_CYCLE - WS2812_T0H)    // 0码低电平
#define WS2812_T1L          (WS2812_BIT_CYCLE - WS2812_T1H)    // 1码低电平
#define WS2812_RESET_CYCLES (uint32_t)((WS2812_RESET_US * WS2812_CLK_FREQ) / 1000) // 复位周期数

// 缓冲区长度计算(前导复位+数据+后导复位)
#define WS2812_BUF_LEN      (WS2812_RESET_CYCLES + WS2812_LED_NUM * 24 + WS2812_RESET_CYCLES/2)

/**
 * @brief  初始化WS2812缓冲区(填充复位信号)
 * @param  buf: 缓冲区指针(需提前定义为WS2812_BUF_LEN长度)
 */
void ws2812_buf_init(uint32_t *buf);

/**
 * @brief  设置单个LED的RGB颜色
 * @param  buf: 缓冲区指针
 * @param  led_idx: LED索引(0~WS2812_LED_NUM-1)
 * @param  g: 绿色分量(0-255)
 * @param  r: 红色分量(0-255)
 * @param  b: 蓝色分量(0-255)
 */
void ws2812_set_single(uint32_t *buf, uint8_t led_idx, uint8_t g, uint8_t r, uint8_t b);

/**
 * @brief  填充所有LED为同一颜色
 * @param  buf: 缓冲区指针
 * @param  g: 绿色分量(0-255)
 * @param  r: 红色分量(0-255)
 * @param  b: 蓝色分量(0-255)
 */
void ws2812_fill_all(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b);

/**
 * @brief  发送WS2812数据(启动DMA和PWM)
 * @param  buf: 缓冲区指针
 */
void ws2812_send(uint32_t *buf);

/**
 * @brief  初始化WS2812硬件(定时器+GPIO)
 */
void ws2812_hw_init(void);

#endif

3.main函数:

#include "CH59x_common.h"

#include "pwm_ws2812.h"

// 定义WS2812缓冲区(按宏定义长度分配)
__attribute__((aligned(4))) uint32_t PwmBuf[WS2812_BUF_LEN];

void DebugInit(void)
{
    GPIOA_SetBits(GPIO_Pin_9);
    GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
    UART1_DefInit();
}

int main()
{
    SetSysClock(CLK_SOURCE_PLL_60MHz);
    DebugInit();
    PRINT("WS2812 Test @ChipID=%02X\n", R8_CHIP_ID);

    // 初始化WS2812硬件和缓冲区
    ws2812_hw_init();
    ws2812_buf_init(PwmBuf);
PRINT("WS2812_BUF_LEN=%d\n",WS2812_BUF_LEN);
    // 测试流程:依次显示黄→青→紫→逐个点亮
    ws2812_fill_all(PwmBuf, 0xFF, 0xFF, 0x00); // 黄色
    for(uint16_t i=0;i<WS2812_BUF_LEN;i++){
        PRINT("%d ",PwmBuf[i]);
    }
    PRINT("\n");
    ws2812_send(PwmBuf);
    DelayMs(1000);

    ws2812_fill_all(PwmBuf, 0xFF, 0x00, 0xFF); // 青色
    ws2812_send(PwmBuf);
    DelayMs(1000);

    ws2812_fill_all(PwmBuf, 0x00, 0xFF, 0xFF); // 紫色
    ws2812_send(PwmBuf);
    DelayMs(1000);

    while(1) // 无限循环,持续律动
    {
        // 1. 从左到右:目标色依次点亮,背景色保持不变
        for(uint8_t j = 0; j < WS2812_LED_NUM; j++)
        {
            // 每次循环先重置所有LED为背景色(避免残留之前的目标色)
            ws2812_fill_all(PwmBuf, 0x55, 0xAA, 0xFF);
            // 将当前位置j的LED设为目标色
            ws2812_set_single(PwmBuf, j, 0xAA, 0x55, 0x00);
            // 发送数据,更新LED显示
            ws2812_send(PwmBuf);
            // 控制律动速度,数值越小越快(可根据需求调整,如300ms)
            DelayMs(300);
        }

        // 2. 从右到左:目标色依次熄灭(恢复背景色),模拟“往回走”
        for(int8_t j = WS2812_LED_NUM - 2; j >= 0; j--) // 从倒数第二个开始(最后一个已点亮,无需重复)
        {
            // 重置所有LED为背景色
            ws2812_fill_all(PwmBuf, 0x55, 0xAA, 0xFF);
            // 将当前位置j的LED设为目标色
            ws2812_set_single(PwmBuf, j, 0xAA, 0x55, 0x00);
            // 发送数据,更新LED显示
            ws2812_send(PwmBuf);
            // 保持与“从左到右”相同的速度,确保律动流畅
            DelayMs(300);
        }
    }
}
posted on 2025-11-01 11:25  WCH蓝牙应用分享  阅读(62)  评论(0)    收藏  举报