ATmega8仿真——外部中断的学习

前面我们学习了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位;

  • 该位为1时全局中断使能允许,单独的中断使能则有对应的中断寄存器控制;
  • 该位为0时则不论单独允许位是否置1,所有中断都被禁止,系统将不响应任何中断。

(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时,

  • INT0置1则外部引脚中断0使能;
  • INT1置1则外部引脚中断1使能。

(4)通用中断标志寄存器GIFR:

位6——INT0是外部中断0的标志位;

位7——INT1是外部中断1的标志位;

  • 当INT0 引脚上的有效事件触发一个中断请求后,INTF0位会变成1。
  • 如果全局中断使能且外部中断0 使能,则MCU将跳至相应的中断向量处开始执行中断服务程序,同时硬件自动将INTF0 标志位清零。

*当外部中断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 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏智能大石头

线程池ThreadPool及Task调度机制分析

近1年,偶尔发生应用系统启动时某些操作超时的问题,特别在使用4核心Surface以后。笔记本和台式机比较少遇到,服务器则基本上没有遇到过。

680
来自专栏Java Edge

长文慎入-探索Java并发编程与高并发解决方案(更新中)1 基本概念2 CPU3 项目准备4线程安全性5发布对象7 AQS9 线程池10 死锁

4368
来自专栏java思维导图

深入源码分析Java线程池的实现原理

程序的运行,其本质上,是对系统资源(CPU、内存、磁盘、网络等等)的使用。如何高效的使用这些资源是我们编程优化演进的一个方向。今天说的线程池就是一种对CPU利用...

592
来自专栏chenssy

【死磕Java并发】-----J.U.C之阻塞队列:SynchronousQueue

原文出处http://cmsblogs.com/ 『chenssy』 【注】:SynchronousQueue实现算法看的晕乎乎的,写了好久才写完,如果当中有什...

3747
来自专栏大内老A

WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)

对于一般的多线程操作,比如异步地进行基于文件系统的IO操作;异步地调用Web Service;或者是异步地进行数据库访问等等,是和具体的线程无关的。也就是说,对...

1777
来自专栏Java架构师学习

带你深入了解Java线程中的那些事

引言 说到Thread大家都很熟悉,我们平常写并发代码的时候都会接触到,那么我们来看看下面这段代码是如何初始化以及执行的呢? public class Thre...

2868
来自专栏zhisheng

JAVA虚拟机关闭钩子(Shutdown Hook)

当你认真的去看一个组件的源码的时候,你会经常看见这种关闭钩子的函数,如果你不了解的话,谷歌一下,你就会发现如下文章就是搜索引擎出来的第一篇,不愧是出自我们优秀的...

1043
来自专栏程序猿DD

Spring Boot使用@Async实现异步调用:ThreadPoolTaskScheduler线程池的优雅关闭

上周发了一篇关于Spring Boot中使用 @Async来实现异步任务和线程池控制的文章:《Spring Boot使用@Async实现异步调用:自定义线程池》...

4267
来自专栏大闲人柴毛毛

Java并发容器大合集

概述         java.util包中的大部分容器都是非线程安全的,若要在多线程中使用容器,你可以使用Collections提供的包装函数:synchro...

3686
来自专栏知识分享

3-MSP430引脚中断

为了写一篇文章做铺垫--提醒着自己,,,,,, 这两天一直在寻找 #pragma vector = PORT1_VECTOR __interrupt void ...

2777

扫码关注云+社区