緣起:
正所謂坐而言不如起而行!平常看到部落格上的 LED 光劍教學,總是覺的超炫超酷,總是想要自幹一隻來玩,但也總是說說而以,並沒有真的動手去做,也就一直停留在嘴炮階段。就在某天閒晃於 FB 上時,看到一位網友問有關 arduino 不能燒錄的問題,剛好前陣子也遇過類似的問題,所以就很不要臉的協助解決,也沒想到真的解了網友的問題,重點是就這麼和對方結下了這段
完全讓我內心的小宇宙爆發了,當下決定希望能協助打造這把光劍,也就這樣,我和網友就一同攜手走向這光劍的不歸路。
第一階段:LED 的變化控制
一開始設定了四種效果,開劍,收劍,流水和呼吸效果。然後再以按鈕來切換這四種效果。起初以為很單純,再加上 arduino 很好編寫,很直覺的就把這四種效果和按鈕偵測寫好了。loop() { // 讀取按鈕狀態,狀態有改變擇進入下一階段 switch(階段) { case 開劍: 開劍效果 break; case 流水: 流水效果 break; case 呼吸: 呼吸效果 break; case 關劍: 關劍效果 break; } }
這份程式當然是有問題的,每種效果都會佔用一段時間,然後才讀取按鈕狀態,結果就是狀態切換非常不靈敏。所以,還是應該照正規的方式來做。
目標:固定時間偵測按鈕,每次 LED 的控制只佔非常小段的時。
方法:使用分時法來控制每個部份,再利用資料結構的方式來組合出不同的 LED 效果。
(由於程式碼太長,就不貼出來傷眼了。)
這個方式很明顯的解了按鈕靈敏度的問題,同時也能呈現 LED 的效果。所以在我測試完後,就把程式碼寄給網友,請他先行測試。
結果,網友那測出來的情況和我不同,他的第三種 LED 效果完全沒有產生!!!這就奇怪了,控制 LED 不就是把 GPIO 拉 High 或是拉 Low 啊,怎麼會我這裡測起來沒問題,但對方測起來就有問題勒?但又非常不想講工程師最常講的理由之首 "在我的機器上沒有問題啊" ,之一 "是你的環境有問題吧" ,之二 "重開機看看" .....Orz
好在我有記得 Jserv 大大的訓誡 "工程師就是要來解問題的" ,所以就火力全開的來解問題吧...
問題:同樣一份程式,在二個地方測起來結果不同
分析:二邊用的開發環境不同,我是用 arduino Mega2560,對方是用 arduino Uno。用相同的編譯環境。我用的是 5v 電壓,對方用的是 3v電壓。
推測:是電壓不同造成 cpu 出問題嗎?是不同的 cpu 特性,而有不同的結果嗎? (雖然覺的很蠢的推測,不過還是先測看看吧)
執行:把二邊的開發環境改成相同,至少先把問題複製出來。
由於我手頭上只有 atmega328 的晶片並沒有 arduino Uno ,不過,晶片和電壓先相同應該就比較接近了吧。所以就用 atmega328 和洞洞版,按照 arduino Uno 的線路圖,開始組起 arduino Uno 的環境。其間,由於沒有程式下載版,就上網買了顆 FT232RL 來組了一版程式下載版(這又是另一個故事了)。
在照圖施工後,
終於完成偽 arduino Uno 的開發環境了,接下來就是把那份程式載入晶片中,結果,果真如網友所說,LED 的第三種效果不會出現.....這倒底是怎麼一回事啊?
所以,就開始把程式一段一段的分解,一段一段的測試,最後,終於發現了,原來,是記憶體不夠啊..................啊................啊................
這一切的一切都是因為,我忘了這二顆晶片的記憶體容量不同,再加上這晶片的記憶體本來就不大,而我當初設計的資料結構,在宣告四種 LED 效果時用了太多的記憶體,而第三種效果剛好就是處於空間的最後部份,所以,就被切掉了....才會造成第三種效果出不來,因為,程式在讀取資料時,由於超出記憶體,所以都讀出零。
在確認問題後,就著手將程式寫法改成以有限記憶體的方式撰寫(本來就應該這樣寫才對啊),這四種 LED 效果直接改相對應程序,儘量不用廣域變數。在本地測試沒問題後,就寄給網友測試,結果當然是沒問題囉....
在網友的趕工下,絕地光劍出現了...
第二階段:絕地光劍胃口變大版
初心版光劍誕生後,就開始覺的孤單,似乎應該有音效,這樣才像絕地光劍啊....
所以,外卦版就開工了,在討論之後,覺的應該加上音效模組,加速計(用來偵測揮劍動作),振動偵測器(用來偵測碰撞)。這次我打算以更嚴謹的方式來執行,在確認線路後我先畫了線路圖。
緊接著就基於上一版,新增加速度計,振動偵測器,音效模組功能。在單獨功能測試沒問題後,就進行音效產生時間的調整,這一部份比較不好調,要調到揮動時能先產生揮劍音效,然後碰撞後再產生碰撞音效的順序真的很難,原因是振動偵測器在揮劍時就有可能會發出訊息,而此時揮劍的音效當未結束,就會有插斷音效的情況產生。在試了幾種方式後,暫時先以延遲偵測的方式來解決,不過,應該有更好的方式。
#include
//===== System Mode =====
enum {
SYSTEM_OFF = 0,
SYSTEM_ON,
SYSTEM_MODE_1,
SYSTEM_MODE_2,
TOTAL_SYSTEM_MODE
} SystemMode_t;
int currentIndex = 0;
int currentTime = 0;
int changeMode = 0;
//===== Polling Time ====
#define SYSTEM_POLLING_TIME 1
//===== Array Led ====
#define LED_ON LOW
#define LED_OFF HIGH
enum {
LED_MODE_BLINK = 0,
LED_MODE_GRADUAL,
LED_MODE_MULTI_GRADUAL,
TOTAL_LED_MODE
} LedMode_t;
#define ARRAY_LED_NUM 6
#define PWD_LED_PIN 4
int ArrayLed[ARRAY_LED_NUM] = {3, 5, 6, 9, 10, 11};
//===== Audio Module =====
#define AUDIO_RESET_PIN 13
#define AUDIO_MODE_PIN_1 1
#define AUDIO_MODE_PIN_2 7
#define AUDIO_MODE_PIN_3 8
#define AUDIO_MODE_PIN_4 12
enum {
AUDIO_MODE_OFF = 0,
AUDIO_MODE_1,
AUDIO_MODE_2,
AUDIO_MODE_3,
AUDIO_MODE_4,
TOTAL_AUDIO_MODE
} AudioMode_t;
//===== Button =====
#define BUTTON_PIN 2
int previousButtonState = HIGH;
int buttonCounter = 0;
//===== Accelerometer =====
#define Register_ID 0
#define Register_2D 0x2D
#define Register_X0 0x32
#define Register_X1 0x33
#define Register_Y0 0x34
#define Register_Y1 0x35
#define Register_Z0 0x36
#define Register_Z1 0x37
#define ADXAddress (0xA7 >> 1)
int reading = 0;
int val=0;
int X0,X1,X_out;
int Y0,Y1,Y_out;
int Z1,Z0,Z_out;
double Xg,Yg,Zg;
#define ACCELEROMETER_SENSOR_DELAY 50
int accelerometerCount = 0;
//===== Vibratormeter =====
#define VIBRATORMETER_PIN 0
#define VIBRATORMETER_SENSOR_DELAY 50
int vibratormeterCount = 0;
#define LED_ON_VALUE 255
#define LED_OFF_VALUE 100
#define LED_SC3_INTER_VALUE 10
#define LED_SC3_CONTINUANCE_VALUE 10
void LED_Scenario_2(int * currentIndex, int * currentTime)
{
int currentValue;
int totalTime;
totalTime = ((LED_ON_VALUE - LED_OFF_VALUE) / LED_SC3_INTER_VALUE) * LED_SC3_CONTINUANCE_VALUE;
switch((*currentIndex))
{
case 0:
// HIGH --> LOW
currentValue = LED_ON_VALUE - (LED_SC3_INTER_VALUE * (*currentTime / LED_SC3_CONTINUANCE_VALUE));
break;
case 1:
// LOW --> HIGH
currentValue = LED_OFF_VALUE + (LED_SC3_INTER_VALUE * (*currentTime / LED_SC3_CONTINUANCE_VALUE));
break;
}
for(int index = 0; index < ARRAY_LED_NUM; index++)
{
analogWrite(ArrayLed[index], currentValue);
}
(*currentTime) += SYSTEM_POLLING_TIME;
if(*currentTime >= totalTime)
{
*currentTime = 0;
(*currentIndex)++;
if((*currentIndex) >= 2) (*currentIndex) = 0;
}
}
#define LED_SC1_INTER_VALUE 5
#define LED_SC1_CONTINUANCE_VALUE 5
void LED_Scenario_1(int * currentIndex, int * currentTime)
{
int currentValue;
int totalTime;
totalTime = ((LED_ON_VALUE - LED_OFF_VALUE) / LED_SC1_INTER_VALUE) * LED_SC1_CONTINUANCE_VALUE;
switch((*currentIndex))
{
case 0: case 1: case 2: case 3: case 4: case 5:
// HIGH --> LOW
currentValue = LED_ON_VALUE - (LED_SC1_INTER_VALUE * (*currentTime / LED_SC1_CONTINUANCE_VALUE));
analogWrite(ArrayLed[*currentIndex], currentValue);
break;
case 6: case 7: case 8: case 9: case 10: case 11:
// LOW --> HIGH
currentValue = LED_OFF_VALUE + (LED_SC1_INTER_VALUE * (*currentTime / LED_SC1_CONTINUANCE_VALUE));
analogWrite(ArrayLed[((*currentIndex) - 6)], currentValue);
break;
}
(*currentTime) += SYSTEM_POLLING_TIME;
if(*currentTime >= totalTime)
{
*currentTime = 0;
(*currentIndex)++;
if((*currentIndex) >= 12) (*currentIndex) = 0;
}
}
#define LED_SC_ON_CONTINUANCE_VALUE 100
void LED_Scenario_on(int * currentIndex, int * currentTime)
{
if((*currentIndex) >= ARRAY_LED_NUM)
return;
if(changeMode == 1)
{
digitalWrite(PWD_LED_PIN, LED_ON);
for(int index = 0; index < ARRAY_LED_NUM; index++)
digitalWrite(ArrayLed[index], LED_OFF);
}
(*currentTime) += SYSTEM_POLLING_TIME;
if(*currentTime >= LED_SC_ON_CONTINUANCE_VALUE)
{
digitalWrite(ArrayLed[*currentIndex], LED_ON);
*currentTime = 0;
(*currentIndex)++;
}
}
#define LED_SC_OFF_CONTINUANCE_VALUE 100
void LED_Scenario_off(int * currentIndex, int * currentTime)
{
if((*currentIndex) >= ARRAY_LED_NUM)
{
digitalWrite(PWD_LED_PIN, LED_OFF);
return;
}
if(changeMode == 1)
{
for(int index = 0; index < ARRAY_LED_NUM; index++)
digitalWrite(ArrayLed[index], LED_ON);
}
(*currentTime) += SYSTEM_POLLING_TIME;
if(*currentTime >= LED_SC_OFF_CONTINUANCE_VALUE)
{
digitalWrite(ArrayLed[ARRAY_LED_NUM - (*currentIndex) - 1], LED_OFF);
*currentTime = 0;
(*currentIndex)++;
}
}
int readButtonState()
{
int ret = 0;
int buttonState = digitalRead(BUTTON_PIN); // read the pushbutton:
if ((buttonState != previousButtonState) // if the button state has changed,
&& (buttonState == HIGH)) { // and it's currently pressed:
ret = 1;
}
// save the current button state for comparison next time:
previousButtonState = buttonState;
return ret;
}
void playAudio(int mode)
{
digitalWrite(AUDIO_RESET_PIN, LOW);
digitalWrite(AUDIO_MODE_PIN_1, HIGH);
digitalWrite(AUDIO_MODE_PIN_2, HIGH);
digitalWrite(AUDIO_MODE_PIN_3, HIGH);
digitalWrite(AUDIO_MODE_PIN_4, HIGH);
switch(mode)
{
case AUDIO_MODE_1:
digitalWrite(AUDIO_MODE_PIN_1, LOW);
break;
case AUDIO_MODE_2:
digitalWrite(AUDIO_MODE_PIN_2, LOW);
break;
case AUDIO_MODE_3:
digitalWrite(AUDIO_MODE_PIN_3, LOW);
break;
case AUDIO_MODE_4:
digitalWrite(AUDIO_MODE_PIN_4, LOW);
break;
}
digitalWrite(AUDIO_RESET_PIN, HIGH);
}
void initAccelerometerSensor()
{
Wire.begin();
delay(100);
Wire.beginTransmission(ADXAddress);
Wire.write(Register_2D);
Wire.write(8); //measuring enable
Wire.endTransmission(); // stop transmitting
}
void getGData()
{
//--------------X
Wire.beginTransmission(ADXAddress); // transmit to device
Wire.write(Register_X0);
Wire.write(Register_X1);
Wire.endTransmission();
Wire.requestFrom(ADXAddress,2);
if(Wire.available()<=2)
{
X0 = Wire.read();
X1 = Wire.read();
X1 = X1 << 8;
X_out = X0 + X1;
}
//------------------Y
Wire.beginTransmission(ADXAddress); // transmit to device
Wire.write(Register_Y0);
Wire.write(Register_Y1);
Wire.endTransmission();
Wire.requestFrom(ADXAddress,2);
if(Wire.available()<=2)
{
Y0 = Wire.read();
Y1 = Wire.read();
Y1 =Y1 << 8;
Y_out = Y0 + Y1;
}
//------------------Z
Wire.beginTransmission(ADXAddress); // transmit to device
Wire.write(Register_Z0);
Wire.write(Register_Z1);
Wire.endTransmission();
Wire.requestFrom(ADXAddress,2);
if(Wire.available()<=2)
{
Z0 = Wire.read();
Z1 = Wire.read();
Z1 = Z1 << 8;
Z_out = Z0 + Z1;
}
//display the real value
Xg = X_out / 256.0;
Yg = Y_out / 256.0;
Zg = Z_out / 256.0;
}
void setup()
{
pinMode(BUTTON_PIN, INPUT);
pinMode(VIBRATORMETER_PIN, INPUT);
pinMode(PWD_LED_PIN, OUTPUT);
for(int index = 0; index < ARRAY_LED_NUM; index++)
pinMode(ArrayLed[index], OUTPUT);
pinMode(AUDIO_MODE_PIN_1, OUTPUT);
pinMode(AUDIO_MODE_PIN_2, OUTPUT);
pinMode(AUDIO_MODE_PIN_3, OUTPUT);
pinMode(AUDIO_MODE_PIN_4, OUTPUT);
pinMode(AUDIO_RESET_PIN, OUTPUT);
// initial Led
digitalWrite(PWD_LED_PIN, LED_OFF);
for(int index = 0; index < ARRAY_LED_NUM; index++)
digitalWrite(ArrayLed[index], LED_OFF);
// initial audio pin
digitalWrite(AUDIO_MODE_PIN_1, HIGH);
digitalWrite(AUDIO_MODE_PIN_2, HIGH);
digitalWrite(AUDIO_MODE_PIN_3, HIGH);
digitalWrite(AUDIO_MODE_PIN_4, HIGH);
digitalWrite(AUDIO_RESET_PIN, HIGH);
// initial accelerometer sensor
initAccelerometerSensor();
}
void loop()
{
int scenarioIndex = 0;
// Read button state
if (readButtonState() == 1)
{
buttonCounter++;
// increment the button counter
if(buttonCounter >= TOTAL_SYSTEM_MODE) buttonCounter = 0;
currentIndex = 0;
currentTime = 0;
changeMode = 1;
vibratormeterCount = 0;
accelerometerCount = 0;
}
// Scenario Loop
switch(buttonCounter)
{
case SYSTEM_OFF:
LED_Scenario_off(¤tIndex, ¤tTime);
if (changeMode == 1) playAudio(AUDIO_MODE_1);
break;
case SYSTEM_ON:
LED_Scenario_on(¤tIndex, ¤tTime);
if (changeMode == 1) playAudio(AUDIO_MODE_2);
break;
case SYSTEM_MODE_1:
LED_Scenario_1(¤tIndex, ¤tTime);
break;
case SYSTEM_MODE_2:
LED_Scenario_2(¤tIndex, ¤tTime);
break;
}
// Accelerometer sensor
if(buttonCounter != SYSTEM_OFF)
{
accelerometerCount++;
if(accelerometerCount > ACCELEROMETER_SENSOR_DELAY)
{
getGData();
if((Xg > 1) || (Yg > 1) || (Zg > 1))
{
playAudio(AUDIO_MODE_4);
vibratormeterCount = 0;
}
accelerometerCount = 0;
}
}
// Vibratormeter sensor
if(buttonCounter != SYSTEM_OFF)
{
vibratormeterCount++;
if(vibratormeterCount > VIBRATORMETER_SENSOR_DELAY)
{
if(digitalRead(VIBRATORMETER_PIN) == 1)
playAudio(AUDIO_MODE_3);
vibratormeterCount = 0;
}
}
changeMode = 0;
delay(SYSTEM_POLLING_TIME);
}
//=========================================================
再來就是振動偵測器的問題,不管怎麼調偵測器的靈敏度,就是沒辦法達到只有碰撞發生時才會有訊號發生,我想,這部份應該不能用振動器來當碰撞偵測訊號 (不知道用加速度計來偵測行不行)。
等絕地光劍外掛音效版組裝完成,再來貼完整的照片及短片....
心得:
這次是我第一次與網友合作 (還跨南北二地) ,除了白天都要上班,假日都要顧小孩之外,各自找時間完成這個案子,過程中遇到了很多奇怪的問題,再加上南北二地以及時間不容易同時有空,原本很單純的題目,做起來格外的辛苦。不過,也特別有成就感。
我們利用了 Gmail, FB, Youtube 來協助這個案子的開發,利用這些軟體,使身在南北二地的我們可以一同討論,研究,打屁,解決問題,以至完成案子(目前在收尾了)。接下來,可以考慮來搞大一點的案子了...哈哈...
===2013-10-10===
收到網友組裝好的光劍了....一大二小....超酷的啊..
比外面賣的質感好太多了,網友真的是不惜成本啊...
質感啊...
簡單的線條和那紅光和藍光...
硬體製作過程請移駕 阿敏的玩具模型世界
收到網友組裝好的光劍了....一大二小....超酷的啊..
比外面賣的質感好太多了,網友真的是不惜成本啊...
質感啊...
簡單的線條和那紅光和藍光...
硬體製作過程請移駕 阿敏的玩具模型世界
您好,
回覆刪除拜讀了此篇超帥的光劍製作文章,讓小弟羨慕不已!
最近小弟也開始自行製作光劍, 希望能夠加入聲光效果,讓光劍的功能更加齊全帥氣,
由您與其他人的部落格知道可以使用arduino來控制LED光與聲音,開始也希望能藉由arduino來實現有聲光效果的光劍, 由於我還是剛接觸arduino的初學者,參考網路上有各別單獨的伸縮電路與呼吸電路的程式碼,但是沒有合在一起(伸縮+呼吸)的程式碼可以參考, 關於兩種功能合併的部份不知從何著手。
當然最終的目的也希望能夠像您一樣同時具有伸縮+呼吸+多軸+敲擊+聲音多功能的效果, 對於初學者的我參考您文章內的程式碼,實在是有看沒有懂,哈哈!
如果小弟先將網路上各別找到的呼吸與伸縮電路的程式碼,合在一起的話,有什麼需要特別注意的地方呢?還是我只要將此兩種程式碼合在一起就可以嗎?(我使用的模組是arduino pro mini)
不好意思,冒昧打擾您請教一些問題
謝謝
作者已經移除這則留言。
回覆刪除請問,LED有另外的驅動電路嗎?arduino能驅動這麼多的LED?
回覆刪除你好 請教一 下 震動開關的程式 是如何起動聲音的 謝謝
回覆刪除你可以參考一下 void playAudio(int mode) 這隻,因為我用的是音效模組,所以是用GPIO 來控制要播的音效
刪除你好 請問三軸加速器是用哪個型號的 ? 謝謝 !
回覆刪除ADXL345 三軸加速度計
刪除你好 請問SYSTEM_POLLING_TIME 1 設定1 ? 謝謝
回覆刪除是的
刪除你好 請問 WT588D 這顆接腳接哪幾腳位 可以告知一下嗎 ? 謝謝 !
回覆刪除你好 使用 PRO MINI 輸出入腳位 好像會不夠 2個 請問是如何解決 ? 謝謝 !
回覆刪除不好意思 D0 D1 忘了算進去
回覆刪除