【STM32】RTT-Studio中HAL库开发教程十一:WS2812彩色RGB模块使用

文章目录


一、简介

当你的项目需要控制多个WS2812灯珠时,传统的GPIO延时方法会让CPU陷入无休止的空转等待,不利于CPU资源的使用。所以需要改用DMA+PWM方案,可以使CPU占用率从98%直降到3%,降低CPU的占用率。

1.GPIO延时方案

用GPIO模拟WS2812时序就像用勺子挖隧道------能实现但效率极低。常见代码里充斥着__NOP()延时循环,每个比特位都需要CPU全程参与电平切换。驱动100个LED需要2400次位操作(24bit/灯珠),每次操作包含数十条指令,这意味着:

  • 72MHz主频下,控制100个LED需要约20ms持续CPU介入
  • 系统响应延迟增加,多任务处理能力急剧下降
  • 刷新率受限,难以实现流畅的动画效果(超过50Hz时CPU已满载)

更糟的是,不同厂商的WS2812对时序要求存在微妙差异。某次我用GPIO方案调试新批次灯带时,发现颜色显示异常,最终发现是T1H时间需要从650ns调整为580ns------这种细微调 整意味着要重新计算所有__NOP()数量。

2. PWM+DMA的硬件加速原理

STM32的定时器PWM配合DMA控制器,能构建一个自主运行的波形发生器。其核心思想是:

  • PWM占空比编码数据:将WS2812的0/1逻辑转换为不同占空比的PWM波形
    • 逻辑"1":74%占空比(1250ns周期中高电平900ns)
    • 逻辑"0":26%占空比(1250ns周期中高电平325ns)
  • DMA自动搬运波形数据:预先计算好所有LED对应的PWM占空比序列,由DMA自动传输到定时器CCR寄存器。这种方案的精妙之处在于:整个数据传输过程完全由硬件完成。CPU只需初始化配置,之后可以处理其他任务或进入低功耗模式。

二、时序图

1.WS2812S

芯片时序分为0码1码,以及复位码

复制代码
   0码:高电平时间(220ns-380ns)   低电平时间(580ns-1us)
   1码:高电平时间(580ns-1us)     低电平时间(220ns-420ns)
   复位码:低电平时间(>280us)

RGB灯珠每个之间采用串联形式,第一个灯的DIN输入PWM信号,输出DO作为下一个灯珠的DIN,以此类推进行硬件连接。每个灯珠有R、G、B三个灯,每个数据包含一个字节,总共三个字节的数据,当下发一串数据时,第一个灯珠获取24Bit的数据锁存,再将剩余数据发送到下一个灯珠,以此类推。


2.硬件连接

常见问题:当灯带长度超过1米时,单独从MCU 取电会导致末端LED颜色异常。建议在灯带中段追加5V电源注入,电源线径不小于18AWG。

信号线 STM32引脚选择建议 注意事项
数据线 TIMx_CHy(支持PWM输出) 需串联100Ω电阻限流
电源正极 5V电源 每50颗LED需额外供电
电源负极 GND 确保与MCU共地

3.数据码实现

通过定时器先实现800KHz的定时器功能,并且能够输出PWM,PWM的默认占空比设置为0。通过配置定时的PWM输出,以及DMA功能,将DMA设置为:

  • Prescaler = 0,168MHz 不分频,;Period = 209; 1.25µs 周期
  • Pulse = 0,默认占空比 0
  • DMA方向:内存 → 外设
  • 外设数据长度:16 位(半字)

实现功能码0和1的波形模拟输出:


三、初始化配置

1.定时器配置

c 复制代码
/**
 * @brief 定时器引脚初始化
 * @param htim
 */
static void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = { 0 };
    if (htim->Instance == TIM1)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();

        GPIO_InitStruct.Pin = GPIO_PIN_8;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;         // 复用推挽输出
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;      // 复用功能 AF1 → 映射到 TIM1
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

/**
 * @brief 定时器初始化
 */
