Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)

回覆文章
yehlu
Site Admin
文章: 3245
註冊時間: 2004-04-15 17:20:21
來自: CodeCharge Support Engineer

Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)

文章 yehlu »

小狐狸事務所

2016年9月12日 星期一
Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)
做完 Arduino 按鈕開關實驗才發現, 一個這麼簡單的按鈕竟然這麼難搞, 印證了物理與數學之間確實有一道鴻溝. 數理邏輯嚴謹, 一就是一, 二就是二, 但在物理機械特性上就沒這麼黑白分明了. 物理世界有一個慣性, 所以一個作用不會一次到位, 按鈕的彈跳現象就是彈性與慣性作用下的結果, 微控器必須不斷追蹤 I/O 輸入的狀態變化, 將雜訊濾除掉, 取得穩態訊號後才能做出符合期望的控制動作, 這種方式叫做輪詢法 (Polling). 前次的按鈕開關測試全部都是這種方法, 參考 :

# Arduino 按鈕開關測試 (一) : 輪詢法

輪詢法必須在 loop() 主迴圈中持續追蹤輸入狀態, 會一直占用主程序資源 (執行時間與記憶體空間). 同時, 由於處理器必須主動檢查周邊狀態, 使得 MPU 的負荷與時間延遲都增加, 如果後面還有其他對 timing 要求很高的程序的話就不太好了.

其實微控器要偵測周邊狀態的變化, 還有一個辦法, 就是使用硬體外部中斷, 透過一個自訂函數來處理中斷事件, 處理器是被動接受中斷要求才去服務周邊, 這樣可大幅提高處理器工作效率. 這兩種 I/O 處理方式比較如下 :
Polling (輪詢法) :
微控器主動檢查輸入腳的狀態變化, 亦即在 loop() 主迴圈中不斷地檢查輸入腳, 若發現有變化就處理, 耗費系統資源較多.
Interrupt (中斷法) :
微控器不須在主迴圈檢查周邊設備狀態, 而是被動因應周邊中斷的觸發, 將目前的狀態存入堆疊, 暫停現在執行中的程序去處理中斷事件, 控制權移轉到中斷處理函數, 處理完再從堆疊取回被中斷程序繼續執行原先的程序, 因此所耗費之系統資源少.
我先將 Arduino 的中斷功能大致整理如下 :

一般微控器大都有特定之數位接腳可觸發外部硬體中斷, Arduino 各種板子的硬體中斷因其使用之微控器不同而有差異, 如下表所示 :

板子 微控器 中斷 0 中斷 1 中斷 2 中斷 3 中斷 4
UNO/NANO/Pro mini ATmega328P D2 D3
Leonardo/Micro ATmega32u4 D3 D2 D0 D1 D7
Mega2560 ATmega2560 D2 D3 D21 D20 D19

採用 ATmega328P 微控器的 UNO, NANO, 或 Pro mini 都只有兩個硬體中斷 D2 與 D3. 除此之外, Arduino 第一個基於 ARM 架構的 Due 採用 Atmel SAM3X8E ARM Cortex-M3 處理器, 其 54 個數位接腳每一個都具有外部硬體中斷功能, 參考 :

# https://www.arduino.cc/en/Reference/AttachInterrupt

這些特定數位接腳的外部硬體中斷功能預設是關閉的, 必須使用 attachInterrupt() 函數予以設定並啟用. 其 API 如下, 具有三個參數 :

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

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

當外部硬體中斷發生後, ATmega328 處理器會將程式暫存器推入堆疊保存, 然後跳到 ISR 所在位址執行 ISR 的第一條指令, 這樣總共需要 82 個時脈週期. 參考 :

# http://www.gammon.com.au/interrupts (超詳細)

Arduino 的中斷相關函數如下表 :

函數 說明
attachInterrupt(int, ISR, mode) 指派中斷服務函式
int=中斷編號, 0 或 1
ISR=中斷服務函式名稱
mode=中斷模式 (LOW, CHANGE, RISING, FALLING)
detachInterrupt(int) 移除指定腳位之中斷功能, int=中斷編號, 0 或 1
noInterrupt() 停止全部中斷功能 (除 reset 外)
interrupts() 重新啟用全部中斷功能

其中後面三個函數都是需要動態控制中斷功能時才會用到, 例如當要取得目前的 LED 狀態時, 我們不想此變數被中斷改變, 這時就可以先停止所有中斷 (Reset 除外), 取得變數值後再恢復 :

noInterrupts(); //disable all interrupts
boolean state=ledState; //get volatile variable set by ISR
interrupts(); //enable interrupt

要注意的是暫停不要太久, 否則會影響計時器的運作 (計數器會溢位). 而 detachInterrupt(int) 則是移除指定腳位 (0/1) 的中斷功能.

