目录

前言

一、WS2812协议

1.1 数据传输编码方式:

 1.2 传输的数据结构

二、驱动方式:SPI+DMA

2.1 原理介绍

2.2 SPI+DMA操作

 2.3 编写代码

2.4 使用

三 总结

参考文章


前言

主要使用的STM32F103C8T6芯片的SPI+DMA方式实现WS2812的驱动协议,总体可以看作是使用SPI来实现一种通信协议来发送信号。

        硬件:STM32F103C8T6

        外设:SPI、DMA


一、WS2812协议

1.1 数据传输编码方式:

T0H0码,高电平时间220ns~380ns
T1H1码,高电平时间580ns~1us
T0L0码,低电平时间580ns~1us
T1L1码,低电平时间220ns~420ns
RES帧单位,低电平时间280us以上

 当使用STM32发送波形高电平(T0H)在220ns~380ns且低电平(TOL)在580ns~1us时表示编码 0,同理 1码和复位码也是如此。总结:一个编码的时间1.25us±300ns之间都能识别到,主要是识别高电平持续的时间;时间段固定,高电平短为0高电平时间长为1,280ns时间无高电平就表示这一个信号结束。

 1.2 传输的数据结构

一颗ws2812的RGB灯里面有三颗灯珠,有红、绿、蓝三个颜色的LED灯。每一个灯使用PWM驱动,发送的数据即为PWM的宽度,一颗LED灯的数据宽度为 8 bits(1 byte)  ,所以一颗 ws2812 RGB灯共需要24 bits(3 bytes)的数据。

一颗RGB灯发送数据的时序如下,高位先行:

G7G6G5G4G3G2G1G0R7...R0B7...B0

二、驱动方式:SPI+DMA

2.1 原理介绍

驱动ws2812的时候一般采用PWMSPI的方式,这两个速度较快,比直接使用IO口进行电平反转要方便,且控制效果更好。这里采用SPI的方式,因为在点灯是的数据发送间隔时间的约束,如果我们使用SPI发送的数据较多,中途遇到cpu中断可能会打断传输数据,所以我这里使用SPI的DMA控制方式实现。

借助 SPI 来控制 WS2812,我们用 SPI 的 MOSI 接口的一个 Byte(8位)模拟 WS2812 的一个位。比如 SPI 设置的 7M 速率,使用SPI发送一个字节(byte)来模拟WS2812的一个编码(0或1)。比如用SPI发送的 0xF8(1111 1000)表示WS2812编码的1,发送的数据是 0xC0 (1100 0000)时这代表编码 0;这是因为在SPI发送一个字节的时间符合WS2812的一个编码的时间,发送0XF8时高电平持续时间在580ns~1us内,发送0xC0时高电平持续220ns~380ns内。即

        SPI发送 0xF8 = 1 ,SPI发送 0xC0 = 0。

SPI发送24个字节可以控制一颗RGB灯,打成一个包,控制n个灯则需要:n x 包 +RET码。

SPI的速度在5.52——9.41MHz范围内,SPI发送一个字节时间长度等于一个编码的时间长度。

2.2 SPI+DMA操作

1. STM32实际操作

        配置使用芯片的外部晶振,设置为频率为72Hz,配置下载端口,初始化配置如下:

配置SPI,主机发送模式,速度为7MHz,Clock Phase: 2Edge。

 添加DMA,DMA使用默认配置:普通模式,从Memory取数据,数据宽度为:Byte和Byte。保存生成代码。

 

 2.3 编写代码

        创建 .h和.c文件,直接把下面代码块复制进去,或者点击创库地址:gitee仓库

        ws2812.h头文件代码

#ifndef __WS2812_H
#define __WS2812_H

#include "main.h"

typedef struct			//颜色结构体
{
  uint8_t R;
  uint8_t G;
  uint8_t B;
}RGBColor_TypeDef;

#define RGB_NUM    30	// RGB灯的数量,即为缓冲区长度

// 复位函数
void RGB_RST(void);
// 颜色设置函数
void RGB_Set_Color(uint8_t LedId, RGBColor_TypeDef Color);
// RGB 刷新函数
void RGB_Reflash(uint8_t reflash_num);

// 各种颜色测试
void RGB_RED(uint16_t RGB_LEN);			//红
void RGB_GREEN(uint16_t RGB_LEN);		//绿
void RGB_BLUE(uint16_t RGB_LEN);		//蓝
void RGB_YELLOW(uint16_t RGB_LEN);		//黄
void RGB_MAGENTA(uint16_t RGB_LEN);		//紫
void RGB_BLACK(uint16_t RGB_LEN);		//黑
void RGB_WHITE(uint16_t RGB_LEN);		//白

#endif /* __WS2812_H */

ws2812.c源代码

/*
 * ws2812.c
 *
 *  Created on: Mar 27, 2023
 *      Author: mxia2
 */

#include "ws2812.h"
#include "spi.h"
#include "dma.h"

