2014年7月19日 星期六

怎麼用 Arduino 控制 WS2812B 做流星燈

網頁最後修改時間:2016/03/02
網頁中所使用的全彩 LED 焊接電路板可至露天賣場訂購:
如果有購買賣場的 WS2812B 焊接電路板,這網頁中所使用到的兩個 Arduino 程式碼已經放置在雲端硬碟中,可直接至雲端硬碟中下載。


這篇網頁主要是使用 WS2812B 焊接電路板實現流星燈往復閃過眼前的感覺 !

流星燈動作照片


因為這東西很多人喜歡,因此提供給購買過的好朋友,WS2812B 實現流星燈的程式碼:
  • WS2812_pcbtest.ino ( 網頁中會提供 )
  • 裡面有三個  WS2812 閃爍樣式:Cylon,Rainbow 和靜態流星三種。

  • WS2812_MeteorLights.ino ( 說明方法,提供單方向流星程式碼給網頁讀者;零件購買者可至雲端硬碟下載完整流星往復程式碼 )
  • 流星燈的 Arduino 原始碼。
Arduino 程式碼 - WS2812B 流星燈

常說:「文不如表,表不如圖,圖不如影片」,真的很難用文字、表格和照片來說明流星燈實際的動作,看過了整個流星動作的樣子之後,相信就可以很容易地使用文字來做描述,並且了解實現的方法,請先看影片的展示再開始。



接線方法:

WS2812B 焊接電路板的接線很簡單,照著電路板背面箭頭的方向一個接一個焊接即可。為了方便起見,電路板的做前端會直接焊上三個 90 度的排針,這樣做的好處是可以直接插在 Arduino 上面,不用再另外拉線;但請注意 ! 這只是展示用,正確的接法為當連接數目變多時,WS2812B 的 +5V 電源要另外提供,Arduino 接腳是無法一次同時提供多顆 WS2812B ( full output )足夠的驅動電流的,使用時請特別注意。

WS2812B 焊接電路板和接示意圖


流星形成的想法與做法:

如何產生流星 ?

想法很簡單,藉由先產生一串靜態流星燈,頭部最亮尾部最暗,兩兩之間的亮度間隔可以相同也可以不同,流星的長度可以自己決定,只要看起來像流星即可。

請先載入 WS2812_pcbtest.ino 程式,將流星利用燈串先顯示出來。

我所使用 WS2812B 有 11 顆  ( 燈串有 11 顆 WS2812B) ( line19 )、流星長度設定為 8 ( line 8 )、亮度變化線性間隔 32 ( line 30  ) 、燈串電源控制接腳為 12 ( line 26 );最後宣告一個 CRGB leds 陣列儲存每一顆流星 RGB 的顏色 ( line 33 )。

setup() 函式中,line 37 ... line 38:設定燈串的電源控制接腳為輸出並設為高準位;line 43:初始化 WS2812B 燈串和設定相關參數。

WS2812_pcbtest.ino, 省略不必要的說明
.. /* 省略 line 1 ... line 14 */
15
16 #include "FastLED.h"
17 
18 // How many leds in your strip?
19 #define NUM_LEDS 11
20 
21 // For led chips like Neopixels, which have a data line, ground, and power, you just
22 // need to define DATA_PIN. 
23 #define DATA_PIN 13
24 
25 // 電源控制接腳
26 #define POWER_PIN    12
27 // 流星長度
28 #define NUM_METEORS    8
29 // 流星亮度間隔 ( HSV )
30 #define  INCREASE_BRIGHTNESS  ( 256 / NUM_METEORS )
31 
32 // Define the array of leds
33 CRGB leds[NUM_LEDS];
34 
35 void setup() { 
36   
37   pinMode( POWER_PIN, OUTPUT );
38   digitalWrite( POWER_PIN, HIGH );
39   
40     // sanity check delay - allows reprogramming if accidently blowing power w/leds
41   delay(2000);  
42   
43   FastLED.addLeds<WS2812B, DATA_PIN>(leds, NUM_LEDS);
44   
45 }
46 
47 void loop() {
48     
49     
..     /* 省略 line 50 ... line 84 */
85 
86     // 靜態流星燈
87     int i, j = 2;
88     for( int i = INCREASE_BRIGHTNESS - 1; i < 256; i += INCREASE_BRIGHTNESS )
89         leds[j++].setHSV( 228, 255, i );
90     FastLED.show();
91     delay(2000);
92 }