中斷處理函數 ISR 係自訂, 但與一般的自訂函數不同, 必須符合下列限制 :
ISR 不可以有參數亦無傳回值.
ISR 要儘可能地短, 最好在五行指令以內, 以免中斷時間過長.
不要在 ISR 內使用與時間有關的函式如 millis(), micro(), 與 delay(), 因為這些函數也是依賴中斷功能去計數或計時 (delay 是依賴 millis, millis 是依賴計時器), 在 ISR 內呼叫它們毫無作用 (micro 只是剛開始有效). 只有 delayMicroseconds() 因為不使用計數器可在 ISR 內正常運作. 如果在 ISR 內必須使用時間延遲功能, 必須自行撰寫時間延遲函數.
在 ISR 執行期間, 序列埠輸出如 Serial.print() 可能會遺失一些資料, 因為新版 Arduino IDE 使用中斷來做序列埠輸出入, 故不要在 ISR 內使用序列埠指令.
因 ISR 不能有參數, 故必須透過全域變數與主程式或其他函數分享資料. 這些可能在 ISR 內被改變其值之全域變數務必加上 volatile (會被改變的) 關鍵字.
中斷 0 與中斷 1 具有相同優先權, 但當一個中斷發生時預設其他中斷會被忽略 (因為中斷功能會被禁能), 直到目前的中斷結束為止. 若要讓其他中斷恢復有效, 必須呼叫 interrupts() 函數來致能.
參考 :

# attachInterrupt()

關於第 6 項我持保留意見, 因為根據下面這篇文章裡的 ATmega328 中斷向量優先表, MPU 在執行每道指令後會檢查這張中斷向量表, 首先檢查是否有按下 Reset 鍵, 接著檢查中斷 0, 然後是中斷 1 ... 如果都沒發生中斷, 就執行下一道指令 (可見我們的外部硬體中斷只是 26 種中斷裡的兩個而已). 所以看起來中斷 0 是比中斷 1 優先的 :

# http://www.gammon.com.au/interrupts (超棒的)

特別注意, ISR 內的全域變數必須宣告為 volatile 是因為編譯器的程式碼優化功能, 在 ISR 中可能會破壞程式碼原本規劃的邏輯, 使得執行功能異常. 例如在趙英傑的 "Arduino 互動設計入門 2" 附錄 D 就舉了一個範例 :

int a, b;
int c=a+b;
int d=a+b;

如果 C 編譯器的優化選項有勾選的話, 那麼上面的程式碼在編譯器執行優化後會變成 :

int a, b;
int c=a+b;
int d=c;

因為編譯器覺得既然 c 與 d 都是 a 與 b 之和, 就不需要浪費時間再做一次 a+b 運算. 這在一般程式不會有問題, 但若 a 或 b 的值會在中斷處理函數中被改變的話, 則 d 的值就會跟 c 不一樣了, 因為萬一執行完 c=a+b 之後發生中斷, 這時系統暫存器狀態會被推入堆疊中保存, 然後程式計數器會被導向中斷處理函數 ISR, 如果 a 或 b 在中斷處理函數內被改變其值, 則當中斷結束, 控制權交回主程式繼續執行 d=c 指令時, 這時 d 的值將無法反映 a 或 b 已經改變的現況, 仍與 c 一樣是舊的數據. 如果 a, b 加上 volatile 宣告的話, 就可以避免這個問題, 這個 volatile 就是通知編譯器, 這兩個全域變數不要進行優化 :

volatile int a, b;

此外, 被編譯器優化過的變數會放一個副本在暫存器中直接運算, 而非從 RAM 裡面重新載入, 但這樣可能被中斷處理函數覆蓋掉原來的值, 導致得到錯誤的運算結果. 而宣告為 volatile 是告訴編譯器, 此變數隨時會被中斷處理函數改變, 執行時必須重新從 RAM 中載入暫存器, 不可以只依賴存放在暫存器裡的副本 (被優化的變數就是直接取用暫存器裡的副本, 這樣就節省了重新自 RAM 載入的時間).

下面這篇文章對 C 語言的 volatile 有深入說明 :

# C語言: 認識關鍵字volatile

此文中有一段重點 : 一般書籍對 volatile 的用法解釋並不多, 所以造成我們對此修飾詞了解不夠或甚至是誤解. 例如volatile 用在指標時, 下面兩個用法意思不同 :
int * volatile ptr :
關鍵字 volatile 修飾 ptr, 意思是指標 ptr 本身是 volatile, 但指標所指之內容不是 volatile, 所以編譯器不會對指標本身進行優化, 但對指標內容 *ptr 之運算卻會進行優化.
volatile int *ptr :
此處 volatile 修飾的是指標內容 *ptr, 因此 volatile 的是指標的內容, 它不會被優化; 但指標本身卻不是 volatile 的, 指標本身的運算是會被優化的.
還有其他 volatile 使用上的陷阱必須注意, 詳參原文. 如果覺得實際執行結果與預期的不同, 有可能是我們對 volatile 的用法了解不夠所致.

