目录
前言
一、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 数据传输编码方式:
T0H | 0码,高电平时间 | 220ns~380ns |
T1H | 1码,高电平时间 | 580ns~1us |
T0L | 0码,低电平时间 | 580ns~1us |
T1L | 1码,低电平时间 | 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灯发送数据的时序如下,高位先行:
G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | R7 | ... | R0 | B7 | ... | B0 |
二、驱动方式:SPI+DMA
2.1 原理介绍
驱动ws2812的时候一般采用PWM或SPI的方式,这两个速度较快,比直接使用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灯的驱动文章来源:https://www.toymoban.com/news/detail-483309.html
如何使用STM32F103C8T6驱动WS2812(PWM+DMA)文章来源地址https://www.toymoban.com/news/detail-483309.html
到了这里,关于使用STM32F103的SPI+DMA驱动ws2812 LED的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!