static void MX_TIM1_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef sConfigOC = { 0 };
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = { 0 };

    __HAL_RCC_TIM1_CLK_ENABLE();

    // ==================== 【关键修复】TIM1 正确配置 ====================
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;                                       // 168MHz 不分频
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;                    // 向上计数模式
    htim1.Init.Period = 209;                                        // 1.25µs 周期
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;              // 时钟分频 1,不分频
    htim1.Init.RepetitionCounter = 0;                               // 高级定时器参数,普通 PWM 设 0
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;   // 开启 ARR 预装载,波形更稳定
    HAL_TIM_Base_Init(&htim1);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;      // 使用内部时钟
    HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);

    HAL_TIM_PWM_Init(&htim1);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;    // 主从模式不用
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);

    // ==================== 【关键修复】PWM 极性正确 ====================
    sConfigOC.OCMode = TIM_OCMODE_PWM1;                             // PWM1 模式,计数器 < CCR1 时输出有效电平
    sConfigOC.Pulse = 0;                                            // 默认占空比 0
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;                     // 高电平为有效电平
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;                      // 关闭快速模式
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;                  // 空闲状态低电平
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);

    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;        // 高级定时器的断路、死区功能全部关闭
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);

    HAL_TIM_MspPostInit(&htim1);
}

2.DMA配置

c 复制代码
/**
 * @brief 定时器DMA初始化
 */
static void MX_DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_tim1_ch1.Instance = DMA2_Stream1;
    hdma_tim1_ch1.Init.Channel = DMA_CHANNEL_6;
    hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;                // 方向:内存 → 外设
    hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;                    // 外设地址不自增
    hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;                        // 内存地址自增
    hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;   // 外设数据长度:16 位(半字)
    hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;      // 内存数据长度:16 位
    hdma_tim1_ch1.Init.Mode = DMA_NORMAL;                               // 正常模式
    hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_LOW;                     // 优先级低
    hdma_tim1_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;                 // 关闭 FIFO,直接模式
    HAL_DMA_Init(&hdma_tim1_ch1);

    __HAL_LINKDMA(&htim1, hdma[TIM_DMA_ID_CC1], hdma_tim1_ch1);         // 把 DMA 和 TIM1 通道 1 绑定

    HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
}

四、完整代码

1.WS2812.C

c 复制代码
#include "WS2812.h"

// DMA发送标志
uint8_t dma_send_flag = 0;
uint8_t rgb_mode_flag = 1;
uint8_t now_r = 0, now_g = 0, now_b = 255;

// 呼吸灯全局变量
static int16_t breath_val = 20;
static int8_t  breath_dir = 2;

// 七彩呼吸全局变量
static uint8_t breath_hue = 0;          // 色相 0~255
static int16_t breath_bright = 0;       // 呼吸亮度
static int8_t  breath_dir1 = 1;         // 亮度变化方向

// 全局缓冲区(必须全局!)0码-54  1码-155
uint16_t ws2812_buf[WS2812_LED_NUM * WS2812_RGB_NUM + 1] = {0};
// 保存每颗灯的原始颜色(必须加这个数组)
uint8_t led_color[WS2812_LED_NUM][3] = {0};  // 灯个数,每颗存 R G B

/**
 * @brief 定时器引脚初始化
 * @param htim
 */
static void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = { 0 };
    if (htim->Instance == TIM1)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();

        GPIO_InitStruct.Pin = GPIO_PIN_8;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;         // 复用推挽输出
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;      // 复用功能 AF1 → 映射到 TIM1
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

/**
 * @brief 定时器DMA初始化
 */
static void MX_DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_tim1_ch1.Instance = DMA2_Stream1;
    hdma_tim1_ch1.Init.Channel = DMA_CHANNEL_6;
    hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;                // 方向:内存 → 外设
    hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;                    // 外设地址不自增
    hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;                        // 内存地址自增
    hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;   // 外设数据长度:16 位(半字)
    hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;      // 内存数据长度:16 位
    hdma_tim1_ch1.Init.Mode = DMA_NORMAL;                               // 正常模式
    hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_LOW;                     // 优先级低
    hdma_tim1_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;                 // 关闭 FIFO,直接模式
    HAL_DMA_Init(&hdma_tim1_ch1);

    __HAL_LINKDMA(&htim1, hdma[TIM_DMA_ID_CC1], hdma_tim1_ch1);         // 把 DMA 和 TIM1 通道 1 绑定

    HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
}