OK, 對中斷有了基本了解之後, 就可以進行測試了. 以下實驗我主要是參考了下面四本書的範例進行修改 :
Arduino 輕鬆入門, 葉難, 博碩出版 (第 3-4 節)
Arduino 完全實戰手冊, 王冠勛譯, 博碩出版 (第 2-2-4 節)
Prototyping Lab, 小林茂, 馥林文化 (第 25 章)
Arduino Cookbook 錦囊妙計, 歐萊禮 (第 18-2 節)
微電腦原理與應用, 黃新賢等, 全華 (第 8 章)
延續上一篇的控制按鈕開關讓 LED 交替明滅的實驗, 將之前輪詢法的測試 1 改為採用中斷法的測試 7 :


測試 7 :

const byte intPin=2; //interrupt pin
const byte ledPin=13; //built-in LED
volatile boolean state=LOW; //initial value of switch pin
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(intPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
attachInterrupt(0, int0, LOW); //assign int0
}

void loop() {
if (state) {digitalWrite(ledPin, HIGH);} //turn LED on
else {digitalWrite(ledPin, LOW);} //turn LED on
}

void int0() { //interrupt handler
state=!state; //reverse state
}

上面程式在 setup() 中用 attachInterrup() 啟動 D2 腳的 LOW 中斷功能, 即當 D2 位準變 LOW 時觸發中斷 0, 執行中斷處理函數 int0(), 將全域變數 state 狀態反轉. 中斷處理函數執行完畢後控制權跳回 loop() 主迴圈時, 就會根據 state 變數的目前狀態來使 LED 明滅. 由於中斷處理函數會改變全域變數 state 之值, 因此必須宣告為 volatile, 以免被編譯器優化而使動作不正常.

程式上傳後執行發現, 跟前一篇的測試 1 一樣, 理論上每按一下按鈕, D13 LED 應該由亮變滅, 或由滅變亮才對, 但實際上並非一亮一滅規律切換, 而是有時候正常, 有時候又不正常, 是典型的彈跳現象, 可見外部硬體中斷不會幫我們解決彈跳問題. 我參考前一篇測試 3-1 的程式, 為上面的程式加上去彈跳功能, 如下面測試 8-1 :

測試 8-1 :

const byte swPin=2; //switch pin
const byte ledPin=13; //built-in LED
volatile boolean ledState=LOW; //initial state of LED
int debounceDelay=200; //debounce delay (ms)

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
attachInterrupt(0, int0, LOW); //assign int0
}

void loop() {
digitalWrite(ledPin, ledState); //toggle LED
}

void int0() { //interrupt handler
if (debounced()) { //debounced: reverse LED state
ledState = !ledState; //reverse LED state
}
}

boolean debounced() { //check if debounced
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else {return false;} //not debounced
}

主要就是在中斷處理程式裡面先去呼叫 debounced() 函數, 看看是否已經撐過了預定的彈跳期間, 如果是的話就傳回 true, 中斷處理程式反轉 LED 狀態後, 主控權回到 loop() 主迴圈時, LED 就會變換狀態了. 使用中斷就不需要再去判斷 D2 是否為 LOW 了, 因為在 attachInterrupt() 時已指定 LOW 觸發中斷. 注意, 雖然 debounced() 函數中有用到 millis(), 但因為它並不是直接放在中斷處理函數 int0() 裡面, 所以它還是有作用的.

如果使用前一篇測試 4 的 debounced(pin) 函數大致可以, 只是偶而會沒反應 (why?), 如下面測試 8-2 所示 :

測試 8-2 :

const byte swPin=2; //switch pin
const byte ledPin=13; //built-in LED
volatile boolean ledState=LOW; //initial state of LED
int debounceDelay=100; //debounce delay (ms)

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
attachInterrupt(0, int0, LOW); //assign int0
}

void loop() {
digitalWrite(ledPin, ledState); //toggle LED
}

void int0() { //interrupt handler
if (debounced(swPin)) { //debounced: reverse LED state
ledState = !ledState; //reverse LED state
}
}

boolean debounced(int pin) {
boolean currentState; //current pin state
boolean previousState; //previous pin state
previousState=digitalRead(pin); //record current state as previous
for (int i=0; i<debounceDelay; i++) { //detect if stable
delay(1); //delay 1 ms
currentState=digitalRead(pin); //get current state
if (currentState != previousState) { //still unstable
i=0; //reset counter
previousState=currentState; //updtae previous state
}
}
if (currentState==LOW) {return true;} //switch pressed (pull-up)
else {return false;} //switch released
}