WS2812B 使用 FastLED 函式庫做控制,函式庫提供了兩種不同的顏色設定方式:RGB 和 HSV ( Hue, Saturation, Value;色調、飽和度和亮度 ),所以在 loop() 函式中,在維持色調與飽和度不變的情況之下,每隔 INCREASE_BRIGHTNESS 調整亮度值,就可以產生相同顏色不同亮度的顯示效果,HSV 色彩空間可參考維基百科的說明。

這邊要特別注意的是:這個程式的 line 50 ... line 84 是兩個燈串顯示的樣式,不過這是用在燈串的測試上,流星測試的程式碼是在 line 86 ... line 91。

WS2812B 燈串有 11 顆,流星全部顯示需要 8 顆 WS2812B。程式執行之後會將流星顯示在燈串的第 3 到第 10 顆之間 ( line 87 ... line 90, 程式是由 0 開始算 )。如下圖,相同顏色但亮度由亮到稍暗,只要移動流星在燈串中的位置並將速度加快,就可以呈現像影片中流星在燈串之中的閃過的樣子出來,這部分將會在下個程式中說明。

靜態流星顯示

有了靜態流星顯示之後,我就可以去微調每一顆的亮度以及增加或是縮短流星的長度值到滿意為止,利用這些調好的數值就可以形成一個暫存的流星顏色數據陣列 meteors[8]。另外再產生一個 WS2812B 燈串的陣列 leds[11],每個迴圈開始前就將裡面的亮度數值清為零,只要依序將 meteors 陣列一次一個像素移入到 leds 內,就可以實現流星的在燈串裡流動的目的,流星移入燈串可分為下面三個過程:

* 流星進入到燈串

流星一個像素一個像素的增加,一直到倒數第二個流星像素 meteors[1],都屬於流星進入到燈串的過程。在這個過程中,流星由頭部一直移入到 leds 燈串裡,因此必須要知道流星頭部與尾部分別對應到燈串的哪一個位置。
流星進入到燈串示意圖

* 流星在燈串中

流星完全進入到燈串後,就開始在燈串流動的過程。只要知道 meteors 陣列要複製到燈串 leds 開頭的位置,整個 meteors 複製過去就可以了。
流星整個在燈串中示意圖

* 流星離開燈串

一旦流星的最前面那個像素離開燈串後,就屬於流星離開燈串的過程。在這個過程中,metrors 會一值減少填入到 leds 像素的數目,直到完全離開燈串為止。


流星開始流開燈串示意圖

上面的過程實作如下程式碼 ( 省略一些無關 ),完全往復如影片的程式請至雲端硬碟下載,這裡只提供單一方向的流星燈程式,但應該不難將其反向程式碼再依樣寫出。


程WS281_MeteorLights.ino 式實作說明:

line 18 ... line 35:是程式裡重要的參數設定,每個參數上面都有說明就不在贅述。其中 INCREASE_BRIGHTNESS ( line 25 ) 亮度間隔值的設定,可以使用不同的方式或方程式來做設定。

line 38;是作為程式除錯用的,不用的時候在前面加雙斜線取消,可以輸出 leds 開頭與結束位置和 meteors 複製的開頭與結速位置。

前面說過,流星 meteors 與燈串 leds 各用兩個陣列來暫存 ( line 43, line 44 ) RGB 資料,而存放在 leds 陣列中的值將會在執行 FastLED.show() 時將數據輸出到 WS2812B 燈串,也就是說:控制 meteors 陣列複製到 leds 的位置,就可以很容易的操作流星在燈串的流動。

