2015-10-26 66 views
1

我有一個限位開關連接到運動控制的Arduino Mega 2650上。限位開關的兩個常開觸點連接到一個Arduino引腳和地,這樣當限位開關接通時,Arduino引腳會短路接地。在Arduino ISR中剔除限制開關延遲

正如預期的那樣,我用這個設置反彈了一些問題。我在ISR中使用計數器進行了確認。最後,我寫了下面的代碼,它似乎可以可靠地確定我的限位開關是否在任何給定的時間點處於齧合或脫離狀態。

const int lsOuterLeftIn = 18; // lsOuterLeftIn is my Limit Switch 
const int LED = 9; 
volatile bool lsEngaged = false; // flag for limit switch engaged 
void setup() { 
    pinMode(lsOuterLeftIn, INPUT_PULLUP); 
    pinMode(LED, OUTPUT); 
    attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING); 
    attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR2, RISING); 
} 
void loop() { 
    if (lsEngaged) digitalWrite(LED, HIGH); 
    else digitalWrite(LED, LOW); 
} 
void ISR1(){ 
    delay(100); 
    lsEngaged = (digitalRead(lsOuterLeftIn)); 
} 
void ISR2(){ 
    delay(100); 
    lsEngaged = (digitalRead(lsOuterLeftIn)); 
} 

但是,這是我的問題。我來到這個Arduino documentation page,和它說

「由於延遲()需要中斷工作,如果調用 在ISR內部,它不會工作。」

但是,我確實使用內部ISR中的delay(),它似乎工作,發生了什麼?我目前是否有這種情況,但可能會輕易中斷,因爲文檔中提到的delay()函數可能會對我造成故障?

+1

