AVR单片机中断实现

时间:2016-10-19 10:06:33   |    娜娜冷制手工皂

  中断嵌套

  对于中断嵌套的处理,不同的单片机处理的方式是不同的,应根据所使用单片机的特点正确实现中断嵌套的处理。

  按照通常的规则,当MCU正在响应一个中断B的过程中,又产生一个其它的中断A申请时,如果这个新产生中断A的优先级比正在响应的中断B优先级高的话,就应该暂停当前的中断B的处理,转入响应高优先级的中断A,待高优先级中断A处理完成后,再返回原来的中断B的处理过程。如果新产生中断A的优先级比正在处理中断B的优先级低(或相同),则应在处理完当前的中断B后,再响应那个后产生的中断A申请(如果中断A条件还成立的话)。

  一些单片机(如8051结构)的硬件能够自动实现中断嵌套的处理,既单片机内部的硬件电路能够识别中断的优先级,并根据优先级的高低,自动完成对高优先级中断的优先响应,实现中断的嵌套处理。

  而另一类的单片机,如我们正在学习的AVR单片机,其硬件系统不支持自动实现中断嵌套的处理。如果在系统设计中,必须使用中断嵌套处理,则需要由用户编写相应的程序,通过软件设置来实现中断嵌套的功能。

  外部中断程序的编写

  我们已经知道,要实现中断程序,首先要在主程序里面对相关中断寄存器进行中断产生条件的设置。然后就是编写中断服务程序。

  本例中中断寄存器的设置如下:

  MCUCR |= (1 << ISC11) | (1 << ISC01) | (1 << ISC00);

  //INT0设置为上升沿中断,INT1为下降沿中断请求

  GICR |= (1 << INT0) | (1 << INT1); //允许INT0、INT1中断

  GIFR |= (1 << INTF1) | (1 << INTF0); //清除INT0、INT1中断标志位

  sei(); //使能全局中断

  中断服务程序的编写具有一定的格式,在不同编译环境下各不相同,在WINAVR(GCC)环境下有两种方式,分别是: ● SIGNAL(中断向量名) { … //中断服务程序内容 }

  ● ISR(中断向量名 )

  { … //中断服务程序内容 }

  在这两种方式中,需要分别添加头文件:#include <avr/signal.h>和#include <avr/interrupt.h>。

  宏INTERRUPT 的用法与SIGNAL 类似,区别在于SIGNAL 执行时全局中断触发位被清除、其他中断被禁止;INTERRUPT 执行时全局中断触发位被置位、其他中断可嵌套执行。

  另外avr-libc 提供两个API 函数用于置位和清零全局中断触发位,它们是经常用到的,

  分别是:void sei(void) 和void cli(void) 由interrupt.h定义

  在本实例中,我们采用包含头文件#include <avr/interrupt.h>,的方式,使用ISR(中断向量名 ){…}来编写中断函数。

  #include <avr/io.h>

  #include <util/delay.h>

  #include <avr/interrupt.h> //中断函数头文件

  unsigned char Disp_Buff[16] = {0xaf,0xa0,0xc7,0xe6,0xe8,0x6e,0x6f,0xa2,

  0xef,0xee,0xeb,0x6d,0x0f,0xe5,0x4f,0x4b};

  //数码管字型码表显示:0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F

  volatile unsigned char Counter; //按键按下次数变量,如果在中断中调用全局变量,必须加

  //volatile来定义,否则变量不会变化

  int main(void)

  {

  PORTB = 0X00; //

  DDRB = 0Xff; //

  PORTC &= ~(1 << PC6); //配置数码管0的位选通口为低电平,不导通数码管

  DDRC |= (1 << PC6); ///配置数码管0的位选通口为输出,选通数码管0

  PORTD = 0X08; //一定要使能K2的上拉电阻,否则会有干扰

  DDRD = 0XF3; //K1、K2按键(PD2、PD3)设置为输入端口

  MCUCR |= (1 << ISC11) | (1 << ISC01) | (1 << ISC00);

  //INT0设置为上升沿中断,INT1为下降沿中断请求

  GICR |= (1 << INT0) | (1 << INT1); //允许INT0、INT1中断

  GIFR |= (1 << INTF1) | (1 << INTF0); //清除INT0、INT1中断标志位

  Counter = 0; //按键按下次数变量清零

  PORTC |= (1 << PC6); //选通数码管0

  sei(); //使能全局中断

  while(1)

  {

  PORTB = Disp_Buff[Counter]; //数码管显示按键按下次数

  }

  }

  //外部中断0函数,当按键K1按下后,进入此中断

  ISR(INT0_vect )

  {

  _delay_ms(20); //按键按下,延时一会再判断是否按下, 以消除干扰

  if((PIND & (1 << PD2))) // 按键真正按下后,进行相应处理

  {

  if(++Counter >= 16) Counter = 0; //次数大于15,清零

  while((PIND & (1 << PD2)));//等待按键释放

  }

  }

  //外部中断1函数,当按键K2按下后,进入此中断

  ISR(INT1_vect)

  {

  _delay_ms(20); //判断按键按下,延时一会再判断是否按下, 以消除干扰

  if(!(PIND & (1 << PD3))) // 按键真正按下后,进行相应处理

  {

  if(Counter) --Counter; // 次数减1

  else Counter = 15; // 次数为零则改成15

  while(!(PIND & (1 << PD3))); //

  }

  }

  在语言C语言编写单片机程序过程,如果要使用外部中断服务程序时,要尽量减少中断服务程序的内容和长度。因为在主程序中可能还要相应别的中断,如果一个中断服务程序过长,很可能会影响到主程序对其他中断的响应。

  常用的处理方法是:在中断服务程序中只改变变量的值,或者设置各种标志,在主程序里面对变量或标志进行判断和处理。本实例为了演示的方便,在中断程序中进行了所有的操作。应该说明的是,这种方法是极不可取的。更为合理的方法是:在中断服务程序里面只改变Counter的值。其余部分都放到主程序里面进行处理。

  另外需要特别强调的一点是,在用WINAVR编写中断服务程序时,如果中断服务程序中用到了全局变量,则在定义全局变量时,必须在变量的数据类型前加 volatile来定义,否则该变量在中断服务程序中不会变化。这是因为在用C语言编写单片机程序时,都会用到编译器的“优化”代码功能,以使程序更加简洁、紧凑。但是毕竟编译器的优化是很死板的,他会把一些对变量的读操作优化掉。这样就导致在全局中断中使用的变量被优化成一个静止变量,即该变量的值不再改变。所以我们要把这些变量定义为volatile,意思是提示编译器:该变量是很容易变化的,不准对该变量的读取进行优化。这样在中断中每次对变量的读写就都可以正确的执行了。

  原文链接:http://www.eeworld.com.cn/mcu/article_2016101930587.html