|
本帖最后由 tsaiwn 于 2015-3-28 02:51 编辑
註: 五四三就是英文的 etc.
現在來跟大家分享關於中断處理的一些常見問題 . . .
常常看到有人問到:
我在中斷的子程序内加進IIC通信後就進不了中斷了..求指點。
或是
我在中断程序内加入 Lcd_IIC 的程序後就死機...求指點。
其實, 不論是 IIC/TWI, 或是 SPI, 以及硬串口、軟串口甚至 Serial.print 都是要靠中断來幫忙處理,
如果你把中断禁止了, 那 IIC/TWI, SPI 都無法動作了 !
啥?
你說你沒有禁止中斷?
可是 Arduino 一旦進入中断程序 就會自動禁止中斷,
因此, 在中断程序內(包括它的子程序內)原則上無法做 IIC 與 SPI 以及軟硬串口的通信!
為何說原則上呢?
因為你還是可以在中断程序內把中斷打開,
只要這樣:
sei( );
可是,
那是否有其他問題就要看看你的中断處理到底做何事情,
以及中断來的時間是否太短 ?
就是只要來得及處理或是中斷重進入(reentrant)不會有問題, 那就可以把中斷打開 !
關於中斷的重進入(reentrant), 請參考維基百科:
http://en.wikipedia.org/wiki/Reentrancy_(computing)
當然, 如果你測試結果沒問題那就放心的打開中斷 !
關於中斷的概念可以看看奈何大神寫的這篇有趣文章:
http://www.arduino.cn/thread-2421-1-1.html
該篇主要是介紹Arduino外部中斷INT0, INT1的使用,
也就是外部 0 號和 1 號中斷(pin2, pin 3)的介紹,
使用 attachInterrupt(INT_number, function, mode);
也可以參考:
http://arduino.cc/en/Reference/attachInterrupt
如果你是要使用內部定時器(timer0, timer1, timer2)定時中斷,
請看我寫的這些貼文:
使用 MsTimer2 庫定時做很多事(教程):
http://www.arduino.cn/thread-12435-1-1.html
使用TimerOne庫(Timer1)定時做多件事(教程):
http://www.arduino.cn/thread-12441-1-4.html
自己控制 timer1 計時器定時做多件事(教程):
http://www.arduino.cn/thread-12445-1-1.html
自己控制 timer2 定時器定時做多件事(教程)":
http://www.arduino.cn/thread-12448-1-1.html
補充設定 timer1 計時器和 timer2 定時器定時做多件事(教程)
http://www.arduino.cn/thread-12452-1-2.html
不論是 SPI, IIC 與軟串口都是大量使用中斷處理(Interrupt),
在中斷處理程序內工作沒處理完之前是在禁止其它中斷的狀態,
如果中斷處理程序做太多事, 本來就會影響其他中斷的進行!!
IIC 使用 ISR(TWI_vect) 中斷處理,
軟串口SoftwareSerial 使用 ISR(PCINT0_vect) 或類似的(PCINT1/2)中斷,
雖然 ISR(PCINT0_vect) 的優先權高於 ISR(TWI_vect),
但一旦進入 ISR(TWI_vect) 內由於中斷請求被禁止,
此時即使軟串口所用的 ISR(PCINT0_vect) 中斷來到,
一樣無法處理, 於是導致軟串口的通信失常或數據遺失 !
關於Arduino的 CPU 之25種中斷之優先順序可看:
http://gammon.com.au/interrupts
就是說, 除了 Reset 之外, 還有 25種中斷可以使用:
(依照優先順序排列, 所以外部 pin 2 的 INT0 是最優先的!, 其次是 pin 3 的 INT1 中斷)
1 Reset
2 External Interrupt Request 0 (pin D2) (INT0_vect)
3 External Interrupt Request 1 (pin D3) (INT1_vect)
4 Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
5 Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
6 Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)
7 Watchdog Time-out Interrupt (WDT_vect)
8 Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9 Timer/Counter2 Compare Match B (TIMER2_COMPB_vect)
10 Timer/Counter2 Overflow (TIMER2_OVF_vect)
11 Timer/Counter1 Capture Event (TIMER1_CAPT_vect)
12 Timer/Counter1 Compare Match A (TIMER1_COMPA_vect)
13 Timer/Counter1 Compare Match B (TIMER1_COMPB_vect)
14 Timer/Counter1 Overflow (TIMER1_OVF_vect)
15 Timer/Counter0 Compare Match A (TIMER0_COMPA_vect)
16 Timer/Counter0 Compare Match B (TIMER0_COMPB_vect)
17 Timer/Counter0 Overflow (TIMER0_OVF_vect)
18 SPI Serial Transfer Complete (SPI_STC_vect)
19 USART Rx Complete (USART_RX_vect)
20 USART, Data Register Empty (USART_UDRE_vect)
21 USART, Tx Complete (USART_TX_vect)
22 ADC Conversion Complete (ADC_vect)
23 EEPROM Ready (EE_READY_vect)
24 Analog Comparator (ANALOG_COMP_vect)
25 2-wire Serial Interface (I2C) (TWI_vect)
26 Store Program Memory Ready (SPM_READY_vect)
-----------------------------------------------------------
關於IIC與軟串口等的源代碼可看這
https://github.com/arduino/Ardui ... duino/avr/libraries
( 軟串口是在 SoftwareSerial.cpp;
IIC 要看 Wire.cpp 和其 utility 目錄內的 twi.c )
接下來,
我們來討論可以用在全部 pin 的 Pin Change Interrupt 針腳狀態改變中斷 !
http://playground.arduino.cc/Main/PinChangeInterrupt
這個在官網已經有大神幫忙寫了Library庫可用:
http://playground.arduino.cc/Main/PinChangeInt
請注意,
如果你使用了軟串口(SoftwareSerial),
就是說你用了以下:
#include <SoftwareSerial.h>
那接著我們要講的 PCI (Pin Change Interrupt) 就不能用了,
因為軟串口的庫已經寫個該三個中斷處理 ISR( ) 如下:
#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
SoftwareSerial::handle_interrupt();
}
#endif
#if defined(PCINT1_vect)
ISR(PCINT1_vect)
{
SoftwareSerial::handle_interrupt();
}
#endif
#if defined(PCINT2_vect)
ISR(PCINT2_vect)
{
SoftwareSerial::handle_interrupt();
}
#endif
這可以在你 Arduino IDE 目錄下的以下檔案找到:
libraries\SoftwareSerial\SoftwareSerial.cpp
在Arduino UNO 以及大部分的板子有 Digital pin 0 到 pin 13,
以及 analog pin A0 到 A5 (又稱 pin 14 到 pin 19);
這 20支 pin 分為三組, 對應到 ISR(PCINT2_vect),
ISR(PCINT0_vect), 以及 ISR(PCINT1_vect) 這三個 ISR( ) 中斷程序:
ISR (PCINT2_vect) 處理 Pin D0 to D7
ISR (PCINT0_vect) 處理 Pin D8 to D13
ISR (PCINT1_vect) 處理 Pin A0 to A5
Pin Change Interrupts 範例:[mw_shl_code=bash,true]ISR (PCINT0_vect)
{
// handle pin change interrupt for pin D8 to D13 here
} // end of PCINT0_vect
ISR (PCINT1_vect)
{
// handle pin change interrupt for pin A0 to A5 here
} // end of PCINT1_vect
ISR (PCINT2_vect)
{
// handle pin change interrupt for pin D0 to D7 here
} // end of PCINT2_vect
void setup ()
{
// pin change interrupt (example for pin D9)
PCMSK0 |= bit (PCINT1); // want pin 9
PCIFR |= bit (PCIF0); // clear any outstanding interrupts
PCICR |= bit (PCIE0); // enable pin change interrupts for D8 to D13
}
[/mw_shl_code]
Table of pins -> pin change names / masks[mw_shl_code=bash,true]
D0 PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1 PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2 PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3 PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4 PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5 PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6 PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7 PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8 PCINT0 (PCMSK0 / PCIF0 / PCIE0)
D9 PCINT1 (PCMSK0 / PCIF0 / PCIE0) <===== PCINT1 代表 pin D9
D10 PCINT2 (PCMSK0 / PCIF0 / PCIE0)
D11 PCINT3 (PCMSK0 / PCIF0 / PCIE0)
D12 PCINT4 (PCMSK0 / PCIF0 / PCIE0)
D13 PCINT5 (PCMSK0 / PCIF0 / PCIE0)
A0 PCINT8 (PCMSK1 / PCIF1 / PCIE1)
A1 PCINT9 (PCMSK1 / PCIF1 / PCIE1)
A2 PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3 PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4 PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5 PCINT13 (PCMSK1 / PCIF1 / PCIE1)
[/mw_shl_code]
Reference: http://gammon.com.au/interrupts
接下來我們來測試兩個使用中斷處理的按鈕 ! (兩個按鈕按下與放掉都會產生中斷)
但也可以不必使用真的按鈕, 拿一條杜邦線或是一條電導線即可測試了 ,
(例如把沒用的網路線剝開裡面有八條電導線可用)
第一個按鈕接 pin2 以便使用外部中斷 INT0,
因為我們設定 pinMode(2, INPUT_PULLUP); 所以按鈕另一端接 GND 即可 !
另一個按鈕接 pin 8, (也是用 PULLUP, 且代碼已經寫成可把 pinPrint 可改為 9, 或 10 都可),
請看代碼如下:
[mw_shl_code=bash,true]// 按鈕接 pin 2
// 另一個測試按鈕接 pin 8 (pinPrint), 或改 9, 或 10 都可, pinPrint:
int pinPrint = 8; // interrupt to print value of cnt; 8/9/10 OK
const byte led = 13;
const byte button = 2; // pin2 是 INT0 外部中斷
volatile int cnt = 0; // 紀錄 button interrupt 次數
volatile int doPrint = 0;
// Interrupt Service Routine (ISR)
void btnChange ( ) { // for INT0 to attach
++cnt;
if (digitalRead (button) == HIGH) digitalWrite (led, HIGH);
else digitalWrite (led, LOW);
} // end of btnChange
////////////////
void setup ()
{
pinMode(led, OUTPUT); digitalWrite(13, 0);
Serial.begin(9600);
pinMode(button, INPUT_PULLUP); //internal pull-up resistor
setup555( pinPrint ); // set interrupt for pin 8
attachInterrupt (0, btnChange, CHANGE); // INT0 == pin 2
Serial.print("Started... cnt ="); Serial.println(cnt);
} // end of setup
void loop ()
{
// loop doing nothing
if(doPrint) {
doPrint = 0;
Serial.println(String("Interrupt cnt=") +cnt+", time=" + millis( ) );
}// if(
// .. .. ..
} // loop(
void setup555(int pin ) {
pinMode(pin, INPUT_PULLUP); // 啟動內部上拉電阻
cli( );
switch(pin) {
case 8: PCMSK0 |= bit (PCINT0); break; // the pin 8
case 9: PCMSK0 |= bit (PCINT1); break; // the pin 9
case 10: PCMSK0 |= bit (PCINT2); break; // the pin 10
}
PCIFR |= bit (PCIF0); // clear any outstanding interrupts
PCICR |= bit (PCIE0); // enable pin change interrupts for D8 to D13
sei( );
}
void haha( ) {
digitalWrite(led, 0 );
doPrint = 1; // 要求 loop ( ) 內要做一次 print
}
ISR (PCINT0_vect) {
// one of pins D8 to D13 has changed
haha( );
}
[/mw_shl_code]
如何測試呢?
(1)把串口監視器 Serial Monitor 開啟
(2)拿一根杜邦線或任意電導線, 一端接 GND,
(a)另一端輕輕觸一下 pin 8 (由 pinPrint 決定, 目前寫成可以 8, 或 9, 或 10, 改 pinPrint 即可 !)
(b)改輕輕觸一下 pin 2 (或按下這按鈕)
你會發現按下這按鈕或插入 pin 2 時 Led 13 熄滅, 拉出時或放掉按鈕時 Led 13 燈亮 !
(c)再改回輕輕觸一下 pin 8
注意串口監視器輸出的答案!
發現了沒, 只插入一下 pin 2 又拔掉,
結果 cnt 的值就跳好多, 理論上應該是多 2 (按下放掉各加 1),
但是實際卻好像發生了很多次甚至一二十次中斷 ! 這就是所謂抖動(Bouncing)的問題 !
還有, 插一下 pin 8 卻印好幾次, 這也是抖動的問題 !
(3)重複剛剛 (2)全部步驟, 注意 LED 13 的亮滅, 以及串口監視器輸出的值 !
--------------------------------------------
再來把以上範例改為方便針對監看 pin 2, 3, 4, 5, 6, 7 這六支 pin
你只要對照面範例與對照表就會改為可監看 pin A0 到 A5 了 !
以下我故意把 pinPrint 設 2, 看看會怎樣 ?!
注意, pin 2 同時也是 INT0 的外部中斷,
就是說以下程序碼中 pinPrint 與 button 都是 2, 這樣用接 GND 線輕觸一下 pin 2,
勢必兩種中斷都會產生, 那先做哪個呢?
其實這之前就說過了 , 在 25 種中斷之中, INT0 的優先權是最高的 !
[mw_shl_code=bash,true]// 按鈕接 pin 2
// 另一個測試按鈕接 pin 7 (pinPrint), 或 6,5,4,3,2 都可, pinPrint:
int pinPrint = 2; // 可故意改為 2 測試看看
const byte led = 13;
const byte button = 2; // pin2 是 INT0 外部中斷
volatile int cnt = 0; // 紀錄 button interrupt 次數
volatile int cnt88 = 0; // by haha
volatile int doPrint = 0;
// Interrupt Service Routine (ISR)
void btnChange ( ) { // for INT0 to attach
++cnt;
//if (digitalRead (button) == HIGH)
digitalWrite (led, HIGH);
//else digitalWrite (led, LOW);
} // end of btnChange
////////////////
void setup ()
{
pinMode(led, OUTPUT); digitalWrite(13, 0);
Serial.begin(9600);
pinMode(button, INPUT_PULLUP); //internal pull-up resistor
setup555( pinPrint ); // set interrupt for pin 8
attachInterrupt (0, btnChange, FALLING); // INT0 == pin 2
Serial.print("Started... cnt ="); Serial.println(cnt);
} // end of setup
void loop ()
{
// loop doing nothing
if(doPrint) {
doPrint = 0;
Serial.println(String("Interrupt cnt=") +cnt+
", cnt88=" + cnt88 +
", time=" + millis( ) );
}// if(
// .. .. ..
} // loop(
void setup555(int pin ) {
pinMode(pin, INPUT_PULLUP); // 啟動內部上拉電阻
cli( );
switch(pin) {
case 2: PCMSK2 |= bit (PCINT18); break; // the pin 2
case 3: PCMSK2 |= bit (PCINT19); break; // the pin 3
case 4: PCMSK2 |= bit (PCINT20); break; // the pin 4
case 5: PCMSK2 |= bit (PCINT21); break; // the pin 5
case 6: PCMSK2 |= bit (PCINT22); break; // the pin 6
case 7: PCMSK2 |= bit (PCINT23); break; // the pin 7
}
PCIFR |= bit (PCIF2); // clear any outstanding interrupts
PCICR |= bit (PCIE2); // enable pin change interrupts for D8 to D13
sei( );
}
void haha( ) {
cnt88++;
digitalWrite(led, 0 );
doPrint = 1; // 要求 loop ( ) 內要做一次 print
}
ISR (PCINT2_vect) {
// one of pins D8 to D13 has changed
haha( );
}[/mw_shl_code]
以上這範例的測試結果也夾檔在後面 !
你可以看出 INT0 的中斷優先處理, 否則在 loop( )內第一次印出的 cnt 就應該是 0 才對 !
[img]http://www.arduino.cn/forum.php?mod=attachment&aid=MTA0NDh8ZDRlZDg0NTF8MTQyNzQ4MTY3OHwzMDYwMXwxMzIwNQ%3D%3D¬humb=yes[/img]
請注意, 目前為止我們還沒用到國外某大神寫的 PinChangeInterrupt 的庫喔 !
關於 P-C-I 庫 : Pin Change Interrupt Library for the Arduino
要使用 Pin Change Interrupt 庫, 必須先下載該 P-C-I 庫來安裝:
抓回 .zip 檔案之後, 從你的 Arduino IDE
Sketch > Import Library... > Add Library...
然後選到該 .zip 檔案, 把 PCI 庫加進去你的 Arduino IDE.
(不必手動解壓縮再把PinChangeInt庫目錄複製到 libraris 目錄, 當然你要那麼做也可以!)
該 P-C-I 庫可到這抓:
https://github.com/GreyGnome/PinChangeInt
或直接點以下連結: (PinChangeInt-master.zip)
https://github.com/GreyGnome/PinChangeInt/archive/master.zip
這個庫從以前 Version 1.0 的 4.5KB,
到現在 2.40版已經有 49KB.
如果你只是要在Arduino UNO 上用, 其實抓P-C-I 庫的 1.0版的就可以了 :-)
不過要注意 1.0版和 1.72版只可用舊的寫法:
PCattachInterrupt(pin, yourISRFunction, FALLING); // FALLING, RISING, CHANGE
PCdetachInterrupt(pin);
(在這篇最後夾檔 1.0 版, 1.72版, 以及 2.402 版本)
其實, 這個 P-C-I 庫所用的方法就是類似上面我寫的範例,
只是P-C-I庫已經考慮所有的 pin, 但上述我寫的範例只有考慮 pin 8, 9, 10 這三支腳位,
且 P-C-I 庫設計成仿照外部中斷INT0, INT1的使用方法,
可以用類似寫法指定監看某 pin 的變化產生中斷, 例如:
attachPinChangeInterrupt(pin, ggyy, FALLING);
表示只要 pin 由高電平往低電平下降(FALLING), 就產生中斷跳入函數 ggyy( ) 內 !
再次提醒, 關於外部中斷INT0, INT1的使用,以及中斷的概念,
還是請看看奈何大神寫的這篇有趣文章:
http://www.arduino.cn/thread-2421-1-1.html
接著我們來看看使用該 P-C-I 庫的簡單範例,
這是我把範例拿來稍微改過,
測試 pin 8 產生中斷的次數, 你可以找一個按鈕連接 pin 8 (另一端接 GND),
或是拿一條杜邦線或任意電導線, 一端接 GND, 另一端輕觸 pin 8
記得要把串口監視器 Serial Monitor 打開觀看!
[mw_shl_code=bash,true]#include < inChangeInt.h>
// 以下用 pin 8, 你可以改為其他 pin
#define testPIN 8
volatile unsigned int interruptCount=0; // 最大到 65535, 接著會歸零從頭算起
// 儘量不要在中斷處理程序內用 Serial.print()
// 中斷程序不要做太多事! 這裡我們只是把中斷次數 + 1
void ggyy() { // 中斷處理程序
interruptCount++;
}
// Attach the interrupt in setup()
void setup() {
pinMode(testPIN, INPUT_PULLUP); // 啟動內部上拉電阻 the pullup resistor.
attachPinChangeInterrupt(testPIN, ggyy, FALLING);
Serial.begin(9600);
Serial.println("start ------");
}
void loop() {
delay(3388); // Every 3.388 second,
Serial.print("Total: ");
Serial.print(interruptCount, DEC); // print the interrupt count.
Serial.println(" interrupt times so far.");
}[/mw_shl_code]
看到了, 很簡單吧 ?
就是先寫好中斷處理函數例如 ggyy( );
然後用類似使用外部中斷 INT0 和 INT1 的方法:
attachPinChangeInterrupt(testPIN, ggyy, FALLING);
表示監督 testPIN 的狀態,
當 testPIN 由高電平往低電平下降(FALLING)時產生中斷, 跳入函數 ggyy( );
你可以把上述例子複製並改變 testPIN 的值為其它 pin 多多測試看看 !
如果你是使用 1.0版本或 1.72版本,
則改用:
PCattachInterrupt(testPIN, ggyy, FALLING);
如何停止某 pin 的中斷處理 ?
很簡單, 與使用外部中斷 INT0, INT1 類似:
detachPinChangeInterrupt(pin);
這樣就可以了 !
如果你是使用 1.0版本或 1.72版本,
對某 pin 啟動中斷用:
PCattachInterrupt(pin, yourFunction, mode);
停止中斷用:
PCdetachInterrupt(pin);
更複雜的用法可自己看抓回 P-C-I 庫內的範例 {:soso_e100:}
參考:
http://playground.arduino.cc/Main/PinChangeInt
http://playground.arduino.cc/Main/PinChangeIntExample
http://playground.arduino.cc/Code/ReadReceiver
|
|