袁胜富 发表于 2022-9-10 22:11

【AT-START-WB415测评】+ SPI+DMA驱动WS2812B

本帖最后由 袁胜富 于 2022-9-10 23:47 编辑

偶然心血来潮,准备用AT32WB415点个WS2812灯。首先我们先来学习理论知识吧。
static/image/hrline/line6.png
一、WS2812的认识
Features and Benefits
 The control circuit and the LED share the only power source.
 Control circuit and RGB chip are integrated in a package of 5050 components, to form a complete addressable
pixel.
 Built-in signal reshaping circuit, after wave reshaping to the next driver, ensure wave-form distortion not
accumulate.
 Built-in electric reset circuit and power lost reset circuit.
 Each pixel of the three primary color can achieve 256 brightness display, completed 16777216 color full color
display, and scan frequency is of 2KHz.
 Cascading port transmission signal by single line.
 Any two point the distance not more than 5m transmission signal without any increase circuit.
 When the refresh rate is 30fps, cascade number are not less than 1024 pixels.
 Send data at speeds of 800Kbps.
 The color of the light is highly consistent, cost-effective.
 No need for external electric components and even the capacitor.
以上是数据手册摘抄下来的,然后来看看WS2812的数据格式
数据传输时间

数据序列和级联法

数据传输以及数据组成方式
通过以上资料我们得知一个灯的数据由24bit组成,GRB中G占位 ,R占8位,B占8位,G代表绿色,R代表红色,B代表蓝色,通过三种颜色可以显示出我们要的颜色。
WS2812B的数据传输速率位800K,1/8000000=1.25us。通过数据手册得知,0码的T0H时间为220ns~380ns,T0L时间为580ns~1us.1码的T1H时间为580ns~1us,T1L时间为220ns~380ns。WS2812使用方法一般为级联使用,由于本身拥有锁存器所以数据流经D1然后流经D2.。。。。

二、AT32WB415的SPI和DMA理论
static/image/hrline/line6.png
在此应用中我们只用到SPI的MOSI引脚,故SPI的工作模式设置为发送模式就行了,通过查阅芯片的Datasheet知道此芯片只用一个SPI为SPI2,MOSI为PB15引脚。
大家务必记住在使用SPI的时候一定要注意CS_SOFT模式是CS的内部电平应该置为高,只有这样SPI才能工作在主机模式,也就是MASTER Mode。
通过芯片手册知道SPI2挂载在APB1总线。在程序中配置AHB为144MHZ ,APB1为72MHZ。
DMA分为DMA1和DMA2,在手册中了解到AT32WB415的DMA很有意思,可以分为固定请求映射模式和灵活的弹性请求映射模式,固定请求映射模式,我实际测出SPI2的发送DMA通道在DMA1的Channel5也就是通道5上。为什么说是测出呢,因为手册没告诉我们啊!请看如下图片

有意思的是,此芯片的弹性请求映射模式是很不错,意思就是支持DMA的外设都可以任意使用任意DMA通道,我测试了一下在此模式下将SPI2的TX映射在DMA1的Channel4上是可以工作的。

三、理论总结
static/image/hrline/line6.png
通过一和二的分析,我们需要利用SPI2发送数据的高低电平时间来模拟WS2812B的数据时序。通过分析计算,我得出要想模拟出合适的时序,SPI2的波特率应该为18MHZ,也就是APB1要4分频(DIV4)。1/18MHZ=55ns,55*16=880ns,880ns在WS2812B的数据传输时间内,这里的16就是SPI的数据模式是16位模式.55*5=275ns用来表示0码的高电平时间。55*11=605ns用来表示1码的高电平时间。通过以上分析0码的16位数据的二进制:1111 1000 0000 0000 ,十六进制0xF800。1码的数据的二进制:1111 1111 1110 0000 ,十六进制0xFFE0.
四、程序编写
static/image/hrline/line6.png
1.头文件
#ifndef __SPI_DMA_WS2812_H
#define __SPI_DMA_WS2812_H

