打印
[开发板与模块]

【ESK32-30519 + ESK32-21001测评】+SPI驱动WS2812B

[复制链接]
1384|15
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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();

}

五、实物效果展示









使用特权

评论回复

相关帖子

沙发
51xlf| | 2023-1-5 11:14 | 只看该作者
spi+dma的形式驱动的。              

使用特权

评论回复
板凳
plsbackup| | 2023-1-5 11:42 | 只看该作者
这个对电流的要求很大吗?              

使用特权

评论回复
地板
kkzz| | 2023-1-5 17:43 | 只看该作者
直接定时器延时就能实现的吧。              

使用特权

评论回复
5
jimmhu| | 2023-1-6 15:21 | 只看该作者
不使用dma,那就是得占用非常多的时间了?

使用特权

评论回复
6
robincotton| | 2023-1-6 19:16 | 只看该作者
这个可以驱动多少个ws2812呢

使用特权

评论回复
7
wwppd| | 2023-2-5 13:13 | 只看该作者
用WS2812制作的灯带控制程序非常实用

使用特权

评论回复
8
sheflynn| | 2023-2-5 14:11 | 只看该作者
通过硬件SPI的可以很巧妙的模拟出WS2812的通信时序,用spi的8位数据模拟ws281x的一位数据。

使用特权

评论回复
9
nomomy| | 2023-2-6 12:42 | 只看该作者
通过配置SPI的8个时钟周期满足WS2812B所需要的1位所占用的时间

使用特权

评论回复
10
lihuami| | 2023-2-7 20:00 | 只看该作者
使用的方法是PWM+DMA传输,也有SPI和直接延时等方法

使用特权

评论回复
11
sanfuzi| | 2023-2-7 20:12 | 只看该作者
怎么操作WS2812B彩灯实现模块的多显示呢?

使用特权

评论回复
12
mickit| | 2023-2-7 20:23 | 只看该作者
ws2812和ws2812b能混用吗

使用特权

评论回复
13
jackcat| | 2023-2-7 20:29 | 只看该作者
直接使用IO口进行电平反转要方便  

使用特权

评论回复
14
cashrwood| | 2023-2-7 20:40 | 只看该作者
驱动ws2812的时候一般采用PWM或者SPI的方式

使用特权

评论回复
15
deliahouse887| | 2023-2-7 21:38 | 只看该作者
WS2812通信速度为1M比较合适,不会造成乱码,通信很稳定。

使用特权

评论回复
16
sesefadou| | 2023-2-7 21:47 | 只看该作者
WS2812B是个好东西,在很多场合,都能使用。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

25

主题

133

帖子

2

粉丝