這個函數與測試 8-1 所用的不同之處在於要傳入按鈕接腳編號, 注意其彈跳時間為 100 毫秒. 實際執行結果發現偶而會動作不正常.

但是如果使用前一篇測試 6-1 的 debounced(pin) 函數卻完全沒反應, 如下測試 8-2 所示 :

測試 8-2 :

const byte swPin=2; //switch pin
const byte ledPin=13; //built-in LED
volatile boolean ledState=LOW; //initial state of LED
int debounceDelay=50; //debounce delay (ms)

int buttonState; //previous stable state of the input pin
int lastButtonState=LOW; //the previous reading from the input pin
long lastDebounceTime=0; //the last time the output pin was toggled

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
attachInterrupt(0, int0, LOW); //assign int0
}

void loop() {
digitalWrite(ledPin, ledState); //toggle LED
}

void int0() { //interrupt handler
if (debounced(swPin)) { //debounced: reverse LED state
ledState = !ledState; //reverse LED state
}
}

boolean debounced(int pin) { //check if debounced
boolean debounced=false; //default
int reading=digitalRead(pin); //current button state
if (reading != lastButtonState) { //button state changed
lastDebounceTime=millis(); //update last debounce time
}
if ((millis() - lastDebounceTime) > debounceDelay) { //overtime
if (reading != buttonState) { //button state has changed
buttonState=reading; //update previous stable button state
if (buttonState == LOW) { //button pressed
debounced=true;
}
}
}
lastButtonState=reading; //update last button state
return debounced;
}

奇怪, 怎麼看都沒問題呀! Why?

總之, 從上面的測試結果來看, 不論是前一篇的輪詢法還是本篇的中斷法, 都能夠的正確地執行出預期邏輯的 ifDebounced() 函數只有測試 3 或 3-1 以及測試 8-1 的這個 (改寫自 "Arduino 完全實戰手冊" 這本書). 似乎簡單才是硬道理啊!

接下來我想測試使用兩個中斷的情況, 我參考了黃新賢等著的 "微電腦原理與應用" 第 8 章的範例改寫, 讓中斷 0 (D2) 與中斷 1 (D3) 同時啟用, 一個控制 D13 LED 快閃 (D2); 另一個控制 D13 LED 慢閃 (D3), 所以我需要兩個按鈕開關分別連接到 D2 (INT 0) 與 D3 (INT 1), 另一端接地, 然後開啟 D2 與 D3 的內建上拉電阻, 以便按鈕未按下時這兩個中斷輸入腳會被上拉到 HIGH 以消除隨機雜訊之影響. 程式如下 :

測試 9 :

const byte swPin2=2; //switch pin for int0 (D2)
const byte swPin3=3; //switch pin for int1 (D3)
const byte ledPin=13; //built-in LED
volatile int blinkRate; //blink rate of LED
int debounceDelay=200; //debounce delay (ms)

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin2, INPUT_PULLUP); //enable pull-up resistor of input pin D2
pinMode(swPin3, INPUT_PULLUP); //enable pull-up resistor of input pin D3
digitalWrite(ledPin, LOW); //set LED OFF
attachInterrupt(0, int0, LOW); //enable int0 (D2)
attachInterrupt(1, int1, LOW); //enable int1 (D3)
}

void loop() {
blinkD13Led(blinkRate);
}

void int0() { //interrupt handler
if (debounced()) {blinkRate=200;} //fast flashing
}

void int1() { //interrupt handler
if (debounced()) {blinkRate=1000;} //slow flashing
}

boolean debounced() { //check if debounced
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else {return false;} //not debounced
}

void blinkD13Led(int t) {
digitalWrite(13, HIGH);
delay(t);
digitalWrite(13, LOW);
delay(t);
}

此程式中我自訂了一個 blinkD13Led() 函數來讓 LED 閃爍一下, 傳入參數 t 可以控制閃爍的頻率. INT 0 與 INT 1 分別設定 LOW 準位觸發中斷, 在各自的中斷處理函數中, 於消除彈跳現象後會更新全域變數 blinkRate 的值, 以便讓 loop() 主迴圈內的 digitWrite() 調整 LED 的閃爍頻率.



左邊按鈕是中斷 1, 接到 D3 (黃線); 右邊是中斷 0, 接至 D2 (白線), 所以按左鍵閃爍變慢, 按右鍵會變快. 兩個若同時按下的話, 右邊的中斷 0 會獲勝 (快閃), 因為照中斷向量順序表來說, 中斷 0 會先被檢出.

