之前發了一個基于涂鴉平臺的寵物自動喂食器點此跳轉,已經有小夜燈功能了,但是躁動的心豈能因此平靜,功能以及花里胡哨,那就讓他華麗起來,除了畫手們的涂裝風格,作為電子工程師,得用我們自己的方式美化。正好手里有個ws2812的燈環,全彩模式開始。
WS2812特點:
1.WS2812燈珠內置控制電路與RGB芯片,集成在一個5050封裝的元器件中,構成一個完整的外控像素點。
2.每個像素點的三基色可實現256級亮度顯示,完成16777216種顏色的全真色彩顯示,掃描頻率不低于400Hz/s。
3.串行級聯接口,能通過一根信號線完成數據的接收與解碼。
4.當刷新速率30幀/秒時,低速模式級聯數不小于512點,高速模式不小于1024點。
5.數據發送速度可達800Kbps。
數據傳輸:
ws2812的每顆燈珠的控制需要24位數據,分為8位綠色+8位紅色+8位藍色,每個像素點的三基色顏色可實現256級亮度顯示,完成16777216種顏色的全真色彩顯示。燈珠之間采用串行級聯,在上電復位以后,控制器可向燈帶發送一串24bit的數據,比如需要點亮10顆燈,我們發送10個24bit的數據到第一個燈,第一個燈的DIN端接受控制器發過來的24bit數據,第一個燈珠會提取第一個24bit數據后會將該數據送進數據鎖存器,剩余的數據經過內部整形電路向下傳送,直到所有的燈都獲取一個24bit數據。這10個24bit數據的發送間隔不能超過50us,否則會導致下一次的顏色數據被第一個重新鎖存,就無法完成完整的點亮。( f# T% }0 [3 q
這里需要著重點一下時序相關的知識,先看時序波形圖和數據傳輸時間表:
根據上面兩個圖可以看到ws2812的電平反轉是納秒級別的,所以在使用單片機外設時我們需要對單片機的外設的傳輸速度進行控制,速度慢了根本無法點亮。; S( G) r; U: ]/ l" e: d( y) W
操作開始:在驅動ws2812的時候一般采用PWM或者SPI的方式,這兩個速度較快,比直接使用IO口進行電平反轉要方便,且控制效果更好。這里我們采用SPI的方式,因為在點燈是的數據發送間隔時間的約束,如果我們使用SPI發送的數據較多,中途遇到中斷可能會打斷我們的點燈,所以我這里使用SPI的DMA進行控制,不怕被其他影響。
$ K& Q* H) y8 A3 N
通過硬件SPI我們模擬WS2812的通信時序。單片機選用STM32F103,主頻72M,SPI1分頻設置為8,這樣SPI1的通信頻率為9M。時間很充足,如果使用SPI2,則需要減小分頻系數,否則無法點亮。我這里采用的便是SPI2,4分頻,因為SPI1被我的屏幕占用了。1 h7 F1 f/ g& Q2 l3 H
.h文件只要定義了燈珠的個數和0碼1碼。不同的燈珠數量只需修改PIXEL_NUM 的值
#ifndef __WS2812_H#define __WS2812_H
#include "stm32f10x.h"
#define PIXEL_NUM 24
//硬件spi模擬ws2811時序(用spi的8位數據模擬ws281x的一位數據)//要將系統時鐘設置為56M,分頻數設置為8,則SPI的通信頻率為7M,傳輸一位數據的時間約為143納秒(ns)//3*143 = 429ns 5*143 = 715ns 符合WS281X芯片的通信時序。// _____ // | |___| 11111000 high level// ___ // | |_____| 11100000 low level" b0 v* d3 B$ G5 m
#define WS_HIGH 0XF8#define WS_LOW 0XE0;
void ws281x_init(void);void ws281x_closeAll(void);void ws281x_rainbowCycle(uint8_t wait);uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue);void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor);void ws281x_show(void);
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);
#endif /* __WS2812_H */
#include "../BOARD/ws2812/ws2812.h"#include "usart.h"#include "delay.h"
uint8_t pixelBuffer[PIXEL_NUM][24] ;" k* H) D$ J, e; F W; H
7 Z0 x! U" e; j% G" m
void ws281x_init(void){ GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //PORTA時鐘使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //SPI1時鐘使能 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA傳輸5 m/ ^; {1 S- T5 l) c
/* PA7 SPI1_MOSI */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PA7復用推挽輸出 SPI GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //設置SPI工作模式:設置為主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //設置SPI的數據大小:SPI發送接收8位幀結構 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步時鐘的空閑狀態為低電平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步時鐘的第2個跳變沿(上升或下降)數據被采樣 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //定義波特率預分頻的值:波特率預分頻值為16 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式 SPI_Init(SPI2, &SPI_InitStructure); //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外設 SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE); DMA_DeInit(DMA1_Channel5); //將DMA的通道1寄存器重設為缺省值 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI2 -> DR); //cpar; //DMA外設ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar; //DMA內存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //數據傳輸方向,從內存讀取發送到外設 DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr; //DMA通道的DMA緩存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數據寬度為8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數據寬度為8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常緩存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x擁有中優先級 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設置為內存到內存傳輸 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根據DMA_InitStruct中指定的參數初始化DMA的通道USART1_Tx_DMA_Channel所標識的寄存器 ws281x_closeAll(); //關閉全部的燈 delay_ms(100); //關閉全部的燈需要一定的時間 }
void ws281x_closeAll(void){ uint16_t i; uint8_t j; for(i = 0; i < PIXEL_NUM; ++i) { for(j = 0; j < 24; ++j) { pixelBuffer[j] = WS_LOW; } } ws281x_show();}
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue){ return green << 16 | red << 8 | blue;}4 @, B7 p) q) {, `, I" X) s
void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor){ uint8_t i; if(n < PIXEL_NUM) { for(i = 0; i < 24; ++i) { pixelBuffer[n] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW); } }}$ s) V [/ M! [& k: l2 S; F
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 < 24; ++i) { pixelBuffer[n] = (((ws281x_color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW); } }} S8 t. {2 h0 ~+ d$ f1 f
void ws281x_show(void){ DMA_Cmd(DMA1_Channel5, DISABLE ); //關閉USART1 TX DMA1 所指示的通道 DMA_CleaRFlag(DMA1_FLAG_TC5); DMA_SetCurrDataCounter(DMA1_Channel5,24 * PIXEL_NUM );//DMA通道的DMA緩存的大小 DMA_Cmd(DMA1_Channel5, ENABLE); //使能USART1 TX DMA1 所指示的通道}( u f4 G3 L2 H# {6 x- J) Q
/ ], A6 r3 L9 b+ c# f9 ~) R
// 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);}0 {# {; H7 z! J0 T$ |# Z
// Fill the dots one after the other with a colorvoid ws281x_colorWipe(uint32_t c, uint8_t wait) { for(uint16_t i=0; i
void ws281x_rainbow(uint8_t wait) { uint16_t i, j;
for(j=0; j<256; j++) { for(i=0; i
// Slightly different, this makes the rainbow equally distributed throughoutvoid ws281x_rainbowCycle(uint8_t wait) { uint16_t i, j;( m/ S/ q* U' D, I4 ]5 B
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); }}# J( `" j3 |5 l7 j2 G g
//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();% `& ^* R: S7 R
delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) { ws281x_setPixelColor(i+q, 0); //turn every third pixel off } } }}2 u% i! Q$ [9 I& R6 l8 R
//Theatre-style crawling lights with rainbow effectvoid 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();. T4 W/ b( e% o, E3 n
delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) { ws281x_setPixelColor(i+q, 0); //turn every third pixel off } } }}
( S" o5 I1 l0 m* `1 r( l- P
) M0 f9 L# m" x8 {
1 Q1 n! D3 w( g9 E, S
const char s[5];int8_t i;; R* I Y$ m( @* q' S" K
int main(void){// usart1_init(115200); delay_init();% R( y0 V" N, i$ y) [% I
ws281x_init();
while(1) { // Some example procedures showing how to display to the pixels: ws281x_colorWipe(ws281x_color(255, 0, 0), 50); // Red ws281x_colorWipe(ws281x_color(0, 255, 0), 50); // Green ws281x_colorWipe(ws281x_color(0, 0, 255), 50); // Blue//colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW // Send a theater pixel chase in... ws281x_theaterChase(ws281x_color(127, 127, 127), 50); // White ws281x_theaterChase(ws281x_color(127, 0, 0), 50); // Red ws281x_theaterChase(ws281x_color(0, 0, 127), 50); // Blue. K/ h) C4 i7 a. t- D) b
//ws281x_rainbow(20); ws281x_rainbowCycle(20); ws281x_theaterChaseRainbow(200); for(i = 0; i < PIXEL_NUM; ++i) { ws281x_setPixelColor(i, ws281x_color(0,250,0)); ws281x_show(); delay_ms(500); } }}4 K1 G9 z9 ^3 Z- g/ {+ n" f
在ws2812.c移植了Adafruit_NeoPixel庫的部分函數,用以實現炫酷的顯示效果。; k h. }7 o9 N
都是基礎的SPI,不說廢話,移植就能用。展示一下效果吧!# U8 ]7 i! d2 f# n1 v2 g. z
『本文轉載自網絡,版權歸原作者所有,如有侵權請聯系刪除』
|