LPC1114外部中斷
LPC1114的每一個引腳都可以響應(yīng)一個外部中斷,所以有多少個引腳就有多少個外部中斷。但LPC1114的中斷系統(tǒng)非常強大,外部中斷只是它其中的一小部分。因此,要用好外部中斷,就必須先來了解LPC1114的整個中斷系統(tǒng)。下面就來看一下它的中斷系統(tǒng)。
本文引用地址:http://m.butianyuan.cn/article/201611/316324.htm在LPC11xx系列處理器中,有一個部分被稱為“私有外設(shè)總線”(Private peripheral bus),它位于Memory map中地址為0xE0000000~0xE0100000的地方,包含有下表中的幾個核心外設(shè)。
其中的Nested Vectored Interrupt Contorller(NVIC)就是中斷系統(tǒng),被稱為“內(nèi)嵌套向量中斷控制器”。它與處理器內(nèi)核緊密耦合,可實現(xiàn)低中斷延遲及對新中斷的有效處理。它具有以下特征:
擁有32路向量中斷;每個中斷的優(yōu)先級均可編程設(shè)置為0~192(步長64),數(shù)值越小優(yōu)先級越高,0級為最高優(yōu)先級;支持電平和邊沿觸發(fā)中斷;支持中斷尾鏈;擁有一個外部不可屏蔽中斷NMI。
NVIC所涉及到的寄存器如下表所示。
從表中可以看出,每個寄存器都是32位的結(jié)構(gòu),都具有可讀可寫的屬性,復位值都為全0。其中ISER寄存器是設(shè)置中斷的使能,32位對應(yīng)32路中斷,值為1使能中斷,值為0不使能中斷。ICER寄存器是設(shè)置中斷的禁能,32位對應(yīng)32路中斷,值為1禁能中斷,值為0不禁能。ISPR寄存器是設(shè)置中斷的掛起,32位對應(yīng)32路中斷,值為1掛起,值為0不掛起。ICPR寄存器是清除中斷的掛起,32位對應(yīng)32路中斷,值為1清除掛起,值為0不清除掛起。IPR0~7寄存器是設(shè)置中斷優(yōu)先級。
下面是NVIC寄存器組所對應(yīng)的結(jié)構(gòu)體形式(位于頭文件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,所以要將基址指針強制轉(zhuǎn)換為上述結(jié)構(gòu)體,還必須要加上下面的定義。
#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位寄存器所對應(yīng)的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路中斷源外,還加入了優(yōu)先級更高5個中斷源。這里先不進行說明,在后面用到時再來討論。定義好上述代碼后,就可以來寫中斷所需要的函數(shù)了。下面就是依據(jù)CMSIS規(guī)范所定義的8個中斷操作函數(shù)(位于頭文件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.讀取某個中斷或異常的掛起狀態(tài)
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
{
return((uint32_t) ((NVIC->ISPR[0] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0));
}
4.把某個中斷或異常的掛起狀態(tài)設(shè)為1
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)
{
NVIC->ISPR[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
5.把某個中斷或異常的掛起狀態(tài)清為0
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
NVIC->ICPR[0] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* Clear pending interrupt */
}
6.把某個中斷或異常的可配置優(yōu)先級設(shè)為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.讀取某個中斷或異常的優(yōu)先級
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 */
}
在上述函數(shù)中有幾點要說明一下,一是數(shù)組的引用其取值只能是0(即第一個元素),這是因為在結(jié)構(gòu)體定義中只定義了一個數(shù)組元素,且由于需要利用數(shù)組的地址連續(xù)性來對映CPU物理地址,所以也不能將其定義為一個普通變量;二是關(guān)鍵字“__INLINE”在頭文件core_cm0.h中已做了宏定義“#define __INLINE __inline”,__inline是通知編譯器其后面的函數(shù)為內(nèi)聯(lián)形式;三是中斷源IRQn要與0x1F與一下,是為了屏蔽高27位的值,因為中斷源的最大值只到31,所以只用了32位中的低5位(31的二進制是11111,十六進制是0x1F);四是在函數(shù)的參數(shù)中,由于引入了枚舉類型,所以可以在調(diào)用函數(shù)的時候,在參數(shù)部分可直接使用枚舉中的名稱,這樣就可以省去記憶32個中斷源在32位寄存器中的對應(yīng)位置,便于書寫和閱讀。例如,要開啟端口0的外部中斷,執(zhí)行程序“NVIC_EnableIRQ(EINT0_IRQn)”即可。
上述就是LPC1114中的整個中斷系統(tǒng),即“內(nèi)嵌套向量中斷控制器”。可以看出,它控制著整個處理器32路中斷源的使能與掛起等8個動作,功能非常強大。但做為外部中斷的端口中斷源卻只有4個,即EINT0_IRQn、EINT1_IRQn、EINT2_IRQn、EINT3_IRQn四個。而每一個端口又對應(yīng)有12個引腳(端口3為6個)又都可以產(chǎn)生外部中斷,那怎么來判斷是那個引腳上申請的中斷呢?這就需要借助前面“通用輸入/輸出端口”部分介紹過的MIS寄存器了。在外部中斷響應(yīng)的服務(wù)程序內(nèi),判別MIS寄存器的各個位,值為1的位所對應(yīng)的就是觸發(fā)本次外部中斷的引腳。
和所有的單片機一樣,在中斷響應(yīng)后,程序指針會跳轉(zhuǎn)到相應(yīng)的中斷向量入口處去執(zhí)行中斷服務(wù)程序,而在C語言中則是以特定形式的中斷入口函數(shù)來呈現(xiàn)。比如EINT0_IRQn、EINT1_IRQn、EINT2_IRQn、EINT3_IRQn四個端口的外部中斷入口函數(shù)分別如下:
void PIOINT0_IRQHandler(void)
{
端口0的中斷服務(wù)程序部分
}
void PIOINT1_IRQHandler(void)
{
端口1的中斷服務(wù)程序部分
}
void PIOINT2_IRQHandler(void)
{
端口2的中斷服務(wù)程序部分
}
void PIOINT3_IRQHandler(void)
{
端口3的中斷服務(wù)程序部分
}
上述函數(shù)的名稱是不能改變的,它標志著特定的中斷入口,除了四個外部中斷以外的其它中斷源,也有各自的中斷入口函數(shù),它們都位于起動文件“startup_LPC11xx.s”中,在以后用到時再討論,這里就不給出了。
下面來討論一個外部中斷的例子,要求使用外部中斷來實現(xiàn)按鍵控制LED的亮滅。程序代碼如下(假設(shè)KEY接在GPIO1.9,LED接在GPIO1.0):
#include
//=================端口1的外部中斷服務(wù)程序=====================
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; //關(guān)閉LED
}
LPC_GPIO1->IC |= 0x200; //清除GPIO1.9引腳上的中斷標志
}
//==========================主程序============================
int main(void)
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); //使能IOCON時鐘
LPC_IOCON->R_PIO1_0 = 0XD1; //把芯片上的33腳設(shè)置為GPIO1.0功能
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); //禁能IOCON時鐘
LPC_GPIO1->DIR &= ~(1<<9); //設(shè)置GPIO1.9為輸入方向
LPC_GPIO1->DIR |= (1<<0); //設(shè)置GPIO1.0為輸出方向
LPC_GPIO1->MASKED_ACCESS[1] = 1; //輸出高電平,關(guān)閉LED
LPC_GPIO1->IS &= ~(1<<9); //選擇中斷為邊沿觸發(fā)
LPC_GPIO1->IEV &= ~(1<<9); //選擇下降沿觸發(fā)
LPC_GPIO1->IE |= (1<<9); //設(shè)置中斷P1.9不被屏蔽
NVIC_EnableIRQ(EINT1_IRQn); //使能GPIO1中斷
while(1)
{
;
}
}
把上述程序編譯后下載到LPC1114中,給系統(tǒng)上電,可以看出在按下KEY后LED亮,放開KEY后LED滅,達到了使用外部中斷控制的目的。
最后說明一點,如果需要打開或關(guān)閉中斷“總中斷”,可調(diào)用“__enable_irq();和__disable_irq();”來實現(xiàn),它們是通過調(diào)用匯編語言來實現(xiàn)這一操作的,具體的原型在頭文件“core_cmFunc.h”中,可自行查看,這里就不詳述了。
評論