请教马老师,有关外部中断中全局中断标志的一个疑问?
马老师,您好!在《中断应用设计要点》这篇文章中,A部分第四条:
4.当MCU响应一个中断时,其硬件系统会自动中断返回地址压入系统堆栈,并将关闭全局中断响应(硬件将中断标志I位清0),清除该中断的中断标志位;执行中断返回指令RETI时,硬件会先允许全局中断响应(硬件将中断标志I位置1),然后从系统堆栈中弹出返回地址到PC程序计数器中,继续执行被中断打断的程序。除此之外,MCU的硬件没有对中断保护做其他处理。
但是在我的程序中,我用INT0外部中断,执行中断返回指令后,中断标志I位还是为0,我必须要加入sei();进行再一次设置才可以进入下一轮中断; 但是在别的中断向量中,比如USART_Rx,不需要加入sei();就可以进行下一轮中断。
请问,这种情况是正确的吗?谢谢。。。 External Interrupts
The External Interrupts are triggered by the INT0, INT1, and INT2 pins. Observe that, if enabled, the interrupts will trigger even if the INT0..2 pins are configured as outputs. This feature provides a way of generating a software interrupt. The external interrupts can be triggered by a falling or rising edge or a low level (INT2 is only an edge triggered interrupt). This is set up as indicated in the specification for the MCU Control Register – MCUCR – and MCU Control and Status Register – MCUCSR. When the external interrupt is enabled and is configured as level triggered (only INT0/INT1), the interrupt will trigger as long as the pin is held low. Note that recognition of falling or rising edge interrupts on INT0 and INT1 requires the presence of an I/O clock, described in “Clock Systems and their Distribution” on page 24. Low level interrupts on INT0/INT1 and the edge interrupt on INT2 are detected asynchronously. This implies that these interrupts can be used for waking the part also from sleep modes other than Idle mode. The I/O clock is halted in all sleep modes except Idle mode.
===========================================================
上面是从手册中对外部中断的描述,注意其中一句:
When the external interrupt is enabled and is configured as level triggered (only INT0/INT1), the interrupt will trigger as long as the pin is held low.
如果你的系统中使用INT0为低电平中断,那么进入中断后就应该通知外部器件将INT0的电平释放抬高了。否则当执行完中断返回后,由于INT0还为低,又是优先级最高的中断,所以马上又进入了INT0中断。因此,在INT0为低的期间,你的程序将老在循环执行INT0的中断过程。
如果你的SEI()是INT0中断返回前的一条指令的话,你实际上在使用中断嵌套过程了。一但你在INT0的中断服务程序中使用了SEI(),那么任何一个新的中断都会打断正在执行的INT0中断服务。
你可以做测试:在INT0中断服务返回前增加一段对INT0引脚电平的读取和判断,如果为低,继续读INT0,等到读到“1”时才执行中断返回。
如果你使用INT0低电平中断,那么上面的分析供参考。
根据我的经验,外部中断的低电平触发方式一般应用于将MCU从休眠状态中唤醒使用,或处理紧急事件(如事件发生,其它工作全部要停止,只能处理该事件的情况)。在正常使用中最好采用边沿或电平改变触发方式。 谢谢马老师的回答。
我没说明我用的是电平改变触发方式,因为我的程序需要判断电平的状态来执行相应的操作。今天我又试验了边沿方式,我得到的结论是:
对于外部中断INT0,若采用电平改变触发方式或边沿触发方式时,一旦INT0中断操作执行后,SREG的标志位I置0,如果需要继续有中断发生时,一定要重新加入sei();才可以。 我简单写了测试程序(cvavr环境)如下:
#include <mega16.h>
unsigned char count;
bit time_125_ok;
// External Interrupt 0 service routine
interrupt void ext_int0_isr(void)
{
count++;
if (count >= 125)
{
count = 0;
time_125_ok = 1;
}
}
void main(void)
{
PORTA=0x00;
DDRA=0xFF;
// External Interrupt(s) initialization
// INT0:On, INT1:Off, INT2:Off
// INT0 Mode: Falling Edge
GICR|=0x40;
MCUCR=0x02;
MCUCSR=0x00;
GIFR=0x40;
// Global enable interrupts
#asm("sei")
while (1)
{
if (time_125_ok)
{
time_125_ok = 0;
PORTA = ~PORTA;
}
}
}
使用M16,在AVR-51的板上运行。
PD2(INT0)输入一个125Hz的方波序列(板上有方波序列发生器),INT0中断服务中的计数器记录中断次数,每125次置标志位为1(正好1秒钟)。
主程序中判断标志,每隔1秒改变PA口的输出,PA口接LED,这样LED每隔1秒亮/暗闪烁。
整个程序仅在初始化后开放中断允许,其它地方没有任何开中断的指令。
程序运行正常,当INT0输入的方波频率为250Hz时,LED闪烁频率也提高一倍。证明你的结论有误。
==============================
完成测试5分钟,回答你的问题需要15分钟。 以下是这段测试程序的汇编代码
+00000000: 940C002B JMP 0x0000002B Jump 到初始化程序(cvavr的)
+00000002: 940C005B JMP 0x0000005B Jump 到INT0的中断服务程序
+00000004: 940C0000 JMP 0x00000000 Jump
+00000006: 940C0000 JMP 0x00000000 Jump
+00000008: 940C0000 JMP 0x00000000 Jump
+0000000A: 940C0000 JMP 0x00000000 Jump
+0000000C: 940C0000 JMP 0x00000000 Jump
+0000000E: 940C0000 JMP 0x00000000 Jump
+00000010: 940C0000 JMP 0x00000000 Jump
+00000012: 940C0000 JMP 0x00000000 Jump
+00000014: 940C0000 JMP 0x00000000 Jump
+00000016: 940C0000 JMP 0x00000000 Jump
+00000018: 940C0000 JMP 0x00000000 Jump
+0000001A: 940C0000 JMP 0x00000000 Jump
+0000001C: 940C0000 JMP 0x00000000 Jump
+0000001E: 940C0000 JMP 0x00000000 Jump
+00000020: 940C0000 JMP 0x00000000 Jump
+00000022: 940C0000 JMP 0x00000000 Jump
+00000024: 940C0000 JMP 0x00000000 Jump
+00000026: 940C0000 JMP 0x00000000 Jump
+00000028: 940C0000 JMP 0x00000000 Jump
+0000002A: 0000 NOP No operation
+0000002B: 94F8 CLI Global Interrupt Disable (关中断,cvavr的初始化开始)
+0000002C: 27EE CLR R30 Clear Register
+0000002D: BBEC OUT 0x1C,R30 Out to I/O location
+0000002E: E0F1 LDI R31,0x01 Load immediate
+0000002F: BFFB OUT 0x3B,R31 Out to I/O location
+00000030: BFEB OUT 0x3B,R30 Out to I/O location
+00000031: BFE5 OUT 0x35,R30 Out to I/O location
+00000032: E1F8 LDI R31,0x18 Load immediate
+00000033: BDF1 OUT 0x21,R31 Out to I/O location
+00000034: BDE1 OUT 0x21,R30 Out to I/O location
+00000035: E08D LDI R24,0x0D Load immediate
+00000036: E0A2 LDI R26,0x02 Load immediate
+00000037: 27BB CLR R27 Clear Register
+00000038: 93ED ST X+,R30 Store indirect and postincrement
+00000039: 958A DEC R24 Decrement
+0000003A: F7E9 BRNE PC-0x02 Branch if not equal
+0000003B: E080 LDI R24,0x00 Load immediate
+0000003C: E094 LDI R25,0x04 Load immediate
+0000003D: E6A0 LDI R26,0x60 Load immediate
+0000003E: 93ED ST X+,R30 Store indirect and postincrement
+0000003F: 9701 SBIW R24,0x01 Subtract immediate from word
+00000040: F7E9 BRNE PC-0x02 Branch if not equal
+00000041: E5E4 LDI R30,0x54 Load immediate
+00000042: E0F0 LDI R31,0x00 Load immediate
+00000043: 9185 LPM R24,Z+ Load program memory and postincrement
+00000044: 9195 LPM R25,Z+ Load program memory and postincrement
+00000045: 9700 SBIW R24,0x00 Subtract immediate from word
+00000046: F061 BREQ PC+0x0D Branch if equal
+00000047: 91A5 LPM R26,Z+ Load program memory and postincrement
+00000048: 91B5 LPM R27,Z+ Load program memory and postincrement
+00000049: 9005 LPM R0,Z+ Load program memory and postincrement
+0000004A: 9015 LPM R1,Z+ Load program memory and postincrement
+0000004B: 01BF MOVW R22,R30 Copy register pair
+0000004C: 01F0 MOVW R30,R0 Copy register pair
+0000004D: 9005 LPM R0,Z+ Load program memory and postincrement
+0000004E: 920D ST X+,R0 Store indirect and postincrement
+0000004F: 9701 SBIW R24,0x01 Subtract immediate from word
+00000050: F7E1 BRNE PC-0x03 Branch if not equal
+00000051: 01FB MOVW R30,R22 Copy register pair
+00000052: CFF0 RJMP PC-0x000F Relative jump
+00000053: E5EF LDI R30,0x5F Load immediate
+00000054: BFED OUT 0x3D,R30 Out to I/O location
+00000055: E0E4 LDI R30,0x04 Load immediate
+00000056: BFEE OUT 0x3E,R30 Out to I/O location
+00000057: E6C0 LDI R28,0x60 Load immediate
+00000058: E0D1 LDI R29,0x01 Load immediate
+00000059: 940C0069 JMP 0x00000069 Jump 到MAIN()函数(用户的初始化)
========================================================================
;INT0中断服务:
+0000005B: 93EA ST -Y,R30 Store indirect and predecrement
+0000005C: B7EF IN R30,0x3F In from I/O location
+0000005D: 93EA ST -Y,R30 Store indirect and predecrement
+0000005E: 9443 INC R4 Increment
+0000005F: E7ED LDI R30,0x7D Load immediate
+00000060: 164E CP R4,R30 Compare
+00000061: F018 BRCS PC+0x04 Branch if carry set
+00000062: 2444 CLR R4 Clear Register
+00000063: 9468 SET Set T in SREG
+00000064: F820 BLD R2,0 Bit load from T to register
+00000065: 91E9 LD R30,Y+ Load indirect and postincrement
+00000066: BFEF OUT 0x3F,R30 Out to I/O location
+00000067: 91E9 LD R30,Y+ Load indirect and postincrement
+00000068: 9518 RETI Interrupt return
;INT0中断返回
=======================================================================
;用户的MAIN()开始
+00000069: E0E0 LDI R30,0x00 Load immediate
+0000006A: BBEB OUT 0x1B,R30 Out to I/O location
+0000006B: EFEF SER R30 Set Register
+0000006C: BBEA OUT 0x1A,R30 Out to I/O location
+0000006D: B7EB IN R30,0x3B In from I/O location
+0000006E: 64E0 ORI R30,0x40 Logical OR with immediate
+0000006F: BFEB OUT 0x3B,R30 Out to I/O location
+00000070: E0E2 LDI R30,0x02 Load immediate
+00000071: BFE5 OUT 0x35,R30 Out to I/O location
+00000072: E0E0 LDI R30,0x00 Load immediate
+00000073: BFE4 OUT 0x34,R30 Out to I/O location
+00000074: E4E0 LDI R30,0x40 Load immediate
+00000075: BFEA OUT 0x3A,R30 Out to I/O location
+00000076: 9478 SEI Global Interrupt Enable (开放全局中断)
----------------------------------------------------------------------------
;while()循环开始
+00000077: FE20 SBRS R2,0 Skip if bit in register set
+00000078: C005 RJMP PC+0x0006 Relative jump
+00000079: 94E8 CLT Clear T in SREG
+0000007A: F820 BLD R2,0 Bit load from T to register
+0000007B: B3EB IN R30,0x1B In from I/O location
+0000007C: 95E0 COM R30 One's complement
+0000007D: BBEB OUT 0x1B,R30 Out to I/O location
+0000007E: CFF8 RJMP PC-0x0007 Relative jump
+0000007F: CFFF RJMP PC-0x0000 Relative jump 谢谢您快速的回答。
我用的JTAG ICE mkII仿真, GCC.
#include <avr/io.h>
#include <avr/interrupt.h>
#define amount_Tux 10
#define RTS_inactive PORTD &= ~(1<<PD3) //level low
#define RTS_active PORTD |=(1<<PD3)//level high
#define CTS_inactive !(PIND &(1<<2)) //level low disable TXEN
#define CTS_active PIND &(1<<2)//level high enable TXEN
uint8_t send;
volatile uint8_t i;
volatile uint8_t counter_interrupt;
//initialisation usart
void Init_Usart_Tux(unsigned int baudrate)
{
UBRR0H = (unsigned char)(baudrate>>8);
UBRR0L = (unsigned char)baudrate;
UCSR0B = 0x00; //disable while setting baud rate
UCSR0A = (1<<U2X0);
// Enable receiver
UCSR0B = (1<<RXEN0);
// Async mode, 8bits data, 1bits stop
UCSR0C = (1<<UCSZ00)|(1<<UCSZ01) ;
}
//init port
void Init_Port_Tux(void)
{
//PORTD.0 -> IN RxD
//PORTD.1 -> OUT TxD
//PORTD.2 -> IN CTS
//PORTD.3 -> OUT RTShigh level means enable RTS
PORTD = 0b11010011;
DDRD= 0b11101010;
}
//init total
void Init_Total_Tux(void)
{
CLKPR = (1<<CLKPCE); // set Clock Prescaler Change Enable
// set prescaler = 8, Inter RC 8Mhz / 8 = 1Mhz
CLKPR = (1<<CLKPS1) | (1<<CLKPS0);
Init_Port_Tux();
Init_Usart_Tux(12); //9600 baudrate
EICRA = (1<<ISC00); // change of level
EIMSK = (1<<INT0);
}
// transmit funtion
void Usart_Tx(unsigned char *datas)
{
while( i<amount_Tux )
{
//while(CTS_inactive);
//{
UDR0=*(datas+i);
while (!(UCSR0A & (1<<UDRE0))); //UDRE0=1
i++;
//}
}
}
/* interruption externe sur CTS le PIND2 */
ISR(INT0_vect)
{
sei(); // 如果不加这个,中断只发生一次,我用JTAG和软件模拟的结果都是一样的
switch (counter_interrupt % 2)
{
case 0:
{
counter_interrupt++;
UCSR0B |= (1<<TXEN0);//activeTXEN0
Usart_Tx(send);
} break;
case 1:
{
counter_interrupt++;
UCSR0B &= ~(1<<TXEN0);
} break;
}
}
// programme main
int main (void)
{
i=0;
counter_interrupt=0;
send=0x85;
send=1;
send=2;
send=3;
send=4;
send=5;
send=6;
send=7;
send=8;
send=9;
Init_Total_Tux();
sei();
RTS_active;
while(1)
{
if(i==amount_Tux)
{
RTS_inactive;
break;
}
}
return 0;
}
在我的中断子程序中,如果不加sei();那么中断只执行一次,所以我才会得出上述的结论。
我以前也是觉得执行中断返回指令RETI时,硬件会先允许全局中断响应(硬件将中断标志I位置1),但实际上我的实验效果不是这样的。不明白为什么会这样? 哈哈,我已经说过,不用仿真器。
仿真器是给明白人用的,不明白的人是越用仿真越迷糊。
你如果想证明INT0中断返回后全局中断标志位的情况,请你将程序中所有的那些其它无关的东西(通信部分)统统去掉,就象我的证明程序一样。用48发出一个周期为1秒的连续方波作为162的INT0中断输入,162写一个最简单的仅包括INT0中断服务的程序,每进一次中断仅将I/O脚取反,(该I/O驱动一个LED)。看看LED是否一秒钟亮、一秒钟暗。然后再下结论。 我明白为什么了,再仔细读这句话:
执行中断返回指令RETI时,硬件会先允许全局中断响应(硬件将中断标志I位置1)
再看我的程序,我的中断程序中包含发送数据这个函数,由于counter_interrupt一开始为0,所以它先执行发送数据这个函数,那么下一个中断是在执行发送数据这个函数的过程中响应的,所以永远都不会执行中断返回指令RETI,也就不会允许全局中断响应(硬件将中断标志I位置1)。
所以我的解决办法加入sei();是正确的,强制将中断标志I位置1,就可以响应下一个中断。
现在我的结论是:以下论述绝对正确,但是要好好体会,可以结合我上面的例子。呵呵!
4.当MCU响应一个中断时,其硬件系统会自动中断返回地址压入系统堆栈,并将关闭全局中断响应(硬件将中断标志I位清0),清除该中断的中断标志位;执行中断返回指令RETI时,硬件会先允许全局中断响应(硬件将中断标志I位置1),然后从系统堆栈中弹出返回地址到PC程序计数器中,继续执行被中断打断的程序。除此之外,MCU的硬件没有对中断保护做其他处理。
再次感谢马老师。 建议大家一般情况下使用AVR时,不要采用中断嵌套的结构。使用不的当,会出现一些不能预计的情况,因为中断是随机产生的。
AVR本身速度非常快,不用中断嵌套肯定能够满足大部分应用。当然,这还要求你的中断服务程序要尽量的短,不要在中断中做很多的事情。关于中断的使用及注意点,在我讲义的第7章有比较详细的介绍。
页:
[1]