使用DMA和PWM驱动16组WS2812 LED的STM32实现
STM32使用DMA和PWM来驱动16组WS2812 LED灯带。WS2812是一种智能控制LED,每个LED都需要24位数据(8位绿色,8位红色,8位蓝色),并且对时序要求非常严格。
硬件设计考虑
- GPIO选择:选择16个可用的GPIO引脚,最好是同一GPIO端口的连续引脚,以便于控制
- 定时器选择:需要至少一个高级定时器(如TIM1或TIM8)支持多通道PWM输出
- DMA配置:使用DMA来高效传输PWM数据,减少CPU负载
- 电源考虑:16组WS2812可能需要相当大的电流,确保电源供应充足
STM32CubeMX配置
1. 时钟配置
- 根据MCU型号配置系统时钟(建议72MHz或更高)
- 确保APB2定时器时钟足够高以产生精确的PWM信号
2. GPIO配置
选择16个GPIO引脚,配置为定时器PWM输出功能。
3. 定时器配置
配置一个高级定时器(如TIM1):
- PWM模式
- 计数器周期:90(对应1.25µs周期,800kHz频率)
- PWM占空比:0码=30,1码=60(对应0.4µs和0.8µs高电平时间)
- 启用所有16个通道
4. DMA配置
为定时器的CCR寄存器配置DMA:
- 方向:内存到外设
- 数据宽度:半字或字(取决于CCR寄存器大小)
- 循环模式
代码实现
#include "main.h"
#include "string.h"
// WS2812时序参数(基于72MHz系统时钟)
#define WS2812_TIM_PERIOD 90 // 1.25µs周期
#define WS2812_0_PULSE 30 // 0码高电平时间 0.4µs
#define WS2812_1_PULSE 60 // 1码高电平时间 0.8µs
// LED数量配置
#define NUM_LEDS_PER_STRIP 16 // 每组LED数量
#define NUM_STRIPS 16 // 组数
// 颜色结构体
typedef struct {
uint8_t g, r, b;
} Color_t;
// 全局变量
Color_t led_data[NUM_STRIPS][NUM_LEDS_PER_STRIP];
uint16_t pwm_data[NUM_STRIPS][24 * NUM_LEDS_PER_STRIP + 50]; // PWM缓冲区(每LED24位+额外复位时间)
// 初始化函数
void WS2812_Init(void) {
// 初始化LED数据(全部关闭)
memset(led_data, 0, sizeof(led_data));
// 初始化PWM数据(全部为0码)
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int i = 0; i < sizeof(pwm_data[0])/sizeof(pwm_data[0][0]); i++) {
pwm_data[strip][i] = WS2812_0_PULSE;
}
}
// 启动定时器和DMA
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)pwm_data[0], sizeof(pwm_data[0])/sizeof(pwm_data[0][0]));
// 启动其他通道的DMA...
}
// 设置单个LED颜色
void WS2812_SetLED(int strip, int led_idx, uint8_t r, uint8_t g, uint8_t b) {
if (strip >= NUM_STRIPS || led_idx >= NUM_LEDS_PER_STRIP) return;
led_data[strip][led_idx].r = r;
led_data[strip][led_idx].g = g;
led_data[strip][led_idx].b = b;
}
// 更新PWM缓冲区
void WS2812_UpdateBuffer(void) {
for (int strip = 0; strip < NUM_STRIPS; strip++) {
int pwm_index = 0;
// 为每个LED生成PWM数据
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
Color_t color = led_data[strip][led];
// 绿色分量(WS2812顺序是GRB)
for (int i = 7; i >= 0; i--) {
pwm_data[strip][pwm_index++] = ((color.g >> i) & 0x01) ? WS2812_1_PULSE : WS2812_0_PULSE;
}
// 红色分量
for (int i = 7; i >= 0; i--) {
pwm_data[strip][pwm_index++] = ((color.r >> i) & 0x01) ? WS2812_1_PULSE : WS2812_0_PULSE;
}
// 蓝色分量
for (int i = 7; i >= 0; i--) {
pwm_data[strip][pwm_index++] = ((color.b >> i) & 0x01) ? WS2812_1_PULSE : WS2812_0_PULSE;
}
}
// 添加复位码(至少50µs的低电平)
for (int i = 0; i < 50; i++) {
pwm_data[strip][pwm_index++] = 0;
}
}
}
// DMA传输完成回调
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
// 可以在这里更新下一次传输的数据
}
// 主循环中的示例使用
int main(void) {
// HAL初始化等...
WS2812_Init();
while (1) {
// 设置一些示例颜色
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
// 示例:彩虹效果
uint8_t r = (strip * 16 + led * 8) % 256;
uint8_t g = (strip * 16 + led * 8 + 85) % 256;
uint8_t b = (strip * 16 + led * 8 + 170) % 256;
WS2812_SetLED(strip, led, r, g, b);
}
}
// 更新PWM缓冲区
WS2812_UpdateBuffer();
// 重启DMA传输
for (int strip = 0; strip < NUM_STRIPS; strip++) {
// 这里需要根据实际使用的定时器和DMA通道进行配置
// HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)pwm_data[strip], sizeof(pwm_data[strip])/sizeof(pwm_data[strip][0]));
}
// 延时
HAL_Delay(100);
}
}
高级实现:使用位带操作优化性能
对于需要更高性能的应用,可以使用STM32的位带功能来同时更新所有16个通道:
// 使用位带操作同时控制16个通道
#define GPIOB_BITBAND ((__IO uint32_t*)0x42400000) // GPIOB位带别名区基地址
void WS2812_SendBits(uint32_t bits) {
// 使用位带操作同时设置所有16个引脚
for (int i = 0; i < 16; i++) {
if (bits & (1 << i)) {
// 设置对应引脚为高电平
GPIOB_BITBAND[i] = 1;
} else {
// 设置对应引脚为低电平
GPIOB_BITBAND[i] = 0;
}
}
}
// 精确时序的发送函数
void WS2812_SendData(void) {
// 禁用中断以确保精确时序
__disable_irq();
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
Color_t color = led_data[strip][led];
uint32_t grb = (color.g << 16) | (color.r << 8) | color.b;
// 发送24位数据
for (int i = 23; i >= 0; i--) {
if (grb & (1 << i)) {
// 发送1码
WS2812_SendBits(0xFFFF); // 所有引脚高
delay_ns(800); // 精确延时
WS2812_SendBits(0x0000); // 所有引脚低
delay_ns(450); // 精确延时
} else {
// 发送0码
WS2812_SendBits(0xFFFF); // 所有引脚高
delay_ns(400); // 精确延时
WS2812_SendBits(0x0000); // 所有引脚低
delay_ns(850); // 精确延时
}
}
}
}
// 发送复位码
WS2812_SendBits(0x0000); // 所有引脚低
delay_us(50); // 50µs延时
// 重新启用中断
__enable_irq();
}
电源管理考虑
驱动16组WS2812需要相当大的电流,特别是当所有LED都设置为白色全亮时:
// 计算所需电流并实施保护
#define MAX_CURRENT_MA 5000 // 电源最大输出电流
uint32_t WS2812_CalculateCurrent(void) {
uint32_t total_current = 0;
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
Color_t color = led_data[strip][led];
// 估算电流:每个LED最大60mA(白色全亮时)
uint32_t led_current = (color.r + color.g + color.b) * 60 / 255;
total_current += led_current;
}
}
return total_current;
}
// 电流限制功能
void WS2812_ApplyCurrentLimit(void) {
uint32_t current = WS2812_CalculateCurrent();
if (current > MAX_CURRENT_MA) {
float scale = (float)MAX_CURRENT_MA / current;
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
led_data[strip][led].r *= scale;
led_data[strip][led].g *= scale;
led_data[strip][led].b *= scale;
}
}
}
}
使用FreeRTOS的任务管理
如果使用FreeRTOS,可以创建专门的任务来处理LED控制:
// FreeRTOS任务函数
void WS2812_Task(void const *argument) {
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
// 更新LED效果
UpdateLEDEffects();
// 应用电流限制
WS2812_ApplyCurrentLimit();
// 更新PWM缓冲区
WS2812_UpdateBuffer();
// 发送数据
WS2812_SendData();
// 精确延时
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(20)); // 50Hz刷新率
}
}
调试和测试
为了确保WS2812驱动正常工作,可以添加调试功能:
// 测试模式
void WS2812_TestPattern(void) {
// 红色测试
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
WS2812_SetLED(strip, led, 255, 0, 0);
}
}
WS2812_UpdateBuffer();
WS2812_SendData();
HAL_Delay(1000);
// 绿色测试
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
WS2812_SetLED(strip, led, 0, 255, 0);
}
}
WS2812_UpdateBuffer();
WS2812_SendData();
HAL_Delay(1000);
// 蓝色测试
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
WS2812_SetLED(strip, led, 0, 0, 255);
}
}
WS2812_UpdateBuffer();
WS2812_SendData();
HAL_Delay(1000);
// 全部关闭
for (int strip = 0; strip < NUM_STRIPS; strip++) {
for (int led = 0; led < NUM_LEDS_PER_STRIP; led++) {
WS2812_SetLED(strip, led, 0, 0, 0);
}
}
WS2812_UpdateBuffer();
WS2812_SendData();
}
参考代码 DMA PWM WS2812 GPIO输出16组 www.youwenfan.com/contentcnj/56131.html
这个实现方案提供了使用STM32的DMA和PWM功能驱动16组WS2812 LED的完整方法。根据具体的STM32型号和需求,可能需要进行一些调整。建议在实际应用前进行充分的测试,特别是时序方面的验证。
浙公网安备 33010602011771号