You are currently viewing 使用Arduino IDE的ESP8266中斷和計時器

使用Arduino IDE的ESP8266中斷和計時器

在本課程中,您將學習如何使用Arduino IDE在ESP8266 NodeMCU中使用中斷(interrupts)和定時器(timers)。中斷使您能夠檢測GPIO狀態的變化,而無需不斷檢查其當前值。對於中斷,當檢測到更改時,將觸發一個事件(調用一個函數)。

舉例來說,我們將使用PIR被動紅外線感測器檢測動作:檢測到物體移動時,ESP8266會啟動計時器並開啟LED預定的秒數。當計時器完成倒數計時時,LED自動熄滅。

要建立一個中斷功能,請使用attachInterrupt()函數

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

其中第一個參數是呼叫 digitalPinToInterrupt() 函數並傳入數位腳編號, 也可以不呼叫此函數直接傳入腳位編號 (不建議); 第二個參數是自訂的中斷服務函數名稱 (Interrupt Service Routine); 第三個參數則是中斷觸發模式, 有 LOW, RISING, FALLING, CHANG, 以及 HIGH (只能用於 Arduino Due)。

在繼續本教程之前,您應該在Arduino IDE中安裝ESP8266插件。如果尚未安裝,請按照本教程在Arduino IDE中安裝ESP8266

介紹ESP8266中斷 

中斷在微控制器程序中很有用,可以幫助解決時序問題。

使用中斷時,不需要一直檢查當前引腳值。當檢測到更改時,將觸發事件–調用一個函數。此功能稱為中斷處理程式(ISR)。

當發生中斷時,處理器將停止執行主程序以執行任務,然後返回主程序,如下圖所示。

ESP8266 NodeMCU中斷簡介:如何工作

這對於在檢測到移動或按下按鈕而無需持續檢查其狀態時觸發動作特別有用。

 attachInterrupt()函數 

 

要在Arduino IDE中設置中斷,請使用 attachInterrupt() 函數,該函數接受以下參數作為輸入:GPIO中斷引腳,要執行的函數的名稱以及模式:

attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode);

GPIO中斷引腳

第一個參數是GPIO中斷。你應該用digitalPinToInterrupt(GPIO)將實際的GPIO設置為中斷引腳。例如,如果要將GPIO 14用作中斷,請使用:

digitalPinToInterrupt(14)

ESP8266支持除GPIO16之外的任何GPIO中的中斷。

ISR

 attachInterrupt() 函數ISR是每次觸發中斷時都會調用的函數的名稱-中斷處理程式(ISR)。

ISR功能應盡可能簡單,以便處理器快速返回主程序的執行。

最好的方法是使用全局變數並在主變數內向主代碼發出中斷已發生的信號。 loop()檢查並清除該標誌,然後執行代碼。

ISR需要 ICACHE_RAM_ATTR 在函數定義之前在RAM中運行中斷代碼。

中斷模式

第三個參數有3種不同的模式:

  • CHANGE(更改):每當引腳更改值時(例如,從HIGH變為LOW或LOW變為HIGH)觸發中斷
  • FALLING:當引腳從高電平變為低電平時觸發
  • RISING:在引腳從低電平變為高電平時觸發

對於我們的範例,將使用RISING模式,因為當PIR被動感測器檢測到物體移動時,其連接的GPIO從LOW變為HIGH。

ESP8266定時器介紹 

本節我們將使用計時器。我們希望LED在檢測到物體移動後保持點亮預定的秒數。而不是使用delay(), 該函數會阻止您的代碼,並且在確定的秒數內不允許您執行其他任何操作,我們將使用計時器。
delay(time in milliseconds);

你打電話的時候 delay(1000) 您的程序在該行上停止1秒鐘。 delay()是一個阻止功能。阻塞功能可阻止程序在完成特定任務之前執行任何其他操作。如果您需要同時執行多個任務,則不能使用delay()。對於大多數項目,應避免使用delay,而應使用計時器。

使用一個叫做 millis() 您可以返回自程序首次啟動以來經過的毫秒數。

