1. 关于中断处理的一些常见问题 . . .
1.1 常常看到有人问到:
- 我在中断的子程序内加进IIC通信后就进不了中断了…求指点。
- 我在中断程序内加入 Lcd_IIC 的程序后就死机…求指点。
其实, 不论是 IIC/TWI, 或是 SPI, 以及硬串口、软串口甚至 Serial.print 都是要靠中断来帮忙处理, 如果你把中断禁止了, 那 IIC/TWI, SPI 都无法动作了!
注意:
Arduino 一旦进入中断程序,就会自动禁止中断, 因此, 在中断程序内(包括它的子程序内)原则上无法做 IIC 与 SPI 以及软硬串口的通信!
为何说原则上呢?
因为你还是可以在中断程序内把中断打开, 只要这样:
sei( ); //打开中断
cli(); //关闭中断
可是, 那是否有其他问题,就要看你的中断处理到底做何事情, 以及中断的时间是否很短 ? 如果两次中断的时间间隔足够长,只要来得及处理或是中断重进入(reentrant)不会有问题, 那就可以把中断打开!
1.2 关于中断的重进入(reentrant), 请参考维基百科:
http://en.wikipedia.org/wiki/Reentrancy_(computing)
当然, 如果你测试结果没问题那就放心的打开中断!
1.3 关于中断的概念可以看看奈何大神写的这篇有趣文章:
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
1.4 如果你是要使用内部定时器(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) 中断来到, 一样无法处理, 于是导致软串口的通信失常或数据遗失!
1.5 Arduino的 CPU 之25种中断之优先级
关于Arduino的 CPU 之25种中断之优先级可看这里:
就是说, 除了 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 | |
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) |
2. 关于IIC与软串口等的源代码可看:
https://github.com/arduino/Arduino/tree/master/hardware/arduino/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
3. Pin Change Interrupt
请注意, 如果你使用了软串口(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找到: 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 (PCINT0_vect) 处理 Pin D8 to D13
ISR (PCINT1_vect) 处理 Pin A0 to A5
ISR (PCINT2_vect) 处理 Pin D0 to D7
3.1 Pin Change Interrupts 范例:
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
}
Table of pins -> pin change names / masks
pin | pin change names / masks |
---|---|
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) |
Reference: http://gammon.com.au/interrupts
3.2 中断测试
接下来我们来测试两个使用中断处理的按钮! (两个按钮按下与松开都会产生中断),但也可以不必使用真的按钮, 拿一条杜邦线即可测试了 , (例如把网络线剥开,里面有八条电导线可用)
第一个按钮接 pin2 以便使用外部中断 INT0
, 因为我们设定 pinMode(2, INPUT_PULLUP);
所以按钮另一端接 GND 即可!
另一个按钮接 pin 8, (也是用 PULLUP, 且代码已经写成可把 pinPrint 可改为 9, 或 10 都可),
3.2.1代码如下:
// 按钮接 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( );
}
3.2.2 如何测试呢?
- 把串口监视器 Serial Monitor 开启
- 拿一根杜邦线或任意电导线, 一端接 GND,
- 另一端轻轻触一下 pin 8 (由 pinPrint 决定, 目前写成可以 8, 或 9, 或 10, 改 pinPrint 即可! )
- 改轻轻触一下 pin 2 (或按下这按钮)
你会发现按下这按钮或插入 pin 2 时 Led 13 熄灭, 拉出时或放掉按钮时 Led 13 灯亮! - 再改回轻轻触一下 pin 8
注意串口监视器输出的答案! 发现了没, 只插入一下 pin 2 又拔掉,
结果cnt 的值就跳好多, 理论上应该是多 2 (按下放掉各加 1),
但是实际却好像发生了很多次甚至一二十次中断! 这就是所谓抖动(Bouncing)的问题!
还有, 插一下 pin 8 却印好几次, 这也是抖动的问题!
- 重复刚刚 (2)全部步骤, 注意 LED 13 的亮灭, 以及串口监视器输出的值!
3.2.3 继续测试
再来把以上范例改为 pin 2, 3, 4, 5, 6, 7 这六支 pin ;
你只要对照范例与对照表就会改为可监看 pin A0 到 A5 了!
以下我故意把 pinPrint 设 2, 看看会怎样 ?!注意, pin 2 同时也是 INT0 的外部中断,就是说以下程序码中 pinPrint 与 button 都是 2, 这样用接 GND 线轻触一下 pin 2, 导致两种中断都会产生, 那先做哪个呢?
其实这之前就说过了 , 在 25 种中断之中, INT0 的优先权是最高的!
// 按钮接 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( );
}
你可以看出 INT0 的中断优先处理, 否则在 loop( )
内第一次印出的 cnt 就应该是 0 才对!
请注意, 目前为止我们还没用到国外大神写的 Pin Change Interrupt
的库!
4. 关于 P-C-I 库 : Pin Change Interrupt Library for the Arduino
4.1 安装PinChangeInt库
要使用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/PinChangeIntgit clone https://github.com/GreyGnome/PinChangeInt.git
或直接点以下链接: (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);
其实, 这个 P-C-I 库所用的方法就是类似上面我写的范例, 只是P-C-I库已经考虑所有的 pin, 但上述我写的范例只有考虑 pin 8, 9, 10 这三支脚位,且 P-C-I 库设计成仿照外部中断INT0, INT1的使用方法,
可以用类似写法指定监看某 pin 的变化产生中断, 例如:
attachPinChangeInterrupt(pin, ggyy, FALLING);
表示只要 pin 由高电平往低电平下降(FALLING), 就产生中断跳入函数 ggyy( ) 内!
接着我们来看看使用该 P-C-I 库的简单范例, 这是我把范例拿来稍微改过,测试 pin 8 产生中断的次数, 你可以找一个按钮连接 pin 8 (另一端接 GND),
或是拿一条杜邦线或任意电导线, 一端接 GND, 另一端轻触 pin 8
记得要把串口监视器 Serial Monitor 打开观看!
#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.");
}
看到了, 很简单吧 ?
就是先写好中断处理函数例如 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
关于Chris J. Kiick和Michael Schwager写的 P-C-I Library:
https://github.com/GreyGnome/PinChangeInt
Alse see:
http://playground.arduino.cc/Main/PinChangeInt
http://playground.arduino.cc/Main/PinChangeIntExample
http://playground.arduino.cc/Code/ReadReceiver