這個範例可能引發一個問題, 就是如果一個中斷發生時, 在其 ISR 執行期間會不會被另一個中斷給中斷 (巢狀中斷, nested interrupts)? 答案是不會, 因為當中斷發生時, ATmega328 處理器就會在硬體上將所有中斷禁止 (Reset 除外), 以避免形成無窮的遞迴中斷 (recursive interrupts), 當然實際上不可能無窮啦! 堆疊很快就會爆掉而當機了. 所以進入 ISR 後預設是不會被中斷, 直到 ISR 執行完畢, 處理器再將中斷致能. 當然有特殊原因的話, 也是可以在 ISR 內呼叫 interrupts() 自行將中斷致能, 但要妥善處理好堆疊以免導致非預期結果.

最後我在 Arduino 官網發現這個 PinChangeInt Library, 這是可將 Arduino UNO, NANO, Duemilanove 這三種板子的數位接腳變成具有外部硬體中斷功能的函式庫, 可在下列網址下載 (不要從說明文件底下所連的 Google Code Archive 下載, 那個少了 PinChangeIntConfig.h 這個檔) :

# https://github.com/Ltalionis/PinChangeInt

解壓縮後將目錄 PinChangeInt-master 放到 Arduino IDE 安裝目錄的 libraries 下, 但因為此函式庫已經比較舊了, 它使用的 WProgram.h 在新版 IDE 已經改名為 Arduino.h, 所以須編輯 PinChangeInt.cpp 檔, 將其中的 include "WProgram.h" 改成 include "Arduino.h" :

#ifndef WProgram_h
#include "Arduino.h"
#endif

其使用方法參考, 必須匯入 PinChangeInt.h 與 PinChangeIntConfig.h 這兩個函式庫, 並使用 PCintPort::attachInterrupt() 來起始指定腳位的硬體中斷功能 :

# PinChangeInt Example

我將上面測試 8-1 的程式改成下列測試 10 :

測試 10 :

#include <PinChangeInt.h>
#include <PinChangeIntConfig.h>

const byte swPin=5; //switch pin
const byte ledPin=13; //built-in LED
volatile boolean ledState=LOW; //initial state of LED
int debounceDelay=200; //debounce delay (ms)

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
PCintPort::attachInterrupt(swPin, int0, RISING); //Enable PCI on swPin
}

void loop() {
digitalWrite(ledPin, ledState); //toggle LED
}

void int0() { //interrupt handler
if (debounced()) { //debounced: reverse LED state
ledState = !ledState; //reverse LED state
}
}

boolean debounced() { //check if debounced
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else {return false;} //not debounced
}

這裡用 D5 腳的上升緣 (RISING) 既然是 Change, 當然也可以用下降緣 (FALLING) 觸發, 但我實驗結果發現用 RISING 很穩, 用 FALLING 有時不靈光, 不知原因為何 (彈跳期間太短嗎?).

還有一個比較新的 PCI Manager 函式庫也可以將其他 D2/D3 以外的數位接腳變成具有外部硬體中斷功能, 下載網址與使用範例如下 :

# PciManager

將解壓縮後的目錄 arduino-pcimanager-master 複製到 Arduino IDE 安裝目錄的 libraries 下, 參考範例將上面測試 10 改為測試 11 如下 :

測試 11 :

#include <PciManager.h>
#include <PciListenerImp.h>

const byte swPin=5; //switch pin
const byte ledPin=13; //built-in LED
volatile boolean ledState=LOW; //initial state of LED
int debounceDelay=200; //debounce delay (ms)

void onPinChange(byte changeKind); //declare self-defined function
PciListenerImp listener(swPin, onPinChange); //create lister object

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
digitalWrite(ledPin, ledState); //set LED OFF
PciManager.registerListener(swPin, &listener); //register change event on swPin
}

void loop() {
digitalWrite(ledPin, ledState); //toggle LED
}

boolean debounced() { //check if debounced
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else {return false;} //not debounced
}

void onPinChange(byte changeKind) { //interrupt handler
if (debounced()) { //debounced: reverse LED state
ledState = !ledState; //reverse LED state
}
}

這裡需匯入 PciManager.h 與 PciListenerImp.h 這兩個函數, 然後在建立 PciListenerImp 物件之前必須先宣告自訂的事件處理函數, 否則會出現如下錯誤 :

pcimanager:7: error: 'onPinChange' was not declared in this scope

PciListenerImp listener(INPUT_PIN, onPinChange);

當然也可以把 onPinChange() 函數放到建立 listener 物件之前, 但這樣結構上有點怪. 測試結果也是 OK 的, 只是這個沒辦法選擇是要上升或下降時觸發中斷而已, 因為我們這裡使用上拉電阻, 所以是下降時觸發; 如果用外部下拉電阻的話, 就會變成上升觸發了.

好了, 關於 Arduino 外部硬體中斷功能的測試大概就是這樣了, 其他比較深奧的需要對 AVR 處理器內部深入了解後才看得懂.

參考 :