millis();

為什麼該功能有用?因為通過使用一些數學運算,您可以輕鬆驗證經過了多少時間而不會阻塞您的代碼。

使用millis()閃爍LED(without delay)

如果您不熟悉 millis()功能,我們建議閱讀本節。如果您已經熟悉計時器,則可以跳到PIR運動傳感器項目。

以下代碼段顯示如何使用 millis()功能建立一個閃礫項目。它將使LED點亮1000毫秒,然後將其關閉。

/*********
  Terry Lee
  完整說明請參閱 http://honeststore.com.tw
*********/

// 常數不會改變。這裡是設置PIN引腳數
const int ledPin =  12;      // the number of the LED pin (D6)

// 可變變數
int ledState = LOW;             // ledState used to set the LED

// 一般來說,您應該使用“unsigned long”來保持時間的變量
// 對於int存儲來說,該值很快就會變得太大
unsigned long previousMillis = 0;        // will store last time LED was updated

// 常數不會改變
const long interval = 1000;           // 閃爍的間隔(毫秒)

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // 這是您將需要一直運行的代碼的位置。

  // 檢查是否是LED閃爍時候;也就是說,如果是當前時間和最後一次眨眼之間的差異
  // LED大於您想要的間隔,則LED閃爍。
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // 保存最後一次閃爍LED
    previousMillis = currentMillis;

    // 如果LED熄滅開啟它,反之亦然
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // 使用digitalWrite函數設定LEDState LED狀態
    digitalWrite(ledPin, ledState);
  }
}

代碼如何工作

讓我們看看如何使用 millis() 功能。

基本上,此程式碼意思為當前的時間記錄(currentMillis)減去先前記錄的時間(previousMillis)。如果餘數大於interval 常數(這種設為1000毫秒),則程序將更新previousMillis 變數,並設置為當前時間,然後打開或關閉LED。

if (currentMillis - previousMillis >= interval) {
    // 保存最後一次閃爍LED
    previousMillis = currentMillis;
    (....)

由於此段程式碼段是非阻塞的,因此位於該if語句之外的任何程式碼均會正常運作。

現在,您應該能夠理解,可以將其他任務添加到您的 loop() 功能,您的程式碼仍將每秒鐘閃爍一次LED。

您可以將此程式碼上傳到ESP8266進行測試。開發板的LED應該每秒閃爍一次。

具有PIR被動感測器的ESP8266 NodeMCU 

在本節中,您將學習如何使用程式碼中的中斷和計時器通過PIR被動感測器檢測物體移動。

所需零件

以下是完成本節所需的零件列表:

  • ESP8266
  • PIR被動感測器(HC-SR501)
  • 5mm LED
  • 330歐姆電阻
  • 麵包板
  • 跳線

原理圖,示意圖

將PIR被動感測器和一個LED組裝到ESP8266。我們將LED連接到GPIO 12 (D6)和PIR被動感測器引腳連接到 GPIO 14 (D5)。

ESP8266 NodeMCU中斷和定時器及PIR運動傳感器原理圖

重要:此項目中使用的HC-SR501 PIR被動感測器的工作電壓為5V。但是,如果您使用的是Mini AM312之類的其他PIR被動感測器,則其工作電壓為3.3V。您可以將其修改為在5V下工作,也可以使用Vin引腳簡單地為其供電。

程式碼

如示意圖所示對電路進行接線後,將提供的程式碼複製到Arduino IDE。

您可以按原樣上傳代碼,也可以修改檢測到運動後LED點亮的秒數。只需更改timeSeconds 變數與您想要的秒數。

/*********
  Terry Lee
  完整說明請參閱 http://honeststore.com.tw  
*********/

#define timeSeconds 3

// 設定LED和PIR Motion Sensor的引腳變數
const int led = D6;
const int motionSensor = D5;

// 計時器:輔助變量
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

// 檢查是否檢測到動作,設置LED高位並啟動計時器
ICACHE_RAM_ATTR void detectsMovement() {
  Serial.println("偵測物體在移動!!!");
  digitalWrite(led, HIGH);
  startTimer = true;
  lastTrigger = millis();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  
  // PIR Motion Sensor mode INPUT_PULLUP
  pinMode(motionSensor, INPUT_PULLUP);
  // 將Motionsensor引腳設置為中斷,分配中斷功能並設置上升模式
  attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

  // Set LED to LOW
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // 目前時間
  now = millis();
  // 在timeSeconds變數中定義的秒數後關閉
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
    Serial.println("物體移動停止...");
    digitalWrite(led, LOW);
    startTimer = false;
  }
}

