4

編者注:本文作者為Akiba,原發于FreakLabs。
很多人問過我關于燈光控制和排序的問題,其中涉及到安裝、顯示和可穿戴設計等方方面面,所以我想干脆做一個詳細的教程好了!
為此我制作了一個文本教程和一個視頻教程,其中視頻教程分為兩部分,如下所示:
視頻一:怎么使用Arduino和Vixen控制發光序列
時間線:
00:15 - 在面包板上搭建LED電路
01:46 - 配置Vixen軟件
03:40 - 設置新的發光順序
05:12 - 檢查音序的串行輸出
07:08 - 編寫Arduino代碼解碼音序數據
10:28 - 在Arduino 系統上測試發光序列
視頻二:怎么使用無線的方式控制發光序列
時間線:
00:32 - 將原來的系統改成收發器系統
01:05 - 寫出freakduino發送器代碼
03:00 - 寫入freakduino接收器代碼
04:52 - 使用Vixen測試無線系統
05:48 - 使用晶體管實現LED發光條和控制電路的交互
06:55 - 戶外測試
07:42 - 實例:花式調酒和Wrecking Crew Orchestra樂隊
而視頻上看不明白的,就讓我們用文字來解決。首先讓我們一起來研究一下怎么使用有線的方式實現對LED發光的控制,這樣操作起來更加簡單,對于新手來說這一步也格外關鍵。
準備Arduino、面包板和六顆LED燈,按下圖的方式進行連接:

這里我們選擇的是D2~D7這六個輸出口連接LED,注意在LED和每個接口之間需要串接一個100Ω的限流電阻。因為LED只需要20mA的電流即可正常發光,串接100Ω的電阻差不多剛好能讓標準LED獲得良好的表現。按照電路圖連接好之后是這樣的:

首先你可以測試一下LED能不能閃,如果你能控制LED閃爍,你就能控制LED的一切。一旦你學會了控制LED,控制其它電子設備也就是說手到擒來的事情。
Vixen
接下來我們來了解一下Vixen軟件,這是一款免費軟件。之前我曾經用Vixen 2開發過一個項目,但Vixen 2用起來老是崩潰,還好現在我們有Vixen 3版本了。另外必須注意的一點是Vixen 3支持的系統至少要是Windows 7,并不支持Windows XP。點擊這里下載。
Vixen 3的設置比Vixen 2要稍微復雜一點,你必須先摸索一下其將發光方式映射到輸出上的方式。(視頻上的解釋更容易懂一點,但是因為說的是英語,我們還是看看文字版吧。)

首先需要配置我們將會用到的發光元素。因為我們只有6個LED,所以我們只需要配置6個單獨的元素,其中每個元素都是可調節的燈光或設備,發光等級從0-255可調。

另外,你還可以設置調光曲線和顯示顏色。調光曲線很容易上手,因為許多LED更多是使用對數調光,而不是線性調光。你可以很輕松地設置設備的線性變化,讓它們的發光亮度在0-255之間均勻變化。


接下來我們需要設置音序器的輸出,這里需要用到通用串行端口輸出將數據輸出到串口,此處需要設置輸出的信道的數量。其將會通過串口為每一個信道一次輸出一個值。在本項目中,我們需要設置發光單元和信道的一一對應,所以我們需要創建6個信道。


然后設置串口參數。Vixen 3在這方面比Vixen 2進步不少,因為Vixen 2只能使用計算機的COM1-4,而Vixen 2則能夠自動檢測計算機的所有串口,并且還都可以使用。串口的設置相對標準,這里采用的參數是57600 bps, 8位, 無奇偶校驗和1位停止位。

接下來,配置音序器輸出發送頭文件,該文件是一個ASCII的“+>”,該頭文件能夠幫助軟件實現同步已避免出現掉幀的情況。最壞的情況下,在再次進行同步之前會掉2幀。如果我們設置的時序解析率是10微秒,那么就將出現20微秒的差異,這點時間人類根本無法感知,所以不用擔心。

最后,我們需要將LED和我們的輸出對應起來。因為我們采用的是一一對應的方式,設置方式就很簡單了,只需要高亮發光元素和信道,然后點擊Patch按鈕即可。完成之后,應該就能看到圖像化的一一對應示意圖了。


到這里,Vixen的設置就完成了。接下來就該設置新的序列了,點擊“Start”按鈕開始設置序列流。

然后點擊New Sequence按鈕,會彈出一個用于創建新序列的窗口,可以看到其中每一個LED對應的序列。

接下來該在時間上加入音頻信號了。

