原文地址:http://feng06.blog.163.com/blog/static/135015020133643345349/
外部中断作为处理器响应外部事件的通道,在控制系统中起着非常重要的作用。下面就来讨论一下LPC1114外部中断的使用情况。
LPC1114的每一个引脚都可以响应一个外部中断,所以有多少个引脚就有多少个外部中断。但LPC1114的中断系统非常强大,外部中断只是它其中的一小部分。因此,要用好外部中断,就必须先来了解LPC1114的整个中断系统。下面就来看一下它的中断系统。
在LPC11xx系列处理器中,有一个部分被称为“私有外设总线”(Private peripheral bus),它位于Memory map中地址为0xE0000000~0xE0100000的地方,包含有下表中的几个核心外设。
其中的Nested Vectored Interrupt Contorller(NVIC)就是中断系统,被称为“内嵌套向量中断控制器”。它与处理器内核紧密耦合,可实现低中断延迟及对新中断的有效处理。它具有以下特征:
拥有32路向量中断;每个中断的优先级均可编程设置为0~192(步长64),数值越小优先级越高,0级为最高优先级;支持电平和边沿触发中断;支持中断尾链;拥有一个外部不可屏蔽中断NMI。
NVIC所涉及到的寄存器如下表所示。
从表中可以看出,每个寄存器都是32位的结构,都具有可读可写的属性,复位值都为全0。其中ISER寄存器是设置中断的使能,32位对应32路中断,值为1使能中断,值为0不使能中断。ICER寄存器是设置中断的禁能,32位对应32路中断,值为1禁能中断,值为0不禁能。ISPR寄存器是设置中断的挂起,32位对应32路中断,值为1挂起,值为0不挂起。ICPR寄存器是清除中断的挂起,32位对应32路中断,值为1清除挂起,值为0不清除挂起。IPR0~7寄存器是设置中断优先级。
下面是NVIC寄存器组所对应的结构体形式(位于头文件core_cm0.h中)。
typedef struct
{
__IO uint32_t ISER[1]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[31];
__IO uint32_t ICER[1]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RSERVED1[31];
__IO uint32_t ISPR[1]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[31];
__IO uint32_t ICPR[1]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[31];
uint32_t RESERVED4[64];
__IO uint32_t IP[8]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register */
} NVIC_Type;
因NVIC寄存器组的基址为0xE000E100,所以要将基址指针强制转换为上述结构体,还必须要加上下面的定义。
#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100UL) /*!< NVIC Base Address */
#define NVIC ((NVIC_Type *) NVIC_BASE ) /*!< NVIC configuration struct */
接下来给出的是上面NVIC32位寄存器所对应的32路中断向量的中断源。
为了能描述上面的32路中断源,在C语言中运用了枚举类型,代码如下所示(位于头文件lpc11xx.h中)。
typedef enum IRQn
{
/****** Cortex-M0 Processor Exceptions Numbers ***************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
HardFault_IRQn = -13, /*!< 3 Cortex-M0 Hard Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M0 SV Call Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M0 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M0 System Tick Interrupt */
/****** LPC11xx/LPC11Cxx Specific Interrupt Numbers **********************************************/
WAKEUP0_IRQn = 0, /*!< All I/O pins can be used as wakeup source. */
WAKEUP1_IRQn = 1, /*!< There are 13 pins in total for LPC11xx */
WAKEUP2_IRQn = 2,
WAKEUP3_IRQn = 3,
WAKEUP4_IRQn = 4,
WAKEUP5_IRQn = 5,
WAKEUP6_IRQn = 6,
WAKEUP7_IRQn = 7,
WAKEUP8_IRQn = 8,
WAKEUP9_IRQn = 9,
WAKEUP10_IRQn = 10,
WAKEUP11_IRQn = 11,
WAKEUP12_IRQn = 12,
CAN_IRQn = 13, /*!< CAN Interrupt */
SSP1_IRQn = 14, /*!< SSP1 Interrupt */
I2C_IRQn = 15, /*!< I2C Interrupt */
TIMER_16_0_IRQn = 16, /*!< 16-bit Timer0 Interrupt */
TIMER_16_1_IRQn = 17, /*!< 16-bit Timer1 Interrupt */
TIMER_32_0_IRQn = 18, /*!< 32-bit Timer0 Interrupt */
TIMER_32_1_IRQn = 19, /*!< 32-bit Timer1 Interrupt */
SSP0_IRQn = 20, /*!< SSP0 Interrupt */
UART_IRQn = 21, /*!< UART Interrupt */
Reserved0_IRQn = 22, /*!< Reserved Interrupt */
Reserved1_IRQn = 23,
ADC_IRQn = 24, /*!< A/D Converter Interrupt */
WDT_IRQn = 25, /*!< Watchdog timer Interrupt */
BOD_IRQn = 26, /*!< Brown Out Detect(BOD) Interrupt */
FMC_IRQn = 27, /*!< Flash Memory Controller Interrupt */
EINT3_IRQn = 28, /*!< External Interrupt 3 Interrupt */
EINT2_IRQn = 29, /*!< External Interrupt 2 Interrupt */
EINT1_IRQn = 30, /*!< External Interrupt 1 Interrupt */
EINT0_IRQn = 31, /*!< External Interrupt 0 Interrupt */
} IRQn_Type;
从上述代码中可以看出,除了32路中断源外,还加入了优先级更高5个中断源。这里先不进行说明,在后面用到时再来讨论。定义好上述代码后,就可以来写中断所需要的函数了。下面就是依据CMSIS规范所定义的8个中断操作函数(位于头文件core_cm0.h中)。
1.允许某个中断或异常
static __INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
NVIC->ISER[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
2.禁止某个中断或异常
static __INLINE void NVIC_DisableIRQ(IRQn_Type IRQn)
{
NVIC->ICER[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
3.读取某个中断或异常的挂起状态
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
{
return((uint32_t) ((NVIC->ISPR[0] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0));
}
4.把某个中断或异常的挂起状态设为1
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)
{
NVIC->ISPR[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
5.把某个中断或异常的挂起状态清为0
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
NVIC->ICPR[0] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* Clear pending interrupt */
}
6.把某个中断或异常的可配置优先级设为1
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[_SHP_IDX(IRQn)] = (SCB->SHP[_SHP_IDX(IRQn)] & ~(0xFF << _BIT_SHIFT(IRQn))) |
(((priority << (8 - __NVIC_PRIO_BITS)) & 0xFF) << _BIT_SHIFT(IRQn)); }
else {
NVIC->IP[_IP_IDX(IRQn)] = (NVIC->IP[_IP_IDX(IRQn)] & ~(0xFF << _BIT_SHIFT(IRQn))) |
(((priority << (8 - __NVIC_PRIO_BITS)) & 0xFF) << _BIT_SHIFT(IRQn)); }
}
7.读取某个中断或异常的优先级
static __INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn)
{
if(IRQn < 0) {
return((uint32_t)((SCB->SHP[_SHP_IDX(IRQn)] >> _BIT_SHIFT(IRQn) ) >> (8 - __NVIC_PRIO_BITS))); } /* get priority for Cortex-M0 system interrupts */
else {
return((uint32_t)((NVIC->IP[ _IP_IDX(IRQn)] >> _BIT_SHIFT(IRQn) ) >> (8 - __NVIC_PRIO_BITS))); } /* get priority for device specific interrupts */
}
8.复位NVIC
static __INLINE void NVIC_SystemReset(void)
{
__DSB(); /* Ensure all outstanding memory accesses included buffered write are completed before reset */
SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk);
__DSB(); /* Ensure completion of memory access */
while(1); /* wait until reset */
}
在上述函数中有几点要说明一下,一是数组的引用其取值只能是0(即第一个元素),这是因为在结构体定义中只定义了一个数组元素,且由于需要利用数组的地址连续性来对映CPU物理地址,所以也不能将其定义为一个普通变量;二是关键字“__INLINE ”在头文件core_cm0.h中已做了宏定义“#define __INLINE __inline”,__inline是通知编译器其后面的函数为内联形式;三是中断源IRQn要与0x1F与一下,是为了屏蔽高27位的值,因为中断源的最大值只到31,所以只用了32位中的低5位(31的二进制是11111,十六进制是0x1F);四是在函数的参数中,由于引入了枚举类型,所以可以在调用函数的时候,在参数部分可直接使用枚举中的名称,这样就可以省去记忆32个中断源在32位寄存器中的对应位置,便于书写和阅读。例如,要开启端口0的外部中断,执行程序“NVIC_EnableIRQ(EINT0_IRQn)”即可。
上述就是LPC1114中的整个中断系统,即“内嵌套向量中断控制器”。可以看出,它控制着整个处理器32路中断源的使能与挂起等8个动作,功能非常强大。但做为外部中断的端口中断源却只有4个,即EINT0_IRQn、EINT1_IRQn、EINT2_IRQn、EINT3_IRQn四个。而每一个端口又对应有12个引脚(端口3为6个)又都可以产生外部中断,那怎么来判断是那个引脚上申请的中断呢?这就需要借助前面“通用输入/输出端口”部分介绍过的MIS寄存器了。在外部中断响应的服务程序内,判别MIS寄存器的各个位,值为1的位所对应的就是触发本次外部中断的引脚。
和所有的单片机一样,在中断响应后,程序指针会跳转到相应的中断向量入口处去执行中断服务程序,而在C语言中则是以特定形式的中断入口函数来呈现。比如EINT0_IRQn、EINT1_IRQn、EINT2_IRQn、EINT3_IRQn四个端口的外部中断入口函数分别如下:
void PIOINT0_IRQHandler(void)
{
端口0的中断服务程序部分
}
void PIOINT1_IRQHandler(void)
{
端口1的中断服务程序部分
}
void PIOINT2_IRQHandler(void)
{
端口2的中断服务程序部分
}
void PIOINT3_IRQHandler(void)
{
端口3的中断服务程序部分
}
上述函数的名称是不能改变的,它标志着特定的中断入口,除了四个外部中断以外的其它中断源,也有各自的中断入口函数,它们都位于起动文件“startup_LPC11xx.s”中,在以后用到时再讨论,这里就不给出了。
下面来讨论一个外部中断的例子,要求使用外部中断来实现按键控制LED的亮灭。程序代码如下(假设KEY接在GPIO1.9,LED接在GPIO1.0):
#include
//=================端口1的外部中断服务程序=====================
void PIOINT1_IRQHandler(void)
{
if((LPC_GPIO1->MIS&0x200)==0x200) //检测是否是GPIO1.9引脚上的中断
{
LPC_GPIO1->MASKED_ACCESS[1] = 0; //开启LED
while(LPC_GPIO1->MASKED_ACCESS[512] != 0x200); //等待GPIO1.9引脚按键释放
LPC_GPIO1->MASKED_ACCESS[1] = 1; //关闭LED
}
LPC_GPIO1->IC |= 0x200; //清除GPIO1.9引脚上的中断标志
}
//==========================主程序============================
int main(void)
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); //使能IOCON时钟
LPC_IOCON->R_PIO1_0 = 0XD1; //把芯片上的33脚设置为GPIO1.0功能
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); //禁能IOCON时钟
LPC_GPIO1->DIR &= ~(1<<9); //设置GPIO1.9为输入方向
LPC_GPIO1->DIR |= (1<<0); //设置GPIO1.0为输出方向
LPC_GPIO1->MASKED_ACCESS[1] = 1; //输出高电平,关闭LED
LPC_GPIO1->IS &= ~(1<<9); //选择中断为边沿触发
LPC_GPIO1->IEV &= ~(1<<9); //选择下降沿触发
LPC_GPIO1->IE |= (1<<9); //设置中断P1.9不被屏蔽
NVIC_EnableIRQ(EINT1_IRQn); //使能GPIO1中断
while(1)
{
;
}
}
把上述程序编译后下载到LPC1114中,给系统上电,可以看出在按下KEY后LED亮,放开KEY后LED灭,达到了使用外部中断控制的目的。
最后说明一点,如果需要打开或关闭中断“总中断”,可调用“__enable_irq();和__disable_irq();”来实现,它们是通过调用汇编语言来实现这一操作的,具体的原型在头文件“core_cmFunc.h”中,可自行查看,这里就不详述了。
原文地址:http://blog.chinaaet.com/detail/14716
/*****************************************************************************
* gpiotest.c: main C entry file for AutoHome-M0 Develop Board
* Copyright(C) 2010, 贞明电子
* All rights reserved.
* http://djbgreen.taobao.com
* History
* 2010.12.12 ver 1.00 绿无涯
******************************************************************************/
#include "LPC11xx.h" /* LPC11xx Peripheral Registers */
#include "gpio.h"
void Delay(uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
void PIOINT1_IRQHandler(void) //中断服务函数
{
uint32_t regVal;
regVal = GPIOIntStatus( PORT1, 4 ); //读取该IO组的中断状态
if ( regVal )
{
LPC_GPIO3->DATA^=(1<<4); //P3_4闪动1次 LED3
GPIOIntClear( PORT1, 4); //清除中断标志
}
return;
}
void PIOINT2_IRQHandler(void)
{
uint32_t regVal;
regVal= GPIOIntStatus( PORT2, 11);
if(regVal)
{
LPC_GPIO2->DATA^=(1<<8); //P2_8闪动1次 LED1
GPIOIntClear( PORT2, 11);
}
return;
}
void PIOINT3_IRQHandler(void)
{
uint32_t regVal;
regVal= GPIOIntStatus( PORT3, 5);
if(regVal)
{
LPC_GPIO2->DATA^=(1<<9); //P2_9闪动1次 LED2
GPIOIntClear( PORT3, 5);
}
return;
}
void UserIO_Init(void)
{
// LPC_IOCON
LPC_GPIO2->DIR=(1<<8)|(1<<9); //P2_8,P2_9 LED1,LED2
GPIOSetDir(PORT3,4,1); //P3_4 output LED3
GPIOSetDir(PORT2,11,0); //Set P2.11 input
GPIOSetDir(PORT1,4,0); //P1_4
GPIOSetDir(PORT3,5,0); //P2_5
//LPC_GPIO2->DIR=~((1<<11)|(1<<5));
//LPC_GPIO1->DIR=~(1<<4);
GPIOSetInterrupt(PORT2,11,0,0,1);//端口号, 位地址, sense, single/doube, 上升/下降沿
GPIOSetInterrupt(PORT3,5,0,0,1);
GPIOSetInterrupt(PORT1,4,0,0,1);
GPIOIntEnable(PORT2,11); //使能该端口中断P2_11
GPIOIntEnable(PORT3,5); //使能该端口中断P3_5 (LPC1343为P2_5)
GPIOIntEnable(PORT1,4); //使能该端口中断P1_4
}
int main(void)
{
SystemInit(); //系统初始化
GPIOInit(); //GPIO初始化,包括GPIO时钟初始化
UserIO_Init(); //用户使用的IO初始化(设置IO方向、中断、IO功能选择)
//有时所有配置正确,却得不到功能,就是没有进行IO功能选择
while(1)
{
}
}
/*********************************************************************************
** End Of File
*********************************************************************************/