Interrupts (2)
Interrupts (2)
This is a Basic Tutorial on Interrupts for LPC2148 MCUs and how to program them.
“An interrupt is a signal sent to the CPU which indicates that a system event has 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 current code and start
executing the ISR. After the ISR execution has finished the CPU gets back to where it had
stopped.
Interrupts in LPC2148 are handled by Vectored Interrupt Controller (VIC) and can be
classified into 2 types based on the priority levels.
2. Normal IRQs or IRQs which can be further classified as : Vectored IRQ and Non-
Vectored IRQ.
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 by the ISR address.
For the Vectored, the System internally maintains a table called IVT or Interrupt Vector
Table which contains the information about Interrupts sources and their corresponding ISR
address. In LPC214x this is facilitated by a register called ‘VICDefVectAddr‘. The user must
assign the address of the default ISR to this register for handling Non-Vectored IRQs.
Note : VIC , as per its design , can take 32 interrupt request inputs but only 16 requests can be
assigned to Vectored IRQ interrupts in its LCP2148 Implementation. We are given a set of 16
vectored IRQ slots to which we can assign any of the 22 requests that are available in LPC2148.
The slot numbering goes from 0 to 15 with slot no. 0 having highest priority and slot no. 15
having lowest priority.
VIC has plenty of registers. Most of the registers that are used to configure interrupts or read
status have each bit corresponding to a particular interrupt source and this mapping is same for
all of these registers.
Here is the complete table which says which bit corresponds to which interrupt source as given
in Datasheet:
Table 1:
Bit 22 21 20 19 18 17 16 15 14 13 12 11
#
Bit 10 9 8 7 6 5 4 3 2 1 0
#
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]
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 :
Table 2:
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 that if the vectored IRQ slot is disabled it will not disable the interrupt but will change the
corresponding interrupt to Non-Vectored IRQ. Enabling the slot here means that it can generate
the address of the ‘dedicated Interrupt handling function (ISR)’ and disabling it will generate the
address of the ‘common/default Interrupt handling function (ISR)’ which is for Non-Vectored
ISR. In simple words if the slot is enabled it points to ‘specific and dedicated interrupt handling
function’ and if its disable it will point to the ‘default function’. This will get more clear as we do
some examples.
Note : The Interrupt Source Number is also called as VIC Channel Mask.
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.
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 :
{ ...}
//====OR Equivalently====
void myISR (void) __irq
{ ...}
1. 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’.
2. 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.
VICVectCntlX = (1<<5) | Y ;
VICIntEnable |= (1<<Y) ;
Using above steps we can Assign TIMER0 Interrupt to Slot number say.. 0 as follows :
VICVectCntl0 = (1<<5) | 4 ; //5th bit must 1 to enable the slot (see the register definition above)
Note the Correspondence between the Bit number in Table 1 and the Source Number in Table 2.
The Bit Number now becomes the Source Number in decimal.
How to write the code for the ISR.
First thing to note here is that each device(or block) in lpc214x has only 1 IRQ associated with
it. But inside each device there may be different sources which can raise an interrupt (or an
IRQ). Like the TIMER0 block(or device) has 4 match + 4 capture registers and any one or
more of them can be configured to trigger an interrupt. Hence such devices have a dedicated
interrupt register which contains a flag bit for each of these source(For Timer block its
‘T0IR‘). So , when the ISR is called first we need to identify the actual source of the interrupt
using the Interrupt Register and then proceed accordingly. Also just before , when the main ISR
code is finished we also need to acknowledge or signal that the ISR has finished executing for
the current IRQ which triggered it. This is done by clearing the the flag(i.e the particular bit) in
the device’s interrupt register and then by writing a zero to VICVectAddr register which
signifies that interrupt has ISR has finished execution successfully.
Programming the Interrupt Service Routine (ISR) :
lets consider two simple cases for coding an ISR
Case #1) First when we have only one ‘internal’ source of interrupt in UART0 i.e an
U0IIR&0x02 match event which raises an IRQ.
Case #2) And when we have multiple ‘internal’ source of interrupt in UART0 i.e. say a
match event for [011] for Receive Line Status(RLS) , [010] for Receive Data
Available(RDA) , [001] for THRE Interrupt. 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 :
regVal = U0IIR; // read the current value in UART0's Interrupt Register and clear the
interrupt flag
}
Even in case #2 things are simple except we need to identify the ‘actual’ source of interrupt.
So , Now its time to go one step further and pop-out Case #3 and Case #4. Both of them deal
with IRQs from different blocks.
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.
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 :
regVal = U0IIR; // read the current value in U0's Interrupt Register which also clears it!
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)
if( T0IR )
Note than UART0’s Interrupt Register is a lot different than TIMER0’s. The first Bit in U0IIR
indicates whether any interrupt is pending or not and its Active LOW! The next 3 bits give the
Identification for any of the 4 Interrupts if enabled.
In case of FIQ
To 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.
License : GPL.
*/
#include <lpc214x.h>
void initClocks(void);
void initTimer0(void);
void setupPLL0(void);
void feedSeq(void);
void connectPLL0(void);
int main(void)
IO0PIN = 0x0;
void initTimer0(void)
{
/*Assuming that PLL0 has been setup with CCLK = 60Mhz and PCLK also = 60Mhz.*/
//----------Configure Timer0-------------
T0CTCR = 0x0;
T0MCR = MR0I | MR1I | MR2I | MR2R; //Set the Match control register
VICVectCntl4 = 0x20 | 4;
{
IO0PIN ^= (1<<0); // Toggle GPIO0 PIN0 .. P0.0
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!