# Arduino – 中斷功能 (寫得好)
# 從 Arduino 到 AVR 晶片(2) -- Interrupts 中斷處理 (深入韌體)
# 關於中断(Interrupt)的一些五四三... 中斷 . . (精闢)
# Do interrupts interrupt other interrupts on Arduino?
# Global manipulation of the interrupt flag
# EXTERNAL INTERRUPTS ON THE ATmega168/328
# Using millis() and micros() inside an interrupt routine


2016-10-27 補充 :

今天在 "Arduino 從零開始學" 這本書上的 4.2.7 硬體中斷這節看到定時中斷, 作者介紹了 FlexiTimer2.h 與 MsTimer2.h 這兩個定時函式庫, 可以很方便地使用定時中斷. 其範例如下 :

#include <MsTimer2.h>
int pin=13;
volatile int state = LOW;
void setup() {
pinMode(pin, OUTPUT);
MsTimer2::set(500, blink);
MsTimer2::start();
}

void loop() {
digitalWrite(pin, state);
}

void blink() {
state = !state;
}

此程式利用定時中斷每 0.5 秒會改變 state 變數之值, 從而使 D13 LED 閃爍. MsTimer2.h 可在此書範例原始碼 zip 檔解開後的 Libraries 目錄中找到, 上面範例在 4-5 資料夾下, 請至碁峰網站下載 :

# http://books.gotop.com.tw/download/ACH018200


張貼者: Tony Huang 於 9/12/2016 11:22:00 下午
以電子郵件傳送這篇文章

BlogThis!

分享至 Twitter

分享至 Facebook

標籤: Arduino
反應:
10 則留言 :
匿名 提到...
你好
請叫個問題
如果我有多個程式在跑
今天我想中斷其中一支
應該怎麼做
因為我現在一按下開關後就全部停擺了
2017年6月11日 上午12:22
Tony Huang 提到...
不太了解, 您說有多個程式在跑, 但 Arduino 同一時間只能跑一個程序哩!

2017年6月13日 下午2:24
傅勁崴 提到...
不好意思可能我表達上有問題
我的意思是說我有一段程序依序判斷在跑
然後我今天想要中段裡面其中一支付程式該怎麼中斷?

2017年6月13日 下午9:22
Tony Huang 提到...
中斷副程式嗎? 我不太能理解, 副程式只能單線被呼叫, 沒辦法同時呼叫. 您的意思是指多執行緒嗎? Arduino 沒有多執行緒喔!

2017年6月13日 下午11:08
傅勁崴 提到...
我的意思是假若好幾支副程式A程式B程式C程式D程式依序被呼叫
然後B程式值行的時間太久所以我想中斷後直接看到C程式的東西該怎麼寫這段?
2017年6月14日 下午3:28
楊家瑋 提到...
你好請問一下 中斷動作結束後 應該是接續著中斷前的程式? 還是會重頭開始跑?

如果我想讓他中斷後重頭開始跑 該怎麼做?
2017年6月14日 下午5:05
Tony Huang 提到...
Dear 楊家瑋 :
中斷處理程式完畢後是回到被中斷的那個地方繼續執行喔, 因為中斷發生時, 執行中的狀態 (程式計數器, 暫存器的值等) 會被放進堆疊中暫存, 中斷處理程式跑完就會從堆疊裡把被中斷前的狀態取出復原, 程式計數器恢復原值, 所以是回到被中斷的地方繼續執行.

2017年6月14日 下午7:31
Tony Huang 提到...
Dear 傅勁崴 :
請問您的意思是中斷後往下一個副程式執行還是指定執行 C 副程式呢? 中斷其實只是臨時出去做一下雜務, 做完要回來原來的地方往下執行, 無法直接跳到另一個副程式.

2017年6月14日 下午10:24
Tony Huang 提到...
Dear 傅勁崴 : 我猜你的意思是要結束現在耗時的副程式, 直接跳到下一個副程式, 我回覆在這一篇 :

http://yhhuang1966.blogspot.tw/2017/06/arduino_14.html

2017年6月14日 下午11:38
小億 提到...
很有用的文章,獲益良多,感謝。

2018年4月8日 上午8:29
張貼留言

這篇文章的連結
建立連結

較新的文章 較舊的文章 首頁
訂閱: 張貼留言 ( Atom )

TRANSLATE
由「Google 翻譯翻譯」技術提供
FOLLOW BY EMAIL

