我在前面使用STM32F103成功能的驱动了WS2812B,这篇将分享在STM32H755的M4内核中驱动WS2812B。
【工程创建】
创建一个基于NUCLEO-H755的stm32cubeMX工程,在TIM中,使能TIM2的通道4(因此从开发板原理图上得到PB11在CN10)中。

【PWM图形配置】
1、使能TIM2及其通道4,同时根据ws2812的通信速率,计算出需要产生的PWM通首速度为800K,再根据TIM1的总线速度为200MHz,因此计算出PSC为1,ARR为250。

2、同时需要将GPIO的通信速度修改为高速

3、添加DMA配置,其配置界面如下图所示:

点击Add添加一个DMA,选择TIM2_CH4,数据传输方向为内存到外设,中断级别为高,内存地址为自增,数据宽度为word。
4、确认dma中断开启:

【代码编写】
1、生成工程后,我们需要首先验证一下TIM2—CH4输出的速度是不是800K
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 30);
下载到开发板后,使用示波器查看PB11是否输出的波形同期为800K的PWM波形。
2、编写ws2812.h内容如下:
/**
******************************************************************************
* @file ws2812b.h
* @brief WS2812B LED驱动头文件
* @details 包含WS2812B LED灯珠的控制函数和配置参数,支持15x15点阵显示
******************************************************************************
* @attention
*
* Copyright (c) 2025 Sundea.
* All rights reserved.
*
******************************************************************************
*/
#ifndef __WS2812_H
#define __WS2812_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
/* Exported types ------------------------------------------------------------*/
/**
* @brief WS2812B颜色结构体定义
* @details 按照GRB顺序存储颜色值
*/
typedef struct
{
uint8_t g; /*!< 绿色分量 (0-255) */
uint8_t r; /*!< 红色分量 (0-255) */
uint8_t b; /*!< 蓝色分量 (0-255) */
} ws2812b_color_t;
/* Exported constants --------------------------------------------------------*/
/**
* @brief WS2812B配置参数
*/
#define WS2812B_NUM_LEDS 8 /*!< LED灯珠总数 (15x15=225个灯珠) */
#define WS2812B_TIM TIM2 /*!< 使用的定时器 */
#define WS2812B_TIM_CHANNEL TIM_CHANNEL_4 /*!< 使用的定时器通道 */
/**
* @brief 定时器配置 (96MHz/120=800kHz)
*/
#define WS2812B_TIM_PERIOD 250 /*!< 定时器重载值ARR */
/**
* @brief WS2812B时序参数 (基于800kHz PWM)
* @details 每个PWM周期 = 1/800kHz = 1.25us
* 0码: 0.4us高电平 + 0.85us低电平
* 1码: 0.8us高电平 + 0.45us低电平
*/
#define WS2812B_PWM_1_HIGH 160 /*!< 1码高电平计数值 (0.8us / 1.25us* 250 ≈ 160) */
#define WS2812B_PWM_0_HIGH 80 /*!< 0码高电平计数值 (0.4us / 1.25us * 250 ≈ 80) */
#define WS2812B_PWM_LOW (WS2812B_TIM_PERIOD - WS2812B_PWM_1_HIGH) /*!< 低电平部分 */
/**
* @brief LED数据配置
*/
#define WS2812B_BITS_PER_LED 24 /*!< 每个LED的位数 */
#define WS2812B_RESET_PULSES 100 /*!< 复位信号脉冲数 (50us复位信号) */
/* Exported functions --------------------------------------------------------*/
/**
* @brief 初始化WS2812B驱动
* @param 无
* @retval 无
*/
void ws2812b_init(void);
/**
* @brief 设置指定坐标位置LED的颜色
* @param x: X坐标 (0-14)
* @param y: Y坐标 (0-14)
* @param r: 红色分量 (0-255)
* @param g: 绿色分量 (0-255)
* @param b: 蓝色分量 (0-255)
* @retval 无
*/
void ws2812b_set_pos(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b);
/**
* @brief 更新显示内容到LED
* @param 无
* @retval 无
*/
void ws2812b_update(void);
/**
* @brief 检查DMA传输是否正在进行
* @param 无
* @retval uint8_t: 1-传输中, 0-空闲
*/
uint8_t ws2812b_is_busy(void);
/**
* @brief WS2812B测试函数
* @param 无
* @retval 无
*/
void ws2812b_test_pattern(void);
#ifdef __cplusplus
}
#endif
#endif /* __WS2812B_H */3、编写ws2812.c,内容如下:
/**
******************************************************************************
* @file ws2812b.c
* @brief WS2812B LED驱动源文件
* @details 实现WS2812B LED灯珠的控制函数,支持15x15点阵显示
******************************************************************************
* @attention
*
* Copyright (c) 2025 Sundea.
* All rights reserved.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "ws2812.h"
#include <string.h>
/* Private variables ---------------------------------------------------------*/
/**
* @brief PWM数据缓冲区
* @details 存储所有LED的PWM数据和复位信号
*/
static uint32_t pwm_buffer[WS2812B_NUM_LEDS * WS2812B_BITS_PER_LED + WS2812B_RESET_PULSES];
/**
* @brief LED颜色数据数组
* @details 存储每个LED的RGB颜色值
*/
static ws2812b_color_t led_colors[WS2812B_NUM_LEDS];
/**
* @brief DMA忙标志
* @details 标识DMA传输是否正在进行
*/
static volatile uint8_t dma_busy = 0;
/* Private function prototypes -----------------------------------------------*/
static uint16_t ws2812b_xy_to_index(uint16_t x, uint16_t y);
static void ws2812b_color_to_pwm(uint32_t *buffer, ws2812b_color_t color);
/**
* @brief 将XY坐标转换为LED索引
* @param x: X坐标 (0-14)
* @param y: Y坐标 (0-14)
* @retval uint16_t: 对应的LED索引
*/
static uint16_t ws2812b_xy_to_index(uint16_t x, uint16_t y)
{
// 直线布线方式:从左到右,从上到下
return y * 15 + x;
}
/**
* @brief 将颜色数据转换为PWM信号
* @param buffer: PWM数据缓冲区指针
* @param color: 颜色数据
* @retval 无
*/
static void ws2812b_color_to_pwm(uint32_t *buffer, ws2812b_color_t color)
{
// 将RGB颜色转换为GRB格式
uint32_t grb_data = ((uint32_t)color.g << 16) | ((uint32_t)color.r << 8) | color.b;
// 从最高位开始发送 (MSB first)
for (int i = 23; i >= 0; i--)
{
// 检查当前位是否为1
if (grb_data & (1 << i))
{
// 发送1码
*buffer = WS2812B_PWM_1_HIGH;
}
else
{
// 发送0码
*buffer = WS2812B_PWM_0_HIGH;
}
// 指向下一个缓冲区位置
buffer++;
}
}
/**
* @brief 初始化WS2812B驱动
* @param 无
* @retval 无
*/
void ws2812b_init(void)
{
// 清零PWM数据缓冲区
memset(pwm_buffer, 0, sizeof(pwm_buffer));
// 清零LED颜色数据数组
memset(led_colors, 0, sizeof(led_colors));
// 初始化DMA忙标志为0(空闲状态)
dma_busy = 0;
// // 配置定时器
// TIM2->ARR = WS2812B_TIM_PERIOD - 1;
// TIM2->CCR1 = 0;
}
/**
* @brief 设置指定坐标位置LED的颜色
* @param x: X坐标 (0-14)
* @param y: Y坐标 (0-14)
* @param r: 红色分量 (0-255)
* @param g: 绿色分量 (0-255)
* @param b: 蓝色分量 (0-255)
* @retval 无
*/
void ws2812b_set_pos(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b)
{
// 检查坐标是否在有效范围内
if (x >= 15 || y >= 15)
return;
// 将XY坐标转换为LED索引
uint16_t index = ws2812b_xy_to_index(x, y);
// 设置红色分量
led_colors[index].r = r;
// 设置绿色分量
led_colors[index].g = g;
// 设置蓝色分量
led_colors[index].b = b;
}
/**
* @brief 更新显示内容到LED
* @param 无
* @retval 无
*/
void ws2812b_update(void)
{
// 检查DMA是否正在传输,如果忙则直接返回
if (dma_busy)
return;
// 定义PWM数据指针,指向缓冲区起始位置
uint32_t *pwm_ptr = pwm_buffer;
// 填充所有LED的数据
for (int i = 0; i < WS2812B_NUM_LEDS; i++)
{
// 将第i个LED的颜色数据转换为PWM信号
ws2812b_color_to_pwm(pwm_ptr, led_colors[i]);
// 移动指针到下一个LED数据位置(每个LED占24位)
pwm_ptr += 24; // 每个LED24位
}
// 添加复位信号 (低电平)
for (int i = 0; i < WS2812B_RESET_PULSES; i++)
{
// 设置占空比为0,即输出低电平
*pwm_ptr++ = 0; // 占空比为0 = 低电平
}
// 设置DMA忙标志为1(传输中)
dma_busy = 1;
// 启动DMA传输
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_4,
pwm_buffer,
WS2812B_NUM_LEDS * WS2812B_BITS_PER_LED + WS2812B_RESET_PULSES);
}
/**
* @brief 检查DMA传输是否正在进行
* @param 无
* @retval uint8_t: 1-传输中, 0-空闲
*/
uint8_t ws2812b_is_busy(void)
{
// 返回DMA忙标志状态
return dma_busy;
}
/**
* @brief DMA传输完成回调函数
* @param htim: TIM句柄指针
* @retval 无
*/
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
// 判断是否为TIM2的回调
if (htim->Instance == TIM2)
{
// 停止DMA传输
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_4);
// 清除DMA忙标志
dma_busy = 0;
}
}
/**
* @brief WS2812B测试函数
* @param 无
* @retval 无
*/
void ws2812b_test_pattern(void)
{
static int i;
// 初始化WS2812B驱动
ws2812b_init();
// 测试1: 设置前几个灯珠为不同颜色
ws2812b_set_pos(i, 0, 255, 0, 0); // 第一个灯珠设为红色
// ws2812b_set_pos(1, 0, 0, 255, 0); // 第二个灯珠设为绿色
// ws2812b_set_pos(2, 0, 0, 0, 255); // 第三个灯珠设为蓝色
// ws2812b_set_pos(3, 0, 255, 255, 0); // 第四个灯珠设为黄色
// 更新显示
ws2812b_update();
// 等待传输完成
while (ws2812b_is_busy())
{
// 延迟1ms
HAL_Delay(1);
}
// 延迟2秒以便观察效果
HAL_Delay(500);
i++;
if(i==8) i= 0;
}4、在main的while循环中执行
ws2812b_test_pattern

【总结】
在ws2812的使用TIM的PWM+DMA驱动,只需要占用1个GPIO,相比SPI占用的时间少,通信的时序比较灵活。因此相比SPI有很大的优势。同时使用DMA传输,点用MCU的资源也比较小。
在编写其驱动时,需要注意配置定时器的ARR,让总线产生800K的PWM时序,同时根据ARR来计算出WS2813的0电平与1电平的计数值。
【实验效果】
我要赚赏金
