前面我们学习了ATmega8的I/O口作为通用数字输入/输出口来用时对LED数码管控制和扫描按键的应用;
但ATmega8多数的I/O口都是复用口,除了作为通用数字I/O使用,还有其第二功能,这里我们学习PD2、PD3两端口的第二功能:外部中断。
1.外部中断的特点:
PD2端口是外部中断源0,PD3端口是外部中断源1。ATmega8的外部中断就是由这两个引脚触发的。
*要注意的是:如果设置允许外部中断产生,即使是INT0和INT1引脚设置为输出方式,外部中断还是会触发的。
外部中断的触发方式有三种可选性:
(1)上升沿触发;
(2)下降沿触发;
(3)低电平触发。
具体方式是由以下三个决定的:
(1)MCU的控制寄存器MCUCR
(2)MCU控制
(3)状态寄存器MCUCSR
*当允许外部中断且设置为低电平触发方式时,只要中断输入引脚保持低电平,就将一直触发产生中断;
*而对于上升沿或者下降沿的中断触发,则需要I/O时钟信号的存在。
要使用外部中断我们首先要了解几个寄存器:
(1)AVR的状态寄存器SREG
(2)MCU控制寄存器MCUCR
(3)通用中断控制寄存器GICR
(4)通用中断标志寄存器GIFR
详细信息有:
(1)AVR的状态寄存器SREG:
SREG的每一位都是一个标志位,位7(全局中断允许位)——I位;
(2)MCU控制寄存器MCUCR:
位0(ISC00)是外部中断0的中断方式控制位0;
位1(ISC01)是外部中断0的中断方式控制位1;
位2(ISC10)是外部中断1的中断方式控制位0;
位3(ISC11)是外部中断1的中断方式控制位1;
参考表与上图类似。
(3)通用中断控制寄存器GICR:
位6——INT0控制外部中断0的使能;
位7——INT1控制外部中断1的使能。
当状态寄存器SREG的I位(全局中断允许位)置1时,
(4)通用中断标志寄存器GIFR:
位6——INT0是外部中断0的标志位;
位7——INT1是外部中断1的标志位;
*当外部中断0被设置为低电平触发方式时,标志INTF0 位将始终为0。
扩展:
中断向量表:Atmega8共有18 个中断源,Flash程序存储器空间的最低位置(0x000—0x012)定义为复位和中断向量空间,也就是说把中断函数的地址保存在这里,当中断发生后就到这里找到对应函数的地址,然后去执行对应的函数。x向量表如下:
在中断向量表中,处于低地址的中断向量对应的中断优先级高,所以系统复位RESET拥有最高优先;
外部中断0高于外部中断1;系统复位REST不是中断。
编程准备:
用ICCAVR的编程,在C中只要用#pragma伪指令和中断向量说明中断服务程序入口地址即可:
#pragma interrupt_handler <函数名>:<中断向量>
例如要定义使用INT0中断服务程序:
#pragma interrupt_handler int0_fun:2
void int0_fun()
{
......
}
2对应INT0的中断服务程序入口地址(由向量表中红色字体可知);
同理,3对应INT1的中断服务程序入口地址。
也可以让多个中断调用同一个函数,如:
#pragma interrupt_handler int_fun:2
#praama interrupt_handler int_fun:3
表示外部中断0和中断1都调用int_fun函数。
2.应用实例——中断计数器
用两个按键作为两个外部中断的触发源,再接一个LED数码管用来显示两位数的数据,电路图如下:
将外部中断0设置为下降沿触发(MCUCR的位1为1,位0为0),中断1设置为低电平触发(MCUCR的位3为0,位2为0);(MCUCR=0x02)
调用同一个中断函数,在中断中做数值加1,然后在LED数码管中显示。
代码如下:
1 #include <iom8v.h>
2 #include <macros.h>
3 #include "Delay.h"
4
5 unsigned char CountNum; //全局变量用于计数
6
7 //指明中断程序入口地址
8 #pragma interrupt_handler int_fun:2
9 #pragma interrupt_handler int_fun:3
10 void int_fun(void)
11 {
12 if(++CountNum>=100)
13 CountNum -= 100;
14 }
15
16 //主函数,显示数据时先关闭中断,然后再打开
17 void main()
18 {
19 unsigned char tempL,tempR;
20 unsigned char num[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
21 //初始化端口
22 DDRB = 0XFF;
23 PORTB = 0XFF;
24 DDRC = 0X03;
25 PORTC = 0XFF;
26 DDRD = 0XFF;
27 PORTD = 0XFF;
28
29 //中断配置
30 SEI(); //打开全局中断
31 MCUCR = 0X02; //外部中断0设置为下降沿触发,中断1设置为低电平触发
32 GICR = 0xC0; //打开INT0、INT1中断
33 GIFR = 0xC0; //清除INT0、INT1中断标志位
34
35 CountNum = 0; //初始化全局变量
36 while(1)
37 {//显示数据时关闭中断
38 CLI(); //关闭全局中断
39
40 //显示十位数
41 tempL = CountNum/10;
42 PORTC &= ~(2);
43 PORTB = num[tempL];
44 delay_ms(1);
45
46 //显示个位数
47 tempR = CountNum%10;
48 PORTC &= ~(1);
49 PORTB = num[tempR];
50
51 SEI(); //打开全局中断
52 delay_ms(1);
53 }
54 }
3.中断触发键盘扫描
按下键盘的任意一个按键就触发一个中断,然后在中断函数中来调用键盘处理函数。
电路图中,比上一讲的实例中多了一个74S10的与非门,作用是任意一个按键按下都可以触发一个INT0中断。
要实现的内容是:
任意一个按键按下触发一个INT0中断,INT0设置为上升沿触发方式(MCUCR=0x03),在中断中做一个标志,表示有按键按下;
然后在主函数中判断该标志位,有按键按下,消除抖动干扰,再做确认哪个按键按下,最后在LED数码管上显示按键的值。
1 #include <iom8v.h>
2 #include <macros.h>
3 #include "Delay.h"
4
5 unsigned char KeyDown;
6
7 //按键扫描函数,返回按键的值
8 //unsigned char ScanKey(void)函数的实现与上一实例类似
9
10 //中断函数,设置一个标志,表示按键按下
11 //指明中断程序入口地址
12 #pragma interrupt_handler int_fun:2
13 void int_fun(void)
14 {
15 KeyDown = 1; //在中断中仅设置一个标志
16 }
17
18 //主函数,扫描按键显示数据
19 void main()
20 {
21 unsigned char temp,keynum;
22 unsigned char num[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
23
24 //初始化端口
25 DDRB = 0xFF;
26 PORTB = 0xFF;
27 DDRC = 0x07;
28 PORTC = 0x38;
29 DDRD &= 0x0F;
30 PORTD |= 0xFC;
31
32 //中断配置
33 SEI(); //打开全局中断
34 MCUCR = 0x03; //INT0上升沿触发
35 GICR |=0x40; //打开INT0中断
36 GIFR = 0xC0; //清除INT0、INT1中断标志位
37
38 KeyDown = 0; //初始化全局变量
39 while(1)
40 {
41 PORTB = 0x40; //没有按键时,LED默认显示-
42 if(KeyDown==1) //检测是否有按键按下
43 {
44 //关闭中断,恢复全局变量
45 GICR &= 0x00;
46 KeyDown = 0;
47 delay_ms(5);
48
49 //防抖动,再次判断是否有按键
50 temp = PINC&0x38;
51 if(temp==0x38) //没有按键
52 {
53 GICR = 0x40; //打开INT0中断
54 continue;
55 }
56
57 //有按键
58 keynum = ScanKey(); //获得按键值
59 PORTB = num[keynum]; //LED显示按键值
60
61 while(temp!=0x38) //等待按键释放
62 temp = PINC&0x38;
63
64 //退出前开启INT0中断
65 GICR = 0x40;
66 DDRC = 0x07;
67 PORTC = 0x38;
68 }
69 }
70 }