ESP32 教學系列(十二):硬體中斷

ESP32 教學系列(十二):硬體中斷

前言

常見的微控制器系列,例如Arduino的ATmega系列(像是ATmega328P)、STM32系列、PIC系列,或是ESP32,通常都支援多種不同的中斷類型。ESP32可以用硬體中斷來處理外部硬體或模組引起的中斷,像是GPIO或是觸碰(TOUGH)中斷;用軟體中斷處理定時器中斷,本篇主要說明的是硬體中斷。

什麼是中斷?

中斷(Interrupt)是一種在微控制器在執行主要工作時,仍然能夠立即回應外部事件的一種機制。一個設計得當的中斷,不僅可以簡化程式邏輯,還能提高處理器的執行效率。我們可以用中斷來偵測開關、旋轉編碼器,或是光遮斷器等的變化。要理解中斷,讓我們先用一個例子來說明。

假如要偵測一個按鈕是否被按下,我們可能會這樣寫:

像這樣依照程式流程定時且反覆檢查數值的方法就稱做輪詢。在不使用中斷的情況下,通常會使用輪詢來檢查外部事件,優點是程式寫起來直覺、好理解,但是這其實會讓ESP32的CPU在很低的效率下運作,因為系統需要在主程式迴圈中不斷的檢查GPIO的狀態。如果主程式區塊還有很多需要處理的任務,檢查的速度也會變慢。

而使用中斷的好處是,只要特定的接腳偵測到狀態發生變化,就能立即「中斷」CPU現在正在做的事情,優先處理目前的事件下要完成的任務。

換句話說,中斷機制允許系統只在事件發生時才進行處理,這麼一來就能節省不必要的處理器運算和電力消耗,而我們也能確保特定事件發生時可以被即時的處理,是不是一舉多得呢?

ISR(Interrupt Survice Routine)

從上面的例子中,我們可以知道中斷的核心概念是「事件驅動」。當有更優先、更需要即時處理的事件發生時,CPU就能夠在最短的時間內反應並且處理完畢,不需要經過輪詢的等待時間。

當中斷發生時,CPU會暫時停止執行主程式,轉而執行相應的處理程序。用中斷的術語來說,這個處理程序叫做「中斷服務程序 (Interrupt Survice Routine, ISR)」。 ISR是專門用來處理中斷事件的一段特殊函式,負責處理中斷發生後要執行的任務或是操作,它不能傳入任何參數,也不會回傳任何內容。當ISR完成任務後,CPU會返回主程式繼續執行原本的程式。

ISR具有以下特點:

  • CPU一次只能處理一個ISR
  • 發生多個中斷時,會依照ISR的優先順序執行
  • 每個ISR都專注於處理特定的事件
  • ISR越簡單越好,要盡可能保持很短的執行時間,才能減少對系統的干擾
  • 處理中斷時,通常會使用全域變數在ISR和主程式之間傳遞資料,為了確保ISR跟主程式共享的變數可以正常更新,會將變數宣告成volatile型態。(讓變數可以被多個執行緒訪問)

ESP32的GPIO中斷

ESP32的內部使用中斷矩陣來處理許多不同的中斷來源。

所有ESP32能使用的GPIO都可以拿來當作GPIO中斷接腳,其中10隻接腳支援觸碰感測器(touch sensor)中斷(請參考進階功能章節)。

ESP32 教學系列(十二):硬體中斷

接下來說明中斷常用的語法:

設定中斷

在Arduino IDE中,我們透過將GPIO接腳與對應的ISR函式綁定來啟用中斷,設定中斷時可以使用 attachInterrupt

attachInterrupt(GPIO_pin, ISR, mode);

參數說明:

  • GPIO_pin: 將其設定為中斷接腳
  • ISR: 當此中斷發生後要執行的ISR函式
  • mode: 定義中斷觸發的條件。有八種模式可以選擇。
//Interrupt Modes
#define DISABLED  0x00
#define RISING    0x01
#define FALLING   0x02
#define CHANGE    0x03
#define ONLOW     0x04
#define ONHIGH    0x05
#define ONLOW_WE  0x0C
#define ONHIGH_WE 0x0D

Tips:

在Arduino-esp32核心函式庫的esp32-hal-gpio.h檔案中可以找到這些模式的定義。

