實驗目的
練習處理外部中斷。在這個實驗中,你將利用 ISR() 巨集指令設定中斷服務函式 (Interrupt Service Routine, ISR),讓程式在 pin 2 產生外部中斷時自動執行 ISR。
材料
- Arduino 主板 x 1
- LED x 1
- Pushbutton x 1
- 10K 歐姆電阻 x 1
- 麵包板 x 1
- 單心線 x N
接線
- 把 LED 接到 pin13,長腳 (陽極) 接到 pin13,短腳 (陰極) 接到 GND
- 把 pushbutton 一支腳接到 +5V,另一支腳接到 pin 2 同時接一顆 10K 電阻連到 GND
INT0 與 INT1 外部中斷
如下圖所示,ATmega328 有兩個外部中斷,編號 0 (INT0) 在 pin 2 上,而編號 1 (INT1) 在 pin 3 上:
我們可以決定什麼時候要觸發 INT0 和 INT1 外部中斷,一共有四種狀況可以選擇:
- LOW: 當 pin 為 LOW 時觸發中斷
- CHANGE: 當 pin 狀態改變時觸發中斷,不管是從 HIGH 到 LOW 或從 LOW 到 HIGH
- FALLING: 當 pin 狀態從 HIGH 到 LOW 時觸發中斷,又稱負緣觸發
- RISING: 當 pin 狀態從 LOW 到 HIGH 時觸發中斷,又稱正緣觸發
選擇的方法是透過 ECIRA (External Interrupt Control Register A) 這個暫存器:
▲ 資料來源: ATmega168/328 Datasheet
以 INT0 外部中斷為例,假如我們希望 pin 2 狀態一有改變就觸發中斷,那麼就要把 ISC01 和 ISC00 設成 01:
除此以外,我們還要設定 EIMSK (External Interrupt Mask Register) 暫存器以啟用 INT0 或 INT1 中斷:
以 INT0 外部中斷為例,要啟用 INT0 外部中斷,得把 INT0 這個位元設成 1:
程式
先來看使用 attachInterrupt() 函式的版本(attachInterrupt.pde),這我們在「attachInterrupt() 與外部中斷」一文中介紹過,程式的邏輯是,當按鍵被按下時打開 LED 燈號,在按鍵放開時關閉 LED 燈號:
要特別注意是,程式裏的 buttonState 變數是宣告成 volatile,這樣做的目的是告訴 Compiler 不要做最佳化,避免變數狀態不同步。給你一個建議,程式主體跟 ISR 都會用到的變數,盡可能把它宣告成 volatile。
接下來是改成用 EICRA 和 EIMSK 暫存器和 ISR() 巨集指令的版本:
簡單的說,要處理 INT0 外部中斷,有三個步驟要做:
- 用 ISR() 巨集指令定義中斷服務函式
- 設定 EIMSK 暫存器啟用 INT0 中斷
- 設定 EICRA 暫存器決定什麼時候該觸發中斷
_BV() 巨集我們在「2.2) Blink part2」一文中已經介紹過,如果不想使用 _BV(),這樣寫也是可以的:
另外,ISR() 巨集指令也可以改用 SIGNAL() 巨集指令,SIGNAL() 是早期寫 ISR 的巨集指令。建議你盡可能使用 ISR() 巨集指令,因為 ISR() 比較直覺,只不過你必須知道 SIGNAL() 和 ISR() 幾乎是一樣的東西,而且很多前輩留下的 AVR 程式是用 SIGNAL() 巨集指令。
5 意見:
LOW: 當 pin 為 LOW 時觸發中斷
CHANGE: 當 pin 狀態改變時觸發中斷,不管是從 HIGH 到 LOW 或從 LOW 到 HIGH
FALLING: 當 pin 狀態從 "LOW 到 HIGH 時觸發中斷" ,又稱負緣觸發
RISING: 當 pin 狀態從 "LOW 到 HIGH 時觸發中斷" ,又稱正緣觸發
我猜打""部分應該其中有一個是打錯的:)是嗎?
我寫錯了!這句:
「FALLING: 當 pin 狀態從 "LOW 到 HIGH 時觸發中斷"」
應該是這樣才對:
「FALLING: 當 pin 狀態從 "HIGH到 LOW 時觸發中斷"」
謝謝你的訂正。:)
Cooper好~~~
這邊有一個問題一直困擾我!!
已知道有設定一個巨集
#define _BV(x) (1 << x)
所以EICRA &= ~_BV(ISC01);的意思應該是
EICRA = EICRA & 1111 1101
好讓EICRA的第一個bit被設定為0
這邊可得到一個結論"ISC01代表1"
所以也可以推出另外的結論
ISC10代表2
ISC11代表3
那這邊衍生出一個問題
假設我想設定ISC11是1
那為什麼我不能直接下指令ISC11=1;,這樣就好了呢?是否一定要使用EICRA |= _BV(ISC11);
再者, 如果可以很單純的使用ISC11=1;
那是否跟ISC11代表3這樣的設定有矛盾呢?
也就是ISC11=3 <=當使用_BV巨集推論出來的
ISC11=1 <==希望直接設定暫存器裡面的資料
希望Cooper能為我解答,謝謝~~~~
我想我有很多觀念需要釐清XDDD
這個是 ISC00~ISC11 的巨集定義 (可在 Arduino tools/avr/avr/include/iom328.h 找到):
#define ISC00 0
#define ISC01 1
#define ISC10 2
#define ISC11 3
要注意,這只是定義該巨集是第幾個位元,不是指 "那一個位元" 喔!
也就是說 ISC10 巨集代表它是第 2 個位元,但並不是指 EICRA 的 bit 2
所以如果你寫:
ISC11 = 1;
那麼這段程式碼在巨集代換後會變成:
3 = 1; // 因為 ISC11 為 3
這樣就錯了。
所以沒有辦法那麼方便,還是得乖乖地搭配 _BV() 巨集來設定指定的位元喔
謝謝!!
原來有這個先設定了~
#define ISC00 0
#define ISC01 1
#define ISC10 2
#define ISC11 3
thx!!
張貼留言