Overview of STM32F1 Interrupt Architecture
The STM32F1 series, built on the ARM Cortex-M3 core, supports a significant number of interrupts. While the Cortex-M3 architecture theoretically supports 240 external interrupts, the STM32F103 line simplifies this by implementing 60 maskable interrupts alongside 16 internal exceptions. These interrupts are managed by the Nested Vectored Interrupt Controller (NVIC) and the System Control Block (SCB).
NVIC Register Structure
In the MDK development environment, the NVIC is accessed via a specific structure. Below is the definition used to interact with the controller registers:
/**
* @brief Structure for handling Nested Vectored Interrupt Controller access.
*/
typedef struct {
__IOM uint32_t SETENA[8]; // Offset: 0x000 (R/W) Interrupt Set Enable
uint32_t RESERVED0[24];
__IOM uint32_t CLRENA[8]; // Offset: 0x080 (R/W) Interrupt Clear Enable
uint32_t RESERVED1[24];
__IOM uint32_t SETPEND[8]; // Offset: 0x100 (R/W) Interrupt Set Pending
uint32_t RESERVED2[24];
__IOM uint32_t CLRPEND[8]; // Offset: 0x180 (R/W) Interrupt Clear Pending
uint32_t RESERVED3[24];
__IOM uint32_t ACTIVE[8]; // Offset: 0x200 (R/W) Interrupt Active Status
uint32_t RESERVED4[56];
__IOM uint8_t PRI[240]; // Offset: 0x300 (R/W) Interrupt Priority (8-bit)
uint32_t RESERVED5[644];
__OM uint32_t SWTRIG; // Offset: 0xE00 ( /W) Software Trigger Interrupt
} NVIC_Registers;
Key registers include:
- SETENA/CLRENA: Enable or disable specific interrupts. For the STM32F103, only
SETENA[0]andSETENA[1]are relevant for the 60 available interrupts. - SETPEND/CLRPEND: Allow manually suspending or releasing interrupts.
- ACTIVE: A read-only register indicating which interrupt is currently executing.
- PRI: Stores the priority level for each interrupt. STM32F1 utilizes only the high 4 bits of each 8-bit priority field.
Priority Grouping Mechanism
The STM32 does not use all 8 bits of the priority register. Instead, it splits the 4 used bits into two segments: Preempt Priority and Sub Priority (Response Priority). The split ratio is determined by the SCB->AIRCR register.
There are 5 configuration groups:
| Group | Preempt Bits | Sub Bits | Preempt Range | Sub Range |
|---|---|---|---|---|
| 0 | 0 | 4 | None | 0-15 |
| 1 | 1 | 3 | 0-1 | 0-7 |
| 2 | 2 | 2 | 0-3 | 0-3 |
| 3 | 3 | 1 | 0-7 | 0-1 |
| 4 | 4 | 0 | 0-15 | None |
Behavior Rules:
- Preempt Priority determines if an interrupt can interrupt another. A higher preempt priority (lower numerical value) can interrupt a lower one.
- Sub Priority is used only when two pending interrupts have the same preempt priority; the one with the higher sub priority (lower numerical value) executes first.
- If both priorities are identical, the interrupt occurring first is handled first.
Example Scenario: Assume Group 2 is selected (2 bits preempt, 2 bits sub).
- Timer2 IRQ: Preempt=1, Sub=0
- EXTI0 IRQ: Preempt=2, Sub=0
- EXTI1 IRQ: Preempt=1, Sub=1
Execution Order: Timer2 and EXTI1 can interrupt EXTI0. However, Timer2 and EXTI1 cannot interrupt eachother if they share the same preempt level (1), but Timer2 will execute before EXTI1 if both are pending simultaneously because its Sub priority (0) is higher than EXTI1's (1).
HAL Library Configuration
The Hardware Abstraction Layer (HAL) simplifies register access through specific functions found in stm32f1xx_hal_cortex.c.
1. Setting the Grouping
This should be called once, typically during initialization. Group 4 is often used for RTOS compatibility.
void Configure_Interrupt_Grouping(void) {
// Set 4 bits for preemption, 0 bits for sub-priority (Group 4)
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
}
Internally, this modifies the SCB->AIRCR register using a specific write-key sequence:
__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t group_val) {
uint32_t ctrl_reg;
uint32_t temp_group = (group_val & 0x07UL);
ctrl_reg = SCB->AIRCR;
ctrl_reg &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk));
ctrl_reg = (ctrl_reg |
((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(temp_group << SCB_AIRCR_PRIGROUP_Pos));
SCB->AIRCR = ctrl_reg;
}
2. Configuring Individual Interrupts
After setting the group, individual interrupts must be assigned priorities and enabled.
void Setup_Peripheral_IRQs(void) {
// Example: Configure USART1 Interrupt
// Assuming Group 4: PreemptPriority is used, SubPriority is ignored
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
// Example: Configure EXTI Line 0
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
3. Disabling Interrupts
To disable an interrupt channel dynamically:
void Stop_External_Interrupt(void) {
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
}