#include "at32wb415.h"
#include "bsp_sys.h"
#include "stdint.h"
#include "bsp_sys.h"
#include "tos_k.h"
/*
要将系统时钟设置为72M,SPI分频数设置为4,则SPI的通信频率为18M,1s/7M≈55ns 即传输一位数据的时间约为55纳秒(ns)16*55 =880ns 符合WS2812

5*55 = 277ns   11*55 = 611ns符合WS281X芯片的通信时序。

1111 1111 1110 0000high level(十六进制:0XFFE0)表示WS281X的1码

1111 1000 0000 0000low level   (十六进制:0XF800)表示WS281X的0码
程序头文件部分: 通过宏的方式定义了灯珠个数和WS281X的0码和1码。
*/

//_____   

// |   |___|    1111 1111 1110 0000 high level

//___         

// |   |_____|   1111 1000 0000 0000low level
#define Delay_ms(wait)tos_task_delay(wait)
#define WS2812B   //选择WS2812B
#define PIXEL_NUM12   //RGB灯的数量                                       //灯带1灯数(组数)
#define PIXEL_NUM22                                                          //灯带2灯数(组数)
#define PIXEL_NUM32                                                          //灯带3灯数(组数)
#define PIXEL_NUM42                                                          //灯带4灯数(组数)

#define PIXEL_NUM(PIXEL_NUM1+PIXEL_NUM2+PIXEL_NUM3+PIXEL_NUM4)//灯数统计
#define GRB24   //3*8 //Red8位 Green8位 Blue8位 加起来24位

#ifdef WS2812B
#define WS_HIGH 0xFFE0
#endif
#define WS_LOW0xF800
/*   
      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
    {
                        uint16_t PixelBuffer1;
                        uint16_t PixelBuffer2;
                        uint16_t PixelBuffer3;
                        uint16_t PixelBuffer4;
    }buff;
    uint16_t All_Buffer;
}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
源文件
#include "spi_dma_ws2812.h"
#include "string.h"
#include "bsp_sys.h"

RGB_PixelBuffer pixelBuffer;
uint32_t SPI_clock;
      
void WS281x_Init(void)
{
spi_init_type spi_init_struct;
gpio_init_type gpio_initstructure;
dma_init_type dma_init_struct = {0};
crm_clocks_freq_type clocks_structer;
crm_clocks_freq_get(&clocks_structer);
SPI_clock = clocks_structer.apb1_freq;//72M HZ
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_SPI2_PERIPH_CLOCK, TRUE);//打开SPI2的外设时钟
gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_pull         = GPIO_PULL_DOWN;
gpio_initstructure.gpio_mode         = GPIO_MODE_MUX;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pins         = GPIO_PINS_15;
gpio_init(GPIOB, &gpio_initstructure);

/* dma2 channel1 configuration */
dma_reset(DMA1_CHANNEL4);
dma_init_struct.buffer_size = 0;
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_base_addr = (uint32_t)&pixelBuffer;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_base_addr = (uint32_t)&SPI2->dt;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_VERY_HIGH;
dma_init_struct.loop_mode_enable = FALSE;
dma_init(DMA1_CHANNEL4, &dma_init_struct);
/* enable dma channel */
dma_channel_enable(DMA1_CHANNEL4, TRUE);
dma_flexible_config(DMA1,FLEX_CHANNEL4,DMA_FLEXIBLE_SPI2_TX);
spi_default_para_init(&spi_init_struct);//SPI2参数默认初始化//
spi_init_struct.transmission_mode                  = SPI_TRANSMIT_HALF_DUPLEX_TX;//仅作为发送模式
spi_init_struct.master_slave_mode                  = SPI_MODE_MASTER;//SPI工作在主机模式
spi_init_struct.mclk_freq_division               = SPI_MCLK_DIV_4;//18MZ
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;//高位先行
spi_init_struct.frame_bit_num          = SPI_FRAME_16BIT;//半字数据模式
spi_init_struct.clock_polarity         = SPI_CLOCK_POLARITY_HIGH;//极性低
spi_init_struct.clock_phase            = SPI_CLOCK_PHASE_2EDGE;//第二脉冲边沿
spi_init_struct.cs_mode_selection      = SPI_CS_SOFTWARE_MODE;//软件选择模式
spi_init(SPI2, &spi_init_struct);
spi_crc_polynomial_set(SPI2, 7);
spi_crc_enable(SPI2, TRUE);
spi_software_cs_internal_level_set(SPI2,SPI_SWCS_INTERNAL_LEVEL_HIGHT);//主机模式 SPI_CS_SOFTWARE_MODE SPI_SWCS_INTERNAL_LEVEL_HIGHT
spi_i2s_dma_transmitter_enable(SPI2,TRUE);//使能SPI2的DMA传输
spi_enable(SPI2, TRUE);//使能SPI2
WS281x_CloseAll();//关闭全部的灯
delay_ms(100); //关闭全部的灯需要一定的时间         
}