下面我用到了一個內置的節奏檢測器,可以自動根據音頻的節奏在時間線上自動進行標記,這讓我們的工作輕松不少。這一步并不是必需的,我們也可以進行手動編輯。


接下來,還需要為已經標記的重音設置表現效果。因為這些標記都是自動生成的,為他們設置好的表現效果看起來就像是精確控制的實時表現。你可以設置LED在重音點改變顏色、閃爍或者亮度突變。

Arduino
接下來是Arduino代碼部分。在編程之前,首先需要檢查串口出來的數據是什么格式的。不過這一步并不是必須的,只是說能夠加深我們對項目的理解,在書寫代碼時也更加方便。
我利用Arduino的代碼實現了一個狀態機,通過這個狀態機,我們可以很容易地實現串行協議的解碼。
#define MAX_CHANNELS 6
int ch;
int state;
int chVal[MAX_CHANNELS] = {0};
int pins[] = {2, 3, 4, 5, 6, 7};
enum states
{
IDLE,
DELIM,
READ,
DISP
};
void setup()
{
for (ch=0; ch<MAX_CHANNELS; ch++)
{
pinMode(pins[ch], OUTPUT);
digitalWrite(pins[ch], LOW);
}
state = IDLE;
ch = 0;
Serial.begin(57600);
}
void loop()
{
if (Serial.available())
{
switch (state)
{
case IDLE:
ch = 0;
if (Serial.read() == '+')
{
state = DELIM;
}
else
{
state = IDLE;
}
break;
case DELIM:
ch = 0;
if (Serial.read() == '>')
{
state = READ;
}
else
{
state = IDLE;
}
break;
case READ:
chVal[ch++] = Serial.read();
if (ch >= MAX_CHANNELS)
{
ch = 0;
state = DISP;
}
break;
case DISP:
state = IDLE;
for (ch=0; ch<MAX_CHANNELS; ch++)
{
if (chVal[ch] > 0)
{
digitalWrite(pins[ch], HIGH);
}
else
{
digitalWrite(pins[ch], LOW);
}
}
break;
}
}
}
這個狀態機擁有4個狀態,第一個狀態為空閑(IDLE)狀態,該狀態為默認狀態。在該狀態時,程序等待“+”符號,這是幀標記的起點。“+”進入后,狀態機進入第二個狀態DELIM。在此狀態下,程序等待第二個分隔符標記“>”,此符號到來后則幀開始運作。這樣做可以減少出現誤報幀的可能,畢竟按一定的序列出現兩個特定符號的可能性還是比較小的。
兩個符號齊備了之后,狀態機進入第三個狀態READ,此時讀取剩余的幀,并將其存儲到數組中。完成之后,狀態機過渡到DISP狀態。此時我們就要展示我們的數據了。在該狀態下,我們會循環數組中的每個數值,如果該數組是非零的,那么就將LED打開,否則就關閉LED。如果我們在這些引腳上再配置上PWM(脈沖寬度調制),就能實現對LED光亮度的控制。但為了教程的簡單,這里暫時只使用了簡單的開關功能。
上面也就基本上實現了Arduino對LED的控制。接下來我們將換用無線的方式來實現這樣的功能。
無線
在開發這個項目時我將Arduino換成了Freakduino,不過本質上都是一樣的。