程式碼如何工作

讓我們看一下程式碼。

首先將兩個GPIO引腳設定為 LED 和 PIR被動感測器 變數。

const int led = D6;
const int motionSensor = D5;

然後,建立變數,該變數將允許您設定計時器以在檢測到物體移動後關閉LED。

unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

now變數保存當前時間。lastTrigger變數保存PIR被動感測器檢測到物體運動的時間。startTimer 是一個布林變數,在檢測到運動時啟動計時器。

setup()

在 setup()裡面,首先以115200鮑率初始化序列端口。

Serial.begin(115200);

將PIR被動感測器設置為 INPUT_PULLUP

pinMode(motionSensor, INPUT_PULLUP);

要將PIR被動感測器引腳設置為中斷功能,請使用 attachInterrupt() 功能如前所述。

attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

GPIO 14引腳將檢測物體移動的數數,它會調用 detectorMovement()函數在 RISING 模式。

LED是一個 OUTPUT 其狀態預設為LOW

pinMode(led, OUTPUT);
digitalWrite(led, LOW);

loop()

loop()功能是不斷地反復運行。在每個循環中,now 變數會一直更新成為當前時間。

now = millis();

在 loop()函數中基本上是沒有動作。只是,當PIR檢測到物體移動時,detectorMovement()函數會被調用。因為我們在 setup()函數中已經設定中斷的函數

detectorMovement()函數功能為在序列監視器中印出”偵測物體在移動!!!”消息,開啟LED,設置 startTimer 布林變數值為true並且更新 lastTrigger 變數值為當前時間。

// 檢查是否檢測到動作,設置LED高位並啟動計時器
ICACHE_RAM_ATTR void detectsMovement() {
  Serial.println("偵測物體在移動!!!");
  digitalWrite(led, HIGH);
  startTimer = true;
  lastTrigger = millis();
}

完成此步驟後,程式碼將返回到 loop()函數。這時,startTimer變數值是true。因此,now減掉lastTrigger時間差若大於我們設定的時間(當檢測到物體移動後動作),則if語句的斷判為true。

 // 在timeSeconds變數中定義的秒數後關閉
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
    Serial.println("物體移動停止...");
    digitalWrite(led, LOW);
    startTimer = false;
  }

“ 物體移動停止…”消息將在序列監視器中印出,LED熄滅,並且 startTimer 變數值設置為false。

範例 

將程式碼上傳到ESP8266。確定選擇正確的開毃板和COM端口。

以115200的鮑率打開序列監視器。

波特率115200的Arduino IDE Open Serial Monitor

將手移到PIR感測器的前面。LED應該亮起,並且在序列監視器中印出消息,提示“ 偵測物體在移動 !!!!”。10秒鐘後,LED應該熄滅。

總結 

綜上所述,中斷對於檢測GPIO狀態的變化並立即觸發功能很有用。您還了解到,應該使用計時器編寫非阻塞程式碼。

我們希望本教程對您有所幫助。我們還有其他教程,介紹如何使用MicroPython和ESP32處理中斷:

  • MicroPython:ESP32和ESP8266中斷
  • ESP32帶PIR運動傳感器,使用中斷和定時器

了解更多有關ESP8266開發板的信息:

  • 使用EPS8266的家庭自動化
  • 使用ESP32和ESP8266進行MicroPython編程
  • ESP8266的免費項目和教程

謝謝閱讀。

發佈留言