本帖最后由 eltonchang2001 于 2022-11-9 13:53 编辑
一、概述
最近得到合泰的HT32F54253开发板,恰好手里有一个WS2812B的RGB灯模块,那就点灯吧。一开始决定用SPI+PDMA的方式点亮的,奈何加了PDMA后代码跑死了,一直看手册和官方别的PMDA例程也没得到解决,索性不发SPI+PDMA驱动WS2812B,那就发一个不带PDMA的吧。
二、WS2812B的理论学习
数据传输时间
时序和级联使用
数据传输方法和数据组成
通过以上图片和参考数据手册得知,WS2812B的数据传输速率是800KHZ。1s/800KHZ=1.25us。WS2812B有VCC、GND、DIN,DOUT四个引脚,数据从DIN流入从DOUT流出,使用多颗灯的时候需要以级联的方式使用。WS2812B由RGB三原色的比例可以显示很多中颜色。其一包数据由24bit构成,绿色(Green)占8位,红色(Red)占8位,蓝色(Blue)占8位,要显示要想显示的颜色只需要改变R、G、B值。其中G排最高位,R中间,B最低位。
三、HT32F54253的SPI知识和如何驱动ws2812B讲解
本次实验选择的是SPI0,由于只用到发送引脚,我只初始化了MOSI引脚(PA5),PA5要使用复用功能5才能作为SPI0的MOSI功能。用过芯片的用户手册查得SPI0外设挂载时钟总线AHB频率为60MHZ,经过8分频后为7.5MHZ给SPI0使用。由于分频之后SPI0的工作频率为7.5MHZ,所在SPI0在发送1bit数据就得花费1s/7.5MHZ≈133ns,经过计算发送一字节数据就得花费8*133ns=1.06us.从WS2812B资料中得知,266ns的0码高电平时间符合,800ns的1码高电平时间也符合。所以SPI在8分频的情况下发送8位数据,其中2位用来代表0码的高电平时间,6位用来表示1码的高电平时间。通过这样的模拟,成功点亮了WS2812B。
四、代码实践
头文件
#ifndef __SPI_DMA_WS2812_H
#define __SPI_DMA_WS2812_H
#include "ht32.h"
#include "ht32_board.h"
#include "stdint.h"
#include "bsp_sys.h"
/*
要将系统时钟设置为60M,SPI分频数设置为8,则SPI的通信频率为7.5M,1s/7.5≈133ns 即传输一位数据的时间约为55纳秒(ns)8*133 =1.06us 符合WS2812
2*133 = 266ns 6*133 = 799ns 符合WS281X芯片的通信时序。
1111 1100high level (十六进制:0XFC)表示WS281X的1码
1100 0000 low level (十六进制:0XC0)表示WS281X的0码
程序头文件部分: 通过宏的方式定义了灯珠个数和WS281X的0码和1码。
*/
// _____
// | |___| 1111 1100 high level
// ___
// | |_____| 1111 0000 low level
#define Delay_ms(wait) delay_ms(wait)
#define WS2812B //选择WS2812B
#define PIXEL_NUM1 2 //RGB灯的数量 //灯带1灯数(组数)
#define PIXEL_NUM2 2 //灯带2灯数(组数)
#define PIXEL_NUM3 2 //灯带3灯数(组数)
#define PIXEL_NUM4 2 //灯带4灯数(组数)
#define PIXEL_NUM (PIXEL_NUM1+PIXEL_NUM2+PIXEL_NUM3+PIXEL_NUM4)//灯数统计
#define GRB 24 //3*8 //Red8位 Green8位 Blue8位 加起来24位
#ifdef WS2812B
#define WS_HIGH 0xFC
#endif
#define WS_LOW 0xC0
/*
R G B
红: 255 0 0
黄: 255 255 0
绿: 0 255 0
青: 0 255 255
蓝: 0 0 255
紫: 255 0 255
白: 255 255 255
*/
typedef union _rgbPixelBuffer
{
struct
{
uint8_t PixelBuffer1[PIXEL_NUM1][GRB];
uint8_t PixelBuffer2[PIXEL_NUM2][GRB];
uint8_t PixelBuffer3[PIXEL_NUM3][GRB];
uint8_t PixelBuffer4[PIXEL_NUM4][GRB];
}buff;
uint8_t All_Buffer[PIXEL_NUM][GRB];
uint8_t Buffer[PIXEL_NUM*GRB];
}RGB_PixelBuffer,*PRGB_PixelBuffer;
void WS281x_Init(void);
void WS281x_CloseAll(void);
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue);
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor);
void WS281x_SetPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue);
void WS281x_Show(void);
void WS281x_RainbowCycle(uint8_t wait);
void WS281x_TheaterChase(uint32_t c, uint8_t wait);
void WS281x_ColorWipe(uint32_t c, uint8_t wait);
void WS281x_Rainbow(uint8_t wait);
void WS281x_TheaterChaseRainbow(uint8_t wait);
void RGB_DEBUG_TEST(void);
#endif
源文件,由于没有调通PDMA,故代码中屏蔽了PDMA部分代码
#include "spi_dma_ws2812.h"
#include "string.h"
RGB_PixelBuffer pixelBuffer;
uint32_t SPI_clock;
void WS281x_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
// PDMACH_InitTypeDef PDMACH_InitStructure;
CKCU_PeripClockConfig_TypeDef CKCUClock = {{0}};
CKCUClock.Bit.SPI0 = 1;//打开SPI0的时钟
CKCUClock.Bit.PA = 1;//打开GPIOA的时钟
CKCUClock.Bit.AFIO = 1;//打开复用时钟
// CKCUClock.Bit.PDMA = 1;//外设DMA时钟
CKCU_PeripClockConfig(CKCUClock, ENABLE);//使能
AFIO_GPxConfig(GPIO_PA,AFIO_PIN_5,AFIO_MODE_5);//GPIOA 的PA5复用为SPI0的MOSI
/* Configure the GPIO pin*/
GPIO_PullResistorConfig(HT_GPIOA, GPIO_PIN_5, GPIO_PR_DOWN);//配置下拉
GPIO_DriveConfig(HT_GPIOA, GPIO_PIN_5,GPIO_DV_4MA);
GPIO_DirectionConfig(HT_GPIOA, GPIO_PIN_5, GPIO_DIR_OUT);
// PDMACH_InitStructure.PDMACH_DstAddr = (uint32_t)&HT_SPI0->DR;
// PDMACH_InitStructure.PDMACH_SrcAddr = (uint32_t)&pixelBuffer;
// PDMACH_InitStructure.PDMACH_AdrMod = SRC_ADR_LIN_INC | DST_ADR_FIX;
// PDMACH_InitStructure.PDMACH_DataSize = WIDTH_8BIT;
// PDMACH_InitStructure.PDMACH_Priority = VH_PRIO;
// PDMACH_InitStructure.PDMACH_BlkCnt = 1;
// PDMACH_InitStructure.PDMACH_BlkLen = 24;
// PDMA_Config(PDMA_CH1, &PDMACH_InitStructure);
// PDMA_EnaCmd(PDMA_CH1, ENABLE);
// PDMA_ClearFlag(PDMA_CH1, PDMA_FLAG_TC | PDMA_INT_GE);
// PDMA_IntConfig(PDMA_CH1, (PDMA_INT_GE | PDMA_INT_TC), ENABLE);
/* SPI Configuration*/
SPI_DeInit(HT_SPI0);
SPI_InitStructure.SPI_Mode = SPI_MASTER;
SPI_InitStructure.SPI_FIFO = SPI_FIFO_DISABLE;
SPI_InitStructure.SPI_DataLength = SPI_DATALENGTH_8;
SPI_InitStructure.SPI_SELMode = SPI_SEL_SOFTWARE;
SPI_InitStructure.SPI_SELPolarity = SPI_SELPOLARITY_HIGH;
SPI_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_HIGH;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_SECOND;
SPI_InitStructure.SPI_TxFIFOTriggerLevel = 0;
SPI_InitStructure.SPI_ClockPrescaler = 8;
SPI_Init(HT_SPI0, &SPI_InitStructure);
SPI_SoftwareSELCmd(HT_SPI0,SPI_SEL_ACTIVE);
// SPI_PDMACmd(HT_SPI0,SPI_PDMAREQ_TX,ENABLE);
SPI_Cmd(HT_SPI0,ENABLE);
WS281x_CloseAll(); //关闭全部的灯
delay_ms(100); //关闭全部的灯需要一定的时间
}
//uint16_t dATA;
//更新颜色显示(在设定颜色后将颜色数据存入缓存只有执行该函数后才会进行显示)
void WS281x_Show(void)
{
// //关闭通道 才能重新写入传输值
// PDMA_EnaCmd(PDMA_CH1, DISABLE);
// SPI_Cmd(HT_SPI0,DISABLE);
// PDMA_TranSizeConfig(PDMA_CH1,1,24);
// //开启通道
// SPI_Cmd(HT_SPI0,ENABLE);
// PDMA_EnaCmd(PDMA_CH1, ENABLE);
// PDMA_SwTrigCmd(PDMA_CH1, ENABLE);
// SPI_SoftwareSELCmd(HT_SPI0,SPI_SEL_ACTIVE);
// while (PDMA_GetFlagStatus(PDMA_CH1, PDMA_FLAG_TC) != SET);
// PDMA_ClearFlag(PDMA_CH1, PDMA_FLAG_GE|PDMA_FLAG_TC);
// dATA=SPI_ReceiveData(HT_SPI0);
int i = 0,k=0;
// for(i=0;i<PIXEL_NUM;i++)
// {
// for(j=0;j<GRB;j++)
// {
// SPI_SendData(HT_SPI0,(u32)pixelBuffer.All_Buffer[i][j]);
// for(k=0;k<10;k++)
// {
// __NOP();
// }
// }
// }
for(i=0;i<PIXEL_NUM*GRB;i++)
{
SPI_SendData(HT_SPI0,(u32)pixelBuffer.Buffer[i]);
for(k=0;k<10;k++)
{
__NOP();
}
}
}
//配置完成之后便可以构思底层控制函数了,为了方便多个LED灯珠的可控制首先要定义一个缓冲区pixelBuffer[PIXEL_NUM][24]\
通过设定颜色将数据填入缓冲区再通过更新函数将数据传入到LED灯珠上。
//关闭所有灯珠
void WS281x_CloseAll(void)
{
uint16_t i;
uint8_t j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < GRB; ++j)
{
pixelBuffer.All_Buffer[i][j] = WS_LOW;
}
}
WS281x_Show();
}
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue)
{
return green << 16 | red << 8 | blue;
}
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < GRB; i++)
{
pixelBuffer.All_Buffer[n][i] = ((GRBColor << i) & 0x800000) ? WS_HIGH : WS_LOW;
}
}
}
void WS281x_SetPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < GRB; ++i)
{
pixelBuffer.All_Buffer[n][i] = (((WS281x_Color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
}
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t WS281x_Wheel(uint8_t wheelPos) {
wheelPos = 255 - wheelPos;
if(wheelPos < 85) {
return WS281x_Color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if(wheelPos < 170) {
wheelPos -= 85;
return WS281x_Color(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return WS281x_Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}
// Fill the dots one after the other with a color
void WS281x_ColorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, c);
WS281x_Show();
Delay_ms(wait);
}
}
void WS281x_Rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, WS281x_Wheel((i+j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void WS281x_RainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
for(i=0; i< PIXEL_NUM; i++) {
WS281x_SetPixelColor(i,WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
//Theatre-style crawling lights.
void WS281x_TheaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) { //do 10 cycles of chasing
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, c); //turn every third pixel on
}
WS281x_Show();
Delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
//Theatre-style crawling lights with rainbow effect
void WS281x_TheaterChaseRainbow(uint8_t wait) {
for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, WS281x_Wheel( (i+j) % 255)); //turn every third pixel on
}
WS281x_Show();
Delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void WS2812_RainbowRotate(uint16_t wait) {
uint16_t i, j;
for (j = 0; j < 256 * 5; j++) { // 5 cycles of all colors on wheel
for (i = 0; i < PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
//hsv和rgb互相转
float retmax(float a, float b, float c) //求最大值
{
float max = 0;
max = a;
if (max < b)
max = b;
if (max < c)
max = c;
return max;
}
float retmin(float a, float b, float c) //求最小值
{
float min = 0;
min = a;
if (min > b)
min = b;
if (min > c)
min = c;
return min;
}
//R,G,B参数传入范围(0~100)
//转换结果h(0~360),s(0~100),v(0~100)
void rgb_to_hsv(uint16_t *H, uint16_t *S, uint16_t *V, uint8_t r, uint8_t g, uint8_t b) {
float max = 0, min = 0;
float R = (float)r;
float G = (float)g;
float B = (float)b;
float h, s, v;
R = R / 255.0;
G = G / 255.0;
B = B / 255.0;
max = retmax(R, G, B);
min = retmin(R, G, B);
v = max;
if (max == 0)
s = 0;
else
s = 1 - (min / max);
if (max == min)
h = 0;
else if (max == R && G >= B)
h = 60 * ((G - B) / (max - min));
else if (max == R && G < B)
h = 60 * ((G - B) / (max - min)) + 360;
else if (max == G)
h = 60 * ((B - R) / (max - min)) + 120;
else if (max == B)
h = 60 * ((R - G) / (max - min)) + 240;
v = v * 100;
s = s * 100;
*H = (int) h;
*S = (int) s;
*V = (int) v;
}
//参数入参范围h(0~360),s(0~100),v(0~100),这里要注意,要把s,v缩放到0~1之间
//转换结果R(0~100),G(0~100),B(0~100),如需转换到0~255,只需把后面的乘100改成乘255
void hsv_to_rgb(int h, int s, int v, uint8_t *r, uint8_t *g, uint8_t *b) {
float C = 0, X = 0, Y = 0, Z = 0;
int i = 0;
float H = (float) (h);
float S = (float) (s) / 100.0;
float V = (float) (v) / 100.0;
float R, G, B;
if (S == 0)
R = G = B = V;
else {
H = H / 60;
i = (int) H;
C = H - i;
X = V * (1 - S);
Y = V * (1 - S * C);
Z = V * (1 - S * (1 - C));
switch (i) {
case 0:
R = V;
G = Z;
B = X;
break;
case 1:
R = Y;
G = V;
B = X;
break;
case 2:
R = X;
G = V;
B = Z;
break;
case 3:
R = X;
G = Y;
B = V;
break;
case 4:
R = Z;
G = X;
B = V;
break;
case 5:
R = V;
G = X;
B = Y;
break;
}
}
R = R * 255;
G = G * 255;
B = B * 255;
*r = (int) R;
*g = (int) G;
*b = (int) B;
}
void RGB_DEBUG_TEST(void)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 255), 10); // Red
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 255), 10); // Green
}
else
{
WS281x_ColorWipe(WS281x_Color(255, 255, 0), 10); // Blue
}
}
}
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
}
else
{
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
}
}
}
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
else
{
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
}
}
WS2812_RainbowRotate(2);
// Some example procedures showing how to display to the pixels:
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
WS281x_TheaterChase(WS281x_Color(127, 127, 127), 10); // White
WS281x_TheaterChase(WS281x_Color(127, 0, 0), 10); // Red
WS281x_TheaterChase(WS281x_Color(0, 0, 127), 10); // Blue
WS281x_Rainbow(10);
WS281x_RainbowCycle(10);
WS281x_TheaterChaseRainbow(10);
WS281x_CloseAll();
}
五、实物效果展示
|