STM32 WS2812B LED 控制系统实现_ws2812b操作
项目概述
基于 STM32F103C8T6 实现的 WS2812B LED 控制系统,通过按键操作实现多种 LED 灯效,并通过 OLED 屏幕显示当前状态。系统支持红绿蓝流水、单色流水、单色拖尾三种灯效模式,以及 LED 数量动态配置功能。
硬件连接
核心功能
1.双模式操作
Mode1:灯效选择(红绿蓝流水、单色流水、单色拖尾)
Mode2:LED 数量配置(0/24/48/60/144/204 颗)
2.按键处理
10ms 硬件消抖,500ms 长按判定
状态机实现可靠的按键逻辑
3.LED 效果
红绿蓝分段流水效果
单点单色流水效果
二次衰减算法实现的单色拖尾效果
软件架构
主要模块
key.c:按键消抖与状态检测
LED_Mode.c:模式管理与 OLED 显示
WS2812B.c:LED 驱动与灯效算法
tim.c:定时器配置与中断处理
关键代码解析
按键消抖状态机
#define DEBOUNCE_MS 10 // 消抖时间窗口(单位:毫秒)#define PRESS_LONG_MS 500 // (可选)长按判定时间(单位:毫秒)uint16_t oled_refresh_cnt = 0;uint8_t oled_refresh_flag = 0;// 定义按键状态的枚举类型typedef enum { KEY_IDLE, // 空闲状态:按键未处于按下或抬起抖动处理中 KEY_DEBOUNCE, // 消抖状态:检测到按下信号后,等待抖动稳定 KEY_DOWN, // 按下状态:已稳定判定按键按下 KEY_RELEASE_DEBOUNCE, // 抬起消抖状态:检测到松开信号后,等待抖动稳定} KeyState_t;// 静态变量:文件内可见,用于记录按键当前状态、计时器以及按下标志static KeyState_t key_state = KEY_IDLE; // 当前按键状态,默认空闲static uint16_t counter_ms = 0; // 毫秒计数,用于消抖和长按判断static uint8_t key_flag = 0; // 按键标志:检测到一次有效按下时置1static uint8_t key_long_flag = 0; // 长按标志
// key.c 按键扫描核心状态机(1ms 周期调用)/* 全局变量说明: key_state —— 当前按键状态,通常为枚举值(KEY_IDLE 等) counter_ms —— 当前状态下持续的时间,单位为毫秒 key_flag —— 短按标志(短按触发后为1,其他时间为0) key_long_flag —— 长按标志(长按触发后为1,其他时间为0) 需定义的常量: DEBOUNCE_MS —— 消抖时间,建议5~20ms PRESS_LONG_MS —— 长按触发时间,建议500~1000ms*/void Key_Scan_1ms(void) { // 读取 GPIOA.1 的电平,按下为低电平(0),因此 level = 1 表示按下 uint8_t level = (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0); // 按键状态机处理逻辑 switch (key_state) { case KEY_IDLE: // 空闲状态,按键未被按下 if (level) { // 检测到按下(低电平),进入消抖阶段 key_state = KEY_DEBOUNCE; counter_ms = 0; // 开始计时 } break; case KEY_DEBOUNCE: // 消抖阶段,等待按键稳定 if (++counter_ms >= DEBOUNCE_MS && level) { // 消抖时间达到,按键依然按下,认为是有效按下 key_state = KEY_DOWN; counter_ms = 0; // 开始长按计时 } else if (!level) { // 如果在消抖期间按键抬起,说明是抖动,回到空闲状态 key_state = KEY_IDLE; } break; case KEY_DOWN: // 按键持续按下状态 if (++counter_ms >= PRESS_LONG_MS && !key_long_flag) { // 按键持续按下超过设定时间,触发长按标志 key_long_flag = 1; } if (!level) { // 按键抬起 key_flag = !key_long_flag; // 如果未触发长按(key_long_flag==0),说明是短按,key_flag = 1; // 如果已经是长按,短按不触发,key_flag = 0; key_state = KEY_RELEASE_DEBOUNCE; // 进入释放消抖阶段 } break; case KEY_RELEASE_DEBOUNCE: // 松开后的消抖阶段 if (++counter_ms >= DEBOUNCE_MS && !level) { // 消抖成功,按键稳定抬起,回到空闲状态 key_state = KEY_IDLE; key_long_flag = 0; // 清除长按标志,为下次按键做准备 } else if (level) { // 抬起过程中又检测到按下,认为误触,退回按下状态 key_state = KEY_DOWN; } break; default: // 兜底保护:未知状态回到初始状态 key_state = KEY_IDLE; break; }}
WS2812B 时序生成
// WS2812B.c - 利用 PWM 位流方式构建 WS2812B 灯珠数据缓冲区// 通过 DMA+TIM2 输出到 WS2812B 数据引脚// 构建 WS2812B 的 PWM 位缓冲void WS2812_BuildBitBuffer(void) { uint32_t idx = 0; // 遍历每个 RGB 通道(LED_COUNT * 3)共用数据缓存 for (uint32_t i = 0; i = 0; b--) { // 判断当前位是 1 还是 0,转换为 PWM 对应的比较值 pwm_buffer[idx++] = (byte & (1 << b)) ? ONE_CODE_COMPARE : ZERO_CODE_COMPARE; // ONE_CODE_COMPARE 代表逻辑 \'1\' 的 PWM 占空比 // ZERO_CODE_COMPARE 代表逻辑 \'0\' 的 PWM 占空比 } } // 补齐复位时间段,确保 WS2812B 正确“锁存”显示 // 通常需要至少 50us 的低电平,此处填充多个 0 来确保总时长 while (idx < MAX_LED_COUNT * 24 + 150) { pwm_buffer[idx++] = 0; }}
// 启动 DMA 传输,将 PWM 缓冲区数据输出给 WS2812Bvoid DMA1_SendLED(void) { DMA_Cmd(DMA1_Channel2, DISABLE); // 关闭 DMA 通道,准备重新配置 // 设置本次 DMA 要传输的 PWM 数据个数(每个 LED 24bit + 复位位) DMA_SetCurrDataCounter(DMA1_Channel2, LED_COUNT * 24 + 1); DMA_Cmd(DMA1_Channel2, ENABLE); // 重新启动 DMA 传输 TIM_Cmd(TIM2, ENABLE); // 启动 TIM2 开始输出 PWM 波形}
配套说明
-
led_buffer[]
:RGB 数据缓冲区,长度需为LED_COUNT * 3
。 -
pwm_buffer[]
:对应 PWM 码流缓冲区,长度建议 ≥MAX_LED_COUNT * 24 + 150
。 -
ONE_CODE_COMPARE
/ZERO_CODE_COMPARE
:这两个宏定义决定了 WS2812B 所需的 PWM 占空比(建议通过逻辑分析仪调试,0.7us 高电平对应 \'1\',0.35us 对应 \'0\')。 -
DMA1_Channel2
和TIM2
:需在外部完成初始化并绑定正确的通道和引脚。
LED 流水效果算法
c
运行
// WS2812B.c - 流水灯拖尾核心逻辑实现// === 流水灯位移函数 ===// 整个 led_buffer 右移一个 RGB 单位,形成“流水”效果void flow(void) { // 临时保存最尾部的 RGB 数据(三个字节) uint8_t t[3] = { led_buffer[(LED_COUNT - 1) * 3], // R led_buffer[(LED_COUNT - 1) * 3 + 1], // G led_buffer[(LED_COUNT - 1) * 3 + 2] // B }; // 从后往前,将前一颗 LED 的颜色复制到后一颗 for (int i = LED_COUNT - 1; i > 0; i--) { led_buffer[i * 3] = led_buffer[(i - 1) * 3]; // R led_buffer[i * 3 + 1] = led_buffer[(i - 1) * 3 + 1]; // G led_buffer[i * 3 + 2] = led_buffer[(i - 1) * 3 + 2]; // B } // 把最尾部颜色放到最前面,实现环形移动 led_buffer[0] = t[0]; led_buffer[1] = t[1]; led_buffer[2] = t[2];}// === 拖尾效果函数(带二次衰减亮度)===// 以指定颜色生成灯带从前向后的拖尾渐变效果void WS2812_Data2(uint8_t r, uint8_t g, uint8_t b) { // 拖尾长度占灯珠总数的 1/3,可自行调整 uint32_t tailLen = LED_COUNT / 3; // 遍历每颗灯珠,依次赋值亮度 for (uint32_t i = 0; i < LED_COUNT; i++) { if (i > 8; // 设置第 i 颗灯的颜色,按亮度比例缩放 WS2812_color(i, (r * bri) >> 8, (g * bri) >> 8, (b * bri) >> 8); } else { // 超出拖尾范围,设为黑色(熄灭) WS2812_color(i, 0, 0, 0); } }}
补充说明
-
flow()
实现的是颜色数据的循环右移,用于制造动态移动效果。 -
WS2812_Data2()
实现静态的尾部渐暗效果(视觉拖尾),并非自动流动,适合配合定时器 +flow()
调用实现动态流水拖尾。 -
WS2812_color(i, r, g, b)
是用户自定义的颜色设置函数,需在别处定义。
使用说明
操作流程
第一层菜单(模式选择)
短按 PA1:切换灯效模式(红绿蓝流水 / 单色流水 / 单色拖尾)
长按 PA1:确认当前模式并运行
第二层菜单(LED 数量配置)
长按 PA2:进入数量配置模式
短按 PA2:循环选择 LED 数量(0→24→48→60→144→204)
长按 PA2:退出配置模式
初始化步骤
调用 OLED_Init() 初始化 OLED 显示
调用 GPIO_TIM2_PWM1_DMA1_init() 初始化 LED 控制引脚与 DMA
在定时器中断中周期性调用 Key_Scan_1ms() 和 Key_Scan_2ms()
通过 LED_Mode_Process() 处理模式逻辑
通过 WS2812_Effects_Process() 更新 LED 效果
项目特点
精准时序控制:通过 PWM+DMA 实现 800kHz 时序,支持 205 颗 LED
低 CPU 占用:DMA 自动传输数据,CPU 占用率 < 5%
可扩展设计:模块化架构,便于添加新灯效和功能
用户友好界面:OLED 实时显示当前模式与配置