这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 综合技术 » 基础知识 » 【STM32H755】TIM+DMA驱动ws2812b

共1条 1/1 1 跳转至

【STM32H755】TIM+DMA驱动ws2812b

工程师
2025-09-27 13:58:17     打赏

我在前面使用STM32F103成功能的驱动了WS2812B,这篇将分享在STM32H755的M4内核中驱动WS2812B。

【工程创建】

创建一个基于NUCLEO-H755的stm32cubeMX工程,在TIM中,使能TIM2的通道4(因此从开发板原理图上得到PB11在CN10)中。

image.png

【PWM图形配置】

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

image.png

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

image.png

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

image.png

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

4、确认dma中断开启:

image.png

【代码编写】

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

image.png

【总结】

在ws2812的使用TIM的PWM+DMA驱动,只需要占用1个GPIO,相比SPI占用的时间少,通信的时序比较灵活。因此相比SPI有很大的优势。同时使用DMA传输,点用MCU的资源也比较小。

在编写其驱动时,需要注意配置定时器的ARR,让总线产生800K的PWM时序,同时根据ARR来计算出WS2813的0电平与1电平的计数值。

【实验效果】




关键词: STM32H755     TIM+DMA     ws2812b    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]