/**
 * @brief 定时器初始化
 */
static void MX_TIM1_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef sConfigOC = { 0 };
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = { 0 };

    __HAL_RCC_TIM1_CLK_ENABLE();

    // ==================== 【关键修复】TIM1 正确配置 ====================
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;                                       // 168MHz 不分频
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;                    // 向上计数模式
    htim1.Init.Period = 209;                                        // 1.25µs 周期
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;              // 时钟分频 1,不分频
    htim1.Init.RepetitionCounter = 0;                               // 高级定时器参数,普通 PWM 设 0
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;   // 开启 ARR 预装载,波形更稳定
    HAL_TIM_Base_Init(&htim1);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;      // 使用内部时钟
    HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);

    HAL_TIM_PWM_Init(&htim1);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;    // 主从模式不用
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);

    // ==================== 【关键修复】PWM 极性正确 ====================
    sConfigOC.OCMode = TIM_OCMODE_PWM1;                             // PWM1 模式,计数器 < CCR1 时输出有效电平
    sConfigOC.Pulse = 0;                                            // 默认占空比 0
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;                     // 高电平为有效电平
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;                      // 关闭快速模式
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;                  // 空闲状态低电平
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);

    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;        // 高级定时器的断路、死区功能全部关闭
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);

    HAL_TIM_MspPostInit(&htim1);
}

/**
 * @brief DMA中断函数
 */
void DMA2_Stream1_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_tim1_ch1);
}

/**
 * @brief DMA传输完成回调
 * @param htim
 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
    // 可以在这里更新下一次传输的数据
    dma_send_flag = 0;
}

/**
 * @brief 真正启动 PWM 的函数
 */
static void WS2812_Start(void)
{
    if (dma_send_flag == 0)
    {
        dma_send_flag = 1;
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)ws2812_buf, WS2812_LED_NUM * WS2812_RGB_NUM + 1);
    }
}

/**
 * @brief 关闭所有灯
 */
static void WS2812_Close_All(void)
{
    uint16_t i;
    // 前 144 个是灯数据
    for (i = 0; i < WS2812_LED_NUM * 24; i++)
    {
        ws2812_buf[i] = 54;
    }
    // 最后1个复位码
    ws2812_buf[WS2812_LED_NUM * 24] = 0;
}

/**
 * @brief 设置第 index 颗灯的 RGB 颜色
 * @param index:0~5(你有6颗灯)
 * @param r:0~255 颜色值
 * @param g:0~255 颜色值
 * @param b:0~255 颜色值
 */
