文章目录
一、简介
当你的项目需要控制多个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颜色值 |