When working with ARM Cortex-M processors like STM32, understanding the different processor modes is crucial. These modes define how the processor executes code and manages system access. In this article, we’ll break down the four essential concepts:
- 🧵 Thread Mode
- 🔁 Handler Mode
- 🔐 Privileged Mode
- 🔒 Unprivileged Mode
Whether you’re building a bare-metal embedded system or working with an RTOS, this beginner-friendly guide will help you understand what these modes are and when they are used.
What is Thread Mode?
Thread Mode is the default mode in which your program starts executing. Think of it as the normal mode where your main()
function and background tasks run.
🔹 Runs application-level code
🔹 Can be privileged or unprivileged
🔹 Ideal for user programs or system tasks
🧠 Example:
int main(void) {
// This is Thread Mode
while(1) {
// Your application logic
}
}
What is Handler Mode?
Handler Mode is entered automatically when an interrupt or exception occurs. The processor switches to this mode to execute the Interrupt Service Routine (ISR).
🔹 Used for handling exceptions or interrupts
🔹 Always runs in privileged mode
🔹 Cannot be unprivileged
🧠 Example:
void TIM2_IRQHandler(void) {
// This code runs in Handler Mode
}
When does the processor switch to Handler Mode?
- A hardware interrupt is triggered (e.g., timer, GPIO, UART)
- A software-triggered exception like SVC or PendSV occurs
- A fault happens (like HardFault or BusFault)
What is Privileged Mode?
Privileged Mode gives the program full access to system resources. It’s like being the system administrator.
🔓 Can:
- Access all memory and registers
- Enable or disable interrupts
- Switch to unprivileged mode
✅ Used by:
- Startup code
- Kernel or system-level services
- ISRs (Interrupt Service Routines)
🧠 Example: Your code runs in privileged mode by default when the MCU powers up.
What is Unprivileged Mode?
Unprivileged Mode has limited access to the system. It’s like a restricted user account.
🔒 Cannot:
- Access certain system registers
- Change back to privileged mode directly
- Modify critical configuration
✅ Used by:
- User applications
- RTOS user threads
- Tasks that should be isolated for security
How to switch to unprivileged mode?
You can write to the CONTROL register:
__asm volatile("MRS R0, CONTROL"); // Read control register
__asm volatile("ORR R0, R0, #1"); // Set bit 0 to enter unprivileged
__asm volatile("MSR CONTROL, R0"); // Write back
❗ Once you’re in unprivileged mode, you cannot switch back to privileged mode without triggering an exception, like
SVC
.
Summary Table
Mode | Type | Access Level | Use Case |
---|---|---|---|
Thread Mode | Normal | Priv or Unpriv | Main code, RTOS tasks |
Handler Mode | Exception | Always Privileged | ISRs, Fault Handlers |
Privileged | Access Level | Full system access | Kernel, Drivers, ISRs |
Unprivileged | Access Level | Restricted access | User apps, RTOS user threads |
Why Are These Modes Important?
These modes are essential for:
- ✨ Security: Prevent untrusted code from damaging the system
- ⚙️ RTOS Support: Allow task isolation and privilege management
- 🧪 Testing: Simulate different behavior based on access level
- 🛡️ Fault Prevention: Avoid accidental writes to protected memory
Final Thoughts
Understanding Thread Mode, Handler Mode, Privileged and Unprivileged Modes is crucial for building reliable and secure embedded applications on ARM Cortex-M microcontrollers. These concepts help separate system-level code from user-level code and enable safer execution in real-time environments.
By leveraging these modes correctly, you can:
- Improve your system’s security
- Create better RTOS applications
- Understand advanced embedded behavior like fault handling and privilege escalation
Full Code Example (STM32, CMSIS, No HAL)
- The
main()
function starts in Thread Mode and Privileged Mode. - It configures the LED pin (e.g., PA5 on STM32F401/STM32F103).
- It then switches to Unprivileged Mode using the CONTROL register.
- It generates a software interrupt (
EXTI3_IRQHandler
). - The ISR runs in Handler Mode (and always Privileged).
- Inside the ISR, we toggle the LED.
#include "stm32f4xx.h"
#include <stdint.h>
/* PA5 = On-board LED for STM32F401/STM32F103 */
#define LED_PIN 5
/* Function to initialize GPIOA pin 5 as output */
void GPIO_Init(void) {
// Enable clock for GPIOA
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Set PA5 as General Purpose Output
GPIOA->MODER &= ~(3U << (LED_PIN * 2)); // Clear mode bits
GPIOA->MODER |= (1U << (LED_PIN * 2)); // Set to Output
// Optional: Set output type to push-pull
GPIOA->OTYPER &= ~(1U << LED_PIN);
// Optional: Set speed to high
GPIOA->OSPEEDR |= (3U << (LED_PIN * 2));
// Optional: No pull-up/pull-down
GPIOA->PUPDR &= ~(3U << (LED_PIN * 2));
}
/* Toggle LED */
void toggle_led(void) {
GPIOA->ODR ^= (1U << LED_PIN);
}
/* Function to trigger a software interrupt for EXTI3 */
void generate_interrupt(void) {
// Enable IRQ3 (EXTI3_IRQn)
NVIC->ISER[0] |= (1 << EXTI3_IRQn);
// Use STIR to trigger EXTI3
*((volatile uint32_t*)0xE000EF00) = (3 & 0x1FF); // IRQn = 3
}
/* Change to Unprivileged Thread Mode */
void switch_to_unprivileged(void) {
__asm volatile("MRS R0, CONTROL");
__asm volatile("ORR R0, R0, #1"); // Set bit 0 to switch to unprivileged
__asm volatile("MSR CONTROL, R0");
__asm volatile("ISB"); // Flush pipeline
}
int main(void) {
GPIO_Init();
// Ensure LED is initially OFF
GPIOA->ODR &= ~(1U << LED_PIN);
// Thread Mode + Privileged
toggle_led(); // Blink once before switching
// Switch to Unprivileged
switch_to_unprivileged();
// Now still in Thread Mode, but Unprivileged
generate_interrupt(); // This will switch to Handler Mode
// Back to Thread Mode (still Unprivileged)
while (1);
}
/* Handler Mode: Always Privileged */
void EXTI3_IRQHandler(void) {
toggle_led(); // ISR toggles LED
// Clear pending bit (not needed for software-triggered)
}
/* Optional: Catch unexpected hard faults */
void HardFault_Handler(void) {
while (1);
}
How to Run
- Board: STM32F4-based board (e.g., Nucleo-F401RE or STM32F103).
- Toolchain: STM32CubeIDE or bare-metal ARM GCC with linker script.
- Steps:
- Copy code into
main.c
- Build and flash it to the board
- Observe:
- LED blinks once from
main()
(Thread + Privileged) - ISR toggles the LED again (Handler + Privileged)
- LED blinks once from
- You’ve just switched between modes and access levels!
- Copy code into
What You Just Learned
Step | Mode | Access Level |
---|---|---|
Running main() | Thread | Privileged |
Switched via CONTROL reg | Thread | Unprivileged |
Triggered interrupt (EXTI3) | Handler | Privileged |
ISR toggled LED | Handler | Privileged |
Back to main() | Thread | Still Unprivileged |
Bonus Tip (RTOS Context)
In an RTOS, the kernel runs in Privileged mode, and tasks run in Unprivileged mode. This is how the system keeps tasks isolated and protected — just like you saw in this demo!
Leave a Reply