本帖最后由 袁胜富 于 2022-9-10 23:47 编辑
偶然心血来潮,准备用AT32WB415点个WS2812灯。首先我们先来学习理论知识吧。
一、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理论
在此应用中我们只用到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上是可以工作的。
三、理论总结
通过一和二的分析,我们需要利用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.
四、程序编写
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 0000 low level (十六进制:0XF800)表示WS281X的0码
程序头文件部分: 通过宏的方式定义了灯珠个数和WS281X的0码和1码。
*/
// _____
// | |___| 1111 1111 1110 0000 high level
// ___
// | |_____| 1111 1000 0000 0000 low level
#define Delay_ms(wait) tos_task_delay(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 0xFFE0
#endif
#define WS_LOW 0xF800
/*
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[PIXEL_NUM1][GRB];
uint16_t PixelBuffer2[PIXEL_NUM2][GRB];
uint16_t PixelBuffer3[PIXEL_NUM3][GRB];
uint16_t PixelBuffer4[PIXEL_NUM4][GRB];
}buff;
uint16_t All_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
源文件
#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[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 < 24; ++j)
{
pixelBuffer.All_Buffer[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] = ((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] = (((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);
}
五、实物效果展示图亮灯照片
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
共1人点赞
|