2017年6月14日 星期三

Arduino 中斷時執行下一個副程式問題

網友在下面這篇文章中留言問了一個問題 :

# Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)

"若好幾支副程式A程式B程式C程式D程式依序被呼叫,然後B程式值行的時間太久所以我想中斷後直接看到C程式"

簡言之就是希望中斷發生後跳到下一個副程式. 因為篇幅有點長, 所以我把回覆內容記在這裡. 已經好久沒寫 Arduino 程式, 有點生疏了. 還好勤於寫測試筆記有案可稽, 要恢復記憶比較容易.

硬體接線很簡單, 我在 D2 接上一個按鈕, 啟用內部上拉電阻讓平時為 HIGH, 而按下按鈕時接地為 LOW 以觸發中斷.

在下列模擬程式中我定義了 a(), b(), c(), d() 四個副程式, 裡面又呼叫一個 doSomething() 副程式來模擬耗時的工作, 這裡使用 delay(1) 跑 10000 次迴圈, 故正常無中斷情況下 10 秒才會跑完. 注意, 這裡不用 delay(1000) 跑 10 圈, 或直用 delay(10000) 的原因是, delay() 被中斷後跑去執行中斷常式再回來時, 似乎不是跳到 delay() 下一個指令, 而是會繼續把 delay() 沒跑完的休息時間跑完, 這樣會造成中斷後還是得等 delay() 休息結束才會去執行下一個副程式.

在 loop() 中依序呼叫此四個副程式, 當中斷發生時主控權會轉移到 int0() 執行, 印出 "Interrupted ..." 同時把預設為 false 的 breakloop 旗標設為 true 以便回到被中斷的 doSomething() 時可以跳出迴圈, 執行下一個副程式 :

const byte intPin=2; //interrupt pin D2
int debounceDelay=200; //debounce delay (ms)
void int0();
void a();
void b();
void c();
void d();
void doSomething();
boolean debounced();
boolean breakloop=false;    #跳出迴圈旗標

void setup() {
  Serial.begin(9600);
  pinMode(intPin, INPUT_PULLUP); //enable pull-up resistor of input pin
  attachInterrupt(0, int0, LOW); //enable int0 (D2)
  }

void loop() {
  a();
  b();
  c();
  d();
  }

void a() {
  Serial.println("running a ...");
  doSomething();
  }

void b() {
  Serial.println("running b ...");
  doSomething();
  }

void c() {
  Serial.println("running c ...");
  doSomething();
  }

void d() {
  Serial.println("running d ...");
  doSomething();
  }  

void doSomething() {
    for (int i=0; i<10000; i++) {
      if (breakloop) {       #若被中斷服務程式 int0() 設定為 true
        breakloop=false;   #重置旗標
        break;                    #跳出迴圈                
        }
      delay(1);
      }
  }

void int0() { //interrupt handler
  if (debounced()) {
    Serial.println("Interrupted ... ");
    breakloop=true;
    }
  }

boolean debounced() { //check if debounced
  static unsigned long lastMillis=0; //record last millis
  unsigned long currentMillis=millis(); //get current elapsed time
  if ((currentMillis-lastMillis) > debounceDelay) {
    lastMillis=currentMillis; //update lastMillis with currentMillis
    return true; //debounced
    }
  else {return false;} //not debounced
  }

監控視窗輸出結果如下 :

running a ...
Interrupted ...
running b ...
Interrupted ...
running c ...
running d ...
Interrupted ...
running a ...
Interrupted ...
running b ...
running c ...
running d ...
running a ...
running b ...
Interrupted ...
running c ...
Interrupted ...
running d ...

每按一下按鈕就會馬上跳到下一個副程式執行.

18 則留言:

  1. 不好意思還讓版主你為了解決我的問題讓你花時間寫這篇文章
    看完這文章讓我對這部份更加的了解
    提供的測試程式也沒問題
    不過我想更加了解今天換成是想重while()中斷出來該怎麼做阿?

    回覆刪除
  2. Dear 傅勁崴 :

    您只要將 for loop 改為 while loop 就行啦! 但這樣就會變成開機後一直執行 a, 中斷後才會執行 b.

    回覆刪除
  3. 你好
    問個問題
    請問
    A副程式B副程式C副程式D副程式
    首先A程式先跑再來換B但是B程式裡頭有while的判斷式造成沒辦法進入C程式
    這我該怎麼做?

    回覆刪除
  4. 你 while 裡面要設條件讓他跳出來, 例如用 breakloop 旗標使其跳出來 :

    void doSomething() {
    while (!breakloop) {
    delay(1);
    }
    breakloop=false;
    }

    回覆刪除
  5. 你好請問一下~
    void int0();
    void a();
    void b();
    void c();
    void d();
    void doSomething();
    這些是必要寫上去的嗎?
    我在寫副程式時上面都沒有寫這些,
    雖然可以跑但是不是有什麼問題會發生還是要注意的呢?

    回覆刪除
  6. 這是 C 語言的要求, Arduino IDE 編譯器似乎不會有問題 (不同編譯器).

    回覆刪除
  7. 感謝,很棒的文章。
    列入我的收藏。

    回覆刪除
  8. 老師您好,請問有辦法是不靠外部的按鈕,而是靠輸入字串或字元, 然後中斷迴圈的方式嗎?

    回覆刪除
  9. 此處講的都是硬體中斷, 即透過 pin2/3 觸發之中斷, 另外還有定時器中斷等, 參考 :

    https://www.arduino.cn/thread-13205-1-1.html

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

    回覆刪除
  11. 請問是否可讓兩個程式同時不停的循環執行呢?
    像是兩個Loop在執行

    回覆刪除
  12. Hi, 這我沒試過, Arduino 是單核, 事實上沒辦法真正 concurrent, 但透過 TimedAction.h 函式庫可以模擬多個 thread 執行, 參考 :

    https://create.arduino.cc/projecthub/reanimationxp/how-to-multithread-an-arduino-protothreading-tutorial-dd2c37

    回覆刪除
  13. 請問一下,怎麼一個按鍵控制4 個燈,每個燈量15分鐘,在切換時要非常順利

    回覆刪除
  14. 按鍵按下去四個燈輪流亮 15 分鐘, 然後不斷輪迴, 直到再次按下按鍵就停止嗎?

    回覆刪除
  15. 大大您好,想請問一下
    我在做輸送帶接紅外線感測物件
    感測到後發送stp
    但因為物體是長的
    想要寫出
    紅外線感測物體後輸送帶停止,等待inf訊號,inf收到訊號後再發送stp
    但使用delay的話又會卡住程式
    請問大大 這裡的等待命令有什麼解法嗎?
    int Sensor=digitalRead(7);
    if(Sensor == 0)
    {
    Serial.println(Sensor);
    digitalWrite(stp, HIGH);
    delay(1000);
    digitalWrite(stp, LOW);

    while(digitalRead(inf) == 0){
    delay(2000);
    }
    else(digitalRead(inf) == 1) {
    break;
    }
    }
    }

    回覆刪除
  16. sleep() 會使程序停住, 這可能要用到多執行緒來做, 但我也沒做過, 可參考這篇 :

    https://www.itread01.com/p/171971.html

    回覆刪除