WS2812_MeteorLights.ino, 重要參數設定,省略不必要的說明
  1 /* 省略 line 1 ... line 12 */
 13 #include "FastLED.h"
 14 
 15 /*==========================================================================
 16 *  重要常數宣告,依實際連接方式作變更
 17 --------------------------------------------------------------------------*/
 18 // 燈串裡 WS2812B 的數目
 19 #define NUM_LEDS  11
 20 
 21 // 在燈串裡的流星長度
 22 #define NUM_METEORS  8
 23 
 24 // 流星亮度間隔 ( HSV )
 25 #define  INCREASE_BRIGHTNESS  ( 256 / NUM_METEORS )
 26 
 27 // 流星每一次停留時間 ( ms)
 28 #define STAY_TIME  45
 29 
 30 // WS2812B 通訊資料接腳
 31 #define DATA_PIN 13
 32 
 33 // 使用接腳作為電源直接驅動 WS2812B,
 34 //    若使用的數目眾多時,要使用額外的電源
 35 #define POWER_PIN  12
 36 
 37 //  DEBUG
 38 #define DEBUG
 39 
 40 /*========================================================================*/
 41 
 42 // Define the array of leds
 43 CRGB leds[NUM_LEDS];
 44 CRGB meteors[NUM_METEORS];
 45 

setup() 函式裡,先初始化 POWER_PIN 為輸出 ( line 48 ) 並直接設為高準位 ( line 49 );再設定 FastLED 函式庫給 WS2812B 使用,並設定資料通訊接腳為 DATA_IN、WS2812B 燈串的數目 NUM_LEDS 和輸出顯示所使用的陣列 leds ( line54 );Serial Port 設定速度為 9600 bps ( line 56 ),如果有開啟 DEBUG 的話就可以在 Serial Monitor 看輸出;line 58 ... line 70:輸出程式設定參數,可以用來確認燈串顯示是否正確,是不是需要再做調整。

line 72 ... line 83:設定流星陣列 meteors 每一個像素的顏色與亮度值,由輸出可看出每一個陣列中像素顏色的亮度值。

FastLED 函式庫使用 RGB 設定的方式去顯示 leds 陣列指定的顏色設定,對於單色調亮度很容易,但是對於混色的話會很困難 ( 想像一下調紅色流星與紫色流星 ),因此我們必須使用另一個色調空間 HSV 來做,這會讓同一種顏色單純調亮度變得很容易。

