2012/02/15

Arduino練習:以開關切換LED明滅狀態

之前已經把Arduino的硬體與軟體安裝設定好、閃爍LED,然後寫程式透過序列埠輸出訊息到電腦上,這一篇是關於讀取開關(按鈕)的狀態,並以之切換LED的明滅。

電路圖(Fritzing格式)與程式原始碼,可在此下載

讀取開關的狀態:

電路圖如下:

從Arduino板子上的5V腳位、GND腳位,接線到麵包板上。
瞬時型開關(momentary switch)的右上腳接到Arduino板子的腳位2,我們將以這個腳位讀取開關的狀態,開關的左下腳連接5V,開關的右下腳連接10k ohm電阻後接地。



當開關處於平常狀態時,右上腳與右下腳是接在一起的,但左右兩邊沒有連接,此時讀取開關的狀態,會讀到LOW低電壓。按下開關時,左右兩邊會接在一起,就會讀到HIGH高電壓。

程式碼如下:
void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
}

void loop(){
  int switchStatus = digitalRead(2);
  Serial.println(switchStatus);
}
首先在setup()裡,設定序列埠傳輸速度,然後以pinMode()設定腳位2的模式,這裡設為INPUT模式,以讀取開關的狀態。(後面將把另一個腳位設為OUTPUT模式,藉以控制LED的明滅。)

然後在loop()裡,以digitalRead()讀取腳位2的狀態,會讀到LOW或HIGH,代表開關的狀態。並輸出到序列埠。

開啟Tools-Serial Monitor可看到執行結果,開關平常狀態為0(LOW),按下後為1(HIGH),因為這是個瞬時型開關,放開手後會回到平常狀態。


在Arduino Uno板子上,有14個數位腳位(digital pin),標示著0、1、2、3、一直到13,這些腳位可以用pinMode()設定為INPUT模式或OUTPUT模式,然後可以用digitalRead()讀取電壓狀態LOW或HIGH,可用digitalWrite()函式設定電壓LOW或HIGH。

使用數位腳位進行讀取時,有一點要注意,以上面這個例子來說,如果沒有加上10k ohm電阻,那麼,當開關處於平常狀態,此時若讀取腳位2的話,或讀到環境雜訊(亂數,一下子是HIGH,一下子是LOW)。你可以把電阻拿掉,看看結果。當你看到LED亂閃時,可以檢查看看電路、開關、電阻是不是鬆了。

好,到這裡已經可以讀取開關的狀態了,接下來讓我們以之控制LED的明滅。

以開關控制LED明滅:

加裝一個LED,長腳插在13腳位,短腳插在GND,其餘跟之前一樣。

程式碼如下:
void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
}

void loop(){
  int switchStatus = digitalRead(2);
  digitalWrite(13, switchStatus);
  Serial.println(switchStatus);
}
以pinMode()將腳位13設為OUTPUT模式,然後,讀取到開關狀態後,以digitalWrite()更新LED的明滅。

平常時,LED滅掉,按下開關才會亮起來,但放開手後LED又會滅掉。

以開關切換LED明滅狀態:

讓我們繼續修改,我希望按一下開關可點亮LED,手放開後保持著明亮狀態,然後再按一下開關,LED才會滅掉,以此類推。

程式碼如下:
static int ledStatus;

void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
 
  ledStatus = LOW;
  digitalWrite(13, ledStatus);
}

void loop(){
  int switchStatus = digitalRead(2);
  if(switchStatus == HIGH){
    ledStatus = ledStatus == HIGH ? LOW : HIGH;
    digitalWrite(13, ledStatus);
  }
 
  Serial.println(ledStatus);
}
以全域靜態變數ledStatus記錄LED的明滅狀態。在loop()裡,若發現按下開關(switchStatus == HIGH),就切換LED的明滅。

聽起來很簡單,寫起來很直覺,可惜,實際動起來後,會很奇怪。點按開關後,有時LED會亮、有時不亮,很奇怪。

解決bounce:

這是因為,開關有所謂的bounce問題,我們人類按一下開關,感覺是切換一次,可是在微觀的電子元件裡,可能會造成好幾次的開、關狀態變換,而且,對人類來說,按一下開關不過是一瞬間的事情,但是這段時間裡,loop函式不知道已經被執行多少次了,也就是說,已經讀取好幾次腳位2的值。用底下程式碼可以更清楚問題所在。

static int ledStatus;

static int count = 0;

void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
 
  ledStatus = LOW;
  digitalWrite(13, ledStatus);
}

void loop(){
  int switchStatus = digitalRead(2);
  if(switchStatus == HIGH){
    ledStatus = ledStatus == HIGH ? LOW : HIGH;
    digitalWrite(13, ledStatus);
   
    count++;
  }
 
  Serial.println(ledStatus);
  Serial.println(count);
}
你會發現,按一下開關,count不只會往上加一,可能會好幾十、幾百。

那麼要怎麼解決bounce問題呢?那就要多寫一些程式碼了。

static int ledStatus;
static unsigned long lastDebounceTime;
#define DEBOUNCE_DELAY 200

void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
 
  ledStatus = LOW;
  digitalWrite(13, ledStatus);
}

void updateLed(){
  unsigned long currentTime = millis();
  if((currentTime - lastDebounceTime) > DEBOUNCE_DELAY){
    lastDebounceTime = currentTime;
  
    ledStatus = ledStatus == HIGH ? LOW : HIGH;
    digitalWrite(13, ledStatus);
    Serial.println(ledStatus);
  }
}

void loop(){
  int switchStatus = digitalRead(2);
 
  if(switchStatus == HIGH){
    updateLed();
  }
}
以全域靜態變數ledStatus記錄LED的明滅狀態。

每當執行loop()時,若腳位2處於HIGH狀態(開關被按下),就執行updateLed()函式。若腳位2處於LOW狀態,就不作事(LED狀態不變)。

在updateLed()裡,首先以Arduino內建函式millis()取得時間放在currentTime變數裡,這是Arduino板子開始執行程式後所經過的時間,單位為千分之一秒,回傳的型別是個unsigned long,所以最多大約是50天,超過後就會溢位回到0開始算。

全域靜態變數lastDebounceTime代表的是,上次切換LED狀態的時間。將currentTime目前時間,減去lastDebounceTime後,若大於DEBOUNCE_DELAY的話,代表距離上次切換LED狀態,已經經過一段夠久的時間了,所以繼續往下執行,先把currentTime存到lastDebounceTime(因為我們將要切換LED狀態了,把時間記錄下來),然後切換LED狀態(亮變滅、滅變亮)。

哇,成功啦,按一下開關,LED亮起,再按一下,LED滅掉,好耶。

如果你的情況仍不完美的話,請試試調整DEBOUNCE_DELAY的值,當我用50或100時,仍無法避免bounce,調到200才ok。你可以繼續往上調,但是,這個數值代表在那一段時間內,都無法切換LED狀態,譬如假設改成3000(三秒)的話,按下開關點亮LED後,這時怎麼按開關都沒反應,要經過三秒後,按下開關才能滅掉LED。

利用別人寫好的程式庫解決bounce:

好累喔,用開關切換LED,怎麼這麼累啊,還好,已經有人把解決bounce的程式寫好了,我們把程式庫拿來用即可。

有套Bounce library for Arduino,下載Bounce.zip,將解壓縮後的目錄,放到Arduino的Sketchbook目錄的子目錄libraries下。先以File-Preferences,查出Sketchbook目錄的路徑所在,底下若無libraries這個子目錄,請自行建立,然後把Bounce.zip解壓縮後的整個目錄放進去。

2014.03.01更新:原網址已經變成新版Bounce2,所以請到GitHub justjoheinz / Bounce下載這篇文章所使用的Bounce程式庫。

(Windows的Sketchbook目錄大概會位於C:\Documents and Settings\My Documents\Arduino,若是Mac,位於家目錄下的Documents/Arduino/,若是Linux,則是家目錄下的sketchbook目錄。)

然後重新啟動Arduino軟體開發環境。

要使用這套程式庫,請選Sketch-Import Library...-Bounce,匯入標頭檔#include "Bounce.h"。(如果在Sketch-Import Library...底下沒看到Bounce,代表你目錄放錯位置了。)

改寫後的程式如下:
#include <Bounce.h>

Bounce bouncer = Bounce(2, 50);
static int ledStatus = LOW;

void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
}

void loop(){
  if(bouncer.update() == true && bouncer.read() == HIGH) {
    ledStatus = ledStatus == HIGH ? LOW : HIGH;
    digitalWrite(13, ledStatus);
  }
}
首先建立Bounce物件,第一個參數是腳位,第二個參數是延遲時間。

然後,以update()更新腳位的狀態,若回傳true,代表腳位的狀態有變(從HIGH到LOW,或從LOW到HIGH)。

然後,以read()讀取更新後的腳位狀態。

在程式進行判斷,若腳位狀態有變(bouncer.update() == true),並且,腳位狀態為高電壓(bouncer.read() == HIGH),那麼就切換LED明滅。

成功啦。


以下為2012/08/25新增內容。

行為:開關處於平常狀態時,LED滅掉;手指按著開關時,LED亮起;但放開手指後LED就立刻滅掉,另外,按著開關超過5秒的話,LED也會滅掉。

修改後的程式碼如下:

#include <Bounce.h>

Bounce bouncer = Bounce(2, 50);
static int ledStatus = LOW;

void setup(){
  Serial.begin(115200);
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13, ledStatus);
}

void loop(){
  bouncer.update();
  if(bouncer.read() == HIGH) {
    ledStatus = bouncer.duration() >= 5000 ? LOW : HIGH;
  }
  else{
    ledStatus = LOW;
  }

  digitalWrite(13, ledStatus);
}

每次進入loop時,就呼叫bouncer.update()更新開關讀取狀態,若開關狀態為HIGH(代表按下開關),繼續呼叫bouncer.duration()做判斷,此函式回傳的是該開關處於該狀態多長時間了,單位為千分之一秒,若超過5秒,就滅掉LED,若未足5秒,就點亮LED;若開關狀態為LOW(沒有按下開關),就滅掉LED。


參考資料:

258 comments:

  1. 你好我想請問一下開關平常狀態為0,按下後為1也就是放開手會回到平常狀態,那我想設計例如開關連續按著超過5秒它會自動回復0(LOW),要怎麼去設定開關按著的時間。

    ReplyDelete
  2. to dg101:
    我已經更新文章了,不知道有無符合你的需求?

    ReplyDelete
    Replies
    1. 太感謝了,受益良多

      Delete
  3. 你好 不好意思我剛接觸程式,想請問您ledStatus = ledStatus == HIGH ? LOW : HIGH; 是什麼意思,有點不太懂

    ReplyDelete
  4. 那一行程式的意思就等同於底下的程式:

    if(ledStatus == HIGH){
    ledStatus = LOW;
    }
    else{
    ledStatus = HIGH;
    }

    其作用為切換ledStatus的狀態(HIGH與LOW)。

    ReplyDelete
  5. 你好 , 我的Arduino也是按實開關燈才能開 ....

    http://farm9.staticflickr.com/8181/8023487010_61dbff24a5_b.jpg

    ReplyDelete
  6. 程序如下

    // This code turns a led on/off through a debounced switch

    #include

    // This code turns a led on/off through a debounced button
    // Build the circuit indicated here: http://arduino.cc/en/Tutorial/Button

    #define BUTTON 2
    #define LED 13

    // Instantiate a Bounce object with a 5 millisecond debounce time
    Bounce bouncer = Bounce( BUTTON, 50 );

    void setup() {
    pinMode(BUTTON,INPUT);
    pinMode(LED,OUTPUT);
    }

    void loop() {
    // Update the debouncer
    bouncer.update ( );

    // Get the update value
    int value = bouncer.read();



    // Turn on or off the LED
    if ( value == HIGH) {
    digitalWrite(LED, HIGH );
    } else {
    digitalWrite(LED, LOW );
    }

    }

    ReplyDelete
  7. 想請問一下如何改成多組按鈕控制呢?

    ReplyDelete
    Replies
    1. 請再說明清楚一點。

      應該是加入多個按鈕,修改程式碼,檢查所有開關的狀態,然後根據開關狀態與需求控制LED。

      Delete
    2. 是的,就是以多組按鈕去控制LED明亮,不知道該如何做修改,不好意思。

      Delete
    3. 先把開關硬體線路接好,然後運用文章裡的Bounce這套程式庫,原本程式裡有這一行
      if(bouncer.update() == true && bouncer.read() == HIGH)

      一樣畫葫蘆,建立多個Bounce物件,並在loop裡取得每個開關的狀態,
      然後根據需求,執行你想要的行為。

      Delete
    4. 好的,謝謝,我試試看。

      Delete
    5. #include

      Bounce bouncer = Bounce(2, 50);
      static int ledStatus = LOW;

      void setup(){
      Serial.begin(115200);
      pinMode(2, INPUT);
      pinMode(13, OUTPUT);
      digitalWrite(13, ledStatus);
      }

      void loop(){
      bouncer.update();
      if(bouncer.read() == HIGH) {
      ledStatus = bouncer.duration() >= 5000 ? LOW : HIGH;
      }
      else{
      ledStatus = LOW;
      }

      digitalWrite(13, ledStatus);
      }


      Bounce bouncer = Bounce(2, 50);
      static int ledStatus = LOW;

      void setup(){
      Serial.begin(115200);
      pinMode(3, INPUT);
      pinMode(12, OUTPUT);
      digitalWrite(12, ledStatus);
      }

      void loop(){
      bouncer.update();
      if(bouncer.read() == HIGH) {
      ledStatus = bouncer.duration() >= 5000 ? LOW : HIGH;
      }
      else{
      ledStatus = LOW;
      }

      digitalWrite(12, ledStatus);
      }


      是這樣嗎??
      可是這行 Bounce bouncer = Bounce(2, 50);
      會跳錯誤 該如何修正

      Delete
  8. 不是。
    為什麼有兩個setup()與兩個loop()?
    重複定義了Bounce bouncer = Bounce(2, 50);,所以有錯誤。

    ReplyDelete
    Replies
    1. #include

      #include

      Bounce bouncer = Bounce(2, 50);
      static int ledStatus = LOW;

      void setup(){
      Serial.begin(115200);
      pinMode((2,3), INPUT);
      pinMode((13,12), OUTPUT);
      digitalWrite((13,12), ledStatus);
      }

      void loop(){
      bouncer.update();
      if(bouncer.read() == HIGH) {
      ledStatus = bouncer.duration() >= 5000 ? LOW : HIGH;
      }
      else{
      ledStatus = LOW;
      }

      digitalWrite(13, ledStatus);

      bouncer.update();
      if(bouncer.read() == HIGH) {
      ledStatus = bouncer.duration() >= 5000 ? LOW : HIGH;
      }
      else{
      ledStatus = LOW;
      }

      digitalWrite(12, ledStatus);
      }

      修改成這樣,程式能順利Verify,但是燈號一直亮著。
      是哪裡還有錯誤呢?
      不好意思

      Delete
    2. 這樣能順利Verify?
      pinMode((2,3), INPUT);語法不對吧?

      試試看這樣吧,我假設你的開關接線與我文章內的接法相同,平時為LOW,按下為HIGH。
      兩個開關,各自獨立控制一個LED。
      #include
      Bounce bouncer1 = Bounce(2, 50);
      Bounce bouncer2 = Bounce(3, 50);
      static int ledStatus1 = LOW;
      static int ledStatus2 = LOW;
      void setup(){
      Serial.begin(115200);
      pinMode(2, INPUT);
      pinMode(3, INPUT);
      pinMode(12, OUTPUT);
      pinMode(13, OUTPUT);
      digitalWrite(12, ledStatus1);
      digitalWrite(13, ledStatus2);
      }
      void loop(){
      if(bouncer1.update() == true && bouncer1.read() == HIGH) {
      ledStatus1 = ledStatus1 == HIGH ? LOW : HIGH;
      digitalWrite(12, ledStatus1);
      }
      if(bouncer2.update() == true && bouncer2.read() == HIGH) {
      ledStatus2 = ledStatus2 == HIGH ? LOW : HIGH;
      digitalWrite(13, ledStatus2);
      }
      }

      Delete
    3. 好的,謝謝。
      我也有用另外一個語法寫出來了,多謝指導!!

      Delete
  9. 大大您好
    我想要寫一組
    可以用遙控器的不同按鈕
    例如1 2來控制不同的LED讓LED亮
    但是程式碼一直寫不出來
    可以請大大提供一些意見嗎??

    ReplyDelete
    Replies
    1. // 顯示紅外線協定與訊號
      #include // 引用 IRRemote 函式庫

      const int irReceiverPin = 2; // 紅外線接收器 OUTPUT 訊號接在 pin 2
      const int A=4,B=5;
      IRrecv irrecv(irReceiverPin); // 定義 IRrecv 物件來接收紅外線訊號
      decode_results results; // 解碼結果將放在 decode_results 結構的 result 變數裏

      void setup()
      {
      Serial.begin(9600); // 開啟 Serial port, 通訊速率為 9600 bps
      irrecv.enableIRIn(); // 啟動紅外線解碼
      pinMode(irReceiverPin, INPUT);
      pinMode(A, OUTPUT);
      pinMode(B, OUTPUT);
      }

      // 顯示紅外線協定種類
      void showIRProtocol(decode_results *results)
      {
      Serial.print("Protocol: ");

      // 判斷紅外線協定種類
      switch(results->decode_type) {
      case NEC:
      Serial.print("NEC");
      break;
      case SONY:
      Serial.print("SONY");
      break;
      case RC5:
      Serial.print("RC5");
      break;
      case RC6:
      Serial.print("RC6");
      break;
      default:
      Serial.print("Unknown encoding");
      }

      // 把紅外線編碼印到 Serial port
      Serial.print(", irCode: ");
      Serial.print(results->value, HEX); // 紅外線編碼
      Serial.print(", bits: ");
      Serial.println(results->bits); // 紅外線編碼位元數
      }

      void loop()
      {
      if (irrecv.decode(&results)) { // 解碼成功,收到一組紅外線訊號
      showIRProtocol(&results); // 顯示紅外線協定種類
      IRaction(results.value);
      irrecv.resume();
      } // 繼續收下一組紅外線訊號
      if(results.value ==2028877){
      digitalWrite(A, HIGH);
      }
      if(results.value ==2027887){
      digitalWrite(B, HIGH);
      }
      }

      Delete
    2. 看起來好像沒問題,2028877跟2027887是不是錯了?
      要加上0x?

      Delete
    3. 但是讀取程式時IRaction(results.value);這行卻出錯
      請問ox是什麼??

      Delete
    4. IRaction是什麼?

      0x代表十六進位。

      Delete
    5. IRaction是我參考別人的文章 接收到紅外線之後的動作

      Delete
    6. 使用
      Serial.print(results->value, HEX); // 紅外線編碼
      的話,印出來的編碼是HEX十六進位。

      如果IRaction(results.value);已經會根據紅外線編碼進行動作,
      那麼為什麼還要
      if(results.value ==2028877){
      digitalWrite(A, HIGH);
      }
      if(results.value ==2027887){
      digitalWrite(B, HIGH);
      }
      }
      呢?

      Delete
    7. > IRaction(results.value);這行卻出錯

      咳,我沒有原始碼,沒辦法除錯。

      Delete
    8. 阿 我真傻 中間這串
      // 顯示紅外線協定種類
      void showIRProtocol(decode_results *results)
      {
      Serial.print("Protocol: ");

      // 判斷紅外線協定種類
      switch(results->decode_type) {
      case NEC:
      Serial.print("NEC");
      break;
      case SONY:
      Serial.print("SONY");
      break;
      case RC5:
      Serial.print("RC5");
      break;
      case RC6:
      Serial.print("RC6");
      break;
      default:
      Serial.print("Unknown encoding");
      }

      // 把紅外線編碼印到 Serial port
      Serial.print(", irCode: ");
      Serial.print(results->value, HEX); // 紅外線編碼
      Serial.print(", bits: ");
      Serial.println(results->bits); // 紅外線編碼位元數
      }

      貌似根本不需要...
      繼續測試中...

      Delete
    9. // 顯示紅外線協定與訊號
      #include // 引用 IRRemote 函式庫

      const int irReceiverPin = 2; // 紅外線接收器 OUTPUT 訊號接在 pin 2
      const int ledPinA=4;
      const int ledPinB=5;
      IRrecv irrecv(irReceiverPin); // 定義 IRrecv 物件來接收紅外線訊號
      decode_results results; // 解碼結果將放在 decode_results 結構的 result 變數裏

      void setup()
      {
      irrecv.enableIRIn(); // 啟動紅外線解碼
      pinMode(irReceiverPin, INPUT);
      pinMode(ledPinA, OUTPUT);
      pinMode(ledPinB, OUTPUT);
      }


      void loop()
      {
      if (irrecv.decode(&results)) { // 解碼成功,收到一組紅外線訊號
      irrecv.resume();
      } // 繼續收下一組紅外線訊號
      if(results.value ==0x2028877){
      digitalWrite(ledPinA, HIGH);
      }
      if(results.value ==0x2027887){
      digitalWrite(ledPinB, HIGH);
      }
      else{
      digitalWrite(ledPinA, LOW);
      digitalWrite(ledPinB, LOW);
      }
      }


      大大您好
      這是我最後測試出來的程式碼
      LED已經可以透過遙控器來控制哪顆燈亮
      但是實際情況就是如果只很快地按一下遙控器(紅外線只接收到一次訊號)
      LED就會持續亮
      如果按著遙控器
      LED就會只閃一下然後就不亮了

      請問我該怎麼修改才可以
      1.按著遙控器LED就持續亮 放開就不亮
      2.接收到訊號後讓LED閃爍個幾秒 然後暗掉

      或是想請問有沒有比較好的建議

      Delete
    10. 每家的遙控器都不一樣,可以在irrecv.decode(&results)之後,
      將results的value、bits、decode_type輸出到序列埠,觀察一下,

      譬如說,某家的遙控器,按著某按鍵的話,會先發射0x2028877(舉例),若繼續按著該鍵不放,則會繼續發射0xFFFFFFFF。

      想要「1.按著遙控器LED就持續亮 放開就不亮」的話,我想到的作法如下:
      以全域變數unsigned long x;記錄收到的results.value,
      根據x點亮相對應的LED,
      以全域變數unsigned long timeNow; 記錄當時的millis(),
      LED持續亮著,
      每次執行loop()時,呼叫millis(),若millis() - timeNow > 300(譬如),就熄滅LED,
      若在那之前又收到紅外線碼,若是別的紅外線碼,熄滅先前的LED,點亮相對應的LED,並更新timeNow,
      若收到的是0xFFFFFFFF,代表同樣的按鍵被按著不放,持續亮起同樣的LED,更新timeNow。

      上述流程應該能達到「1.按著遙控器LED就持續亮 放開就不亮」的需求,但要先確定你的紅外線遙控器持續按著按鍵不放的行為。

      至於「2.接收到訊號後讓LED閃爍個幾秒 然後暗掉」,不能直接使用delay,那會停住整支程式。咳咳,請您自己想一想吧,可以運用「有限狀態機 finite state machine」的程式設計技巧。:D

      Delete
    11. 大大您好請問可以跟您取得連絡方式嗎?
      想寄影片給您

      Delete
    12. 咳咳,您可以上傳到youtube,然後留下網址。

      Delete
    13. 葉大您好

      這是我們的程式碼

      這是發射端的
      #include
      const int buttonPinA = 4;
      const int buttonPinB = 5;
      int buttonStateA = 0;
      int buttonStateB = 0;
      IRsend irsend; // 定義 IRsend 物件來發射紅外線訊號
      void setup()
      {
      pinMode(buttonPinA, INPUT);
      pinMode(buttonPinB, INPUT);
      }
      void loop()
      {
      buttonStateA = digitalRead(buttonPinA);
      buttonStateB = digitalRead(buttonPinB);
      if (buttonStateA == HIGH) {
      irsend.sendNEC(0x2028877,32);
      }
      if (buttonStateB == HIGH) {
      irsend.sendNEC(0x2027887,32);
      }
      }



      這是接收端
      #include // 引用 IRRemote 函式庫

      const int irReceiverPin = 2; // 紅外線接收器 OUTPUT 訊號接在 pin 2
      const int ledPinA=4;
      const int ledPinB=5;
      IRrecv irrecv(irReceiverPin); // 定義 IRrecv 物件來接收紅外線訊號
      decode_results results; // 解碼結果將放在 decode_results 結構的 result 變數裏

      void setup()
      {
      irrecv.enableIRIn(); // 啟動紅外線解碼
      pinMode(irReceiverPin, INPUT);
      pinMode(ledPinA, OUTPUT);
      pinMode(ledPinB, OUTPUT);
      }


      void loop()
      {
      if (irrecv.decode(&results)) { // 解碼成功,收到一組紅外線訊號
      irrecv.resume();
      } // 繼續收下一組紅外線訊號
      if(results.value ==0x2028877){
      digitalWrite(ledPinA, HIGH);
      }
      if(results.value ==0x2027887){
      digitalWrite(ledPinB, HIGH);
      }
      if(results.value ==0xFFFFFFF){
      digitalWrite(ledPinA, LOW);
      digitalWrite(ledPinB, LOW);
      }
      else{
      digitalWrite(ledPinA, LOW);
      digitalWrite(ledPinB, LOW);
      }
      }


      還有影片
      http://www.youtube.com/watch?v=3nvau96b73w&feature=youtu.be

      想請問程式碼該如何修改才可以在我們將開關撥回到中間的時候讓燈熄滅
      還有讓LED改成以閃爍的方式來顯示
      還有LED好像會不是很規律地閃爍 請問這方面有什麼建議嗎?

      Delete
    14. 我寫了一份程式,但是我並沒有建構電路,所以也不知道到底行不行,試試看吧。
      傳送方http://pastebin.com/LKEJbdDH
      接收方http://pastebin.com/w7a1WrEV

      不行的話,咳咳,請自行想辦法吧。

      Delete
    15. 測試非常成功
      接下來我再試試看閃爍

      非常感謝您的幫忙

      Delete
    16. 葉大不好意思
      想請教您 if(t / DELAY_A / 2 == 0)
      是什麼意思?

      Delete
    17. t / DELAY_A後,得到一個整數倍數,
      然後/ 2若== 0,代表是偶數,若不== 0,代表是奇數。

      Delete
    18. 嗚嗚不好意思><
      小弟研究了許久還是不懂這一串該怎麼解釋
      void showLedA(){
      if(ledA == false){
      digitalWrite(ledPinA, LOW);
      }
      else{
      unsigned long t = millis();
      if(t / DELAY_A / 2 == 0)
      digitalWrite(ledPinA, LOW);
      else
      digitalWrite(ledPinA, HIGH);
      }
      }

      Delete
    19. 全域變數ledA若是false,就熄滅LED A。
      若是true,閃爍LED A,閃爍頻率由#define DELAY_A 300控制,

      啊,我寫錯了,應該是if(t / DELAY_A % 2 == 0),很抱歉。

      譬如,當unsigned long t = millis()是600~899時,t/DELAY_A會是2,再%2會等於0,熄滅LED,當t是900~1199時,t/DELAY_A會是3,再%2會等於1,點亮LED。然後依此類推,應該能讓LED閃爍。

      Delete
    20. This comment has been removed by the author.

      Delete
    21. 使用全域變數記錄狀態,譬如
      int state;
      0代表不做事,
      1代表按數字1,
      2代表長按數字1,
      3代表按數字2,
      4代表長按數字2,
      等等,然後
      void loop(){
      switch(state){
      case 0:
      // 不做事
      //
      break;
      case 1:
      // 執行數字1的功能
      // 執行完後須將state設為0
      break;
      // 依此類推
      }
      }

      大概是這樣吧,在loop裡還要判斷哪個數字被按、是否長按,然後隨之更改state。

      Delete
    22. This comment has been removed by the author.

      Delete
    23. 沒有使用程式庫的話,那就要自己呼叫millis(),記錄時間,判斷是否長按。

      Delete
    24. 請問一下,類似上面網友提到的東西
      但是紅外線似乎很受到角度的影響
      目前我知道的,可以從藍芽與RFID著手
      請問大大建議哪一種呢?
      或是有什麼參考資料嗎? 不好意思麻煩你。

      Delete
    25. 無線傳輸,通常會想到藍牙、ZigBee、WiFi,各有各的優缺點,零件價錢、傳輸範圍、速率、易用性、功率、等等,須根據需求選擇,無法建議,也沒有一定的答案。

      Delete
  10. 請問一下
    如果有四個俺紐並且要互相不衝突(DELAY)
    有辦法解嗎?

    ReplyDelete
    Replies
    1. 用Bounce程式庫就能達到你的要求。

      Delete
  11. 不好意思再請教一個問題
    我點進去您所放的BOUNCE裡面
    然後按了 https://github.com/thomasfredericks/Bounce-Arduino-Wiring/archive/master.zip
    下載了Bounce-Arduino-Wiring-master.ZIP
    可是我把它裡面有個bounce2的資料夾拉進去libraries下
    您所寫的bounce的程式宣告卻是錯的

    ReplyDelete
  12. 該網頁已經變成新版本了,也就是Bounce2。

    原本的Bounce程式庫,請到https://github.com/justjoheinz/Bounce下載。

    ReplyDelete
  13. Anonymous14/3/14 17:06

    您好 我目前會了讓LED自動點滅

    #define LED_PIN 13
    void setup() {
    pinMode(LED_PIN, OUTPUT);
    }

    void loop() {
    digitalWrite(LED_PIN, HIGH);
    delay(3000);
    digitalWrite(LED_PIN, LOW);
    delay(3000);

    那如果我加一個按鈕 按下去才會點滅
    該如何寫呢? 謝謝

    ReplyDelete
    Replies
    1. 呃,這一篇就是講以開關切換LED明滅。

      Delete
    2. Anonymous14/3/14 17:31

      對不起 我打錯了 應該是加一個按鈕 按下去會閃爍

      Delete
    3. 主要部分是讀取開關腳位的狀態,根據狀態判斷要不要讓LED閃爍。

      但LED閃爍的部份不能直接在loop函式裡使用delay,那會影響讀取部分,
      可使用millis函式加上全域變數記錄上次變化LED狀態的時間,根據重置後經過的時間以及開關狀態,來決定是否點亮LED,
      或是使用計時器程式庫http://yehnan.blogspot.tw/2012/03/arduino.html。

      Delete
  14. 您好:
    有關紅外線偵測這部分,我有兩個問題想請教您,範例是以sony的protocol來做發送,請問是否能單純發送紅外光而不是以任何公司的protocol來做發送?
    另外我還有個問題,範例上接收端若有收到發射端的訊號,在serialport上則會顯示他的value,遮斷後則不會傳送,我目前想在這部分做修改,若有遮斷則傳送"0"出去,表示沒有收到訊號,這部分該如何下手?我改了好久都沒辦法寫出來,感謝

    ReplyDelete
    Replies
    1. 單純發送紅外光,應該可以使用IRremote程式庫的IRsend類別的sendRaw方法。

      ...若有遮斷則傳送"0"出去,表示沒有收到訊號...
      經過多久時間沒收到訊號才要傳送"0"呢?
      應該要有個全域變數,記錄上次傳送"0"或收到紅外線訊號的時間(millis()),
      然後在loop()裡,呼叫millis()判斷是否已經經過一段時間且沒有收到紅外線訊號了,若是就送出"0"。然後補足其他相關部分的程式碼,大致上如此。

      Delete
    2. 我後來再想是否能從Library直接改成我想要的,但改完後似乎跟原本的一樣,收到紅外線丟值出來

      不知葉大是否對這部分有所研究?

      library更改如下:
      IRsend::enableIROut(int khz)裡的digitalWrite(TIMER_PWM_PIN, HIGH); // When not sending PWM, we want it low
      我將它改成high還是無法....

      Delete
    3. 你想要的功能,我已經回覆了。

      ...若有遮斷則傳送"0"出去,表示沒有收到訊號...
      經過多久時間沒收到訊號才要傳送"0"呢?
      應該要有個全域變數,記錄上次傳送"0"或收到紅外線訊號的時間(millis()),
      然後在loop()裡,呼叫millis()判斷是否已經經過一段時間且沒有收到紅外線訊號了,若是就送出"0"。然後補足其他相關部分的程式碼,大致上如此。

      大致上就是要使用millis()的寫法,或是使用計時器(Timer)來實作此功能。

      Delete
    4. 你改的
      IRsend::enableIROut(int khz)裡的digitalWrite(TIMER_PWM_PIN, HIGH);
      跟你要的功能無關。
      原本是LOW,只是讓連接紅外線發射器的腳位處於LOW狀態而已,也就是初始狀態(不發射)。

      Delete
    5. 感謝 已經成功了
      另外我想再問您,若我不使用Library,將紅外線發送端的S與VCC相接,使其一直傳送紅外線,這樣是否可行?

      Delete
    6. 成功了?恭喜你。

      這樣送出去的紅外線是什麼東西?
      我不知道,也沒試過。

      您成功後,可以寫寫分享文,造福大家:)。

      Delete
  15. Anonymous23/5/14 13:33

    您好~
    我想利用arduino製作如影片中的盒子
    https://www.youtube.com/watch?v=UmQ5LsNMXZ4

    但是利用開關控制伺服馬達的部分
    我不知該如何下手
    想請您給點建議~非常感謝!!!

    ReplyDelete
  16. 讀取開關狀態(http://yehnan.blogspot.tw/2012/02/arduinoled.html),然後轉動伺服馬達(http://yehnan.blogspot.tw/2013/09/arduinotower-pro-sg90.html),應該就這樣了吧,沒想到其他的。

    ReplyDelete
    Replies
    1. Anonymous2/8/14 15:41

      我後來有成功做出來了~!
      抱歉這麼久才來跟您道謝><
      謝謝葉難大大~您的文章真的介紹的很棒!!!

      Delete
  17. 你好:
    我有做個實驗,把電阻拿掉,發現arduino會重啟
    所以不能5v 0i 到GND
    一定要 有電流到GND
    是這個意思嗎?

    ReplyDelete
    Replies
    1. 5V不能直接接GND,那會形成短路。

      請問你說的把電阻拿掉,那剩下什麼?
      0i是什麼?

      Delete
  18. 哈~抱歉...
    0i是指0A ,電流0,太久沒摸都忘了怎麼表示
    然後短路是因為R=0造成電流無限大,不是i=0
    剛剛回去翻書才又想起來。
    哈~我懂為什麼要加電阻了,謝謝^^

    ReplyDelete
  19. 請問 Arduino要如何跟GY-31感測器使用

    要利用 GY-31來感測 兩邊顏色 一紅一藍 當GY-31感測其中一邊顏色時 其中一邊要轉變成紫色或其他顏色 程式碼該如何寫

    ReplyDelete
    Replies
    1. 我沒有GY-31。

      到網路上搜尋一陣子後,並沒有找到GY-31的Arduino程式庫,必須自己控制GY-31的腳位S0 S1 S2 S4 OUT,讀取RGB值。

      我找到兩個網址,似乎有用:
      http://forum.arduino.cc/index.php/topic,236840.0.html

      http://forums.parallax.com/showthread.php/136258-Code-for-using-a-TCS3200-with-Arduino-and-a-question

      Delete
  20. 那請問 LaunchPad 這版子 能跟GY-31搭嗎 一樣的做動

    ReplyDelete
    Replies
    1. 咳咳,我沒用過LaunchPad。

      「理論上」是可以的,同樣都是透過幾個腳位,根據GY-31腳位的功能輸出輸入訊號。

      Delete
  21. 不好意思 想利用此程式的無段開關控制繼點器開關,請問要從何下手,
    謝謝

    ReplyDelete
    Replies
    1. 你是說讀取無段開關的狀態,然後控制繼電器?
      控制繼電器可以參考Cooper Maa的文章http://coopermaa2nd.blogspot.tw/2011/03/lab21-12v.html。

      Delete
  22. 本篇文章可以控制LED的開與關,無段開關壓一下打開LED;壓第2下關閉LED,
    所做的繼電器控制是隨著無段開關動作。
    如果我壓開關第一下繼電器常閉接點導通,壓一下常閉接點不導通。

    ReplyDelete
    Replies
    1. 嗯,那就是把這一篇文章中,連接LED的輸出腳位改為接到繼電器的控制腳位,但中間應該需要加入電晶體以放大電流,請參考http://coopermaa2nd.blogspot.tw/2011/03/lab21-12v.html的電路圖。

      Delete
  23. 想請問一下, 我想製作一個有兩種功能的 LDE燈, 按開關一下 三顆LED 全亮 ;再按一下 三顆LED 輪流亮5秒;再按一下 全部LED燈都熄滅,
    不知道程式部分需要如何撰寫

    ReplyDelete
    Replies
    1. 或許先看看這篇「Arduino練習:以開關切換LED是否閃爍http://yehnan.blogspot.tw/2014/03/arduinoled.html」,可以約略想出該怎麼寫。

      另可運用「有限狀態機」的程式撰寫技巧,在拙作「Arduino輕鬆入門:範例分析與實作設計http://yehnan.blogspot.tw/2014/02/arduino_21.html」裡,多處運用此技巧。

      大意如下:
      定義三種狀態,以一個全域變數記錄此狀態,每按一次開關就切換狀態。
      loop()裡有個switch,根據三種狀態、分別執行不同的功能(也就是你想要的三種功能)。

      > 三顆LED 輪流亮5秒
      這又稍微麻煩一點,必須在此狀態裡,以millis()得知時間或使用Timer程式庫,進行LED的亮滅動作。

      Delete
  24. 您好,想請問一下這個函式庫有判斷長按的部分媽? 希望說能按一下打開,長按關閉這樣。 THX

    ReplyDelete
    Replies
    1. 沒有,需要自己寫。

      想要高階一點的功能的話,可搜尋「arduino button library」,看看有無合用的程式庫。

      Delete
  25. This comment has been removed by the author.

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. 問個問題,我結合四位數七段顯示器以及步進馬達,目的讓步進馬達執行正反轉,然後顯示器再進行計數。執行結果OK。但想問下,為何馬達運轉時顯示器會全暗,僅顯示千位數0 。然後馬達跑完加1 後四位顯示器+1顯示時正常。
    // 定義腳位


    //參考此網址接角http://yehnan.blogspot.tw/2013/08/arduino_26.html
    #include
    #define PIN_0 2
    #define PIN_g 3
    #define PIN_c 4
    #define PIN_h 5
    #define PIN_d 6
    #define PIN_e 7
    #define PIN_b 8
    #define PIN_1 9
    #define PIN_2 10
    #define PIN_f 11
    #define PIN_a 12
    #define PIN_3 13

    // 共有4個七段顯示器,分別由針腳PIN_0、PIN_1、PIN_2、PIN_3控制
    // 七段顯示器裡有8個LED(包含小數點)
    #define POS_NUM 4
    #define SEG_NUM 8
    const byte pos_pins[POS_NUM] = {PIN_0, PIN_1, PIN_2, PIN_3};
    const byte seg_pins[SEG_NUM] = {PIN_a, PIN_b, PIN_c, PIN_d, PIN_e, PIN_f, PIN_g, PIN_h};
    //不進馬達
    Stepper stepper(200, 14, 16, 15, 17);
    //我手上的馬達轉一圈為48步 (360/7.5 deg),定義11, 10, 9, 8為輸出腳位


    // 底下定義由七段顯示器顯示數字時所需要的資料
    #define t true
    #define f false
    const boolean data[10][SEG_NUM] = {
    {t, t, t, t, t, t, f, f}, // 0
    {f, t, t, f, f, f, f, f}, // 1
    {t, t, f, t, t, f, t, f}, // 2
    {t, t, t, t, f, f, t, f}, // 3
    {f, t, t, f, f, t, t, f}, // 4
    {t, f, t, t, f, t, t, f}, // 5
    {t, f, t, t, t, t, t, f}, // 6
    {t, t, t, f, f, f, f, f}, // 7
    {t, t, t, t, t, t, t, f}, // 8
    {t, t, t, t, f, t, t, f}, // 9
    };

    // 一支方便的函式,以格式字串輸出到序列埠
    void pf(const char *fmt, ... ){
    char tmp[128]; // max is 128 chars
    va_list args;
    va_start (args, fmt );
    vsnprintf(tmp, 128, fmt, args);
    va_end (args);
    Serial.print(tmp);
    }

    // 設定某個七段顯示器所顯示的數字,
    // 參數pos為0~3,指出想要更新哪一個七段顯示器,
    // 參數n為0~9,顯示數字
    void setDigit(int pos, int n){
    if(pos < 0 || 3 < pos){
    pf("error pos=%d\n", pos);
    return;
    }

    // 控制想要更新哪一個七段顯示器,將其腳位設為LOW
    // 其他腳位則設為HIGH,代表不更新。
    for(int p = 0; p < POS_NUM; p++){
    if(p == pos)
    digitalWrite(pos_pins[p], LOW);
    else
    digitalWrite(pos_pins[p], HIGH);
    }

    // 寫入數字
    if(0 <= n && n <= 9){
    for(int i = 0; i < SEG_NUM; i++){
    digitalWrite(seg_pins[i], data[n][i] == t ? HIGH : LOW);
    }
    }
    else{
    for(int i = 0; i < SEG_NUM; i++){
    digitalWrite(seg_pins[i], LOW);
    }
    digitalWrite(PIN_h, HIGH);
    pf("error pos=%d, n=%d\n", pos, n);
    }
    }

    // 設定整個四合一型七段顯示器想要顯示的數字
    // 參數number的範圍應是0~9999
    void setNumber(int number)
    {
    int n0, n1, n2, n3;
    n3 = number / 1000;
    number %= 1000;
    n2 = number / 100;
    number %= 100;
    n1 = number / 10;
    n0 = number % 10;

    // 求出每個位數的值後,分別更新
    // 注意,此處以delay(5)隔開每個位數的更新
    setDigit(0, n0); delay(5);
    setDigit(1, n1); delay(5);
    setDigit(2, n2); delay(5);
    setDigit(3, n3); delay(5);
    }

    unsigned long time_previous;



    void setup() {
    Serial.begin(115200);

    stepper.setSpeed(60); // 將馬達的速度設定成20RPM (太大趕不上)


    for(int i = 0; i < POS_NUM; i++){
    pinMode(pos_pins[i], OUTPUT);
    digitalWrite(pos_pins[i], HIGH);
    }
    for(int i = 0; i < SEG_NUM; i++){
    pinMode(seg_pins[i], OUTPUT);
    digitalWrite(seg_pins[i], LOW);
    }

    time_previous = millis();
    }

    int number = 0;




    void loop() {
    // 經過一秒後就讓number加1
    unsigned long time_now = millis();
    if(time_now - time_previous > 15000){
    number++;
    stepper.step(1000);//正半圈
    delay(1000);
    stepper.step(-1000);//反半圈
    delay(1000);
    time_previous += 15000;
    pf("number=%d\n", number);
    }
    // 不斷地寫入數字
    setNumber(number);

    if(number>9999)
    number=0;

    }

    ReplyDelete
    Replies
    1. 因為這是四合一型七段顯示器,採用掃描(scan)的方式來顯示,必須不斷呼叫setNumber()更新。
      當loop()內因delay(1000);而停止更新時,四合一型七段顯示器就無法正常顯示。

      Delete
  28. 看完yehnan版主的文章,發現跟Cooper Ma 的文章都相當棒,也都很以初學者的角度去回覆問題,台灣就是需要像你們這種熱血份子阿,^^

    ReplyDelete
  29. This comment has been removed by the author.

    ReplyDelete
  30. 大大們~請問一下我這樣寫七段顯示器的程式碼幫我看一下那裡有錯誤

    void setup() {
    pinMode(2, OUTPUT);
    pinMode(3, OUTPUT);
    pinMode(4, OUTPUT);
    pinMode(5, OUTPUT);
    pinMode(6, OUTPUT);
    pinMode(7, OUTPUT);
    pinMode(8, OUTPUT);
    pinMode(9, OUTPUT);
    pinMode(10, OUTPUT);
    digitalWrite(10, 0); // 關閉小數點
    }


    void loop() {

    // 顯示數字 'H'
    digitalWrite(2, 1);
    digitalWrite(3, 1);
    digitalWrite(4, 1);
    digitalWrite(5, 0);
    digitalWrite(6, 0);
    digitalWrite(7, 0);
    digitalWrite(8, 1);
    digitalWrite(9, 1);
    digitalWrite(10, 1);
    delay(1000);
    // 顯示數字 'L'
    digitalWrite(2, 1);
    digitalWrite(3, 0);
    digitalWrite(4, 0);
    digitalWrite(5, 1);
    digitalWrite(6, 0);
    digitalWrite(7, 0);
    digitalWrite(8, 1);
    digitalWrite(9, 1);
    digitalWrite(10, 0);
    delay(1000);

    // 設定某個七段顯示器所顯示的數字,
    // 參數pos為0~3,指出想要更新哪一個七段顯示器,
    // 參數n為0~9,顯示數字
    void setDigit(int pos, int n){
    if(pos < 0 || 3 < pos){
    pf("error pos=%d\n", pos);
    return;
    }

    // 控制想要更新哪一個七段顯示器,將其腳位設為LOW
    // 其他腳位則設為HIGH,代表不更新。
    for(int p = 0; p < POS_NUM; p++){
    if(p == pos)
    digitalWrite(pos_pins[p], LOW);
    else
    digitalWrite(pos_pins[p], HIGH);
    }

    // 寫入數字
    if(0 <= n && n <= 9)
    {
    for(int i = 0; i < SEG_NUM; i++){
    digitalWrite(seg_pins[i], data[n][i] == t ? HIGH : LOW);
    }
    else{
    for(int i = 0; i < SEG_NUM; i++){
    digitalWrite(seg_pins[i], LOW);
    }
    digitalWrite(PIN_h, HIGH);
    pf("error pos=%d, n=%d\n", pos, n);
    }
    }
    }


    因為我錯誤訊息是這個
    Blink.ino: In function 'void loop()':
    Blink:43: error: a function-definition is not allowed here before '{' token
    Blink:72: error: expected `}' at end of input

    ReplyDelete
    Replies
    1. 我只是借用一下上面的七段顯示器的程式碼用一下~看看能不能有動作~但它還是出現錯誤
      可以幫我解答一下嗎?

      Delete
    2. 你的錯誤訊息出現的地方,不在你給的程式碼裡。

      Delete
    3. Blink:43: error: a function-definition is not allowed here before '{' token
      Blink:72: error: expected `}' at end of input
      應該是你copy&paste時、插入到錯的地方了,造成語法錯誤。

      Delete
    4. 請參考http://yehnan.blogspot.tw/2013/08/arduino_26.html這篇的程式碼,
      首先修改const boolean data[10][SEG_NUM] = {這個陣列裡的定義,
      原本只有數字0~9(10個)的亮滅定義,應可擴充讓它顯示H與L。

      然後修改函式void setDigit(int pos, int n){,
      這個函式的參數n,原本應該會是0~9,修改後,
      當n為10或11時,就從陣列data拿出輸出H或L的亮滅資料。

      不過上面這些都是針對四合一的七段顯示器,如果你只有一個七段顯示器,還須修改,一除掉不需要的程式碼。

      Delete
    5. 還是你可以教我如何寫出七段顯示器的程式碼呢!?

      Delete
    6. 還請參考

      Arduino練習:seven-segment display七段顯示器與時鐘
      http://yehnan.blogspot.tw/2012/02/arduinoseven-segment-display.html

      Arduino練習:四合一的七段顯示器
      http://yehnan.blogspot.tw/2013/08/arduino_26.html

      Delete
    7. 所以參考七段顯示器與時鐘的定義~就可以做出七段顯示器的程式了嗎?

      Delete
    8. 呃,你想要我回答什麼呢...:D

      Delete
    9. 也可以參考拙作《Arduino輕鬆入門:範例分析與實作設計》 http://yehnan.blogspot.tw/2014/02/arduino_21.html 的章節5.2:七段顯示器,
      嘻嘻:)。

      Delete
  31. 你好,最近在程式撰寫上有點問題,已經改過許多撰寫方式,也找過很多資料,依然徒勞無功,可以幫個忙嗎?
    我想用APP的按鈕當作開關,讓Arduino 8腳外接出來的LED可以按一下長開、再按一下常關
    但是在宣告部分不知道哪裡出問題,遲遲會跳出別的迴圈
    程式部分c =='a'為宣告的按鈕

    const int ledPin = 8; // LED接至 pin 8
    int buttonState = 0; // 存放按鈕狀態
    int old_buttonState = 0; // 存放按鈕上一次的狀態
    int ledState = 0; // 存放LED狀態,0:關;1:亮

    //發現是按鈕要求
    //function 1
    buttonState = digitalRead('a'); //讀取按鈕的狀態
    if ((c == 'a') && (old_buttonState == LOW)) {
    ledState = 1 - ledState; //反置 LED 狀態
    }

    old_buttonState == buttonState; //把這次的按鈕狀態存起來供下次判斷

    if (ledState == 1) {
    digitalWrite(ledPin, HIGH); //輸出高電位給LED
    }
    else {
    digitalWrite(ledPin, LOW); //輸出低電位給LED
    }

    ReplyDelete
  32. > APP的按鈕
    你說的APP,是ios或android的app嗎?

    > 但是在宣告部分不知道哪裡出問題,遲遲會跳出別的迴圈
    什麼?看不懂。跳出別的迴圈?

    > buttonState = digitalRead('a'); //讀取按鈕的狀態
    digitalRead('a') 這個寫法正確嗎?

    old_buttonState == buttonState; 這是比較,不是assign,
    應該改成old_buttonState = buttonState; 吧?

    ReplyDelete
    Replies
    1. APP是android的APP
      這個程式只是一部分,前面還有一部分是搭配APP要做密碼輸入的
      digitalRead('a') 這個寫法我不知道正不正確,我沒寫過相關的
      我是搭配藍芽模組連接用arduino開發平台上的monitor得知按鈕輸出結果為字元a
      所以才會這樣撰寫,也有上網爬過文章,都沒有類似的可以參考,所以一直不知道怎麼寫才正確
      那段程式我是參考以下程式去修改的
      以下程式則是使用13腳外接的LED
      if (c == 'a')
      {
      readString="";
      digitalWrite(13, HIGH);
      delay(1000);
      digitalWrite(13, LOW);
      delay(1000);
      break;
      }

      Delete
    2. > digitalRead('a') 這個寫法我不知道正不正確
      呃,不知道? 編譯後至少知道能不能通過編譯器吧?

      > 得知按鈕輸出結果為字元a
      那麼字元a應該儲存在某變數裡吧,digitalRead是去讀取Arduino的數位腳位。

      你問的問題跟給的資訊,配合不起來,無法幫你。

      Delete
  33. 程式都可以通過翻譯器,但是在執行時APP會出現BUG
    想請問這段是甚麼意思?
    if (c == 'a')
    {
    readString="";

    是如果c=a
    readString讀取所輸入字串嗎?

    ReplyDelete
    Replies
    1. APP出現bug?你的程式是不是分為Arduino端與Android端?

      if (c == 'a') 如果變數c的內容等同於字元'a'的話,就執行
      {
      readString=""; readStrng應該是個指標,指向空字串""


      這些跟Arduino或Android無關,是C程式語言。

      Delete
    2. 程式是結合HC-06和Arduino UNO與Android APP
      如果這個程式的按鈕用實體按鈕來宣告會相對簡單很多
      但是我想結合APP
      如果我想要利用接收字元來做開關要怎麼撰寫比較好呢?
      接收a LED為常開 在接收一次LED為常關

      Delete
    3. 在所有函式外面宣告全域變數,譬如int led_status = LOW;,
      拿個變數去接收字元,假設變數名為c,收到字元後,
      if(c == 'a'){
      led_status = !led_status; 切換:LOW與HIGH
      }
      digitalWrite(LED_PIN, led_status);

      Delete
  34. 你好,我想問一下對於輸入腳位與執行的問題
    當我的訂digital 0為輸入時,UNO的序列埠送出的數值不會隨著電位變化
    當我的訂digital 1為輸入時,UNO的序列埠會隨著電位變化暫停運作
    請問可能是什麼原因,謝謝
    附上程式碼

    int x=0;
    void setup()
    {
    Serial.begin(9600);
    pinMode(0,INPUT);
    }
    void loop()
    {
    x=digitalRead(0);
    Serial.print(x);
    delay(100);
    }

    ReplyDelete
    Replies
    1. UNO的序列埠是digital 0 RX與1 TX啊,
      把1改成輸入,自然會影響序列埠的運作。

      Delete
  35. Anonymous4/12/14 18:30

    This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  36. Anonymous4/12/14 19:14

    This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  37. Anonymous4/12/14 19:30

    This comment has been removed by a blog administrator.

    ReplyDelete
  38. This comment has been removed by the author.

    ReplyDelete
  39. 您好~
    有個LED燈閃爍的問題想請教您~
    我打算利用wifi收到的字串來做LED燈閃爍的開關
    收到字串"1" LED燈就做 持續明滅閃爍的動作
    收到字串"2" LED燈就做 關閉LED燈的動作
    以下部分是處理的程式碼
    if ( inData[0] == '1' ) {
    while(1) {
    digitalWrite(LED, HIGH);
    delay(50);
    digitalWrite(LED, LOW);
    delay(50);
    }
    }

    if ( inData[0] == '2' )
    {
    digitalWrite(LED, LOW);
    }

    但是這樣寫我只要接收到"1"他就一直持續閃爍停不下來XD
    不知道葉大有沒有什麼推薦的方式可以解決這個問題呢~

    謝謝您~~

    ReplyDelete
    Replies
    1. '1'是字元,"1"是字串。

      while(1){ ... } 這是無窮迴圈。

      可行作法很多,譬如
      void loop(){
      if(inData[0] == '1'){
      digitalWrite(LED, HIGH);
      delay(50);
      digitalWrite(LED, LOW);
      delay(50);
      }
      else if(inData[0] == '2'){
      digitalWrite(LED, LOW);
      }
      }

      另可參考
      Arduino練習:以開關切換LED是否閃爍
      http://yehnan.blogspot.tw/2014/03/arduinoled.html

      以及「有限狀態機」的程式技巧,拙作很多範例都有使用此技巧。
      《Arduino輕鬆入門:範例分析與實作設計》
      http://yehnan.blogspot.tw/2014/02/arduino_21.html

      Delete
    2. 感謝葉大的回覆!!
      我研究看看~有任何問題再請教您 謝謝哦

      Delete
    3. 您好~
      有個LED燈閃爍的問題想請教您~
      在 2013/08/31
      葉大有撰寫一程式

      #include
      Bounce bouncer1 = Bounce(2, 50);
      Bounce bouncer2 = Bounce(3, 50);
      static int ledStatus1 = LOW;
      static int ledStatus2 = LOW;
      void setup(){
      pinMode(2, INPUT);
      pinMode(3, INPUT);
      pinMode(12, OUTPUT);
      pinMode(13, OUTPUT);
      digitalWrite(12, ledStatus1);
      digitalWrite(13, ledStatus2);
      }
      void loop(){
      if(bouncer1.update() == true && bouncer1.read() == HIGH)
      {
      ledStatus1 = ledStatus1 == HIGH ? LOW : HIGH;
      digitalWrite(12, ledStatus1);
      }
      if(bouncer2.update() == true && bouncer2.read() == HIGH)
      {
      ledStatus2 = ledStatus2 == HIGH ? LOW : HIGH;
      digitalWrite(13, ledStatus2);
      }
      }

      驗證時會出現
      _20150305-2.ino:20:4: error: 'bouncer2' was not declared in this scope
      編譯時發生錯誤
      葉大 請幫忙看一下問題在哪
      謝謝

      Delete
    4. 我重新編譯後,並無問題。
      請把
      #include
      改成
      #include 左角括號Bounce.h右角括號

      Delete
    5. 葉大謝謝
      我再試試看

      Delete
    6. This comment has been removed by the author.

      Delete
  40. 葉大你好:
    我無意間發現你的示意圖是不是畫錯了,這樣開關應該是沒作用才對耶

    Behram

    ReplyDelete
    Replies
    1. 沒錯啊。

      當開關處於平常狀態,此時讀取開關的狀態,會讀到LOW低電壓。按下開關時,就會讀到HIGH高電壓。

      Delete
    2. 應該是最上面那張圖~ 你最後的實體接線圖沒錯~~

      Delete
    3. 你確定是這一篇?

      電路圖跟照片都一樣啊。

      Delete
    4. 二號腳位在LOW的時候,應該要加個電阻接到GND喔~
      不然會有雜訊,就算是沒按下開關也會收到不穩定的訊號

      Delete
  41. 你好
    我想問.....為什麼我不管用哪台電腦 uno upload 一次後 之後想在 upload 都會....
    Serial port 'COM' already in use.....
    plz help

    ReplyDelete
    Replies
    1. 序列埠被佔用了。
      是否有其他軟體使用該序列埠?
      Arduino IDE序列埠監控視窗佔據該序列埠?

      Delete
    2. 是否有其他軟體使用該序列埠? 應該不是 , 因為我沒有開其他軟體,除了開發環境的資料夾
      Arduino IDE序列埠監控視窗佔據該序列埠? IDE序列埠監控視窗佔據該序列埠-> 意思是說開發環境會一直占用視窗嗎?
      ><..

      Delete
    3. 打錯...是說開發環境會一直占用COM嗎?

      Delete
    4. 關閉Arduino IDE序列埠監控視窗的話,應該就不會佔用。
      你是Mac OS X嗎?或許跟其他程式庫起衝突,譬如RXTX。

      或許可到裝置管理員,先disable再enable。
      或許可試試解除安裝Arduino的驅動程式,然後重新安裝。
      試試插別的USB埠。

      Delete
    5. 你好 我試過
      1.裝置管理員,先disable再enable。
      2.解除安裝Arduino的驅動程式,然後重新安裝。
      3.插別的USB埠。
      都不行。所以可能跟RXTX 有關
      我看過http://forum.arduino.cc/index.php?topic=25541.0
      跟我問題一模一樣.....可是我看不懂他怎麼解決的...
      他說他的是有一條線連到RX的port 可是我沒有...
      所以還有什麼可能嗎? 拜託大大解惑

      Delete
    6. 你好 我試過
      1.裝置管理員,先disable再enable。
      2.解除安裝Arduino的驅動程式,然後重新安裝。
      3.插別的USB埠。
      都不行。所以可能跟RXTX 有關
      我看過http://forum.arduino.cc/index.php?topic=25541.0
      跟我問題一模一樣.....可是我看不懂他怎麼解決的...
      他說他的是有一條線連到RX的port 可是我沒有...
      所以還有什麼可能嗎? 拜託大大解惑

      Delete
    7. This comment has been removed by the author.

      Delete
    8. btw ... 我是win 7
      THX

      Delete
    9. btw ... 我是win 7
      THX

      Delete
    10. http://forum.arduino.cc/index.php?topic=25541.0
      的原因是他有使用Rx的腳位。應該跟你不一樣。
      沒遇過你的問題,我猜是Windows 7霸佔著COM不放。
      或可試試底下的建議囉。

      根據http://arduino.cc/en/guide/troubleshooting,Windows 7可能需要重新安裝驅動程式。所以到裝置管理員,選更新驅動程式,把目錄指向Arduino軟體裡存放驅動程式的目錄。

      修改序列埠的編號,譬如把COM3改成COM4或COM5。

      改用Arduino 1.6.3試試看。

      Delete
  42. 可以問 如果案鍵按下去 要讓它動30秒後 然後停止 要怎麼寫 ...
    arduino 都停不了 拜託求解

    ReplyDelete
    Replies
    1. 定義全域變數儲存狀態,利用millis()取得時間,比較前後時間設定狀態,
      在loop()裡,根據狀態做相對應的事情。

      大概是這樣。

      Delete
  43. 您好:我是一個Arduino超級新手,苦惱很久想跟您請教一下。
    我們要做的是超音波距離感測,當感測障礙距離<=20cm的時候LED亮起,
    持續5秒鐘若持續感測到障礙蜂鳴器響起,當障礙物消失LED與蜂鳴器關閉。
    問題是感測到障礙物不管是一瞬間或者是不到五秒,會經過約五秒鐘的時間,蜂鳴器叫一聲之後與LED一起延遲熄滅。
    亦或是感測障礙超過五秒後挪開障礙物,蜂鳴器與LED會延遲約五秒才關閉。
    測試後發現可能是delay的問題,查了很多資料不明就裡,看完您的文章覺得或許與bounce有關係,不過只是新手的猜想而已哈哈,想要跟您請教一下怎麼修改才是正確的:
    #define TRIGPIN 10
    #define ECHOPIN 13
    #define led2 =7
    #define led1 =8;

    long ping()
    {
    digitalWrite(TRIGPIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIGPIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIGPIN, LOW);
    return pulseIn(ECHOPIN, HIGH)/58;
    }

    void setup()
    {
    pinMode(TRIGPIN, OUTPUT);
    pinMode(ECHOPIN, INPUT);
    pinMode(led1, OUTPUT);
    pinMode(led2, OUTPUT);
    Serial.begin(9600);
    }

    void loop()
    {
    long cm = ping();
    Serial.println( cm );
    if (cm > 20 )
    digitalWrite(LED2,LOW);
    digitalWrite(LED2,LOW);
    if (cm <= 20)
    {
    digitalWrite(LED1, HIGH);
    delay (5000)
    digitalWrite(LED2, HIGH);
    }




    }
    delay(100);
    }

    ReplyDelete
    Replies
    1. 你的程式碼,根本就不能編譯吧。

      你說的行為,
      A:「
      我們要做的是超音波距離感測,當感測障礙距離<=20cm的時候LED亮起,
      持續5秒鐘若持續感測到障礙蜂鳴器響起,當障礙物消失LED與蜂鳴器關閉。

      B:「
      問題是感測到障礙物不管是一瞬間或者是不到五秒,會經過約五秒鐘的時間,蜂鳴器叫一聲之後與LED一起延遲熄滅。
      亦或是感測障礙超過五秒後挪開障礙物,蜂鳴器與LED會延遲約五秒才關閉。


      你寫的程式碼會得到B。
      你在程式碼裡放進delay(5000),光這行大概就沒辦法得到A。

      Delete
    2. This comment has been removed by the author.

      Delete
    3. This comment has been removed by the author.

      Delete
  44. 非常感謝您的指正,事實就像您所說的。
    放上delay後什麼都不對了。
    想要請教您怎麼改才能得出我們想要的。

    ReplyDelete
    Replies
    1. 可運用 有限狀態機 的程式撰寫技巧,
      一開始進入狀態1,LED與蜂鳴器關閉,並且感測障礙物,
      沒有障礙物就維持狀態1,發現障礙物的話,點亮LED,
      進入狀態2,並且記錄進入狀態2的時間。

      在狀態2時,繼續感測障礙物,
      若障礙物沒了,回到狀態1;
      若在五秒後還有障礙物,(每次都抓出目前時間、與進入狀態2的時間作比較),
      進入狀態3。

      狀態3,LED與蜂鳴器都開啟,持續感測障礙物,
      若有障礙物,保持狀態3,
      若沒障礙物,回到狀態1。

      Delete
  45. 請問一下,Arduino有辦法兩個按鈕控制一個LED燈亮滅不同秒數?
    假如 按下按鈕1,LED亮1秒,熄滅
    按下按鈕2,LED亮2秒,熄滅
    LED是同一顆
    有辦法嗎?
    感謝

    ReplyDelete
    Replies
    1. 有啊。

      請參考其他文章http://yehnan.blogspot.tw/2012/02/arduino_21.html
      譬如以開關切換LED明滅狀態、以開關切換LED是否閃爍、Simon Says請你跟我這樣做等等。

      Delete
    2. 我做出來,只有一個按鈕有作用
      const int buttonPin = 5.7;
      void setup(){
      Serial.begin(9600);
      pinMode(9, OUTPUT);
      pinMode(buttonPin , INPUT);
      digitalWrite(buttonPin , HIGH);
      }

      void loop(){
      int status = digitalRead(buttonPin );
      digitalWrite(9, status);
      Serial.println(status);
      }

      Delete
    3. 你只有讀取一個按鈕。
      int status = digitalRead(buttonPin );

      而且const int buttonPin = 5.7;這不對吧,腳位編號是整數,不是浮點數。

      Delete
    4. 是這樣嗎?
      const int buttonPin = 5 ;
      const int buttonPin1 = 7;
      const int ledPin = 9;
      boolean buttonState;
      void setup(){
      Serial.begin(9600);
      pinMode(9, OUTPUT);
      pinMode(5, INPUT);
      pinMode(7 , INPUT);
      digitalWrite(5 , HIGH);
      digitalWrite(7 , HIGH);
      }

      void loop()
      {
      {
      int status = digitalRead(5);
      digitalWrite(9, status);
      Serial.println(status);
      }
      { int status = digitalRead(7);
      digitalWrite(9, status);
      Serial.println(status);
      }
      }

      Delete
    5. 讀取按鈕狀態後,你需要控制LED點亮的時間。

      按下按鈕5,點亮LED,此期間按下兩個按鈕都沒作用,過了一秒後,熄滅LED。
      按下按鈕7,點亮LED,此期間按下兩個按鈕都沒作用,過了兩秒後,熄滅LED。

      Delete
    6. 那這樣要改哪裡,研究了好幾天,還是不行

      Delete
    7. 大概是這樣吧 http://pastebin.com/iA2qJgdL
      並未真的連接電路,僅供參考。
      也沒有解決開關的bounce問題,不過就需求而言,應該沒必要。

      #define BTN1_PIN 5
      #define BTN2_PIN 7
      #define LED_PIN 9

      #define BTN1_PERIOD 1000
      #define BTN2_PERIOD 2000

      void setup(){
      Serial.begin(9600);
      pinMode(BTN1_PIN, INPUT);
      digitalWrite(BTN1_PIN, HIGH);
      pinMode(BTN2_PIN, INPUT);
      digitalWrite(BTN2_PIN, HIGH);
      pinMode(LED_PIN, OUTPUT);
      digitalWrite(LED_PIN, LOW);
      }

      typedef enum{
      S_start,
      S_btn1_pressed,
      S_btn2_pressed,
      } State;
      State state = S_start;

      unsigned long t_old;

      void loop(){
      unsigned long t;
      switch(state){
      case S_start:
      if(digitalRead(BTN1_PIN) == LOW){
      digitalWrite(LED_PIN, HIGH);
      t_old = millis();
      state = S_btn1_pressed;
      }
      if(digitalRead(BTN2_PIN) == LOW){
      digitalWrite(LED_PIN, HIGH);
      t_old = millis();
      state = S_btn2_pressed;
      }
      break;
      case S_btn1_pressed:
      t = millis();
      if((t - t_old) > BTN1_PERIOD){
      digitalWrite(LED_PIN, LOW);
      state = S_start;
      }
      break;
      case S_btn2_pressed:
      t = millis();
      if((t - t_old) > BTN2_PERIOD){
      digitalWrite(LED_PIN, LOW);
      state = S_start;
      }
      break;
      }
      }

      Delete
    8. 謝謝,我在參考看看,有問題再發問

      Delete
  46. 請問一下 能用一顆開關控制8顆LED嗎..
    就是 11111111 這樣八顆 如果按一下 就會亮一顆是11111110
    按第二下就亮成11111101 用2進制的方式亮 如果按7下就亮11111000這樣

    有辦法嗎?

    ReplyDelete
    Replies
    1. 定義全域的整數變數,初始值是0000 0000。(跟你的相反)
      在loop()裡,偵測開關狀態,每按一下,該變數+1。
      +1後若超過8,設回0。

      在loop()裡,呼叫bitRead()讀取每個位元的狀態,點亮/熄滅LEDs。
      http://www.arduino.cc/en/Reference/BitRead

      Delete
  47. 我想以一個按鈕做馬達正反轉控制
    目前做到按著按鈕馬達正轉
    放開按鈕反轉
    而且馬達會一直運轉無法控制
    請問有可能以一個按鈕做到控制馬達正反轉嗎?
    材料有:
    繼電器兩顆
    一個按鈕
    一個微動開關(HIGH:LOW 表示開關門)

    ReplyDelete
    Replies
    1. 控制直流馬達正反轉,通常會使用H橋電路,例如L293D。
      然後根據按鈕狀態,控制流經馬達的電流方向。

      我沒用過繼電器來控制馬達。

      Delete
  48. Anonymous29/7/15 17:54

    int b1=2;
    int b2=3;
    int led= 8;

    void setup() {
    digitalWrite(led,LOW);
    pinMode(b1,INPUT);
    pinMode(b2,INPUT);
    pinMode(led,OUTPUT);
    }

    void loop() {
    digitalRead(led);
    digitalRead(b1);
    digitalRead(b2);


    while(led==LOW){
    if (b1==HIGH){
    b2=true;
    if(b2==HIGH){
    digitalWrite(led,HIGH);
    }
    else{
    digitalWrite(led,LOW);
    }
    }
    else{
    b2=false;
    }
    }
    delay(20);
    while(led==HIGH){
    if (b2==HIGH){
    b1=true;
    if(b1==HIGH){
    digitalWrite(led,HIGH);
    }
    else{
    digitalWrite(led,LOW);
    }
    }
    else{
    b1=false;
    }
    }
    delay(20);

    }
    这个code怎样让led保持亮着

    ReplyDelete
    Replies
    1. 不懂你的問題。改成
      int led= 8;
      void setup() {
      pinMode(led,OUTPUT);
      digitalWrite(led,HIGH);
      }
      void loop(){
      }
      不就一直亮著了嗎?

      Delete
  49. 請問一下為什麼pin腳2 設INPUT
    不用電阻連GND 就會出現亂數
    知道結果卻不明原因 希望板大講解指點一下

    ReplyDelete
    Replies
    1. 應該不會亂數才對。

      詳細的電路圖?程式碼?

      Delete
  50. 請問如果想要把音樂和開關LED做結合
    要怎麼改程式碼 我是個初學者
    音樂程式碼如下
    int speaker=9;
    char toneName[]="CDEFGABH";
    unsigned int frequency[8]={523,587,659,694,784,880,988,1046};
    char beeTone[]="HHGGEFAGGAGFGFECDCCCAGAGECDCEGHBAGAGEDCDGC";
    byte beeBeat[]={2,1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,2,2,1,2,1,4,2,1,2,1,3,2,1,3,2,1,3,1,1,1,1,1,1,2,1,2};
    int beeLen=sizeof(beeTone);
    unsigned long tempo=180;
    int i,j;
    void setup()
    {
    }
    void loop()
    {
    for(i=0;i<beeLen;i++)
    playTone(beeTone[i],beeBeat[i]);
    delay(3000);
    }
    void playTone(char toneNo,byte beatNo)
    {
    unsigned long duration=beatNo*60000/tempo;
    for(j=0;j<8;j++)
    {
    if(toneNo==toneName[j])
    {
    tone(speaker,frequency[j]);
    delay(duration);
    noTone(speaker);
    }
    }
    }

    ReplyDelete
    Replies
    1. > 和開關LED做結合
      什麼意思?

      我猜應該是讀取開關狀態,控制播放哪首音樂吧,
      那就使用digitalRead,然後根據挑選音樂播放。
      不過使用playTone的話,微控制器都會被它佔住,所以在播放期間,loop都不會執行,也就無法讀取開關狀態。

      Delete
  51. 葉大您好,有些問題想請教您
    以下目的是要讓舵機按下按鈕轉動 及放即停
    按下按鍵時 BT接收 一個 '1'
    放開按鍵時 BT接收 一個'0'
    但寫while(1) if...
    傳送1後就會卡在裡面 等執行到 i≠<5 時才會送出'0'
    沒辦法隨時跳開 請問有什麼好的建議嗎?

    #include
    #include
    #include
    int val,i=0;
    int ang[5] = {0, 40, 80, 120, 160};

    //int reang[5] = {255, 192, 128, 64, 0};
    int anglength = sizeof(ang) / sizeof(ang[4]);
    Servo myservo;
    SoftwareSerial BT(0,1);
    void setup() {
    Serial.begin(9600);
    BT.begin(9600);
    myservo.attach(3);
    myservo.write(0);
    }

    void loop() {
    if(Serial.available() >0){
    val = BT.read();

    while(1){
    if(val=='1' && i<5){
    i=i+1;
    Serial.println(val);
    Serial.println(ang[i]);
    delay(500);

    }

    if(val=='0'){
    Serial.println("stop");
    delay(200);
    }
    }//while(1)


    }//if ava
    }//loop

    ------------------------------------------------------------------------------
    改這樣寫還跳不開 有什麼好方法嗎?
    再麻煩葉大了!!
    if(val=='1' && i<5){
    for(i=0;i<5;i++){
    Serial.println(val);
    Serial.println(ang[i]);
    delay(500);
    if(val=='0')break;
    }
    }

    if(val=='0'){
    Serial.println("stop");
    delay(200);
    }

    ReplyDelete
    Replies
    1. while(1)是無窮迴圈,直到break才會跳出,但在你的程式碼裡,不會執行到break。
      你既想要隨時接收BT傳來的資料,又想要控制舵機,嗯...。
      我想,在loop裡隨時接收BT資料,存放在全域變數裡,
      在loop裡判斷該全域變數,若為'1',進入另一函式,控制舵機;
      不可使用delay,應使用millis來計算時間,經過一段時間就改變角度。

      這樣應該行得通。

      Delete
    2. 葉大您好,感謝您回覆我
      我是想用BT傳送的資料來做i的遞增或遞減
      再把矩陣的值丟到 myservo.write(ang[i]) 來讓舵機轉動
      不好意思我是初學者,不太懂"loop裡隨時接收BT資料,存放在全域變數裡"
      能再給些提示嗎?
      另外delay是讓我監視埠時能較輕易看到有無正常停止
      為什麼要使用millis來計算時間呢?
      不好意思,問題有點多
      在勞煩葉大有空時回復一下
      謝謝您,您辛苦了

      Delete
    3. 使用myservo.write來轉動舵機,若使用delay在兩兩角度之間插入一段時間,
      當delay時,微控制器的CPU就停在那裡,什麼也不做,
      於是你就沒辦法即時接收BT資料,但你卻想要收到1就轉、收到0就停,互相違背。

      可以去找找有無非同步式的servo程式庫,要不然就要自己寫。

      用millis的目的是去掉delay,讓loop不會因delay而暫停太久。

      Delete
  52. 請教葉大:如果我想使用兩個無段開關去控制一個電燈,按一下開,按第二下關,那程式該怎寫呢?

    ReplyDelete
    Replies
    1. 不就是這篇介紹的內容嗎?

      Delete
    2. 葉大:太神了…因為急用才上網找,將來一定拜讀您的大作。謝謝了…

      Delete
  53. 葉大師,

    我要讓這個按鈕能夠在2秒鐘內按2次,才控制LED亮1秒.....

    如何撰寫程式呢?

    感謝你....

    ReplyDelete
  54. 嗯,可使用「有限狀態機」的程式技巧,
    請參考拙作 Arduino輕鬆入門:範例分析與實作設計
    http://yehnan.blogspot.tw/2014/02/arduino_21.html
    的第3.5節介紹這個技巧,之後的3.6、6.5、7.2、8.7都有運用該技巧的範例。

    ReplyDelete
  55. 嗯,你的需求其實不是什麼艱難的任務,但卻很特殊,因此自己寫的話,需要花上不少力氣;
    我不知道是否有人已經寫好相關的程式庫了。

    ReplyDelete
  56. 葉大師,光是您願意回復我這個陌生人的問題,

    小弟就感動萬分了.....我會好好研究的

    除了上述問題外,我還有一個問題請教....

    我有一個控制的條件如下,先進行紅外線偵測,如果成立則啟動超音波偵測;超音波偵測成立則讓蜂鳴器作用.....
    這個控制裡有兩個感知器,是不是沒有辦法寫在同一個程式呢?兩個loop.....

    下列是超音波偵測所用程式...
    include
    #define Trip 3
    #define Echo 2
    Ultrasonic ultrasonic(Trip, Echo);
    int IN1=12;
    int IN2=13;

    void setup() {
    pinMode(IN1,OUTPUT);
    pinMode(IN2,OUTPUT);

    }
    void loop() {
    int cmMsec;
    long microsec = ultrasonic.timing();
    cmMsec = ultrasonic.convert(microsec, Ultrasonic::CM);
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,LOW);

    ReplyDelete
    Replies
    1. 你程式碼沒貼完吧?

      同樣的,你想要的功能,可運用有限狀態機的技巧來撰寫。大致情況如下:
      讓Arduino進入狀態1,偵測紅外線,若成立就進入狀態2。
      狀態2,這個狀態應該有限制一段時間,在時間內偵測超音波,若沒偵測到,就回到狀態1;若有,進入狀態3。
      狀態3,蜂鳴器響起。

      你的描述並沒有說在何種狀況下可以脫離狀態3。


      Delete
    2. 狀態3作用1秒,回到狀態1.....

      不好意思,剛剛沒貼完.....

      我等等來研究有限狀態機.....

      #include
      #define Trip 3
      #define Echo 2
      Ultrasonic ultrasonic(Trip, Echo);
      int IN1=12;
      int IN2=13;

      void setup() {
      pinMode(IN1,OUTPUT);
      pinMode(IN2,OUTPUT);

      }
      void loop() {
      int cmMsec;
      long microsec = ultrasonic.timing();
      cmMsec = ultrasonic.convert(microsec, Ultrasonic::CM);
      digitalWrite(IN1,LOW);
      digitalWrite(IN2,LOW);

      if ( cmMsec < 30 ) {

      digitalWrite(IN1,HIGH);
      digitalWrite(IN2,LOW);
      delay(100);
      }
      }

      Delete
  57. 我等等去找葉大師的書來看,

    ReplyDelete
  58. 葉大師,我去買了您的書...
    裡頭的內容非常豐富,
    但你3.5章裡舉的範例,小弟程度不夠,實在不懂.....
    「有限狀態機」的程式技巧,
    這些不同狀態是怎麼轉換的?

    有沒有更簡單的例子呢?

    感謝


    #include
    #define SERIAL_BAUDRATE 19200
    #define LED_PIN 11
    #define SWITCH_PIN 7
    #define DEBOUNCE_DELAY 50 // milliseconds
    #define DURATION 5000 // milliseconds

    Bounce bouncer = Bounce(SWITCH_PIN, DEBOUNCE_DELAY);

    // 定義狀態機的可能狀態
    typedef enum{
    S_OFF,
    S_OFF_NO_PRESS,
    S_ON,
    S_ON_PRESS
    } State;
    State state = S_OFF;

    void updateLed(){
    if(state == S_OFF || state == S_OFF_NO_PRESS){
    digitalWrite(LED_PIN, LOW);
    }
    else if(state == S_ON || state == S_ON_PRESS){
    digitalWrite(LED_PIN, HIGH);
    }
    else{
    Serial.print("Error: in wrong state ");
    Serial.print(state);
    }
    }
    void setup() {
    Serial.begin(SERIAL_BAUDRATE);
    pinMode(LED_PIN, OUTPUT);

    updateLed();
    }

    unsigned long time_previous;
    void loop() {
    bouncer.update();
    boolean switch_status = bouncer.read();

    // 以switch述句根據狀態作相對應的動作
    switch(state){
    case S_OFF:
    if(switch_status == LOW){
    time_previous = millis();
    state = S_OFF_NO_PRESS;
    }
    break;
    case S_OFF_NO_PRESS:
    if(switch_status == LOW){
    unsigned long time_current = millis();
    if(time_current - time_previous >= DURATION){
    state = S_ON;
    }
    }
    else{
    state = S_OFF;
    }
    break;
    case S_ON:
    if(switch_status == HIGH){
    time_previous = millis();
    state = S_ON_PRESS;
    }
    break;
    case S_ON_PRESS:
    if(switch_status == HIGH){
    unsigned long time_current = millis();
    if(time_current - time_previous >= DURATION){
    state = S_OFF;
    }
    }
    else{
    state = S_ON;
    }
    break;
    }
    // 前面更新狀態state後,接下來只要根據state更新LED即可
    updateLed();
    }

    ReplyDelete
  59. 我現在可以
    1.讓紅外線偵測到物體啟動超音波偵測,
    2.超音波偵測到物體啟動蜂鳴器

    分開可以,
    但我沒有辦法在同一個程式裡頭讓這兩個同時存在....

    ReplyDelete
    Replies
    1. 嗯,現在回頭看,該範例的確稍不適合作為狀態機的第一個範例。

      我把你想要的功能,大概框架寫出來了,但沒測試過,請自行斟酌採用。
      http://pastebin.com/eD1YReqq
      程式裡有很多空白處,須自行填寫。

      Delete
  60. 這是紅外線作用控制超音波的程式
    int IRin = 7;
    int ledPin = 11;
    int IRinStatus;
    void setup() {
    pinMode(ledPin, OUTPUT);
    pinMode(IRin, INPUT);
    }
    void loop() {
    IRinStatus = digitalRead(IRin);
    if ( IRinStatus == HIGH)
    {
    digitalWrite(ledPin,HIGH);
    delay(2000);
    }
    else
    {
    digitalWrite(ledPin,LOW);
    }
    }

    ReplyDelete
  61. 這是我目前測試超音波作用的程式

    #include
    #define Trip 3
    #define Echo 2
    Ultrasonic ultrasonic(Trip, Echo);
    int IN1=8;
    int IN2=9;
    int IN3=10;

    void setup() {
    pinMode(IN1,OUTPUT);
    pinMode(IN2,OUTPUT);
    pinMode(IN3,OUTPUT);

    }
    void loop() {
    int cmMsec;
    long microsec = ultrasonic.timing();
    cmMsec = ultrasonic.convert(microsec, Ultrasonic::CM);
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,LOW);

    if ( cmMsec <80 and cmMsec > 50 ) {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,LOW);
    delay(100);
    }
    else if (cmMsec < 50 and cmMsec > 30) {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,LOW);
    delay(100);
    }

    else if ( cmMsec < 30 ) {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,HIGH);
    delay(100);
    }
    else {
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,LOW);
    delay(100);
    }
    }

    ReplyDelete
  62. 請看我上面的留言。

    ReplyDelete
  63. 葉大師,謝謝~~~~我去研究了

    ReplyDelete
  64. 葉大師,我還得加入一個按鈕能夠在2秒鐘內按2次,才控制LED亮1秒.....

    能寫入這個程式嗎

    ReplyDelete
  65. 葉大師,我按照您的指導,成功了......

    我先自己研究如何加入一個按鈕能夠在2秒鐘內按2次,才控制LED亮1秒.....

    有疑問再請教您

    萬分感謝

    ReplyDelete
  66. 葉大師,
    程式如下,
    開始大約一分鐘狀態之間的轉換就會不正常

    有方法解決嗎

    #include
    #define Trip 3
    #define Echo 2
    Ultrasonic ultrasonic(Trip, Echo);
    int ALARM = 8;
    int IRin = 7;
    int IRtest = 9;
    int ULTRASONICtest = 10;
    int IRinStatus;

    typedef enum{
    S_IR,
    S_ULTRASONIC,
    S_ALARM,
    } State;
    State state = S_IR;

    void setup(){
    pinMode(IRin, INPUT);
    pinMode(IRtest, OUTPUT);
    pinMode(ULTRASONICtest, OUTPUT);
    pinMode(ALARM,OUTPUT);
    }

    unsigned long time_old;

    void loop(){
    switch(state){
    case S_IR:{
    // 偵測紅外線
    IRinStatus = digitalRead(IRin);
    digitalWrite(IRtest,HIGH);
    digitalWrite(ULTRASONICtest,LOW);
    digitalWrite(ALARM,LOW);
    if(IRinStatus == HIGH){ // 若偵測到,就進入狀態S_ULTRASONIC
    state = S_ULTRASONIC;
    time_old = millis(); // 記錄進入該狀態的起始時間
    }
    // 若沒偵測到,就停在狀態S_IR,繼續偵測
    }
    break;
    case S_ULTRASONIC:{
    // 偵測超音波
    int cmMsec;
    long microsec = ultrasonic.timing();
    cmMsec = ultrasonic.convert(microsec, Ultrasonic::CM);
    digitalWrite(ALARM,LOW);
    digitalWrite(IRtest,LOW);
    digitalWrite(ULTRASONICtest,HIGH);
    if( cmMsec <30){ // 若偵測到,就進入狀態S_ALARM
    state = S_ALARM;
    time_old = millis(); // 記錄進入該狀態的起始時間
    }
    else{ // 若沒偵測到,而且已經停在此狀態超過5秒,就回到狀態S_IR
    unsigned int t = millis();
    if(t - time_old >= 3000){
    state = S_IR;
    }
    }
    }
    break;
    case S_ALARM:{
    // 蜂鳴器發聲
    digitalWrite(ALARM,HIGH);
    digitalWrite(IRtest,LOW);
    unsigned int t = millis();
    if(t - time_old >= 500){ // 若已經停在此狀態超過3秒,就回到狀態S_IR
    state = S_IR;
    }
    }
    break;
    }
    }

    ReplyDelete
  67. 每次大約1分鐘就會不正常

    ReplyDelete
  68. 老師,
    我在程式裡放了三個輸出,對應狀態接上LED燈,以瞭解狀態是否依照需求轉換,
    pinMode(IRtest, OUTPUT);
    pinMode(ULTRASONICtest, OUTPUT);
    pinMode(ALARM,OUTPUT);

    前一分鐘5秒都正常,(時間固定,跟次數無關)
    之後會常亮紅外線LED不滅,
    靠近紅外線偵測,應該要到超音波偵測,此時應該要滅紅外線LED,亮超音波LED,
    但是紅外線LED不滅,超音波LED極微弱亮,
    此時靠近超音波偵測,ALARM燈也是作用,但亮度不正常

    ReplyDelete
    Replies
    1. 嗯,寫錯了。
      把兩處unsigned int t改成unsigned long t

      Delete
  69. 改好了,作用持續正常.....

    ReplyDelete
  70. 葉老師,

    在目前模式之下,

    我如何再加入一個按鈕,當"在2秒鐘內按2次"(或是長按3秒),那個容易寫擇其一,
    能將前述作用取消,並且能讓LED亮一秒....

    拜請老師指導,感謝

    ReplyDelete
    Replies
    1. 自己寫寫看吧。

      先不管紅外線與超音波,同樣用狀態機的技巧,寫出你要的功能,然後再合併。

      Delete
  71. 好,我來研究,到時後再請教老師怎麼合併兩個狀態機

    謝謝

    ReplyDelete
  72. This comment has been removed by the author.

    ReplyDelete
  73. 老師您好 您的解釋真的很詳盡 令人受益良多 但小弟不才有個地方還是看不懂
    請問一開始只有宣告static unsigned long lastDebounceTime 但他並沒有值呀,要怎麼與millis()做比較呢?

    ReplyDelete
    Replies
    1. 靜態變數初始值為0

      Delete
  74. 葉老師您好,

    我測試一開始的開關程式,為什麼HIGH LOW 的狀態是相反的呢?
    不管是用Mega2560 還是 UNO 都有這個狀況,是不是都處在 High voltage parallel programming 狀態下編譯啊?

    意思是:電路接好,程式上傳完,led燈就自己亮了,按下按鈕後 led才會滅
    如果把按鈕拿掉,也是直接亮著的,這個狀況我無解啊…還請教一下老師您

    ReplyDelete
    Replies
    1. > 為什麼HIGH LOW 的狀態是相反的呢?
      電路接錯了?

      > 處在 High voltage parallel programming 狀態下編譯啊?
      啥?這麼高級的東西。需要特別的燒錄器吧。

      Delete