//更新颜色显示(在设定颜色后将颜色数据存入缓存只有执行该函数后才会进行显示)
void WS281x_Show(void)
{
      // 检查DMA发送通道内是否还有数据
      while(dma_data_number_get(DMA1_CHANNEL4)){};
      //关闭通道 才能重新写入传输值
      dma_channel_enable(DMA1_CHANNEL4, FALSE);
      dma_data_number_set(DMA1_CHANNEL4, PIXEL_NUM * GRB);
      //开启通道
      dma_channel_enable(DMA1_CHANNEL4, TRUE);
      while(dma_data_number_get(DMA1_CHANNEL4)){};
       while(dma_flag_get(DMA1_FDT4_FLAG) != SET);
       dma_flag_clear(DMA1_FDT4_FLAG);
       dma_channel_enable(DMA1_CHANNEL4, FALSE);
}
//配置完成之后便可以构思底层控制函数了,为了方便多个LED灯珠的可控制首先要定义一个缓冲区pixelBuffer\
通过设定颜色将数据填入缓冲区再通过更新函数将数据传入到LED灯珠上。
//关闭所有灯珠
void WS281x_CloseAll(void)
{
uint16_t i;
uint8_t j;

for(i = 0; i < PIXEL_NUM; ++i)
{
    for(j = 0; j < 24; ++j)
    {
      pixelBuffer.All_Buffer = 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 = ((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 = (((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, 255, 255), 10); // Red
                        }
                        else if(i==1)
                        {
                              WS281x_ColorWipe(WS281x_Color(255, 255, 255), 10); // Green
                        }
                        else
                        {
                              WS281x_ColorWipe(WS281x_Color(255, 255, 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
                        }
                        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);
}


五、实物效果展示图亮灯照片


weifeng90 发表于 2022-9-10 22:54

有程序嘛

袁胜富 发表于 2022-9-10 22:57

weifeng90 发表于 2022-9-10 22:54
有程序嘛

还在编辑文章

6552918 发表于 2022-9-11 11:20

用SPI有2个问题,1 SPI占用管脚比较多,2 SPI想让WS2812B跑到800K的满速,时钟很难有合适的,要么主频降速要么WS2812降速。

袁胜富 发表于 2022-9-11 14:52

6552918 发表于 2022-9-11 11:20
用SPI有2个问题,1 SPI占用管脚比较多,2 SPI想让WS2812B跑到800K的满速,时钟很难有合适的,要么主频降速 ...

确实这样的

gouguoccc 发表于 2022-9-11 18:18

为什么要用SPI接口呢?

袁胜富 发表于 2022-9-11 20:10

gouguoccc 发表于 2022-9-11 18:18
为什么要用SPI接口呢?

换一种思路点亮呗,PWM是首选。

juliestephen 发表于 2022-9-12 13:04

这个不错,真是巧妙。

xiaoyaodz 发表于 2022-9-12 13:29

之前用过stm32实现过ws2812驱动的

guijial511 发表于 2022-9-12 14:13

PWM是首选

Undshing 发表于 2022-10-5 20:57

spi也不错

pl202 发表于 2022-11-1 09:47

ws2812比bapa102好在哪儿?   

jimmhu 发表于 2022-11-1 10:02

这个dma操作是什么的代码            

gygp 发表于 2022-11-1 10:30

能够使用串口实现吗            

nomomy 发表于 2022-11-1 11:41

ws2812灯珠怎样防止烧坏

macpherson 发表于 2022-11-1 15:32

7.2V电压会损坏WS2812B灯带吗?

wengh2016 发表于 2022-11-1 16:19

spi的mosi的引脚操作的吧            

burgessmaggie 发表于 2022-11-1 17:12

arduino怎么操作WS2812B彩灯模块

cashrwood 发表于 2022-11-1 18:01

Ws2812 时序有要求吗               

1988020566 发表于 2022-11-1 18:55

WS2812B可以只用PWM实现吗
页: [1] 2
查看完整版本: 【AT-START-WB415测评】+ SPI+DMA驱动WS2812B