static void WS2812_Set_Led(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
{
    // 越界保护
    if (index >= WS2812_LED_NUM)
        return;

    // 计算这颗灯在缓冲区的起始位置
    uint16_t pos = index * 24;

    // WS2812S 协议顺序:G → R → B
    uint8_t i;

    // ========== 绿色 8bit ==========
    led_color[index][1] = g;
    for (i = 0; i < 8; i++)
    {
        ws2812_buf[pos++] = (g & (0x80 >> i)) ? 155 : 54;
    }

    // ========== 红色 8bit ==========
    led_color[index][0] = r;
    for (i = 0; i < 8; i++)
    {
        ws2812_buf[pos++] = (r & (0x80 >> i)) ? 155 : 54;
    }

    // ========== 蓝色 8bit ==========
    led_color[index][2] = b;
    for (i = 0; i < 8; i++)
    {
        ws2812_buf[pos++] = (b & (0x80 >> i)) ? 155 : 54;
    }
}

/**
 * @brief 设置第 index 颗灯的亮度
 * @param index:灯号 0~5
 * @param brightness:亮度 0~255(0最暗,255最亮)
 */
static void WS2812_Set_Led_Brightness(uint8_t index, uint8_t brightness)
{
    if(index >= 6) return;

    // 取出原来保存的颜色
    uint8_t r = led_color[index][0];
    uint8_t g = led_color[index][1];
    uint8_t b = led_color[index][2];

    // 按亮度缩放(整数运算,无浮点)
    r = (uint16_t)r * brightness / 255;
    g = (uint16_t)g * brightness / 255;
    b = (uint16_t)b * brightness / 255;

    // 重新填充到 DMA 缓冲区
    uint16_t pos = index * 24;
    uint8_t i;

    for(i=0; i<8; i++) ws2812_buf[pos++] = (g & (0x80>>i)) ? 155 : 54;
    for(i=0; i<8; i++) ws2812_buf[pos++] = (r & (0x80>>i)) ? 155 : 54;
    for(i=0; i<8; i++) ws2812_buf[pos++] = (b & (0x80>>i)) ? 155 : 54;
}

/**
 * @brief HSV 转 RGB 简单版
 * @param hue:0~255 色相
 * @param sat:0~255 饱和度
 * @param val:0~255 亮度
 * @param r,g,b 灯颜色
 */
static void HSV_To_RGB(uint8_t hue, uint8_t sat, uint8_t val, uint8_t *r, uint8_t *g, uint8_t *b)
{
    uint8_t region, rem;
    uint8_t p, q, t;

    if (sat == 0)
    {
        *r = val;
        *g = val;
        *b = val;
        return;
    }

    region = hue / 43;
    rem = (hue - region * 43) * 6;

    p = (val * (255 - sat)) >> 8;
    q = (val * (255 - ((sat * rem) >> 8))) >> 8;
    t = (val * (255 - ((sat * (255 - rem)) >> 8))) >> 8;

    switch (region)
    {
        case 0:  *r = val; *g = t;   *b = p;   break;
        case 1:  *r = q;   *g = val; *b = p;   break;
        case 2:  *r = p;   *g = val; *b = t;   break;
        case 3:  *r = p;   *g = q;   *b = val; break;
        case 4:  *r = t;   *g = p;   *b = val; break;
        default: *r = val; *g = p;   *b = q;   break;
    }
}

/**
 * @brief MSH指令
 * @param argc
 * @param argv
 * @return
 */
static int WS2812_RGB_Ctrl(int argc, char **argv)
{
    if (argc != 2 && argc != 3 && argc != 4 && argc != 6)
        return RT_TRUE;

    switch (argc)
    {
        case 2:
        {
            if (!strcmp(argv[1], "close"))
            {
                WS2812_Close_All();
            }
            break;
        }

        case 3:
        {
            if (!strcmp(argv[1], "mode"))
            {
                rgb_mode_flag = atoi(argv[2]);
            }
            break;
        }

        case 4:
        {
            if (!strcmp(argv[1], "lum"))
            {
                uint8_t lum, index;
                index = atoi(argv[2]);
                lum = atoi(argv[3]);

                WS2812_Set_Led_Brightness(index, lum);
            }
            else
            {
                now_r = atoi(argv[1]);
                now_g = atoi(argv[2]);
                now_b = atoi(argv[3]);
                for (int i = 0; i < WS2812_LED_NUM; ++i)
                {
                    led_color[i][0] = now_r;
                    led_color[i][1] = now_g;
                    led_color[i][2] = now_b;
                }
            }
            break;
        }

        case 6:
        {
            if (!strcmp(argv[1], "color"))
            {
                uint8_t r, g, b, index;
                index = atoi(argv[2]);
                r = atoi(argv[3]);
                g = atoi(argv[4]);
                b = atoi(argv[5]);

                WS2812_Set_Led(index, r, g, b);
            }
            break;
        }
    }

    WS2812_Start();

    return RT_TRUE;
}
MSH_CMD_EXPORT_ALIAS(WS2812_RGB_Ctrl, rgb, WS2812 RGB Ctrl);

/*=====================================================外部调用函数=====================================================*/
/**
 * @brief WS2812芯片初始化
 */
void WS2812_Init(void)
{
    MX_DMA_Init();        // 先开DMA
    MX_TIM1_Init();       // 再初始化TIM1

    for (int i = 0; i < WS2812_LED_NUM; ++i)
    {
        WS2812_Set_Led(i, 0, 0, 255);
    }
    WS2812_Start();
}

/**
 * @brief 关闭RGB
 */
void WS2812_Close_RGB(void)
{
    WS2812_Close_All();
    WS2812_Start();
    rgb_mode_flag = 0;
}

/**
 * @brief 呼吸灯
 */
void WS2812_Breath_Task(void)
{
    breath_val += breath_dir;

    if (breath_val >= 255)
    {
        breath_val = 254;
        breath_dir = -2;
    }
    else if (breath_val <= 20)
    {
        breath_val = 20;
        breath_dir = 2;
    }

    if (dma_send_flag == 0)
    {
        for (int i = 0; i < WS2812_LED_NUM; ++i)
        {
            WS2812_Set_Led_Brightness(i, breath_val);
        }
        WS2812_Start();
    }

    rt_thread_mdelay(10);
}

/**
 * @brief 流水灯
 * @param r:0~255 颜色值
 * @param g:0~255 颜色值
 * @param b:0~255 颜色值
 */
void WS2812_Flow_Led(uint8_t r, uint8_t g, uint8_t b)
{
    uint8_t i;

    // 1. 依次点亮
    for (i = 0; i < WS2812_LED_NUM; i++)
    {
        WS2812_Close_All();          // 全部熄灭
        WS2812_Set_Led(i, r, g, b);  // 点亮当前灯
        WS2812_Start();
        rt_thread_mdelay(120);      // 流水速度
    }

    // 2. 依次熄灭
    for (i = 0; i < WS2812_LED_NUM; i++)
    {
        WS2812_Close_All();
        WS2812_Start();
        rt_thread_mdelay(80);
    }
}

/**
 * @brief 跑马灯函数:单灯循环流动
 * @param r:0~255 颜色值
 * @param g:0~255 颜色值
 * @param b:0~255 颜色值
 */
void WS2812_Marquee_Led(uint8_t r, uint8_t g, uint8_t b)
{
    static uint8_t pos = 0;  // 当前亮灯位置
    static int8_t dir = 1;    // 方向:1=向右,-1=向左

    // 全部关灯
    WS2812_Close_All();

    // 只点亮当前位置
    WS2812_Set_Led(pos, r, g, b);

    // 发送刷新
    WS2812_Start();

    // 位置更新
    pos += dir;

    // 边界反弹(左右来回跑)
    if (pos >= WS2812_LED_NUM - 1)
    {
        dir = -1;  // 到最右边 → 向左跑
    }
    else if (pos <= 0)
    {
        dir = 1;   // 到最左边 → 向右跑
    }

    // 跑马速度(可调)
    rt_thread_mdelay(80);
}

/**
 * @brief 开合灯:中间向两边张开,再向中间收拢
 * @param r,g,b 灯颜色
 * @param speed 速度
 */
void WS2812_OpenClose_Led(uint8_t r, uint8_t g, uint8_t b, uint8_t speed)
{
    int mid = WS2812_LED_NUM / 2;

    // 1. 中间向两边张开
    for (int d = 0; d < mid; d++)
    {
        WS2812_Close_All();

        for (int i = mid - d; i <= mid + d; i++)
        {
            if (i >= 0 && i < WS2812_LED_NUM)
            {
                WS2812_Set_Led(i, r, g, b);
            }
        }

        while (HAL_DMA_GetState(&hdma_tim1_ch1) != HAL_DMA_STATE_READY)
            ;
        WS2812_Start();
        rt_thread_mdelay(speed);
    }

    // 2. 两边向中间收拢
    for (int d = mid; d >= 0; d--)
    {
        WS2812_Close_All();

        for (int i = mid - d; i <= mid + d; i++)
        {
            if (i >= 0 && i < WS2812_LED_NUM)
            {
                WS2812_Set_Led(i, r, g, b);
            }
        }

        WS2812_Start();
        rt_thread_mdelay(speed);
    }
}

/**
 * @brief 七彩丰富呼吸灯,放在主循环一直调用
 * @param led_index:灯号
 */
void WS2812_Rainbow_Breath(void)
{
    uint8_t r, g, b;

    // 1. 亮度呼吸:0 <-> 255
    breath_bright += breath_dir1;
    if (breath_bright >= 255)
    {
        breath_bright = 255;
        breath_dir1 = -1;
    }
    else if (breath_bright <= 0)
    {
        breath_bright = 0;
        breath_dir1 = 1;
    }

    // 2. 色相自增,自动变七彩
    breath_hue++;

    // 3. HSV 转真实 RGB
    HSV_To_RGB(breath_hue, 220, breath_bright, &r, &g, &b);

    // 4. 设置灯颜色并刷新
    for (int i = 0; i < WS2812_LED_NUM; ++i)
    {
        WS2812_Set_Led(i, r, g, b);
    }
    WS2812_Start();

    // 控制整体速度,数值越大越慢
    rt_thread_mdelay(15);
}

2.WS2812.h

c 复制代码
#ifndef APPLICATIONS_WS2812_H_
#define APPLICATIONS_WS2812_H_

#include "drv_common.h"
#include <string.h>
#include <stdlib.h>

TIM_HandleTypeDef htim1;
DMA_HandleTypeDef hdma_tim1_ch1;

#define WS2812_LED_NUM     6    // 你当前有6颗灯
#define WS2812_RGB_NUM     24   // rgb位数

extern uint8_t rgb_mode_flag;
extern uint8_t now_r, now_g, now_b;

extern void WS2812_Init(void);
extern void WS2812_Close_RGB(void);
extern void WS2812_Breath_Task(void);
extern void WS2812_Flow_Led(uint8_t r, uint8_t g, uint8_t b);
extern void WS2812_Marquee_Led(uint8_t r, uint8_t g, uint8_t b);
extern void WS2812_OpenClose_Led(uint8_t r, uint8_t g, uint8_t b, uint8_t speed);
extern void WS2812_Rainbow_Breath(void);

#endif /* APPLICATIONS_WS2812_H_ */

3.main.c

c 复制代码
#include <rtthread.h>
#include "WS2812.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

int main(void)
{
    int count = 1;

    WS2812_Init();

    while (count++)
    {
        switch (rgb_mode_flag)
        {
            case 1:  WS2812_Breath_Task();                          break;
            case 2:  WS2812_Flow_Led(now_r, now_g, now_b);          break;
            case 3:  WS2812_Marquee_Led(now_r, now_g, now_b);       break;
            case 4:  WS2812_OpenClose_Led(now_r, now_g, now_b, 40); break;
            case 5:  WS2812_Rainbow_Breath();                       break;
            case 6:  WS2812_Close_RGB();                            break;
            default: rt_thread_delay(1000);                         break;
        }
    }

    return RT_EOK;
}

五、测试验证

1.功能实现

代码实现了RGB的六种控制功能,包括呼吸灯、流水灯、跑马灯、开合灯和七彩呼吸灯。

MSH控制指令:

MSH指令 功能
rgb close 关闭所有RGB灯
rgb mode [cnt] cnt = 1:呼吸灯功能 cnt = 2:流水灯功能 cnt = 3:跑马灯功能 cnt = 4:开合灯功能 cnt = 5:七彩呼吸灯 cnt = 6:关闭灯功能
rgb lum [cnt] [val] cnt = 0 - 5 主要是用来设置哪一个灯珠 val = 0 - 255 代表灯珠设置的亮度
rgb [r] [g] [b] r = 0 - 255 r颜色值 g = 0 - 255 g颜色值 b = 0 - 255 b颜色值
rgb [index] [r] [g] [b] index = 0 - 5 主要是用来设置哪一个灯珠 r = 0 - 255 r颜色值 g = 0 - 255 g颜色值 b = 0 - 255 b颜色值

相关推荐
恶魔泡泡糖1 小时前
stm32F103C8T6标准库外部中断点灯
stm32·单片机·嵌入式硬件
The_superstar61 小时前
衡山派LVGL注意点
单片机·lvgl·衡山派·俊杰
fengfuyao9852 小时前
STM32 ADC音频采样与FFT频谱分析实现
stm32·嵌入式硬件·音视频
张海森-1688202 小时前
海思原生 isp调试工具使用方法
单片机
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 4 天:集成电路、芯片、FPGA
单片机·嵌入式硬件
项目題供诗2 小时前
STM32-对射式红外传感器计次&旋转编码器计次(九)
人工智能·stm32·嵌入式硬件
llilian_162 小时前
如何甄选专业级失真度测量仪校准装置
人工智能·功能测试·单片机·嵌入式硬件·测试工具·51单片机
bubiyoushang8882 小时前
利用数字电位器MCP41010控制开关电源输出电压
单片机
一个人的嵌入式~3 小时前
Lin协议介绍
单片机·dsp开发