搜尋此網誌

2013年9月24日 星期二

土炮絕地光劍外掛音效版

緣起:

正所謂坐而言不如起而行!平常看到部落格上的 LED 光劍教學,總是覺的超炫超酷,總是想要自幹一隻來玩,但也總是說說而以,並沒有真的動手去做,也就一直停留在嘴炮階段。


就在某天閒晃於 FB 上時,看到一位網友問有關 arduino 不能燒錄的問題,剛好前陣子也遇過類似的問題,所以就很不要臉的協助解決,也沒想到真的解了網友的問題,重點是就這麼和對方結下了這段 孽緣 緣份。原來他是想土炮一隻光劍,打算用 arduino uno 來控制 LED ,營造出炫麗的效果。這下子又勾起我那光劍的夢想,再加上該強大的網友打造的劍把...







完全讓我內心的小宇宙爆發了,當下決定希望能協助打造這把光劍,也就這樣,我和網友就一同攜手走向這光劍的不歸路。


第一階段: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(&currentIndex, &currentTime);
  if (changeMode == 1) playAudio(AUDIO_MODE_1);
  break;
 case SYSTEM_ON:
  LED_Scenario_on(&currentIndex, &currentTime);
  if (changeMode == 1) playAudio(AUDIO_MODE_2);
  break;
 case SYSTEM_MODE_1:
  LED_Scenario_1(&currentIndex, &currentTime);
  break;
 case SYSTEM_MODE_2:
  LED_Scenario_2(&currentIndex, &currentTime);
  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===
收到網友組裝好的光劍了....一大二小....超酷的啊..


比外面賣的質感好太多了,網友真的是不惜成本啊...

質感啊...

簡單的線條和那紅光和藍光...


硬體製作過程請移駕 阿敏的玩具模型世界



12 則留言:

  1. 您好,
    拜讀了此篇超帥的光劍製作文章,讓小弟羨慕不已!
    最近小弟也開始自行製作光劍, 希望能夠加入聲光效果,讓光劍的功能更加齊全帥氣,
    由您與其他人的部落格知道可以使用arduino來控制LED光與聲音,開始也希望能藉由arduino來實現有聲光效果的光劍, 由於我還是剛接觸arduino的初學者,參考網路上有各別單獨的伸縮電路與呼吸電路的程式碼,但是沒有合在一起(伸縮+呼吸)的程式碼可以參考, 關於兩種功能合併的部份不知從何著手。
    當然最終的目的也希望能夠像您一樣同時具有伸縮+呼吸+多軸+敲擊+聲音多功能的效果, 對於初學者的我參考您文章內的程式碼,實在是有看沒有懂,哈哈!
    如果小弟先將網路上各別找到的呼吸與伸縮電路的程式碼,合在一起的話,有什麼需要特別注意的地方呢?還是我只要將此兩種程式碼合在一起就可以嗎?(我使用的模組是arduino pro mini)
    不好意思,冒昧打擾您請教一些問題
    謝謝

    回覆刪除
  2. 作者已經移除這則留言。

    回覆刪除
  3. 請問,LED有另外的驅動電路嗎?arduino能驅動這麼多的LED?

    回覆刪除
  4. 你好 請教一 下 震動開關的程式 是如何起動聲音的 謝謝

    回覆刪除
    回覆
    1. 你可以參考一下 void playAudio(int mode) 這隻,因為我用的是音效模組,所以是用GPIO 來控制要播的音效

      刪除
  5. 你好 請問三軸加速器是用哪個型號的 ? 謝謝 !

    回覆刪除
  6. 你好 請問SYSTEM_POLLING_TIME 1 設定1 ? 謝謝

    回覆刪除
  7. 你好 請問 WT588D 這顆接腳接哪幾腳位 可以告知一下嗎 ? 謝謝 !

    回覆刪除
  8. 你好 使用 PRO MINI 輸出入腳位 好像會不夠 2個 請問是如何解決 ? 謝謝 !

    回覆刪除
  9. 不好意思 D0 D1 忘了算進去

    回覆刪除