// 常用的颜色,亮度调的比较低
const RGBColor_TypeDef RED      = {30 ,0  ,  0};
const RGBColor_TypeDef GREEN    = {0  , 50,  0};
const RGBColor_TypeDef BLUE     = {0  ,  0, 100};
const RGBColor_TypeDef YELLOW   = { 30, 30,  0};
const RGBColor_TypeDef MAGENTA  = { 30,  0, 30};
const RGBColor_TypeDef BLACK    = {  0,  0,  0};
const RGBColor_TypeDef WHITE    = { 80, 80, 80};

//模拟bit码:0xC0 为 0,0xF8 为 1
const uint8_t code[]={0xC0,0xF8};

//灯颜色缓存区,定义为30颗灯长度缓冲区
RGBColor_TypeDef RGB_DAT[RGB_NUM];

//SPI底层发送接口,一次发24个字节,相当于1个灯
extern DMA_HandleTypeDef hdma_spi1_tx;

static void SPI_Send(uint8_t *SPI_RGB_BUFFER)
{
  /* 判断上次DMA有没有传输完成 */
	while(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY);
  /* 发送一个(24bit)的 RGB 数据到 2812 */
	HAL_SPI_Transmit_DMA(&hspi1,SPI_RGB_BUFFER,24);
}

//颜色设置函数,传入 ID 和 颜色,进而设置缓存区
void RGB_Set_Color(uint8_t LedId, RGBColor_TypeDef Color)
{
  if(LedId < RGB_NUM)
	{
		RGB_DAT[LedId].G = Color.G;
		RGB_DAT[LedId].R = Color.R;
		RGB_DAT[LedId].B = Color.B;
	}
}

//刷新函数,将颜色缓存区刷新到WS2812,输入参数是指定的刷新长度
void RGB_Reflash(uint8_t reflash_num)
{
	static uint8_t RGB_BUFFER[24]={0};
	uint8_t dat_b,dat_r,dat_g;
	//将数组颜色转化为 24 个要发送的字节数据
	if(reflash_num>0 && reflash_num<=RGB_NUM)
	{
		for(int i=0;i<reflash_num;i++)
		{
			dat_g = RGB_DAT[i].G;
			dat_r = RGB_DAT[i].R;
			dat_b = RGB_DAT[i].B;
			for(int j=0;j<8;j++)
			{
				RGB_BUFFER[7-j] =code[dat_g & 0x01];
				RGB_BUFFER[15-j]=code[dat_r & 0x01];
				RGB_BUFFER[23-j]=code[dat_b & 0x01];
				dat_g >>=1;
				dat_r >>=1;
				dat_b >>=1;
			}
			SPI_Send(RGB_BUFFER);
		}
	}
}

//复位函数
void RGB_RST(void)
{
	uint8_t dat[100] = {0};
  /* 判断上次DMA有没有传输完成 */
	while(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY);
  /* RGB RESET */
	HAL_SPI_Transmit_DMA(&hspi1,dat,100);
	HAL_Delay(10);
}

//常用颜色的点亮测试函数

void RGB_RED(uint16_t RGB_LEN)
{
  uint8_t i;
  for(i=0;i<RGB_LEN;i++){
	  RGB_Set_Color(i,RED);
  }
  RGB_Reflash(RGB_LEN);
}

void RGB_GREEN(uint16_t RGB_LEN)
{
  uint8_t i;
  for(i=0;i<RGB_LEN;i++){
	  RGB_Set_Color(i,GREEN);
  }
  RGB_Reflash(RGB_LEN);
}

void RGB_BLUE(uint16_t RGB_LEN)
{
  uint8_t i;
  for(i=0;i<RGB_LEN;i++){
	  RGB_Set_Color(i,BLUE);
  }
  RGB_Reflash(RGB_LEN);
}

void RGB_WHITE(uint16_t RGB_LEN)
{
  uint8_t i;
  for(i=0;i<RGB_LEN;i++){
	  RGB_Set_Color(i,WHITE);
  }
  RGB_Reflash(RGB_LEN);
}

2.4 使用

在主函数中使用

#include "main.h"
#include "dma.h"
#include "spi.h"
#include "gpio.h"
#include "ws2812.h"

void SystemClock_Config(void);

int main(void)
{

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

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

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	RGB_RED(5);
	RGB_RST();
	HAL_Delay(1000);

	RGB_GREEN(4);
	RGB_RST();
	HAL_Delay(1000);

	RGB_BLUE(3);
	RGB_RST();
	HAL_Delay(1000);

	RGB_WHITE(2);
	RGB_RST();
	HAL_Delay(1000);

	RGB_WHITE(5);
	RGB_RST();
	HAL_Delay(1000);

  }
  /* USER CODE END 3 */
}

使用ST-LINK下载到stm32f103芯片里,使用5颗灯查看效果,成功点亮。 

 


三 总结

LED的驱动方式,使用PWM驱动

使用SPI的协议来模仿其他单线的协议使用方便快捷,这也是一种不错的选择。

参考文章

STM32CubeMX-SPI+DMA 驱动 2812 灯带

WS2812b幻彩ARGB灯珠的STM32F103的CPU-SPI方式驱动

STM32 SPI+DMA实现WS2812灯的驱动

如何使用STM32F103C8T6驱动WS2812(PWM+DMA)

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