STM32+PWM+DMA驱动WS2812 —— 2024年9月24日

一、项目简介

采用STM32f103C8t6单片机,使用HAL库编写。项目中针对初学者驱动WS2812时会遇到的一些问题,给出了解决方案。

二、ws2812驱动原理

WS2812采用单线归零码的通讯方式,即利用高低电平的持续时间来确定0和1。这种通信方式优点是只需要一根通信线,缺点是对通信的时序要求较高。

以官方数据手册的时序图为例,通信速率为800kbit/s,也就是PWM波的速率为800Kbit/s,每个PWM的周期为1.25微妙。PWM的一个周期即为一个数据帧,每个数据帧由一段高电平和一段低电平组成。下图为官方规定的数据传输时间:

|-----|-----------|------------------|
| T0H | 0码,高电平时间 | 0.22 us~0.35 us |
| T0L | 0码,低电平时间 | 0.58 us~1.0 us |
| T1H | 1码,高电平时间 | 0.58 us~1.0 us |
| T1L | 1码,低电平时间 | 0.22 us~0.42 us |
| RES | 帧间隔,低电平时间 | 50us以上 |

也就是说:一个1码,由2/3左右的高电平 和 1/3左右的低电平组成。

一个0码,由1/3左右的高电平 和 2/3左右的低电平组成。

若定时器的时钟频率为72MHz,那么预分频值设置为0,比较值设置为89,这样产生的PWM波的频率就为800KHz,周期为1.25us。要发送1码时,设置占空比为60。要发送0码时,设置占空比为29。

每个WS2812需要用24bit的数据来控制,当n个ws2812进行级联的时候,第一个灯会将第一个24bit的数据拦截,将后面的数据进行转发。第二个灯又会拦截第二个24bit的数据,将后面的数据进行转发。后面的逻辑也是一样,数据每经过一个灯,数据的前24bit就会被拦截下来,作为这个灯的显示内容。数据传输方法如下图所示:

代码编写逻辑:初始化的时候,需生成一个显存数组,由DMA将数组中的内容实时搬运到定时器的比较寄存器中,DMA要开启循环模式。之后我们只需要更新显存数组中的数据,WS2812的显示内容就会被实时更新。

三、Cube MX 生成底层代码

1、配置Debug的模式

2、配置外部晶振

3、配置时钟

4、配置定时器

5、配置定时器的DMA

6、生成代码

四、代码编写

1、下面ws2812.c的代码

cpp 复制代码
#include "ws2812.h"

//显存数组,长度为 灯的数量*24+复位周期
uint16_t WS2812_RGB_Buff[LED_NUM*DATA_LEN+WS2812_RST_NUM] = {0}; 

/**
 * 函数:WS2812单灯设置函数
 * 参数:num:灯的位置,R、G、B分别为三个颜色通道的亮度,最大值为255
 * 作用:单独设置每一个WS2812的颜色
***/
void WS2812_Set(uint16_t num,uint8_t R,uint8_t G,uint8_t B)
{
  uint32_t indexx=(num*(3*8));
  for (uint8_t i = 0;i < 8;i++)
  {
	//填充数组
	WS2812_RGB_Buff[indexx+i]      = (G << i) & (0x80)?WS_H:WS_L;
	WS2812_RGB_Buff[indexx+i + 8]  = (R << i) & (0x80)?WS_H:WS_L;
	WS2812_RGB_Buff[indexx+i + 16] = (B << i) & (0x80)?WS_H:WS_L;
  }
}

//WS2812初始化函数
void WS2812_Init()
{
	//设置关闭所有灯
  for(int i=0;i<8;i++)
  {
	WS2812_Set(i,0,20,0);
  }
  //作用:调用DMA将显存中的内容实时搬运至定时器的比较寄存器
  HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_1,(uint32_t *)WS2812_RGB_Buff,sizeof(WS2812_RGB_Buff)/sizeof(uint16_t)); 
}

2、下面为ws2812.h的代码

cpp 复制代码
#include "main.h"
#include "tim.h"

#define WS_H           60   // 1 码相对计数值
#define WS_L           29   // 0 码相对计数值
#define WS_REST        40   // 复位信号脉冲数量
#define LED_NUM         8   // WS2812灯个数
#define DATA_LEN       24   // WS2812数据长度,单个需要24个字节
#define WS2812_RST_NUM 50   // 官方复位时间为50us(40个周期),保险起见使用50个周期

void WS2812_Init(void);
void WS2812_Set(uint16_t num,uint8_t R,uint8_t G,uint8_t B);

3.下面为main.c中调用的代码,效果为流水灯

cpp 复制代码
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  WS2812_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	//效果一,流水灯
	for(int i=0;i<8;i++)
	{
		HAL_Delay(100);
		WS2812_Set(i,2*(i+1),4*(i+1),10*(i+1));
	}
	HAL_Delay(300);
	for(int i=0;i<8;i++)
	{
		WS2812_Set(i,0,0,0);
	}
	HAL_Delay(100);

	//效果二,跑马灯
//	for(int i=0;i<8;i++)
//	{
//		HAL_Delay(100);
//		WS2812_Set(i,0,20,0);
//		if(i==0) WS2812_Set(7,0,0,0);
//		else     WS2812_Set(i-1,0,0,0);	
//	}
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

五、烧录效果

六、注意事项

1、DMA的搬运方向为 内存(Memory) 到 外设(Peripheral)

2、DMA的模式为循环模式

3、DMA设置内存地址自增

七、其他问题请留言

相关推荐
cjy_Somnr12 小时前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰12 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤14 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟16 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞16 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲17 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up17 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技1 天前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
单片机日志1 天前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣1 天前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法