Introduction to Interrupts
This is a Basic Tutorial on Interrupts for LPC2148 ARM 7 MCUs and how to program them for those who are new to interrupts. To start with , first lets see : what interrupts, IRQs and ISRs are. As per wiki : “An interrupt is a signal sent to the CPU which indicates that a system event has a occurred which needs immediate attention“. An ‘Interrupt ReQuest‘ i.e an ‘IRQ‘ can be thought of as a special request to the CPU to execute a function(small piece of code) when an interrupt occurs. This function or ‘small piece of code’ is technically called an ‘Interrupt Service Routine‘ or ‘ISR‘. So when an IRQ arrives to the CPU , it stops executing the code current code and start executing the ISR. After the ISR execution has finished the CPU gets back to where it had stopped.
Interrupts in LPC214x are handled by Vectored Interrupt Controller (VIC) (which is specific to ARM based microcontrollers and CPUs) and are classified into 3 types based on the priority levels.(Though Interrupts can classified in many different ways as well)
- Fast Interrupt Request i.e FIQ : which has highest priority
- Vectored Interrupt Request i.e Vectored IRQ : which has ‘middle’ or priority between FIQ and Non-Vectored IRQ.
- Non-Vectored IRQ : which has the lowest priority.
But I’d like to Classify them as 2 types :
- Fast IRQs or FIQs
- Normal IRQs or IRQs which can be further classified as : Vectored IRQ and Non-Vectored IRQ.
Okay .. so what does Vectored mean ?
In computing the term ‘Vectored‘ means that the CPU is aware of the address of the ISR when the interrupt occurs and Non-Vectored means that CPU doesn’t know the address of the ISR nor the source of the IRQ when the interrupt occurs and it needs to be supplied with the ISR address. For the Vectored Stuff , the System internally maintains a table called IVT or Interrupt Vector Table which contains the information about Interrupts sources and their corresponding ISR address.
If you are still confused than here it is in a nutshell : The difference between Vectored IRQ(VIRQ) and Non-Vectored IRQ(NVIRQ) is that VIRQ has dedicated IRQ service routine for each interrupt source which while NVIRQ has the same IRQ service routine for all Non-Vectored Interrupts. You will get a clear picture when I cover some examples in examples section.
Example: For example if you working with 2 interrupt sources say.. UART0 and TIMER0. Now if you want to give TIMER0 a higher priority than UART0 .. then assign TIMER0 interrupt a lower number slot than UART0 Like .. hook up TIMER0 to slot 0 and UART0 to slot 1 or TIMER0 to slot 4 and UART to slot 9 or whatever slots you like. The number of the slot doesn’t matter as long TIMER0 slot is lower than UART0 slot.
Here is the complete table which says which bit corresponds to which interrupt source as given in Datasheet:
Bit# | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 |
IRQ | USB | AD1 | BOD | I2C1 | AD0 | EINT3 | EINT2 | EINT1 | EINT0 | RTC | PLL | SPI1/SSP |
Bit# | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IRQ | SPI0 | I2C0 | PWM | UART1 | UART0 | TIMR1 | TIMR0 | ARMC1 | ARMC0 | N/A | WDT |
Note : TIMR0 = TIMER0 , TIMR1 = TIMER1 , ARMC1 = ARMCore1 , ARMC2 = ARMCore2.
LPC2148 ARM 7 Interrupt Related Registers
Now we will have a look at some of the important Registers that are used to implement interrupts in lpc214x:
1) VICIntSelect (R/W) :
This register is used to select an interrupt as IRQ or as FIQ. Writing a 0 at a given bit location(as given in Table 1) will make the corresponding interrupt as IRQ and writing a 1 will make it FIQ. For e.g if you make Bit 4 as 0 then the corresponding interrupt source i.e TIMER0 will be IRQ else if you make Bit 4 as 1 it will be FIQ instead. Note than by default all interrupts are selected as IRQ. Note that here IRQ applies for both Vectored and Non-Vectored IRQs. [Refer Table 1]
2) VICIntEnable (R/W) :
This is used to enable interrupts. Writing a 1 at a given bit location will make the corresponding interrupt Enabled. If this register is read then 1’s will indicated enabled interrupts and 0’s as disabled interrupts. Writing 0’s has no effect. [Refer Table 1]
3) VICIntEnClr (R/W) :
This register is used to disable interrupts. This is similar to VICIntEnable expect writing a 1 here will disabled the corresponding Interrupt. This has an effect on VICIntEnable since writing at bit given location will clear the corresponding bit in the VICIntEnable Register. Writing 0’s has no effect. [Refer Table 1]
4) VICIRQStatus (R) :
This register is used for reading the current status of the enabled IRQ interrupts. If a bit location is read as 1 then it means that the corresponding interrupt is enabled and active. Reading a 0 is unless here lol.. [Refer Table 1]
5) VICFIQStatus (R) :
Same as VICIRQStatus except it applies for FIQ. [Refer Table 1]
6) VICSoftInt :
This register is used to generate interrupts using software i.e manually generating interrupts using code i.e the program itself. If you write a 1 at any bit location then the correspoding interrupt is triggered i.e. it forces the interrupt to occur. Writing 0 here has no effect. [Refer Table 1]
7) VICSoftIntClear :
This register is used to clear the interrupt request that was triggered(forced) using VICSoftInt. Writing a 1 will release(or clear) the forcing of the corresponding interrupt. [Refer Table 1]
8) VICVectCntl0 to VICVectCntl15 (16 registers in all) :
These are the Vector Control registers. These are used to assign a particular interrupt source to a particular slot. As mentioned before slot 0 i.e VICVectCntl0 has highest priority and VICVectCntl15 has the lowest. Each of this registers can be divided into 3 parts : {Bit0 to bit4} , {Bit 5} , {and rest of the bits}.
The first 5 bits i.e Bit 0 to Bit 4 contain the number of the interrupt request which is assigned to this slot. The interrupt source numbers are given in the table below :
Interrupt Source | Source number In Decimal |
Interrupt Source | Source number In Decimal |
WDT | 0 | PLL | 12 |
N/A | 1 | RTC | 13 |
ARMCore0 | 2 | EINT0 | 14 |
ARMCore1 | 3 | EINT1 | 15 |
TIMER0 | 4 | EINT2 | 16 |
TIMER1 | 5 | EINT3 | 17 |
UART0 | 6 | ADC0 | 18 |
UART1 | 7 | I2C1 | 19 |
PWM | 8 | BOD | 20 |
I2C0 | 9 | ADC1 | 21 |
SPI0 | 10 | USB | 22 |
SPI1 | 11 |
The 5th bit is used to enable the vectored IRQ slot by writing a 1.
Note : The Interrupt Source Number is also called as VIC Channel Mask. For this tutorial I’ll be calling it as “Interrupt Source Number” to keep things simple and clear. (^_^)
The rest of the bits are reserved.
9) VICVectAddr0 to VICVectAddr15 (16 registers in all) :
For Vectored IRQs these register store the address of the function that must be called when an interrupt occurs. Note – If you assign slot 3 for TIMER0 IRQ then care must be taken that you assign the address of the interrupt function to corresponding address register .. i.e VICVectAddr3 in this example.
10) VICVectAddr :
This must not be confused with the above set of 16 VICVecAddrX registers. When an interrupt is Triggered this register holds the address of the associated ISR i.e the one which is currently active. Writing a value i.e dummy write to this register indicates to the VIC that current Interrupt has finished execution. In this tutorial the only place we’ll use this register .. is at the end of the ISR to signal end of ISR execution.
11) VICDefVectAddr :
This register stores the address of the “default/common” ISR that must be called when a Non-Vectored IRQ occurs.
Now , that we’ve seen all the Registers , lets consider another Example with a Simple Diagram : Here we have 4 IRQs Configured. TIMER0 and SPI0 are configured as Vectored IRQs with TIMER0 having Highest Priority. UART0 and PWM IRQs are setup as Non-Vectored IRQs. The whole setup is as given below:
Configuring and Programming Interrupts (ISR)
To explain How to configure Interrupts and define ISRs I’ll use Timers as an example , since this Timers are easy to play. Hence , I Assume you are comfortable with using timers with LPC214x ;).
First lets see how to define the ISR so that compiler handles it carefully. For this we need to explicitly tell the compiler that the function is not a normal function but an ISR. For this we’ll use a special keyword called “__irq” which is a function qualifier. If you use this keyword with the function definition then compiler will automatically treat it as an ISR. Here is an example on how to define an ISR in Keil :
__irq void myISR (void)
{
...
}
//====OR Equivalently====
void myISR (void) __irq
{
...
}
Note that both are perfectly valid ways of defining the ISR. I prefer to use __irq before the function name.
Now lets see how to actually setup the interrupt for ARM based microcontrollers like lpc2148. Consider we wanna assign TIMER0 IRQ and ISR to slot X. Here is a simple procedure like thing to do that :
A Simple 3 Step Process to Setup / Enable a Vectored IRQ
- First we need to enable the TIMER0 IRQ itself! Hence , from Table 1 we get the bit number to Enable TIMER0 Interrupt which is Bit number 4. Hence we must make bit 4 in VICIntEnable to ‘1’.
- Next , from Table 2 we get the interrupt source number for TIMER0 which is decimal 4 and OR it with (1<<5) [i.e 5th bit=1 which enables the slot] and assign it to VICVectCntlX.
- Next assign the address of the related ISR to VICVectAddrX.
Here is a simple Template to do it:
Replace X by the slot number you want .., then Replace Y by the Interrupt Source Number as given in Table 2 and finally replace myISR with your own ISR’s function Name .. and you’r Done!
VICIntEnable |= (1<<Y) ;
VICVectCntlX = (1<<5) | Y ;
VICVectAddrX = (unsigned) myISR;
Using above steps we can Assign TIMER0 Interrupt to Slot number say.. 0 as follows :
VICIntEnable |= (1<<4) ; // Enable TIMER0 IRQ
VICVectCntl0 = (1<<5) | 4 ; //5th bit must 1 to enable the slot (see the register definition above)
VICVectAddr0 = (unsigned) myISR;
//Vectored-IRQ for TIMER0 has been configured
Having done that now its time to write the code for the ISR.
Programming the Interrupt Service Routine (ISR) :
Okay to keep things keep simple lets consider two simple cases for coding an ISR (I’ll use TIMER0 for generating IRQs since its easy to understand and play with) :
- Case #1) First when we have only one ‘internal’ source of interrupt in TIMER0 i.e an MR0 match event which raises an IRQ.
- Case #2) And when we have multiple ‘internal’ source of interrupt in TIMER0 i.e. say a match event for MR0 , MR1 & MR2 which raise an IRQ.
In case #1 things are pretty straight forward. Since we know only one source is triggering an interrupt we don’t need to identify it – though its a good practice to explicitly identify it. The ISR then will be something like :
__irq void myISR(void)
{
long int regVal;
regVal = T0IR; // read the current value in T0's Interrupt Register
//... MR0 match event has occured .. do something here
T0IR = regval; // write back to clear the interrupt flag
VICVectAddr = 0x0; // The ISR has finished!
}
Even in case #2 things are simple except we need to identify the ‘actual’ source of interrupt.
#define MR0I_FLAG (1<<0)
#define MR1I_FLAG (1<<1)
#define MR2I_FLAG (1<<2)
__irq void myISR(void)
{
long int regVal;
regVal = T0IR; // read the current value in T0's Interrupt Register
if( T0IR & MR0I_FLAG )
{
//do something for MR0 match
}
else if ( T0IR & MR1I_FLAG )
{
//do something for MR1 match
}
else if ( T0IR & MR2I_FLAG )
{
//do something for MR2 match
}
T0IR = regVal; // write back to clear the interrupt flag
VICVectAddr = 0x0; // Acknowledge that ISR has finished execution
}
Soo , Now its time to go one step further and pop-out Case #3 and Case #4 out of the blue :P. Both of them deal with IRQs from different blocks viz. TIMER0 and UART0.
- Case #3) When we have Multiple Vectored IRQs from different Devices. Hence Priority comes into picture here.
- Case #4) Lastly when we have Multiple Non-Vectored IRQs from different Devices.
Don’t worry if your not acquianted with with UARTs , I’ll be Posting a Tutorial on UART with LPC214x shortly – Its in the pipeline :).
For Case #3 , Consider we have TIMER0 and UART0 generating interrupts with TIMER0 having higher priority. So in this case we’ll need to write 2 different Vectored ISRs – one for TIMER0 and one for UART0. To keep things simple lets assume that we have only 1 internal source inside both TIMER0 and UART0 which generates an interrupt. The ISRs will be something as given below :
__irq void myTimer0_ISR(void)
{
//Same as in case #1
}
__irq void myUart0_ISR(void)
{
long int regVal;
regVal = U0IIR; // read the current value in U0's Interrupt Register
// which also clears it!
//Something inside UART0 has raised an IRQ
VICVectAddr = 0x0; // The ISR has finished!
}
For Case #4 too we have TIMER0 and UART0 generating interrupts. But here both of them are Non-Vectored and hence will be serviced by a common Non-Vectored ISR. Hence, here we will need to check the actual source i.e device which triggered the interrupt and proceed accordingly. This is quite similar to Case #2. The default ISR in this case will be something like :
__irq void myDefault_ISR(void)
{
long int T0RegVal , U0RegVal;
T0RegVal = T0IR; // read the current value in T0's Interrupt Register
U0RegVal = U0IIR; // read the current value in U0's(Uart 0) Interrupt Identification Register
if( T0IR )
{
//do something for TIMER0 Interrupt
T0IR = T0RegVal; // write back to clear the interrupt flag for T0
}
if( ! (U0RegVal & 0x1) )
{
//do something for UART0 Interrupt
//No need to write back to U0IIR since reading it clears it
}
VICVectAddr = 0x0; // The ISR has finished!
}
But Wait! What about FIQ ?
Well , you can think FIQ as a promoted version of a Vectored IRQ. To promote or covert a Vectored IRQ to FIQ just make the bit for corresponding IRQ in VICIntSelect register to 1 and it will be become an FIQ. [Refer Table 1] Also Note that its recommended that you only have one FIQ in your system. FIQs have low latency than VIRQs and usually used in System Critical Interrupt Handling.
In Case Interrupts are Not working in Keil UV4 / UV5 / Crossworks for ARM MCUs :
For those of you who are using Keil UV Version 4 or 5 (or higher) you’ll need to edit Target Option settings and those who are using Crossworks for ARM etc .. you need to manually enable global interrupt. I’ve posted a solution for this @ https://www.ocfreaks.com/lpc2148-keil-uvision-arm7-interrupt-problem-issue-fix/. For uVision 5 you can have a look here @ https://www.ocfreaks.com/create-keil-uvision5-project-lpc2148-arm7-mcu/ Please go through that post to make Interrupts work.
Example Source for all Cases:
Example 1 – for Case #1:
For 1st example I’ll cover an interrupt driven ‘blinky’ using timers which I’ve also use in the LPC214x Timer Tutorial which can be found here : https://www.ocfreaks.com/lpc2148-timer-tutorial/ You can get the Complete Source and KeilUV4 Project files from the given link itself. Here is a snippet of the code for Case #1:
/*
(C) Umang Gajera | Power_user_EX - www.ocfreaks.com 2011-13.
More Embedded tutorials @ www.ocfreaks.com/cat/embedded
Soruce for Interrupt Tutorial Case #1.
License : GPL.
*/
#include <lpc214x.h>
#define MR0I (1<<0) //Interrupt When TC matches MR0
#define MR0R (1<<1) //Reset TC when TC matches MR0
#define DELAY_MS 500 //0.5 Second(s) Delay
#define PRESCALE 60000 //60000 PCLK clock cycles to increment TC by 1
void initClocks(void);
void initTimer0(void);
__irq void T0ISR(void);
void initClocks(void);
int main(void)
{
initClocks(); //Initialize CPU and Peripheral Clocks @ 60Mhz
initTimer0(); //Initialize Timer0
IO0DIR = 0xFFFFFFFF; //Configure all pins on Port 0 as Output
IO0PIN = 0x0;
T0TCR = 0x01; //Enable timer
while(1); //Infinite Idle Loop
//return 0; //normally this wont execute ever :P
}
void initTimer0(void)
{
/*Assuming that PLL0 has been setup with CCLK = 60Mhz and PCLK also = 60Mhz.*/
//----------Configure Timer0-------------
T0CTCR = 0x0;
T0PR = PRESCALE-1; //(Value in Decimal!) - Increment T0TC at every 60000 clock cycles
//Count begins from zero hence subtracting 1
//60000 clock cycles @60Mhz = 1 mS
T0MR0 = DELAY_MS-1; //(Value in Decimal!) Zero Indexed Count - hence subtracting 1
T0MCR = MR0I | MR0R; //Set bit0 & bit1 to High which is to : Interrupt & Reset TC on MR0
//----------Setup Timer0 Interrupt-------------
VICVectAddr4 = (unsigned )T0ISR; //Pointer Interrupt Function (ISR)
VICVectCntl4 = 0x20 | 4; //0x20 (i.e bit5 = 1) -> to enable Vectored IRQ slot
//0x4 (bit[4:0]) -> this the source number - here its timer0 which has VIC channel mask # as 4
//You can get the VIC Channel number from Lpc214x manual R2 - pg 58 / sec 5.5
VICIntEnable = 0x10; //Enable timer0 int
T0TCR = 0x02; //Reset Timer
}
__irq void T0ISR(void)
{
long int regVal;
regVal = T0IR; //Read current IR value
IO0PIN = ~IO0PIN; //Toggle all pins in Port 0
T0IR = regVal; //Write back to IR to clear Interrupt Flag
VICVectAddr = 0x0; //This is to signal end of interrupt execution
}
void initClocks(void)
{
// This function is used to config PPL0 and setup both
// CPU and Peripheral clock @ 60Mhz
// You can find its definition in the attached files or case #2 source
}
Example 2 – for Case #2:
This is an extended version of blinky which uses 3 Match Registers i.e 3 Interrupts sources within TIMER0 itself. Each Match register Interrupt is used to Turn on and off an LED which is connected to a particular GPIO Pin. Here we have 3 LEDs connected to PIN0 , PIN1 and PIN2 of PORT0 of LPC2148. MR0 is used for PIN0 i.e first LED , similarly MR2 and MR3 for PIN1 and PIN2 i.e for second and thrid LED respectively. MR0 has been configured to Trigger an Interrupt when 500ms have elapsed after TC is reset. MR1 has been configured to Trigger an Interrupt when 1000ms have elapsed after TC is reset. MR2 has been configured to Trigger an Interrupt when 1500ms have elapsed at it Resets the TC so the cycle starts again. Given a period of 1.5 seconds i.e 1500ms here is what happens :
2) MR1 Toggles PIN1 of PORT0 i.e P0.1 at 1000ms
3) MR2 Toggles PIN2 of PORT0 i.e P0.2 at 1500ms and also Resets the TC
*) This cycle of Toggling each of the LEDs one by one keeps on repeating…
/*
(C) Umang Gajera | Power_user_EX - www.ocfreaks.com 2011-13.
More Embedded tutorials @ www.ocfreaks.com/cat/embedded
LPC2148 Interrupt Example.
License : GPL.
*/
#include <lpc214x.h>
#define PLOCK 0x00000400
#define MR0I (1<<0) //Interrupt When TC matches MR0
#define MR1I (1<<3) //Interrupt When TC matches MR1
#define MR2I (1<<6) //Interrupt When TC matches MR2
#define MR2R (1<<7) //Reset TC when TC matches MR2
#define MR0I_FLAG (1<<0) //Interrupt Flag for MR0
#define MR1I_FLAG (1<<1) //Interrupt Flag for MR1
#define MR2I_FLAG (1<<2) //Interrupt Flag for MR2
#define MR0_DELAY_MS 500 //0.5 Second(s) Delay
#define MR1_DELAY_MS 1000 //1 Second Delay
#define MR2_DELAY_MS 1500 //1.5 Second(s) Delay
#define PRESCALE 60000 //60000 PCLK clock cycles to increment TC by 1
void delayMS(unsigned int milliseconds);
void initClocks(void);
void initTimer0(void);
__irq void myTimer0_ISR(void);
void setupPLL0(void);
void feedSeq(void);
void connectPLL0(void);
int main(void)
{
initClocks(); //Initialize CPU and Peripheral Clocks @ 60Mhz
initTimer0(); //Initialize Timer0
IO0DIR = 0xFFFFFFFF; //Configure all pins on Port 0 as Output
IO0PIN = 0x0;
T0TCR = 0x01; //Enable timer
while(1); //Infinite Idle Loop
//return 0; //normally this wont execute ever :P
}
void initTimer0(void)
{
/*Assuming that PLL0 has been setup with CCLK = 60Mhz and PCLK also = 60Mhz.*/
//----------Configure Timer0-------------
T0CTCR = 0x0;
T0PR = PRESCALE-1; //60000 clock cycles @60Mhz = 1 mS
T0MR0 = MR0_DELAY_MS-1; // 0.5sec (Value in Decimal!) Zero Indexed Count - hence subtracting 1
T0MR1 = MR1_DELAY_MS-1; // 1sec
T0MR2 = MR2_DELAY_MS-1; // 1.5secs
T0MCR = MR0I | MR1I | MR2I | MR2R; //Set the Match control register
//----------Setup Timer0 Interrupt-------------
//I've just randomly picked-up slot 4
VICVectAddr4 = (unsigned)myTimer0_ISR; //Pointer Interrupt Function (ISR)
VICVectCntl4 = 0x20 | 4;
VICIntEnable = 0x10; //Enable timer0 int
T0TCR = 0x02; //Reset Timer
}
__irq void myTimer0_ISR(void)
{
long int regVal;
regVal = T0IR; // read the current value in T0's Interrupt Register
if( T0IR & MR0I_FLAG )
{
//do something for MR0 match
IO0PIN ^= (1<<0); // Toggle GPIO0 PIN0 .. P0.0
}
else if ( T0IR & MR1I_FLAG )
{
//do something for MR1 match
IO0PIN ^= (1<<1);// Toggle GPIO0 PIN1 .. P0.1
}
else if ( T0IR & MR2I_FLAG )
{
//do something for MR0 match
IO0PIN ^= (1<<2);// Toggle GPIO0 PIN2 .. P0.2
}
T0IR = regVal; // write back to clear the interrupt flag
VICVectAddr = 0x0; // Acknowledge that ISR has finished execution
}
void initClocks(void)
{
setupPLL0();
feedSeq(); //sequence for locking PLL to desired freq.
connectPLL0();
feedSeq(); //sequence for connecting the PLL as system clock
//SysClock is now ticking @ 60Mhz!
VPBDIV = 0x01; // PCLK is same as CCLK i.e 60Mhz
//PLL0 Now configured!
}
//---------PLL Related Functions :---------------
//Using PLL settings as shown in : https://www.ocfreaks.com/lpc214x-pll-tutorial-for-cpu-and-peripheral-clock/
void setupPLL0(void)
{
//Note : Assuming 12Mhz Xtal is connected to LPC2148.
PLL0CON = 0x01;
PLL0CFG = 0x24;
}
void feedSeq(void)
{
PLL0FEED = 0xAA;
PLL0FEED = 0x55;
}
void connectPLL0(void)
{
while( !( PLL0STAT & PLOCK ));
PLL0CON = 0x03;
}
Note: TC is reset only when MR2 matches TC. Though this can be used using simple delay but the example presented here does this by purely using Interrupts!
Example 3 – for Case #3:
Consider we have TIMER0 generating interrupt every 500ms and UART0 generates an interrupt when some data arrives i.e when you press a key in the terminal. TIMER0 ISR will Toggle P0.2 and UART0 ISR will Toggle P0.3. Here TIMER0 ISR will have a higher priority over UART0 ISR. Hence when TIMER0 interrupt occurs and at the same time UART0 interrupt occurs , first TIMER0 ISR will be serviced and then UART0.
Here I’m only giving the code to Enable both the Interrupts and its ISR. You can find the complete code in files attached below.
Code for Configuring Both TIMER0 and UART0 Interrupts is as given Below:
//----------Setup TIMER0 Interrupt-------------
//Using Slot 0 for TIMER0
VICVectAddr0 = (unsigned)myTimer0_ISR; //Pointer Interrupt Function (ISR)
VICVectCntl0 = 0x20 | 4;
VICIntEnable |= (1<<4); //Enable timer0 int , 4th bit=1
//----------Setup UART0 Interrupt-------------
//Any Slot with Lower Priority than TIMER0's slot will suffice
VICVectAddr1 = (unsigned)myUart0_ISR; //Pointer Interrupt Function (ISR)
VICVectCntl1 = 0x20 | 6;
VICIntEnable |= (1<<6); //Enable Uart0 interrupt , 6th bit=1
Both the ISRs will be as follows :
__irq void myTimer0_ISR(void)
{
long int regVal;
regVal = T0IR; // read the current value in T0's Interrupt Register
IO0PIN ^= (1<<2); // Toggle 3rd Pin in GPIO0 .. P0.2
T0IR = regVal; // write back to clear the interrupt flag
VICVectAddr = 0x0; // Acknowledge that ISR has finished execution
}
__irq void myUart0_ISR(void)
{
long int regVal;
regVal = U0IIR; // Reading U0IIR also clears it!
//Recieve Data Available Interrupt has occured
regVal = U0RBR; // dummy read
IO0PIN ^= (1<<3); // Toggle 4th Pin in GPIO0 .. P0.3
VICVectAddr = 0x0; // Acknowledge that ISR has finished execution
}
Example 4 – for Case #4:
Even Here I’ll give the main code and as with above you can find the complete code in the attached files.
In the Non-Vectored Case the configuration code will be as shown below:
VICDefVectAddr = (unsigned)myDefault_ISR; //Pointer to Default ISR
//----------Enable (Non-Vectored) TIMER0 Interrupt-------------
VICIntEnable |= (1<<4); //Enable timer0 int , 4th bit=1
//----------Enable (Non-Vectored) UART0 Interrupt-------------
VICIntEnable |= (1<<6); //Enable Uart0 interrupt , 6th bit=1
And the code for the Non-Vectored ISR is as given Below:
__irq void myDefault_ISR(void)
{
long int T0RegVal , U0RegVal;
T0RegVal = T0IR; // read the current value in T0's Interrupt Register
U0RegVal = U0IIR;
if( T0IR )
{
IO0PIN ^= (1<<2); // Toggle 3rd Pin in GPIO0 .. P0.2
T0IR = T0RegVal; // write back to clear the interrupt flag
}
if( !(U0RegVal & 0x1) )
{
//Recieve Data Available Interrupt has occured
U0RegVal = U0RBR; // dummy read
IO0PIN ^= (1<<3); // Toggle 4th Pin in GPIO0 .. P0.3
}
VICVectAddr = 0x0; // Acknowledge that ISR has finished execution
}
Advanced Interrupt Handling:
Nested Interrupts:
Interrupts too can be nested. But we need to take some special care when dealing with nested interrupts since in this case the “context” and the “stack” come into picture. When a nested interrupt has finished execution we must get back to the correct context. Similarly too much of nesting and the stack will get overloaded. Covering Nested interrupt is not within the scope of this tutorial. Here are some useful documents which will surely help you out with interrupt nesting.
For more on Nested Interrupts you can refer the following documents :
Spurious Interrupts:
I like to call “Spurious Interrupt” as “Zombie Interrupts” 😀 – in my opinion that suits it more. These can happen in virtually any interrupt driven system and must be identified and handled properly else undefined system behaviour may occur. Such interrupts happen when an IRQ is generated out of no where (Magic! lol..). Here the IRQ comes into exsistence even when none of the conditions for triggering that IRQ have been met. Normally speaking its an interrupt that was not required in the first place. There are many reasons as to why it can happen :
- 1) Inherent bug/flaw in system design
- 2) Electrical Interference / Noise
- 3) Software Bug
- 4) Signal glitches on Level Sensitive interrupts
- 5) Faulty components
- 6) Misconfigured Hardware
- 7) etc… (You’ll get plently of info just by googling the term)
In respect to LPC214x(also LPC2000) MCUs , the probability of occurrence of Spurious Interrupts is high when Watch Dog Timer or UART is used. The datasheet(manual) has special note on Spurious interrupts on page 61. Covering the “handling of Spurious Interrupts” is too not in the scope of this article.
For more information on handling of spurious interrupts I would like my readers to go through the following documents :
- Page 61 of LPC214x Usermanul Rev 2.0
- NXP Application Note : AN10414
Understanding VIC in Detail:
Finally , I’d like to share one of the best guide I’ve found for Understanding the general Architecture of VIC in detail .. which is the following document from ARM itself :