2023年4月3日 星期一

MicroPython 學習筆記 : 硬體中斷 (IRQ) 測試

我學習 MicroPython 這麼久以來似乎沒測試過 GPIO 腳中斷輸入 (IRQ) 功能, 今天為了完成鄉下頂樓離網太陽能系統 ATS 切換偵測通知功能, 先簡單測試一下 MicroPython 的中斷用法. 關於中斷可參考以前測試 Arduino 時的筆記, 觀念都是一樣的 :


簡言之, 因應 GPIO 輸入狀態變化而進行處理的方法有兩種, 一是輪詢法 (polling), 這是 MPU 主動用無限迴圈不斷掃描檢查某些輸入腳, 檢查狀態是否有改變, 此法會讓 MPU 的程序耗費耗費時間做狀態檢查, 對於 timing 很在乎的應用不適合. 二是中斷法 (interrupt), 這是當中斷輸入出現時, MPU 被動回應外部中斷去執行事先定義好的中斷處理函式, 未發生中斷時 MPU 可執行其他工作, 所以中斷法比較有效率. 

MicroPython 的中斷處理參考官網教學 :


實際範例可參考下面關於 PIR 人體移動偵測的教學文章 :


由第一篇 Random Nerd 的文章可知, ESP8266 除了 GPIO16 以外, 其他 GPIO 腳都可用做中斷輸入; 而 ESP32 則是除了 GPIO6 與 GPIO11 這兩支腳外皆可當中斷輸入

使用中斷功能的程序如下 :
  1. 匯入 machone.Pin 類別 : 
    from machine import Pin
  2. 定義一個中斷處理函式 (發生中斷時呼叫之用) :
    def irq_handler(pin):
        #do something
    注意, MicroPython 會將發生中斷的 Pin 物件自動傳入中斷處理函式, 除此之外不能傳入其它參數.  
  3. 定義一個中斷輸入腳 (例如 GPIO5) :
    irq_pin=Pin(5, Pin.IN) 
    也可以啟用上拉電阻 :
    irq_pin=Pin(5, Pin.IN, Pin.PULL_UP) 
  4. 呼叫中斷輸入腳 Pin 物件的 irq(trigger, handler) 方法 :
    irq_pin.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler)
    trigger 參數可用選項 :
    Pin.IRQ_RISING (=1, 上升緣觸發中斷)
    Pin.IRQ_FALLING (=2, 下降緣觸發中斷)
    3 (上升緣或下降緣都會觸發中斷)
例如 : 

>>> from machine import Pin   
>>> Pin.IRQ_RISING         (trigger=1 :上升緣觸發)
1
>>> Pin.IRQ_FALLING     (trigger=2 : 下降緣觸發)
2

下面以一個簡單的範例來測試 ESP8266 的中斷功能, 此例使用 D1 mini 開發板, 板載 LED 位於 GPIO2 (D4 腳), 當中斷發生時可點亮此 LED (注意是 Low 驅動), 另外用 GPIO5 (D1 腳) 當中斷輸入腳並啟用上拉電阻, 外面接一個按鈕另一端接地, 故平常中斷輸入腳為 High (上拉之故), 當按鈕被按下時接地變成 Low, 如果設定觸發型態為下降緣就會觸發中斷.

注意, 按鈕開關為兩個同時開關, 它有四個接點, 其中兩組較短距離的接點平時為開路, 按下時才接通; 而兩組較長距離接點則一直都接通 :





D1 mini 接腳圖如下 : 


Source : DIYIOT


D1 mini 腳位的 GPIO 編號對照如下表 : 


 板子腳位 ESP8266 腳位 功能
 D0 GPIO16 IO
 D1 GPIO5 IO, SCL 
 D2 GPIO4 IO, SDA 
 D3 GPIO0 IO, 內建 10K 上拉電阻
 D4 GPIO2 IO, 內建 10K 上拉電阻與 LED
 D5 GPIO14 IO, SCK
 D6 GPIO12 IO, MISO
 D7 GPIO13 IO, MOSI
 D8 GPIO15 IO, SS, 內建 10K 上拉電阻
 TX TXD UART 送端
 RX RXD UART 收端
 A0 ADC 類比輸入 (0~3.3V)


參考 : 