將 HSV 轉換為 RGB 值,FastLED 函式庫使用 setHSV( Hue, Saturation, Value函式,Hue Saturation 可以另外再提出使用參數設定,但這邊我將其固定為 Hue = 237, Saturation = 255,調整 Value ( 又可為 Brightness ) 31、63、...、255,完成整個 meteors 陣列的設定。

line 85 ... line 93:省略的另一種設定亮度的順序,0 ... 7 設定為亮到滅,如果不習慣程式裡的設定方式,可把這段註解掉的程式碼打開來用。

WS2812_MeteorLights.ino, setup()
 45 
 46 void setup() { 
 47       
 48     pinMode( POWER_PIN, OUTPUT );
 49     digitalWrite( POWER_PIN, HIGH );
 50       
 51     // sanity check delay - allows reprogramming if accidently blowing power w/leds
 52     delay(2000);  
 53       
 54     FastLED.addLeds<WS2812B, DATA_PIN>(leds, NUM_LEDS);
 55      
 56     Serial.begin(9600);
 57       
 58     #ifdef DEBUG
 59         Serial.print(F("NUM_LEDS")); Serial.print("\t");
 60         Serial.print(F("NUM_METEORS")); Serial.print("\t");
 61         Serial.print(F("INCREASE_BRIGHTNESS")); Serial.print("\t");
 62         Serial.print(F("STAY_TIME"));
 63         Serial.println("");  
 64         Serial.print(NUM_LEDS); Serial.print("\t");  
 65         Serial.print(NUM_METEORS); Serial.print("\t");
 66         Serial.print(INCREASE_BRIGHTNESS); Serial.print("\t");
 67         Serial.print(STAY_TIME);
 68         Serial.print(INCREASE_BRIGHTNESS);
 69         Serial.println("");
 70     #endif
 71   
 72     int i = 0, j=0;
 73     // 下面顯示出整串流星的長度以及亮度變化,由 0 .. 7 是滅到亮
 74     for( i = INCREASE_BRIGHTNESS - 1; i < 256; i += INCREASE_BRIGHTNESS )
 75     {
 76         #ifdef DEBUG
 77             Serial.print( i );
 78             Serial.print("\t");
 79             Serial.println( j );
 80         #endif
 81         
 82         meteors[j++].setHSV( 228, 255, i );
 83     }
 84        
 ..     /* 省略 line 85 ... line 92 */
 93     
 94 }
 95 

loop() 函式裡,for 迴圈中 line 105 ... line 130 對應到上節流星的形成與想法中的說明,藉由流星進入、保持在和離開燈串三個過程中指定流星位於燈串中的位址,最後將 meteors 複製到 leds 中 ( line 140 ... line 143 ) 再將其顯示出來 ( line 145 ),每一次顯示只維持 STAY_TIME 的時間 ( ms ),在流星整個離開燈串後暫停 1500 ms ( line 148 )。

STAY_TIME 的設定很重要 ! 太慢會有停頓感,太快則流星前端會太亮後端拖尾會變短,而預設值 45 ms 的動作情況就會像影片中所展示的一樣,每個人的感覺不一樣,再依自己喜歡做調整就可以了!

WS2812_MeteorLights.ino, loop()
 96 void loop() {
 97   int i, ea, eb;        // 流星現在在燈串的位置;ea: 低位元組,eb: 高位元組
 98   int ma, mb;      // 流星陣列複製的起始位置;ma: 低位元組,mb: 高位元組
 99 
100 /*==========================================================================
101 *  流星由近端到遠端移動
102 --------------------------------------------------------------------------*/
103   for( i = 0; i < ( NUM_LEDS + NUM_METEORS ); i++ )    // ea: 流星起頭的位置
104   {
105         // 設定 leds 全部為 black
106         for( ea = 0; ea < NUM_LEDS; ea++ )
107             leds[ea] = CRGB::Black;
108           
109       // 找到複製 meteors 到 leds 的起頭與結束位置
110       if( i < NUM_METEORS ) // 整個流星還未完全進入到燈串當中 ( i = 0 ... 7 )
111       {
112           ma = 0;
113           mb = i + 1;
114           ea = NUM_METEORS - i - 1;
115           eb = NUM_METEORS;
116       }
117       else if( i >= NUM_LEDS )    // 流星頭部已經開始超出燈串 ( i = 11 ... 18 )
118       {
119           ma = i - NUM_METEORS + 1;
120           mb = NUM_LEDS;
121           ea = 0;
122           eb = NUM_LEDS + NUM_METEORS - 1 - i;
123       }
124       else    // 流星在燈串中 ( i = 8 ... 10 )
125       {
126           ma = i - NUM_METEORS + 1;
127           mb = i + 1;
128           ea = 0;
129           eb = NUM_METEORS;
130       }
131       
132       #ifdef DEBUG
133           Serial.print(F("i")); Serial.print(i); Serial.print("\t");
134           Serial.print(F("ma")); Serial.print(ma); Serial.print("\t");
135           Serial.print(F("mb")); Serial.print(mb); Serial.print("\t");
136           Serial.print(F("ea")); Serial.print(ea); Serial.print("\t");
137           Serial.print(F("eb")); Serial.print(eb); Serial.println("");
138       #endif
139       
140       for( ma; ma < mb; ma++ )    // 複製流星的起頭位置
141       {
142           leds[ma] = meteors[ea++];
143       }
144       
145         FastLED.show();
146         delay(STAY_TIME);    // default 45 ms
147   } 
148   delay(1500);
149     /* 省略 line 149 ... line 200 */
...   
201 }

看看完整程式執行時所拍的照片,拍了很多張才抓到流星 !
WS2812_Meteors.ino 程式執行照片 -1
WS2812_Meteors.ino 程式執行照片 -2
WS2812_Meteors.ino 程式執行照片 -3
WS2812_Meteors.ino 程式執行照片 -4

結論:

WS2812B 焊接電路板隨貨附的是函式庫,但是沒有實際使用的例子可以展示給購買的使用者,雖然限定是 WS2812B 的晶片,但是查看一下網頁中所使用的 FastLED 函式庫裡的程式碼可以知道,WS2812、WS2812B、WS2811、WS2811_400 和 NEOPIXEL,都可以使用這個程式碼來做流星燈 (其他的就需要改一下程式碼以符合傳送的要求,但是WS2812_MeteorLights.ino line 109 ... line 130 這裡就不需要做變更了 )。

{ Arduino libraries } \ FastLED \ FastLED.h, addLEDs()
 90 
 91  template<EClocklessChipsets CHIPSET, uint8_t DATA_PIN> 
 92  CLEDController *addLeds(const struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
 93   switch(CHIPSET) { 
 94 #ifdef FASTSPI_USE_DMX_SIMPLE
 95    case DMX: { static DMXController<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
 96 #endif
 97    case TM1809: { static TM1809Controller800Khz<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
 98    case TM1803: { static TM1803Controller400Khz<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
 99    case UCS1903: { static UCS1903Controller400Khz<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
100    case WS2812: 
101    case WS2812B:
102    case WS2811: { static WS2811Controller800Khz<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
103    case NEOPIXEL: { static WS2811Controller800Khz<DATA_PIN, GRB> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
104    case WS2811_400: { static WS2811Controller400Khz<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
105   }
106  }

使用可程式化的 LED 來做流星燈非常方便,不需要去擔心漸層亮度如何呈現,只需要知道哪一個像素需要哪一種亮度就可以了,好看嗎 ? 自己動手做做看吧!

玩得愉快 !!!


2014/07/19 程式碼編譯出現找不到標頭檔的問題 ?

有網友回覆編譯時找不到標頭檔的問題,原因是這網頁使用最新版的 FaseLED 函式庫,而雲端硬碟附給 WS2812B 用的是舊版本的,因為新版的作者修改了標頭檔的一些設定,所以 Arduino 編譯時會找不到標頭檔。

所以如果有購買賣場 LED 驅動晶片或是燈的話,Arduino 驅動的函式庫可由下面官方連結直接下載:
如果你是使用舊版本而更新到新版本,只要在原本的程式標頭檔宣告的地方將 FaseSPI_LED2.h 改成 FastLED.h 即可重新編譯上傳。

詳細的 FastLED 使用說明與版本更新資料可參考 GitHub 網頁:


<< 網頁相關文章 >>

8 則留言:

  1. 想請問 如果要讓一維陣列跑電腦圖片(揮起來會是一張圖)
    那要透過哪些IC呢?

    回覆刪除
  2. 想請問 如果要讓5050一維陣列可以跑2D圖片
    那麼要怎麼寫/或是額外使用什麼IC呢?

    回覆刪除
  3. Google 一下 "WS2812 Arduino" 就會有範例可以查到。

    回覆刪除
  4. 請問一下 專題arduino 利用光敏電阻控制燈條變化 目前已紅綠藍黃紫水藍 六種變化 請問還有神麼變化可以用 老師說要32種變化

    回覆刪除
    回覆
    1. 網頁最下面有函式庫的連結,裡面有詳細的使用說明。

      R、G、B 三種顏色若是以 8-bit 來設定顏色,每一種可以有 256 色,三種顏色混色之後可以產生 16,777,216 種顏色變化。

      要設定不同顏色,可以使用小畫家裡面的"編輯色彩"按鈕,在出現的視窗裡面可以手動調整色盤或是直接設定弘、綠、藍色三種顏色的值,就會產生相對應的變化。

      CRGB 可以設定RGB三色個別的數值產生對應的顏色
      https://github.com/FastLED/FastLED/wiki/Pixel-reference

      刪除
  5. 您好 可以給我您的購買拍賣網頁連結嗎? 您內文中的我點下去是空白的~

    回覆刪除