Email address...
網誌存檔
► 2018 ( 350 )
► 2017 ( 317 )
▼ 2016 ( 296 )
► 十二月 ( 17 )
► 十一月 ( 23 )
► 十月 ( 28 )
▼ 九月 ( 24 )
關於運動手環
颱風政治學
2016 年第 37 周記事
谷歌街景圖的歷史印象
Arduino 聲音測試 (二) : 模擬特雷門琴
★ 用 Blynk 同時控制多個設備
母親兩周年忌日
玉山信用卡紅利點數兌換
買瓦斯爐
2016 年第 36 周記事
Arduino 的聲音測試 (一)
數位日記簿重現江湖
雙颱來襲
梨汁蜂蜜治咳嗽
Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)
吸盤回春術
2016 年第 35 周記事
Arduino 的按鈕開關測試 (一) : 輪詢法 (Polling)
大同複合無水料理鍋
Verilog 與 VHDL
2016 年第 34 周記事
年金是保險不是福利
★ Blynk 的虛擬腳位用法整理
杭州錢氏家族
► 八月 ( 28 )
► 七月 ( 33 )
► 六月 ( 28 )
► 五月 ( 26 )
► 四月 ( 24 )
► 三月 ( 24 )
► 二月 ( 25 )
► 一月 ( 16 )
► 2015 ( 296 )
► 2014 ( 323 )
► 2013 ( 267 )
► 2012 ( 49 )
► 2011 ( 178 )
► 2010 ( 201 )
► 2009 ( 256 )
► 2008 ( 303 )
文章標籤
人工智慧 ( 46 )
八字學 ( 1 )
大數據 ( 5 )
小狐狸 ( 72 )
小狐狸生態 ( 22 )
工作 ( 35 )
手機 ( 12 )
日文 ( 2 )
木工 ( 18 )
比特幣 ( 1 )
主機 ( 37 )
占星術 ( 2 )
生活 ( 781 )
生活雜記 ( 8 )
好書 ( 192 )
好站 ( 182 )
自動化 ( 23 )
系統 ( 4 )
其他 ( 63 )
物理學 ( 4 )
物聯網 ( 138 )
相術 ( 1 )
科學 ( 20 )
英文 ( 6 )
音樂 ( 32 )
飛控 ( 9 )
食譜 ( 29 )
修行 ( 22 )
旅行 ( 36 )
能源 ( 51 )
動畫 ( 1 )
區塊鏈 ( 1 )
專案 ( 20 )
教育 ( 17 )
理財 ( 79 )
軟體開發 ( 8 )
通訊 ( 2 )
創客 ( 22 )
單晶片 ( 30 )
無人機 ( 7 )
硬體 ( 29 )
統計 ( 4 )
評論 ( 159 )
雲端 ( 9 )
園藝 ( 1 )
新知識 ( 1 )
新產品 ( 1 )
新軟體 ( 5 )
資安 ( 3 )
資料科學 ( 2 )
農業 ( 19 )
電子零件 ( 14 )
電子學 ( 12 )
電腦 ( 155 )
電腦技術 ( 9 )
演算法 ( 4 )
網頁技術 ( 67 )
網路 ( 26 )
網路爬蟲 ( 10 )
網購 ( 150 )
語言學 ( 33 )
語音辨識 ( 8 )
數學 ( 1 )
養生 ( 20 )
樹莓派 ( 90 )
機器人 ( 5 )
機器學習 ( 39 )
歷史 ( 44 )
戲劇 ( 2 )
醫藥 ( 53 )
繪圖 ( 3 )
讀書劄記 ( 8 )
Android ( 11 )
AngularJS ( 1 )
App ( 2 )
Appfog ( 4 )
Arduino ( 159 )
ASP ( 11 )
AutoIt ( 14 )
Big Data ( 1 )
Blynk ( 7 )
Bootstrap ( 1 )
C 語言 ( 18 )
Chatbot ( 1 )
CKeditor ( 1 )
Corona ( 2 )
CSS ( 6 )
D3.js ( 1 )
Data Mining ( 1 )
EasyUI ( 42 )
ESP8266 ( 115 )
ExtJS ( 22 )
Fintech ( 8 )
GAE ( 39 )
Google ( 18 )
Highcharts ( 2 )
HTML5 ( 16 )
IC 設計 ( 2 )
Java ( 96 )
JavaFX ( 1 )
Javascript ( 35 )
Joomla ( 3 )
jQuery ( 73 )
jQuery Mobile ( 2 )
JSP ( 2 )
Linux ( 4 )
LoRa ( 5 )
Lua ( 5 )
MacOS ( 1 )
Mathematics ( 1 )
MicroPython ( 42 )
MongoDB ( 1 )
NLP ( 3 )
Node.js ( 8 )
PHP ( 83 )
Praat ( 10 )
Python ( 142 )
R 語言 ( 14 )
Raspberry Pi ( 61 )
Ruby ( 1 )
SQL ( 13 )
Swift ( 2 )
TCP/IP ( 5 )
TinyMCE ( 1 )
tkinter ( 3 )
UAV ( 1 )
Verilog ( 1 )
VoIP ( 1 )
Wireshark ( 1 )
WordPress ( 5 )
WSH ( 5 )
關於我自己
我的相片
Tony Huang