[這裏有一個答案(HTTP:// stackoverflow.com/問題/ 32646071 /中斷返回輕微延遲/ 32647699#32647699)應該解決您的所有問題。 – Lundin

+2

在中斷中使用忙等待循環是一個非常糟糕的主意 - 總是! – Olaf

+1

您應該(也)提供反彈*硬件*(RC低通),因爲反彈可能太快而無法由μC處理。 – JimmyB

回答

2

TomKeddie的回答看起來正確:你不會有任何問題。無論如何,在我看來,你的代碼至少有兩個原因在概念上是錯誤的。現在我會解釋你爲什麼。

有兩種輸入:你必須立即回答的和你必須回答但不是直接威脅的輸入。例如,通常一個安全終止點屬於第一組,因爲一旦你點擊它就需要停止執行器。另一方面,UI按鈕屬於第二組,因爲您不需要立即回答它。

注意:在一個完成良好的程序中,您通常可以在十分之一毫秒內回答第二種輸入,因此用戶永遠不會看到延遲。

現在,如果您的輸入屬於第二組輸入,則不應使用ISR讀取它,因爲您可能會阻止更重要的事情。而是在主循環中讀取它,並正確地將其刪除。例如,您可以使用Bounce庫,或通過實現它自己:

#define CHECK_EVERY_MS 20 
#define MIN_STABLE_VALS 5 

unsigned long previousMillis; 
char stableVals; 

... 

void loop() { 
    if ((millis() - previousMillis) > CHECK_EVERY_MS) 
    { 
     previousMillis += CHECK_EVERY_MS; 
     if (digitalRead(lsOuterLeftIn) != lsEngaged) 
     { 
      stableVals++; 
      if (stableVals >= MIN_STABLE_VALS) 
      { 
       lsEngaged = !lsEngaged; 
       stableVals = 0; 
      } 
     } 
     else 
      stableVals = 0; 
    } 

    ... 
} 

這將檢查每20ms如果該值改變。然而,該值只有在穩定超過5個週期(即100毫秒)時才被更新。

這樣你就不會阻止你的主程序執行該任務。

另一方面,如果您的輸入對您的設備造成嚴重威脅(例如終止),您需要儘快回覆。如果這是您的情況,您在回答前等待100毫秒,這與您輸入速度的需求相反。

當然,您不能反彈這樣的輸入,因爲反彈會導致延遲。但是,您可以對另一個國家享有特權。在連接到地面終止的情況下,嚴重的威脅是當輸入狀態是地面時。因此,我建議您設置變量這樣的方式:

  1. 當針下降你馬上把它設置爲0
  2. 當引腳變爲了您等待100毫秒(主循環)然後設置它。

的代碼要做到這一點是一樣的東西:

#define CHECK_EVERY_MS 20 
#define MIN_STABLE_VALS 5 

unsigned long previousMillis; 
char stableVals; 

attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING); 

... 

void loop() { 
    if ((millis() - previousMillis) > CHECK_EVERY_MS) 
    { 
     previousMillis += CHECK_EVERY_MS; 
     if ((digitalRead(lsOuterLeftIn) == HIGH) && (lsEngaged == LOW)) 
     { 
      stableVals++; 
      if (stableVals >= MIN_STABLE_VALS) 
      { 
       lsEngaged = HIGH; 
       stableVals = 0; 
      } 
     } 
     else 
      stableVals = 0; 
    } 

    ... 
} 

void ISR1() 
{ 
    lsEngaged = LOW; 
} 

正如你所看到的唯一中斷是下降一個,也是最重要的,這是非常短的。

如果您需要執行其他指令,例如停止電機,您可以在ISR1功能中(如果它們很短)。

只需記住:中斷服務程序必須是儘可能短,因爲當微控制器是他們中的一個變成盲目的一切

+0

感謝您的深入瞭解。在您確定的兩種情況中,我的設置確實屬於第二種情況,即我需要立即迅速處理中斷。但是,有一個問題。當限位開關脫開時,'ISR1'被再次調用,但是這次只是我不想做任何事情。我該如何處理? ISR1被調用是因爲你得到了一個'RISING'邊緣,後面跟着'RISING'和'FALLING'邊緣 –

+0

那麼,在我上傳的基本草圖中,你沒有問題,因爲即使lsEngaged已經設置爲LOW在這種情況下。如果你想在第一次進入'ISR1'時執行某些操作,你可以利用'lsEngaged'本身:例如,你可以把函數改成'{if(lsEngaged){... do當它下降時只有一次...} lsEngaged = LOW; }'。這樣,只要'lsEngaged'回到'true'(在'main'循環中),'if'中的代碼就會被執行。 – frarugi87

2

在AVR上,delay()如下實現。沒有涉及中斷(microsoft()返回timer0計數值,yield()指的是不會在簡單草圖中使用的調度器)。

我覺得這個評論對於可移植性來說是有意義的,你正在使用一個可以在越來越多平臺上工作的環境。你在AVR上做的很好,而不是在另一個平臺上。

我建議旋轉等待一個簡單的循環。除非功耗是一個問題,否則CPU不會做其他任何事情,但這超出了這個範圍。

https://github.com/arduino/Arduino/blob/79f5715c21a81743443269a855979a64188c93df/hardware/arduino/avr/cores/arduino/wiring.c

void delay(unsigned long ms) 
{ 
    uint16_t start = (uint16_t)micros(); 

    while (ms > 0) { 
     yield(); 
     if (((uint16_t)micros() - start) >= 1000) { 
      ms--; 
      start += 1000; 
     } 
    } 
} 
+0

'delay()'的實現特定於Arduino佈線庫而不是AVR。它可以以多種方式實施。 – Clifford

0

從你的反跳碼,它看起來像你可以抽出的反應時間爲100ms到開關接合。因此,如果您不需要在事件的微妙時間內做出反應,可以考慮每10ms(例如,從計時器ISR)輪詢輸入。

(使用外部中斷的原因只有兩個:1.您需要對信號做出快速響應(μs!),或2.您需要從深度節能模式中喚醒,其中定時器是。不活躍對於一切,你可以去基於定時器的輪詢)

僞代碼:。

#define STABLE_SIGNAL_DURATION 5 

uint8_t button_time_on = 0; 

volatile bool button_is_pressed = false; 

... 
// Every 10ms do (can be done in a timer ISR): 

if (read_button_input() == ON) { 

    if (button_time_on >= STABLE_SIGNAL_DURATION) { 

    button_is_pressed = true; 

    } else { 
    button_time_on++; 
    } 

} else { 
    button_time_on = 0; // button not pressed (any more). 
    button_is_pressed = false; 
} 

... 

main()

bool button_press_handled = false; 

while(1) { 
    // do your other main loop stuff... 

    button_press_handled = button_press_handled && button_is_pressed; 

    if (!button_press_handled && button_is_pressed) { 

    // Handle press of the button 

    // ... 

    // Note that we handled the event for now: 
    button_press_handled = true; 
    } 
}