For C/C++ language users, a function library for interrupt control is already provided in the CMSIS. The CMSIS-CORE APIs are included in the device driver libraries from all major microcontroller vendors and is openly accessible. More details of the CMSIS are covered in
Chapter 3—Introduction to Embedded Software Development (
Section 3.5—Cortex® Microcontroller Software Interface Standard).
For users programming the Cortex-M0 or Cortex-M0+ processor using assembly language, it could be handy to have a set of generic functions for handling interrupt control with the Nested Vectored Interrupt Controller (NVIC).
21.8.1. Enable and Disable Interrupts
The enable and disable of interrupts is quite simple. The following functions “nvic_set_enable” and “nvic_clr_enable” require the interrupt number as input, which is stored in R0 before the function call.
;-------------------------
; Enable IRQ
; - input R0 : IRQ number. E.g. IRQ#0 = 0
ALIGN
nvic_set_enable FUNCTION
PUSH {R1, R2}
LDR R1,=0xE000E100 ; NVIC SETENA
MOVS R2, #1
LSLS R2, R2, R0
STR R2, [R1]
POP {R1, R2}
BX LR ; Return
ENDFUNC
;-------------------------
; Disable IRQ
; - input R0 : IRQ number. E.g. IRQ#0 = 0
ALIGN
nvic_clr_enable FUNCTION
PUSH {R1, R2}
LDR R1,=0xE000E180 ; NVIC CLRENA
MOVS R2, #1
LSLS R2, R2, R0
STR R2, [R1]
POP {R1, R2}
BX LR ; Return
ENDFUNC
;-------------------------
To use the functions, just put the interrupt number in R0, and call the function. For example,
MOVS R0, #3 ; Enable Interrupt #3
BL nvic_set_enable
The FUNCTION and ENDFUNC keywords are used to identify start and end of a function in ARM® assembler (including Keil MDK-ARM). This is optional. The “ALIGN” keyword ensures correct alignment of the starting of the function.
21.8.2. Set and Clear Interrupt Pending Status
The assembly functions for setting and clear of interrupt pending status are very similar to the ones for enable and disable interrupts. The only changes are labels and NVIC register address values.
;-------------------------
; Set IRQ Pending status
; - input R0 : IRQ number. E.g. IRQ#0 = 0
ALIGN
nvic_set_pending FUNCTION
PUSH {R1, R2}
LDR R1,=0xE000E200 ; NVIC SETPEND
MOVS R2, #1
LSLS R2, R2, R0
STR R2, [R1]
POP {R1, R2}
BX LR ; Return
ENDFUNC
;-------------------------
; Clear IRQ Pending
; - input R0 : IRQ number. E.g. IRQ#0 = 0
ALIGN
nvic_clr_pending FUNCTION
PUSH {R1, R2}
LDR R1,=0xE000E280 ; NVIC CLRPEND
MOVS R2, #1
LSLS R2, R2, R0
STR R2, [R1]
POP {R1, R2}
BX LR ; Return
ENDFUNC
;-------------------------
Note that sometimes clearing of pending status of an interrupt might not be enough to stop the interrupt from happening. If the interrupt source generates an interrupt request continuously (level output), then the pending status could remain high even if you try to clear it at the NVIC.
21.8.3. Setting Up Interrupt Priority Level
The assembly function to set up priority level for as interrupt is a bit more complex. First, it requires two input parameters: the interrupt number and the new priority level. Secondly, the priority level register address has to be calculated as there are up to eight priority registers. And finally, the function needs to perform a read-modify-write operation to the correct byte inside the 32-bit priority level register, as the priority level registers are word access only.
;-------------------------
; Set interrupt priority
; - input R0 : IRQ number. E.g. IRQ#0 = 0
; - input R1 : Priority level
ALIGN
nvic_set_priority FUNCTION
PUSH {R2-R5}
LDR R2,=0xE000E400 ; NVIC Interrupt Priority #0
MOV R3, R0 ; Make a copy of IRQ number
MOVS R4, #3 ; clear lowest two bit of IRQ number
BICS R3, R4
ADDS R2, R3 ; address of priority register in R2
ANDS R4, R0 ; byte number (0 to 3) in priority register
LSLS R4, R4, #3 ; Number of bits to shift for priority & mask
MOVS R5, #0xFF ; byte mask
LSLS R5, R5, R4 ; byte mask shift to right location
MOVS R3, R1
LSLS R3, R3, R4 ; Priority shift to right location
LDR R4, [R2] ; Read existing priority level
BICS R4, R5 ; Clear existing priority value
ORRS R4, R3 ; Set new level
STR R4, [R2] ; Write back
POP {R2-R5}
BX LR ; Return
ENDFUNC
;-------------------------
In most applications, however, you can use a much simpler code to set up priority levels of multiple interrupts in one go at the beginning of the program. For example, you can predefine the priority levels in a table of constant values, and then copy it to the NVIC priority level registers using a short instruction sequence:
LDR R0,=PrioritySettings ; address of priority setting table
LDR R1,=0xE000E400 ; address of interrupt priority registers
LDMIA R0!,{R2-R5} ; Read Interrupt Priority 0-15
STMIA R1!,{R2-R5} ; Write Interrupt Priority 0-15
LDMIA R0!,{R2-R5} ; Read Interrupt Priority 16-31
STMIA R1!,{R2-R5} ; Write Interrupt Priority 16-31
…
ALIGN 4 ; Ensure that the table is word aligned
PrioritySettings ; Table of priority level values (example values)
DCD 0xC0804000 ; IRQ 3- 2- 1- 0
DCD 0x80808080 ; IRQ 7- 6- 5- 4
DCD 0xC0C0C0C0 ; IRQ 11-10- 9- 8
DCD 0x40404040 ; IRQ 15-14-13-12
DCD 0x40404080 ; IRQ 19-18-17-16
DCD 0x404040C0 ; IRQ 23-22-21-20
DCD 0x4040C0C0 ; IRQ 27-26-25-24
DCD 0x004080C0 ; IRQ 31-30-29-28