這里我使用的是900 MHz大覆蓋面積版的Freakduino,這個版本的無線穿墻效果不錯,范圍也比較大。另外,900MHz頻段的干擾也比較少,很少WiFi具有這個頻段,在人多的地方進行表演時,這一點格外重要。
#include <chibi.h>
#define MAX_CHANNELS 6
#define MY_ADDR 5
#define DEST_ADDR 3
int ch;
int state;
byte chVal[MAX_CHANNELS] = {0};
enum states
{
IDLE,
DELIM,
READ,
DISP
};
void setup()
{
state = IDLE;
ch = 0;
chibiInit();
chibiSetShortAddr(MY_ADDR);
Serial.begin(57600);
}
void loop()
{
if (Serial.available())
{
switch (state)
{
case IDLE:
ch = 0;
if (Serial.read() == '+')
{
state = DELIM;
}
else
{
state = IDLE;
}
break;
case DELIM:
ch = 0;
if (Serial.read() == '>')
{
state = READ;
}
else
{
state = IDLE;
}
break;
case READ:
chVal[ch++] = Serial.read();
if (ch >= MAX_CHANNELS)
{
ch = 0;
state = DISP;
}
break;
case DISP:
state = IDLE;
chibiTx(DEST_ADDR, chVal, MAX_CHANNELS);
break;
}
}
}
首先讓我們來看看發送器代碼,這個代碼和上面有線版本的代碼大致類似。其中最主要的不同點是我們需chibiArduino來通過無線的方式來發送代碼。chibiArduino是一個無線協議棧,是我根據開放的IEEE 802.15.4標準編寫的,我已經在配置和特性上對其進行了很大程度的簡化,你只需要簡單地啟動它就可以實現數據的收發了。不過對一般用戶而言,使用什么樣的通信標準其實都沒有關系。
仔細研究一下代碼,你會發現其中還新定義了源地址和目標地址。源地址是我們自己的地址,而目標地址則是數據需要發送到的地址。
我也使用chibiInit()函數初始化了chibiArduino棧,這一步會將棧和寄存器設置成默認值,并使其為數據接收做好準備。chibiSetShortAddr()函數則可以根據我們設置的設備地址和其它設備建立通信。我們只需要設置短地址一次就可以了,然后改地址會被存儲到非易失性存儲器中。但在這個項目中,我們每一次開機都要對地址進行設置。
我們仍然使用Vixen的狀態機來實現對串行協議的解碼。在循環代碼中,在DISP狀態的主要不同在于我們不再循環數據數列和開關LED,我們將數列和通過無線的方式發送給接收器,這一步通過chibiTx()函數實現。chibiTx()含有三個參數:目標地址、以數列形式存儲的數據和數據的長度。
#include <chibi.h>
#define MAX_CHANNELS 6
#define MY_ADDR 3
#define DEST_ADDR 5
int pins[] = {2, 3, 4, 5, 6, 7};
int i;
void setup()
{
for (i=0; i<MAX_CHANNELS; i++)
{
pinMode(pins[i], OUTPUT);
digitalWrite(pins[i], LOW);
}
chibiInit();
chibiSetShortAddr(MY_ADDR);
Serial.begin(57600);
}
void loop()
{
int i;
// Check if any data was received from the radio. If so, then handle it.
if (chibiDataRcvd() == true)
{
int len, rssi, src_addr;
byte buf[100]; // this is where we store the received data
// retrieve the data and the signal strength
len = chibiGetData(buf);
// discard the data if the length is 0. that means its a duplicate packet
if (len == 0) return;
rssi = chibiGetRSSI();
src_addr = chibiGetSrcAddr();
// Print out the message and the signal strength
Serial.print("Data from node 0x");
Serial.print(src_addr, HEX);
Serial.print(": ");
for (i=0; i<len; i++)
{
if (buf[i] > 0)
{
digitalWrite(pins[i], HIGH);
}
else
{
digitalWrite(pins[i], LOW);
}
Serial.print(buf[i]);
Serial.print(" ");
}
Serial.print(", RSSI = 0x"); Serial.println(rssi, HEX);
}
}
接下來是接收端的代碼。在接收端我們需要接受發送端傳送的數據,并將這些數據通過LED表現出來。
同樣我們首先設置源地址和目標地址,然后我們初始化引腳和chibi棧。
在循環函數中,我們加入了一些新代碼。在循環函數中,首先會對數據的接受情況進行檢查,如果數據接受,則函數chibiDataRcvd()會返回“真”,然后就接受數據。為了接受數據,我們聲明了一個len變量,其存儲的是要接收的數據的字節長度以及一個100字節長度的字節數列。為什么要選擇100字節呢?因為chibiArduino棧的最大負載為100字節。你可以設置小一點以節約RAM。
然后chibiGetData()函數被作為字節數列的一個參數調用。其將會將接收到的數據逐個寫入到這個字節數列中,并返回接收數據的長度。另外,我們進行了一下長度檢查,以防止數據長度為0的情況。如果長度為0,則說明我們之前已經接受到過這個數據,這是一個重復的數據包。接收到的數據應該包含6個字節,接下來就是逐個字節檢查,并將對應的LED的亮度進行相應的調整。
另外我還在chibiArduino棧中額外增加了一些其它的數據,這些數據不是必需的,但是對調試等工作有很重要的幫助。比如記錄信號強度的變化,多個源地址可以從多個來源接收信號,同時也可以對可能的設備故障進行檢查,這在戶外應用時還是大有裨益的。
至此,對Arduino和Vixen實現對LED燈光的控制的介紹就完成了,趕快自己做一個來裝飾你的家吧。
2015-2016賽季全球創客馬拉松深圳大學站已經開始接受報名啦!關注“硬創邦”(微信號:leiphone_bang),回復“深大”即可參與報名!此外還可加入全球創客馬拉松主群(群號:259592983),參與我們的互動討論~

雷峰網原創文章,未經授權禁止轉載。詳情見轉載須知。