> 技术文档 > STM32 WS2812B LED 控制系统实现_ws2812b操作

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_Channel2TIM2:需在外部完成初始化并绑定正确的通道和引脚。

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 实时显示当前模式与配置