外部中断作为处理器响应外部事件的通道,在控制系统中起着非常重要的作用。从前面的讨论中我们知道,在NVIC中有8个外部中断源,下面就来讨论一下这8个外部中断的使用情况。
LPC824的每一根引脚都可以响应一个外部中断,所以理论上有多少个引脚就有多少个外部中断。但由于LPC824采用了引脚挂接外部中断源的形式,所以并不是所有的引脚都可以同时设置为外部中断引脚。在LPC824中,可同时响应的外部中断源只有8个(即NVIC的8路引脚中断),所以同时只能有8个外部中断引脚在工作,但这8个外部中断引脚可选择从PIO0_0至PIO0_28中的任意一根。
LPC824外部引脚中断所涉及到的寄存器如下表所示。
从上表中可以看到,在LPC824的引脚中断控制中,一共使用了13个寄存器。下面是这些寄存器组所对应的结构体形式(位于头文件LPC82x.h中)。
typedef struct {
__IO uint32_t ISEL;
__IO uint32_t IENR;
__O uint32_t SIENR;
__O uint32_t CIENR;
__IO uint32_t IENF;
__O uint32_t SIENF;
__O uint32_t CIENF;
__IO uint32_t RISE;
__IO uint32_t FALL;
__IO uint32_t IST;
__IO uint32_t PMCTRL;
__IO uint32_t PMSRC;
__IO uint32_t PMCFG;
} LPC_PIN_INT_Type;
因为引脚中断寄存器组的基址为0xA0004000,所以要将基址指针强制转换为上述结构体,还必须要加上下面的定义。
#define LPC_PIN_INT_BASE 0xA0004000UL
#define LPC_PIN_INT ((LPC_PIN_INT_Type *) LPC_PIN_INT_BASE)
下面先对其中与外部引脚中断相关的10个寄存器的功能进行讨论,另外3个与模式匹配相关的寄存器后面再专门讨论。先来看引脚中断模式寄存器ISEL,其字节地址为0xA0004000,下表给出了它的全部位结构。
(1)第0到7位为8个外部中断引脚的模式选择位,置0时,对应的外部中断被设置为边沿触发型,置1时,设置为电平触发型,默认为边沿触发型。
(2)第8到31位为保留位。
下表给出的是引脚中断电平或上升沿中断使能寄存器IENR的全部位结构,其字节地址为0xA0004004。
(1)第0到7位为引脚中断的上升沿或电平中断选择位,置0时,对应的外部中断被禁用上升沿或电平中断,置1时,使能上升沿或电平中断,默认为禁用上升沿或电平中断。
(2)第8到31位为保留位。
下表给出的是引脚中断电平或上升沿中断置位寄存器SIENR的全部位结构,其字节地址为0xA0004008。SIENR寄存器实际上为了对IENR寄存器进行单独的置位操作而设立的,如果是按字(或字节)操作直接写IENR寄存器即可。
(1)在第0到7位中写入1会置位IENR寄存器中相对应的位,从而使能上升沿或电平中断,置0时无影响。
(2)第8到31位为保留位。
下表给出的是引脚中断电平或上升沿中断清零寄存器CIENR的全部位结构,其字节地址为0xA000400C。CIENR寄存器实际上为了对IENR寄存器进行单独的清零操作而设立的,如果是按字(或字节)操作直接写IENR寄存器即可。
(1)在第0到7位中写入1会清零IENR寄存器中相对应的位,从而禁用上升沿或电平中断,置0时无影响。
(2)第8到31位为保留位。
下表给出的是引脚中断有效电平或下降沿中断使能寄存器IENF的全部位结构,其字节地址为0xA0004010。
(1)第0到7位为引脚中断的下降沿或有效中断电平选择位,置0时,对应的外部中断被禁用下降沿中断或置位有效中断电平为低电平,置1时,使能下降沿中断使能或置位有效中断电平为高电平,默认为禁用下降沿中断或置位有效中断电平为低电平。
(2)第8到31位为保留位。
下表给出的是引脚中断有效电平或下降沿中断置位寄存器SIENF的全部位结构,其字节地址为0xA0004014。SIENF寄存器实际上为了对IENF寄存器进行单独的置位操作而设立的,如果是按字(或字节)操作直接写IENF寄存器即可。
(1)在第0到7位中写入1会置位IENF寄存器中相对应的位,从而使能下降沿中断或置位有效中断电平为高电平,写0时无影响。
(2)第8到31位为保留位。
下表给出的是引脚中断有效电平或下降沿中断清零寄存器CIENF的全部位结构,其字节地址为0xA0004018。CIENF寄存器实际上为了对IENF寄存器进行单独的清零操作而设立的,如果是按字(或字节)操作直接写IENF寄存器即可。
(1)在第0到7位中写入1会清零IENF寄存器中相对应的位,从而禁用下降沿中断或置位有效中断电平为低电平,写0时无影响。
(2)第8到31位为保留位。
下表给出的是引脚中断上升沿寄存器RISE的全部位结构,其字节地址为0xA000401C。
(1)在第0到7位中写入1会清除相应引脚的上升沿检测标记,从而为下一次上升沿检测作准备,写0时无影响。若在相应的位上读取到1,则表示自复位或上一次向该位写1清除起,对应的引脚上检测到了上升沿,读取到0,则表示对应引脚上未检测到上升沿。
(2)第8到31位为保留位。
下表给出的是引脚中断下降沿寄存器FALL的全部位结构,其字节地址为0xA0004020。
(1)在第0到7位中写入1会清除相应引脚的下降沿检测标记,从而为下一次下降沿检测作准备,写0时无影响。若在相应的位上读取到1,则表示自复位或上一次向该位写1清除起,对应的引脚上检测到了下降沿,读取到0,则表示对应引脚上未检测到下降沿。
(2)第8到31位为保留位。
下表给出的是引脚中断状态寄存器IST的全部位结构,其字节地址为0xA0004024。
(1)在第0到7位中写入1时,对于边沿触发型中断,将会清除对应引脚的上升和下降沿检测,对于电平触发型中断,将会切换对应引脚上的有效电平,写0时无影响。若在相应的位上读取到1,则表示对应的中断引脚上有正在请求的外部中断,读取到0,则表示对应的中断引脚上无正在请求的外部中断。
(2)第8到31位为保留位。
注意,电平触发的中断会由硬件在有效电平消失时自动清除标志,并没有软件清除电平中断标志的操作。
以上10个寄存器就是LPC824中与外部引脚中断相关寄存器,其实除了这10个寄存器以外,还有1个名为PINTSEL的寄存器也与外部引脚中断密切相关,只不过它不在上述寄存器组中,而是位于SYSCON模块中。下表就给出了引脚中断选择寄存器PINTSEL的全部位结构,这样的寄存器一共有8个(PINTSEL0~PINTSEL7),其字节地址从0x40048178到0x40048194。
(1)第0到5位用来选择外部中断的引脚编号,范围从PIO0_0到PIO0_28共29根引脚。
(2)第6到31位为保留位。
注意,PINTSEL寄存器一共有8个,也即LPC824的外部引脚中断同时最多只能使用8个,但每一个外部中断都可以在全部PIO0_0到PIO0_28端口中选择引脚。 相当于PINTSEL0~PINTSEL7是8个外部中断源,具体哪个中断源使用哪个引脚,是根据PINTSEL寄存器的配置来确定的。
在讨论完了寄存器的功能后,下面就来分解一下把某个引脚配置为外部中断模式的具体步骤:
1、确定要配置为中断模式的引脚,然后在SYSCON模块的PINTSEL寄存器中进行选择设置,一共可以配置8个外部中断引脚。例如,执行“LPC_SYSCON->PINTSEL0 |= 0x01;”语句后,就把PIO0_1引脚设置为了外部中断引脚。
2、确定是电平触发还是边沿触发,通过ISEL寄存器进行选择配置。例如,执行“LPC_PIN_INT->ISEL &= ~0x01;”语句后,就把PIO0_1引脚设置为边沿触发方式(其实默认就是边沿触发方式,此句也可不写)。
3、若上一步配置成电平触发,则需要确定是低电平触发还是高电平触发,若是边沿触发,则需要确定是上升沿触发还是下降沿触发,通过IENR或IENF寄存器进行执行配置。例如,执行“LPC_PIN_INT->IENR |= 0x01;”语句后,就把PIO0_1引脚设置为上升沿触发方式;执行“LPC_PIN_INT->IENF |= 0x01;”语句后,就把PIO0_1引脚设置为下降沿触发方式。
4、在第3步中,还可以通过访问SIENR和CIENR寄存器来更改IENR寄存器中的某一位,通过访问SIENF和CIENF寄存器来更改IENF寄存器中的某一位。SIENR、CIENR和SIENF、CIENF这四个寄存器其实是IENR和IENF寄存器的伴侣寄存器,用来优化位操作,以避免对IENR和IENF寄存器直接执行“读—改—写”的操作,提高效率。
5、使能NVIC中的相关外部中断。例如,执行“NVIC_EnableIRQ(PIN_INT0_IRQn);”语句后,就使能了PIO0_1上的外部引脚中断。
6、在中断服务程序中,需要清除原有的外部中断标记,以保证下一次外部中断顺利触发,通过访问RISE寄存器来清除上升沿中断标记,通过访问FALL寄存器来清除下降沿标记。例如,执行“LPC_PIN_INT->RISE |= 0x01;”语句后,PIO0_1原来的上升沿中断标记就被清除了。执行“LPC_PIN_INT->FALL |= 0x01;”语句后,PIO0_1原来的下降沿中断标记就被清除了。
7、在第6步中,也可以通过访问IST寄存器来清除边沿(包括上升沿和下降沿)触发的标记。执行“LPC_PIN_INT->IST |= 0x01;”语句后,PIO0_1原来的边沿中断标记就被清除了。
上述步骤也可通过下图来描述。
最后需要说明一点,即使某个引脚并不作为GPIO使用,也能配接到PININT的中断源上。比如:要自动探测4个I2C接口中哪一个被连接到主机上,即可以把它们4个的SCL线所在的IO引脚分别配接到4路PININT中断上。通信时,发生哪个中断,就认为连接到了哪个I2C接口上。而此时,IO引脚并没有作为GPIO使用,而是作为I2C的SCL信号接口。
此外,对于外部引脚中断,在MDK5的开发环境中有特定的入口函数形式,比如对于PINTSEL0的中断,函数形式如下所示。
void PIN_INT0_IRQHandler(void)
{
PINTSEL0中断服务程序部分
}
下面来看一个外部引脚中断的实际例子,电路原理图如下所示。
在上面的电路中,两个按键S1和S2分别接到了引脚PIO0_4和PIO0_1上,并且都为其配置了上拉阻。现在要求按下S1时,对LED1状态进行取反,按下S2时,对LED2状态进行取反,其中S1采用上升沿方式检测,S2采用下降沿方式检测。完整代码如下:
#include
//************************端口初始化***********************************
void Port_init(void)
{
LPC_GPIO_PORT->DIRSET0 = (1<<7) | (1<<13); //设置端口为输出方向
LPC_GPIO_PORT->SET0 = (1<<7) | (1<<13); //熄灭LED
}
//***************************主函数************************************
int main(void)
{
Port_init(); //调用端口初始化
LPC_SYSCON->PINTSEL0 = 0x1;//选择PIO0_1作为外部中断引脚
LPC_SYSCON->PINTSEL1 = 0x4;//选择PIO0_4作为外部中断引脚
// LPC_PIN_INT->ISEL &= ~0x3; //边沿触发
LPC_PIN_INT->IENR |= 0x1; //PINTSEL0上升沿使能
LPC_PIN_INT->IENF |= 0x2; //PINTSEL1下降沿使能
NVIC_EnableIRQ(PIN_INT0_IRQn);//使能PINTSEL0中断
NVIC_EnableIRQ(PIN_INT1_IRQn);//使能PINTSEL1中断
while(1)
{
;
}
}
//*********************PINTSEL0中断(S2)*****************************
void PIN_INT0_IRQHandler(void)
{
LPC_GPIO_PORT->NOT0 = 0x2000; //LED2取反
LPC_PIN_INT->RISE |= 0x1;
}
//*********************PINTSEL1中断(S1)*****************************
void PIN_INT1_IRQHandler(void)
{
LPC_GPIO_PORT->NOT0 = 0x80; //LED1取反
LPC_PIN_INT->FALL |= 0x2;
}
把程序编译后下载到LPC824中,初始状态LED都不亮,按下S1,LED1仍然不亮,松开S1,LED1亮,证明上升沿有效,再次按下S1,LED1仍然亮,松开S1,LED1熄灭,证明上升沿有效。 按下S2,LED2亮,松开S2,LED2仍然亮,证明下降沿有效,再次按下S2,LED2熄灭,松开S2,LED2仍然熄灭,证明下降沿有效。