說明

  • DISABLED: 禁用GPIO中斷
  • RISING: 上升邊沿觸發(rising edge trigger)
  • FALLING: 下降邊沿觸發(falling edge trigger)
  • CHANGE: 任意邊沿觸發 (any edge trigger)
  • ONLOW: 低電位觸發(low level trigger)
  • ONHIGH: 高電位觸發(high level trigger)
  • ONLOW_WE和ONHIGH_WE是當ESP32在 Light-sleep模式下用來喚醒CPU的中斷,一般比較少使用到。”WE”是wakeup enable的縮寫。

關閉中斷

detachInterrupt語法可以用來關閉中斷,取消ESP32對特定接腳的監聽。

detachInterrupt(GPIO_pin);

當我們關閉特定接腳的中斷時,需要再次呼叫attachInterrupt 或是重新啟動系統才能重新啟用中斷。

中斷服務程序(ISR)

ISR函式會在中斷被呼叫時執行,它的語法結構看起來會像這樣:

void IRAM_ATTR ISR() {
    // 中斷函式內容
}

其中ISR是自訂的函式名稱,而IRAM_ATTR的意思是將ISR函式存放在IRAM(Instruction RAM),而不是flash。從IRAM讀取ISR 函式可以加快載入速度。

接下來,讓我們用實際的範例來了解如何使用GPIO中斷。

範例1:使用按鈕改變LED狀態

這個範例使用按鈕觸發ESP32的GPIO中斷,當按鈕狀態有變化(上升或下降)時,LED的狀態也會跟著改變。

ESP32教學系列(十二):硬體中斷

材料清單

連接示意圖

ESP32教學系列(十二):硬體中斷

範例程式碼

程式碼說明

  1. 一開始我們先在setup()中,用attachInterrupt設定中斷條件。 將btn_pin設為中斷接腳,指定toggleLED為ISR,觸發條件為CHANGE。 只要接腳偵測到電位狀態的改變,就會呼叫toggleLED
    attachInterrupt(btn_pin, toggleLED, CHANGE);
  2. toggleLED中,如果直接讀取按鍵狀態會產生開關彈跳的問題。在撰寫ISR函式時,要避免使用delay()延時,因此我們在範例程式中使用millis()計時,避免讀取到按鈕不穩定的狀態。
  3. 為了確保執行ISR時能夠正常更新變數內容,變數btn_state、previous_time、current_time都宣告為volatile。

進階功能:觸碰感測器中斷

ESP32除了可以使用按鈕或外部感測器觸發GPIO中斷之外,還可以使用內建的觸碰感測器/電容式感測器(Touch sensor/ Capacitive sensor)功能來觸發中斷。

下圖中標示為粉紅色的都是能夠讀取觸碰感測器的接腳,從TOUCH_0~TOUCH_9,一共有10隻接腳。

ESP32教學系列(十二):硬體中斷

有關觸摸接腳的原理我們以後找機會再詳細介紹,此處先說明如何讀取觸碰感測器數值,並且藉由手指觸摸來觸發中斷訊號。

取得感測數值

如果要取得目前的觸碰感測數值,可以用touchRead

touchRead(touch_pin);  // 取得接腳的感測數值

參數說明:

  • touch_pin: 傳入T0~T9之間的任意接腳

設定中斷

一般的GPIO中斷使用attachInterrupt來設定中斷事件,而觸碰接腳則是使用touchAttachInterrupt

touchAttachInterrupt(touch_pin, touch_ISR, threshold);

參數說明:

  • touch_pin: 傳入T0~T9之間的任意接腳
  • touch_ISR: 觸碰事件的ISR函式
  • threshold: 呼叫中斷的臨界值,小於臨界值表示偵測到觸碰。可以藉由touchRead的結果設定此數值。

範例2:觸碰中斷

這個範例使用導線來偵測手指是否碰觸到接腳。當手指觸碰到導線時,就會呼叫中斷函式,點亮開發板上面的LED燈,並且在序列埠監控視窗顯示數值和結果。

材料清單

  • ESP32開發板 x1
  • micro USB 傳輸線 x1
  • 麵包板 x1
  • 杜邦線(公-公) x1

連接示意圖

ESP32教學系列(十二):硬體中斷

範例程式碼

執行結果

ESP32教學系列(十二):硬體中斷

小結

硬體中斷可以用來偵測接腳狀態的變化,它讓微控制器的CPU不用一直在主程式循環中反覆檢查GPIO的狀態,也能夠在發生變化時立即做出反應。在這篇文章中,我們介紹了ESP32的兩種常見的GPIO中斷,也透過實際的範例程式碼說明中斷的使用方式。