Lecture05
Lecture05
5 Interrupts
Contents
2016 5 - Interrupts
5.2
Introduction
You are studying at your desk at home. The phone rings (an interrupt). You
An interrupt is a
request by another stop studying and answer the phone (you accept the interrupt). It is your friend,
module for access
to CPU processing who wants to know the URL for a particular Freescale datasheet relating to the
time
K70 so she can look up some information required to complete a laboratory
assignment. You give her the URL (you process the interrupt request
immediately). You then hang up and go back to studying. Note that the
additional time it will take you to complete your study is miniscule, yet the
amount of time for your friend to complete her task may be significantly
reduced (she didn’t have to wait until you were free). This simple example
clearly illustrates how interrupts can drastically improve response time in a
real-time system.
5.1 Exceptions
Exceptions are events that cause changes to program flow. When one happens,
the processor suspends the current executing task and executes a part of the
program called an exception handler. After the execution of the exception
handler is completed, the processor then resumes normal program execution. In
the ARM® architecture, interrupts are one type of exception.
5 - Interrupts 2016
5.3
5.2 Interrupts
An interrupt is an event triggered inside the microcontroller, usually by
internal or external hardware, and in some cases by software. The exception
handler for an interrupt is referred to as an interrupt service routine (ISR). On
completion of the ISR, software execution returns to the next instruction that
would have occurred without the interrupt.
An interrupt causes
the main thread to
be suspended, and
Hardware Busy Ready Busy the interrupt thread
is run
Hardware Hardware
needs carrying
service out task
Main Main Main
Thread
Saves Restores
execution execution
state state
Interrupt ISR
Thread
ISR
provides
service
Figure 5.1
2016 5 - Interrupts
5.4
5.2.1 Using Interrupts
Each potential interrupt source has a separate arm bit, e.g. RIE (the UART
receive interrupt enable bit). The software must set the arm bits for those
devices from which it wishes to accept interrupts, and deactivate the arm bits
within those devices from which interrupts are not to be allowed. After reset,
all the interrupt arm bits are set to deactivate the corresponding interrupt.
Each potential interrupt source has a separate flag bit, e.g. RDRF (the UART
receive data register full flag). The hardware sets the flag when it wishes to
request an interrupt. The software must clear the flag in the ISR to signify it
has handled the interrupt request, and to allow the device to again trigger an
interrupt.
There are a number of special registers in the MCU that contain the processor
status and define the operation states and interrupt/exception masking. Special
registers are not memory mapped, which means special assembly language
instructions are required to access them.
5 - Interrupts 2016
5.5
The following figure shows the hardware arrangement for interrupt generation.
interrupt
sources
RIE 31:1 0
PRIMASK I
RDRF
TIE
TDRE
UART INT interrupt
TIE LPTMR pending
NVIC
address vector
TIF
address
32
Figure 5.2
3. The main program is resumed when the ISR executes the EXC_RETURN
instruction:
Hardware pulls all the registers from the stack, including the PC, so
that the program continues from the point where it was interrupted.
2016 5 - Interrupts
5.6
Some interrupts share the same interrupt vector. For example, the reception and
transmission of a byte via the UART leads to just one interrupt, and there is
one vector associated with it. In Figure 5.2, the two interrupt sources are ORed
together to create one interrupt request. In such cases, the ISR is responsible
for polling the status flags to see which event actually triggered the interrupt.
Care must be taken because both flags may be set, and only the hardware
events that are enabled must be serviced by the software.
For example, the UART shares an interrupt for transmit and receive operations.
Therefore, in the ISR, we would need code to respond to either of those events,
but only if the corresponding interrupt enable bit is enabled:
...
// Receive a character
if (UART2_C2 & UART_C2_RIE_MASK)
{
// Clear RDRF flag by reading the status register
if (UART2_S1 & UART_S1_RDRF_MASK)
// Do something with the received byte
...
}
// Transmit a character
if (UART2_C2 & UART_C2_TIE_MASK)
{
// Clear TDRE flag by reading the status register
if (UART2_S1 & UART_S1_TDRE_MASK)
{
// Get a new byte and transmit it
...
Listing 5.1 – Polling the Source of an Interrupt in an ISR
5 - Interrupts 2016
5.7
The vector table starts at memory address 0. The first entry is special – it is not
an address but the initial value of the stack pointer. It is needed because some
exceptions such as the NMI could happen as the processor just comes out of
reset and before any other initialization steps are executed.
Memory Exception
Address Vectors Number
0x0000_03FC IRQ #239 255
0x0000_0048 IRQ #2 18
0x0000_0044 IRQ #1 17
0x0000_0040 IRQ #0 16
0x0000_003C SysTick 15
0x0000_0038 PendSV 14
0x0000_0034 Reserved 13
0x0000_0030 Debug Monitor 12
0x0000_002C SVC 11
0x0000_0028 Reserved 10
0x0000_0024 Reserved 9
0x0000_0020 Reserved 8
0x0000_001C Reserved 7
0x0000_0018 Usage Fault 6
0x0000_0014 Bus Fault 5
0x0000_0010 MemManage Fault 4
0x0000_000C HardFault 3
0x0000_0008 NMI 2
0x0000_0004 Reset 1
0x0000_0000 Initial value of SP 0
2016 5 - Interrupts
5.8
Memory Memory
Address RAM Address RAM
Execution continues at the address pointed to by the vector for the highest-
A higher priority
exception pre-empts priority interrupt that was pending at the beginning of the interrupt sequence –
a currently
executing exception this is the interrupt service routine. If an interrupt source of higher priority
handler – this is
called a nested occurs during execution of the ISR, the ISR will itself be interrupted – this is
exception
called interrupt nesting.
The body of an interrupt service routine varies according to the source of the
interrupt. For an interrupt service routine written to handle external events,
they typically respond to the interrupt by retrieving or sending external data,
e.g. the reception of a byte of data via the UART is normally handled via an
ISR which places the received byte into a FIFO for later processing by the
main function.
5 - Interrupts 2016
5.9
5.4.1 Declaring Interrupt Service Routines in C for ARM® Cortex®-M
Processors
The use of the EXC_RETURN value for triggering exception returns allows
exception handlers (including interrupt service routines) to be written as
normal C functions.
2016 5 - Interrupts
5.10
In GNU C, you use function attributes to declare certain things about functions
called in your program which help the compiler optimize calls and check your
code more carefully. You can also use attributes to control memory placement,
code generation options or call/return conventions within the function being
annotated. Many of these attributes are target-specific. For example, many
targets support attributes for defining interrupt handler functions, which
typically must follow special register usage and return conventions.
In the GNU Compiler Collection (GCC) for ARM® processors, the function
attribute interrupt is used to indicate that the specified function is an
interrupt service routine. For example, to declare an ISR for a UART, you
would use:
void __attribute__ ((interrupt)) UART_ISR(void)
{
/* code goes here */
}
The interrupt function attribute for the ISR is really only needed for
previous generations of ARM® processors, since the Cortex®-M has a special
hardware instruction for exception return, as outlined in the previous section.
However, we will still define our ISRs with a function attribute as a matter of
style – it will make it easier for anyone reading our code to see that the
function’s intended use is as an ISR.
5 - Interrupts 2016
5.11
To place the address of the ISR in the vector table, which is defined in
vectors.c in the Generated_Code folder, we need to find the relevant
vector number and insert the address of the ISR in the table. The vector number
for a particular interrupt source is given in Table 3-5 of the K70 Sub-Family
Reference Manual. For example, if UART_ISR is used for UART2’s transmit
and receive ISR, we would put:
...
(tIsrFunc)&Cpu_Interrupt, /* 0x3F UART1_RX_TX */
(tIsrFunc)&Cpu_Interrupt, /* 0x40 UART1_ERR */
(tIsrFunc)&UART_ISR, /* 0x41 UART2_RX_TX */
(tIsrFunc)&Cpu_Interrupt, /* 0x42 UART2_ERR */
(tIsrFunc)&Cpu_Interrupt, /* 0x43 UART3_RX_TX */
...
Listing 5.2 – Vector Table Extract Showing ISR Address
The assembly language instruction CPSIE stands for Change Processor State
Interrupt Enable and the f parameter refers to the single-bit “fault mask”
register FAULTMASK. This register is similar to PRIMASK, but it also blocks
the HardFault exception.
Interrupts are disabled by default on reset, but it is good style to disable them
before you embark on peripheral module initialization – it acts as a reminder
that no interrupts will occur. You should then enable interrupts before the main
loop of your program:
...
__DI();
TowerInit();
__EI();
while (1)
{
/* Main loop */
}
2016 5 - Interrupts
5.12
5.5.1 Interrupt Latency
In many cases, rather than simply disabling all interrupts to carry out a certain
time-sensitive task, you only want to disable interrupts with priority lower than
a certain level. In this case, you write the required masking priority level to the
BASEPRI register.
When you enable an interrupt source in your application, you get to decide on
its priority level (0-15). Some of the exceptions (reset, NMI and HardFault)
have fixed priority levels. Their priority levels are represented with negative
numbers to indicate that they are of higher priority than other exceptions.
5 - Interrupts 2016
5.13
Microcontroller
Cortex-M processor
Peripheral NMI
Processsor
NVIC Core
Peripherals
IRQs System
Exceptions
I/O Port
SysTick timer
I/O Port
To support this, the NVIC contains programmable registers for interrupt enable
control, pending status, and read-only active status bits.
2016 5 - Interrupts
5.14
The pending status of the interrupts are stored in programmable registers in the
NVIC. When an interrupt input of the NVIC is asserted, it causes the pending
status of the interrupt to be asserted. The pending status remains high even if
the interrupt request is de-asserted.
The pending status means it is put into a state of waiting for the processor to
serve the interrupt. In some cases, the processor serves the request as soon as
an interrupt becomes pending. However, if the processor is already serving
another interrupt of higher or equal priority, or if the interrupt is masked by one
of the interrupt masking registers (e.g. PRIMASK), the pended request will
remain until the other interrupt service routine is finished, or when the interrupt
masking is cleared.
When the processor starts to process an interrupt request, the pending status of
the interrupt is cleared automatically.
The pending status of interrupts are stored in interrupt pending status registers,
which are accessible from software code. Therefore, you can clear the pending
status of an interrupt or set it manually. If an interrupt arrives when the
processor is serving another higher-priority interrupt and the pending status is
cleared before the processor starts responding to the pending request, the
request is cancelled and will not be served.
The pending status of an interrupt can be set even when the interrupt is
disabled. In this case, when the interrupt is enabled later, it can be triggered
and get served. In some cases this might not be desirable, so in this case you
will have to clear the pending status manually before enabling the interrupt in
the NVIC.
5 - Interrupts 2016
5.15
There are a number of registers in the NVIC for interrupt control (exception
type 16 up to 255). By default, after a system reset, all interrupts:
are disabled (enable bit = 0)
have priority level of 0 (highest programmable level)
have their pending status cleared
The Interrupt Enable register is programmed through two addresses. To set the
enable bit, you need to write to the NVIC’s Set Enable Register, NVICSERx;
to clear the enable bit, you need to write to the NVIC’s Clear Enable Register
NVICCERx. In this way, enabling or disabling an interrupt will not affect other
interrupt enable states. The NVICSERx / NVICCERx registers are 32-bits
wide; each bit represents one interrupt input. As there are more than 32
external interrupts in the Cortex®-M4 processor, there is more than one
NVICSERx and NVICCERx register.
The interrupt-pending status can be accessed through the Interrupt Set Pending
(NVICISPx) and Interrupt Clear Pending (NVICICPx) registers. Similarly to
the enable registers, there is more than one pending ISP and ICP register.
The values of the pending status registers can be changed by software, so you
can cancel a current pended exception through the NVICICPx register, or
generate software interrupts through the NVICISPx register.
See Section 3.2.2.3.1 of the K70 Sub-Family Reference Manual for an example
on how to access the correct NVIC registers for a particular interrupt source.
PMcL The Nested Vectored Interrupt Controller (NVIC) Index
2016 5 - Interrupts
5.16
EXAMPLE 5.1 Real-Time Interrupt using the Low Power Timer
The code below shows a simple scheme that shows the duration of the ISR and
the timing operation of the main loop.
Code to generate
and respond to real- void LPTMR_Init(void)
time interrupts {
// Enable clock gate to LPTMR module
SIM_SCGC5 |= SIM_SCGC5_LPTIMER_MASK;
// Initialize NVIC
// see p. 91 of K70P256M150SF3RM.pdf
// Vector 0x65=101, IRQ=85
// NVIC non-IPR=2 IPR=21
// Clear any pending interrupts on LPTMR
NVICICPR2 = (1 << 21);
// Enable interrupts from LPTMR module
NVICISER2 = (1 << 21);
5 - Interrupts 2016
5.17
// Clear bits 0 and 1 of Port A
// Assumes Port A already set up for output
GPIOA_PCOR = 0x00000003;
// Interrupt counter
Count = 0;
// Foreground is ready
Ack = 1;
}
void main(void)
{
// Globally disable interrupts while we set up
__DI();
PortA_Init();
LPTMR_Init();
// Globally enable interrupts
__EI();
for (;;)
{
if (Ack == 0)
{
Ack = 1;
// Toggle bit 1
GPIOA_PTOR = 0x00000002;
}
}
...
(tIsrFunc)&Cpu_Interrupt, /* 0x63 TSI0 */
(tIsrFunc)&Cpu_Interrupt, /* 0x64 MCG */
(tIsrFunc)&LPTMR_ISR, /* 0x65 LPTimer */
(tIsrFunc)&Cpu_Interrupt, /* 0x66 Reserved102 */
(tIsrFunc)&Cpu_Interrupt, /* 0x67 PORTA */
...
2016 5 - Interrupts
5.18
PTA[0]=1
0
Ack
1
Ack
1 Ack = 1
0 Ack = 0
toggle PTA[1]
Count++
PTA[0]=0
main
exc_return
ISR
5 - Interrupts 2016
5.19
Background
Processing exc_return exc_return exc_return
Figure 5.7
The main program performs the necessary initialization and then enters the
“background” portion of the program, which is often nothing more than a
simple loop that processes non-critical tasks and waits for interrupts to occur.
Examples of background processing include: processing data from an input
device, creating data for an output device, making calculations based on
analog-to-digital conversion results, determining the next digital-to-analog
output, and updating a display seen by human eyes.
2016 5 - Interrupts
5.20
An interrupt-driven
input routine
RDRF set InChar
Read data
from input
FIFO_Put FIFO_Get
yes yes
FIFO FIFO
full? empty?
no no
Put FIFO Get
buffer
Return data
Error to caller Error
exc_return return
Figure 5.8
5 - Interrupts 2016
5.21
When the background thread puts the first byte into the FIFO buffer,
the output device is idle and already in the “ready” state, so no interrupt
request from the output device is about to occur. The output ISR will
not be invoked and the data will not be removed from the buffer.
The background thread checks the output busy flag every time it writes data Output devices need
into the buffer. If the device is busy, then a device ready interrupt is expected to be kick started
and nothing needs to be done; otherwise, the background thread arms the
output and calls SendData to “kick start” the output process.
The SendData routine is responsible for retrieving the data from the buffer
and outputting it. If there is no more data in the buffer, then it must disarm the
output to prevent further interrupts.
2016 5 - Interrupts
5.22
Kick starting an
interrupt-driven
output routine for a OutChar SendData TDRE set
device that requests FIFO_Get
interrupts on FIFO_Put
yes yes
transitioning from FIFO
full?
FIFO
empty?
busy to ready
no no
Put FIFO Get
buffer
Call SendData
Error
Write data
to output Error
yes
error?
no
error?
no
output yes
yes
device
busy? Disarm
output
exc_return
no
Arm return Foreground thread (ISR)
output
Call SendData
(Kick Start)
return
Figure 5.9
5 - Interrupts 2016
5.23
5.9.2 Output Device Interrupt Request on Ready
In this case, an output device sets its interrupt request flag when it is idle and
ready for output (this will be the case after a reset condition, too). This means
that upon initially arming the interrupt for such a device, an ISR will be
invoked immediately. In the context of serial port transmission this creates two
problems:
The technique to handle this type of interrupt is to modify both the OutChar
routine and the ISR. The UART transmit interrupt is armed after every
FIFO_Put (if the UART transmit interrupt were already armed, then rearming
would have no effect). If the transmit FIFO is empty, then the ISR should
disarm the transmit interrupt.
An interrupt-driven
output routine for
Init OutChar TDRE set devices that request
an interrupt when
FIFO_Init FIFO_Get
FIFO_Put they are ready
Init yes yes
FIFO FIFO
full? empty?
no no
TIE = 0
Put FIFO Get
buffer
return Error
Write data
to output Error
Initialization
yes
error?
no
error?
no
yes
TIE = 1
TIE = 0
return
exc_return
Background thread (main)
Foreground thread (ISR)
Figure 5.10
2016 5 - Interrupts
5.24
Communication
character into the buffer. This is a safe operation, because the byte is added to
between threads is the end of the buffer. However, if the TxFIFO is keeping track of the number
accomplished using
global variables of bytes in the buffer with a global variable called NbBytes, then it must read,
increment and write to this variable. A problem arises if a foreground thread
(ISR) interrupts the background thread in the middle of the read-modify-write
access to the global variable – erroneous values of NbBytes can result.
5 - Interrupts 2016
5.25
NbBytes++;
and NbBytes is byte-sized, then the compiler generates the following code:
Since we are not using a real-time operating system (which would inherently
support a multithreaded program by providing interthread communication
mechanisms), one way of protecting the integrity of shared global variables is
to disable interrupts during the critical section. This is a simple and acceptable
method of protecting a critical section for a small embedded system.
It is important not to disable interrupts too long so as not to affect the dynamic
performance of other threads. There is a problem however – consider what
would happen if you simply add an “interrupt disable” at the beginning and an
“interrupt enable” at the end of a critical section:
A problem with
__DI(); // disable interrupts
disabling and
NbBytes++; // critical section
enabling interrupts
__EI(); // enable interrupts
to make a critical
What if interrupts were in a disabled state on entry into the critical section? section
Unfortunately, we have enabled them on exiting the critical section! What we
need to do is save the state of the interrupts (enabled or disabled) before we
enter the critical section, and restore that state on exiting.
2016 5 - Interrupts
5.26
Firstly, the \ character that appears at the end of each line is C’s way of
extending a single expression across more than one line.
5 - Interrupts 2016
5.27
Thirdly, there are two global variables used by the macros, which are defined
in CPU.c:
volatile uint8_t SR_reg; /* Current value of the FAULTMASK register */
volatile uint8_t SR_lock = 0x00U; /* Lock */
2016 5 - Interrupts
5.28
You can use the macros, with nesting, as shown in the example below:
void function(void)
{
EnterCritical();
...
EnterCritical();
...
ExitCritical();
...
ExitCritical();
}
Listing 5.3 – Nesting Critical Sections
To reiterate – whenever two (or more) threads share a global variable, you
must protect access to that variable by operating in a critical section. The
macros are not robust and you must guarantee that EnterCritical() and
ExitCritical() occur in nested pairs. Be careful in your code that you do
not enter a critical section inside a function and then exit that function without
a corresponding ExitCritical(). Such a situation may arise when there
are multiple exit points from a function:
void function(void)
{
EnterCritical();
...
if (error)
return; /* Error! We have not “called” ExitCritical() */
...
ExitCritical();
}
Listing 5.4 – Incorrect Coding for a Critical Section
5.11 References
Yiu, J.: The Definitive Guide to ARM® Cortex®-M3 and ARM Cortex®-M4
Processors, Newnes, 2014. ISBN-13: 978-0-12-408082-9
5 - Interrupts 2016