測試 1 : 下降緣觸發中斷 (trigger=Pin.IRQ_FALLING) [看原始碼  

#main.py
from machine import Pin
from time import sleep

def irq_handler(pin):       
    global irq_pin               # 全域變數
    global irq_occurred      # 全域變數
    irq_occurred=True       # 設定中斷旗標
    irq_pin=pin                  # 儲存發生中斷的 Pin 物件

irq_occurred=False            # 中斷旗標預設為未發生
led_pin=Pin(2, Pin.OUT) 
led_pin.value(1)                 # 關閉板載 LED 燈 (Low 驅動)
btn_pin=Pin(5, Pin.IN, Pin.PULL_UP)      # 設定 GPIO5 為輸入腳並啟用上拉電阻
btn_pin.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler)     # 設定為中斷輸入腳

while True:
    if irq_occurred:
        print(f'{irq_pin} 發生中斷')
        led_pin.value(0)           # 點亮板載 LED
        sleep(3)
        led_pin.value(1)           # 熄滅板載 LED
        irq_occurred=False      # reset 中斷旗標

在 Thonny 中編輯此程式並另存為 main.py 上傳到 D1 mini, 按 Reset 鈕會自動執行此 main.py 程式進入無限迴圈, 當按下按鈕時板載 LED 會亮, 命令列出現 'Pin(5) 發生中斷', 三秒後 LED 會熄滅, 又可再按按鈕觸發中斷 (3 秒內按無作用), 結果如下 :


MicroPython v1.18 on 2022-01-17; ESP module with ESP8266
Type "help()" for more information.
>>> 
Pin(5) 發生中斷
Pin(5) 發生中斷
Pin(5) 發生中斷
Pin(5) 發生中斷
Pin(5) 發生中斷
Pin(5) 發生中斷




也可以改用上升緣觸發中斷, 只要將 trigger 參數改成 Pin.IRQ_RISING (=2) 即可, 例如 :


測試 2 : 下降緣觸發中斷 (trigger=Pin.IRQ_RISING) [看原始碼

from machine import Pin
from time import sleep

def irq_handler(pin):
    global irq_pin
    global irq_occurred
    irq_occurred=True
    irq_pin=pin

irq_occurred=False
led_pin=Pin(2, Pin.OUT)
led_pin.value(1) 
btn_pin=Pin(5, Pin.IN, Pin.PULL_UP)
btn_pin.irq(trigger=Pin.IRQ_RISING, handler=irq_handler)

while True:
    if irq_occurred:
        print(f'{irq_pin} 發生中斷')
        led_pin.value(0)
        sleep(3)
        led_pin.value(1)
        irq_occurred=False
        
這時按下按鈕時不會觸發中斷, 放開按鈕時才會.

如果下降緣與上升緣都要觸發, 則 trigger 參數要傳入 3, 對, 就是 3, 很奇怪的是, Pin 物件並沒有定義像 Pin.IRQ_BOTH, Pin.IRQ_RIGING_FALLING  或 Pin.IRQ_FALLING_RISING 的常數來代表上升緣與下降緣都觸發的常數, 但傳入 trigger=3 就能 work : 

MicroPython v1.18 on 2022-01-17; ESP module with ESP8266
Type "help()" for more information.
>>> from machine import Pin      
>>> dir(Pin)   
['__class__', '__name__', 'value', '__bases__', '__dict__', 'IN', 'IRQ_FALLING', 'IRQ_RISING', 'OPEN_DRAIN', 'OUT', 'PULL_UP', 'init', 'irq', 'off', 'on']
    
但傳入 trigger=3 就有上升緣與下降緣都觸發中斷的效果, 例如 :


測試 3 : 下降緣或上升緣觸發中斷 (trigger=3) [看原始碼 
    
from machine import Pin
from time import sleep

def irq_handler(pin):
    global irq_pin
    global irq_occDIRurred
    irq_occurred=True
    irq_pin=pin

irq_occurred=False
led_pin=Pin(2, Pin.OUT)
led_pin.value(1) 
btn_pin=Pin(5, Pin.IN, Pin.PULL_UP)
btn_pin.irq(trigger=3, handler=irq_handler)

while True:
    if irq_occurred:
        print(f'{irq_pin} 發生中斷')
        led_pin.value(0)
        sleep(3)
        led_pin.value(1)
        irq_occurred=False
        
測試時先按住按鈕, 這時按下時的下降緣會觸發中斷, 板載 LED 會亮 3 秒後熄滅, 這時再放開按鈕又會因上升緣而觸發中斷. 


2023-04-03 補充 :

搜尋舊筆記時發現原來我在 2017 年時就曾經用中斷來測試 PIR 了, 參考 : 


沒有留言 :