檢視我的完整簡介
總瀏覽量
Sparkline 3,537,192
GOOGLE+ FOLLOWERS

常用連結
台大網路測速
R manual
Python 機器學習
HomePi
線上 OCR
C Compiler
線上 C 編譯器
muckibu.de
Aliexpress
G.T.Wang 部落格
Zamzar 線上轉檔
HTML Cleaner
螢幕錄影
CH.Tseng
Pinterest
Inside
中華電信 Hibox
bittrix24
box
Dropbox
OneDrive
Google 協作平台
Hinet 信箱
高科大圖書館
高師大圖書館
高雄市立圖書館
IBM Swift Sandbox
TutorialsPoint
Youtube-mp3
Kej's Retriever
熱門文章

Arduino 基本語法筆記
Arduino 的程式語法基於 C/C++, 其實就是客製化的 C/C++ 語言, 其程式架構仿自廣為藝術與設計界人士熟悉的 Processing 語言, 而其開發工具 Arduino IDE 則是衍生自以 Processing 為基礎的電子開發設計平台 Wiring . 由於...

Arduino 串列埠測試 (UART)
今天要下午才進辦公室, 早上都在家, 所以研究測試了一下 Arduino 的串列埠, 紀錄整理如下. 所謂串列埠是源自 IBM PC 的 RS-232 通訊協定, 也就是個人電腦後面的 COM 埠 (9 針公座 DB-9), 現在新的桌上型電腦與筆電大都沒有接出 COM 埠了, ...

Python 內建 GUI 模組 tkinter 測試 (一) : 建立視窗
最近因為玩樹苺派的關係, 接觸到 Python 內建的 GUI 開發模組 Tkinter (意思是 Tk Interface), 初步覺得比用 Java 的 Swing 還要來得容易, 因此就來學看看唄! Tk 原先是為 Tcl 語言 所開發的 GUI 套件, 因為是 T...

Java 複習筆記 : 陣列
陣列是程式員最常用的資料結構, Java 的陣列屬於傳統的固定式陣列, 亦即陣列元素資料類型必需相同, 而且元素個數必須先宣告才能使用, 不像 Javascript 等動態語言之陣列允許異質性資料, 且長度不需先宣告即可使用. 當然, Java 陣列也不支援關聯性陣列, Java...
jQuery UI 的日期選擇器 datepicker 測試
近兩周都在玩 jQuery UI 的日曆小工具 (或日期選擇器 datepicker), 就是讓使用者可以直接在日曆上選取日期的小工具, 而且日期格式可以指定. 這個 widget 小工具根據書上講是 jQuery UI 最古老, 功能選項也最龐大的一個, 照官網範例三兩下就看到...

★ ESP8266 WiFi 模組 AT command 測試
這是我今年四月以 $180 向露天賣家 XLAN 買的超小型 WiFi 模組 (現已降價為 $145), 採用上海 樂鑫科技 的 ESP8266 晶片, 板子型式是 ESP-01, 這是 Arduino 最經濟的 WiFi 方案, 因為目前買一塊內建 WiFi 的 Arduin...

★ ESP8266 WiFi 模組與 Arduino 連線測試
經過兩個月來的摸索, 對於 Arduino 經由 ESP8266 連上網路的實驗終於來到實作階段. 這當中也分心去研究如何製做 ESP8266 的轉接板, 雖已經有腹案, 但覺得還是先把連網實驗做完再來搞定轉接板好了. 這兩塊板子互連主要有兩個障礙, 一是開發階段串列埠不夠的問題...
jQuery 套件 DataTables 的測試
ExtJS 框架以其起家的 DataGrid 元件聞名, 而 jQuery 框架雖然沒有內建類似 DataGrid 的功能, 但也有 dataTables 套件可用. 其實這是產品設計策略的差異, ExtJS 是一次到位, 提供全方位解決方案 (但是比較龐大, 學習曲線陡峭)...

Arduino 溫濕度感測器 DHT11 測試
今年三月底跟露天賣家 盼盼 ( ccdogccdogkimo ) 採購了一批模組零件, 其中包含了一顆 DHT11 溫溼度模組, 最近為了測試 ESP8266 重看了 Proteus 的大作 : # 初遇 IoT ( Internet of Thing, 物聯網 ) - 使...
如何在 jQuery UI 的日期選擇器上附加時間選擇器 timepicker
jQuery UI 的日期選擇器 非常容易使用, 但是在專案中我通常要用到 "2013-04-17 12:10:00" 這樣的完整日期時間格式 (例如要設定佈告發布時間, 修改日誌紀錄時間等等), 這樣 jQuery UI 的datepicker 小工具就沒辦...
技術提供:Blogger.
回覆文章

回到「Arduino」