Table of Contents
KEY TAKEAWAYS
- Microcontrollers have multiple reset sources: Power-On Reset (POR), external pin reset, watchdog timer reset, brown-out reset, and software reset
- The reset status register tells your firmware WHY it restarted — critical for distinguishing cold boot from watchdog recovery
- The Watchdog Timer (WDT) is your last line of defense against firmware hangs — it automatically resets the MCU if your code stops responding
- Understanding the boot sequence (reset vector → stack pointer init → .bss clear → .data copy → main) is essential for writing startup code and debugging hard faults
What Happens When a Microcontroller Resets
When a microcontroller resets, the CPU jumps to the reset vector — a fixed memory address that determines where execution begins — before running any of your application code.
A reset forces the microcontroller into a known, deterministic state. The CPU stops executing whatever it was doing, all peripheral registers return to their default values, the program counter loads the address from the reset vector (typically at address 0x00000000 or 0x00000004 on ARM Cortex-M), and execution begins from scratch. It is the hardware equivalent of “turn it off and on again” — except it happens in microseconds and can be triggered automatically by multiple internal circuits.
Understanding reset sources is crucial because your firmware often needs to behave differently depending on why it restarted. A cold power-on requires full initialization. A watchdog reset means something went wrong and you may want to log the error. A brownout reset suggests a power supply problem that may recur.
Types of Reset Sources
1. Power-On Reset (POR)
When you first apply power to the microcontroller, the internal Power-On Reset circuit holds the chip in reset until the supply voltage (VDD) rises above a threshold — typically around 1.8V to 2.0V. This ensures the chip does not start executing code while the voltage is still rising and unstable, which could cause unpredictable behavior.
The POR circuit includes a small timer that adds a delay (tens to hundreds of microseconds) after the voltage crosses the threshold, allowing the oscillator to stabilize and internal voltage regulators to settle. Only then is the reset released and code execution begins.
The POR is completely automatic — you do not configure it. It is always active. However, you should understand the POR threshold voltage when designing the power supply circuit. If your supply ramps up very slowly (soft-start), the POR circuit may release reset before the voltage is stable, requiring an external reset supervisor.
2. External Reset (NRST Pin)
Most microcontrollers have a dedicated reset pin, typically called NRST or RESET (active low). Pulling this pin low forces an immediate reset. This is used for:
- Manual reset buttons during development
- External watchdog circuits
- Debug probes (JTAG/SWD) resetting the target
- System-level reset controllers in multi-chip designs
A proper external reset circuit typically includes a pull-up resistor (10kΩ to VDD) and a filter capacitor (100nF) to prevent noise from triggering a false reset. If you add a reset button, connect it between NRST and ground — pressing the button pulls the pin low and triggers a reset.
3. Watchdog Timer Reset (WDT)
The Watchdog Timer is a countdown timer that resets the microcontroller if it reaches zero. Your firmware must periodically “kick” (or “feed”) the watchdog to prevent it from expiring. If your code gets stuck in an infinite loop, deadlocks, or crashes, it stops feeding the watchdog, the counter reaches zero, and the chip resets automatically.
Most microcontrollers have two types of watchdog:
- Independent Watchdog (IWDG) — clocked from a separate low-speed oscillator (not the main system clock). Runs even if the main clock fails. Simple but coarse timeout.
- Window Watchdog (WWDG) — must be fed within a specific time window (not too early, not too late). More restrictive, catches more failure modes.
Here is how to configure an Independent Watchdog with a 1-second timeout:
#include <stdint.h>
/* IWDG registers (from datasheet register map) */
#define IWDG_BASE 0x40003000UL
#define IWDG_KR (*(volatile uint32_t *)(IWDG_BASE + 0x00)) /* Key Register */
#define IWDG_PR (*(volatile uint32_t *)(IWDG_BASE + 0x04)) /* Prescaler Register */
#define IWDG_RLR (*(volatile uint32_t *)(IWDG_BASE + 0x08)) /* Reload Register */
#define IWDG_SR (*(volatile uint32_t *)(IWDG_BASE + 0x0C)) /* Status Register */
/* Key values (from datasheet) */
#define IWDG_KEY_ENABLE 0xCCCC /* Start the watchdog */
#define IWDG_KEY_RELOAD 0xAAAA /* Reload the counter (feed the dog) */
#define IWDG_KEY_UNLOCK 0x5555 /* Enable write access to PR and RLR */
/*
* Configure IWDG with approximately 1 second timeout.
*
* LSI oscillator = 40 kHz (typical, varies 30-60 kHz)
* Prescaler = 64 → IWDG clock = 40000 / 64 = 625 Hz
* Reload value = 625 → timeout = 625 / 625 = 1.0 second
*/
void watchdog_init(void)
{
IWDG_KR = IWDG_KEY_UNLOCK; /* Unlock PR and RLR registers */
IWDG_PR = 4; /* Prescaler divider = 64 */
IWDG_RLR = 625; /* Reload value for ~1 second */
while (IWDG_SR != 0) /* Wait for registers to update */
;
IWDG_KR = IWDG_KEY_ENABLE; /* Start the watchdog */
IWDG_KR = IWDG_KEY_RELOAD; /* Initial feed */
}
/* Call this regularly in your main loop — at least once per second */
void watchdog_feed(void)
{
IWDG_KR = IWDG_KEY_RELOAD;
}
/*
* WARNING: Once started, the IWDG cannot be stopped (only a reset stops it).
* This is a safety feature — malicious or buggy code cannot disable the watchdog.
*/4. Software Reset
Your firmware can trigger a reset programmatically. This is useful for applying configuration changes that require a restart, recovering from unrecoverable errors, or performing a firmware update (reset into bootloader). On ARM Cortex-M, the Application Interrupt and Reset Control Register (AIRCR) provides a software reset:
#define SCB_AIRCR (*(volatile uint32_t *)0xE000ED0CUL)
#define AIRCR_VECTKEY (0x05FA << 16) /* Required key to write AIRCR */
#define AIRCR_SYSRESETREQ (1U << 2) /* System reset request bit */
void software_reset(void)
{
/* Ensure all outstanding memory accesses complete */
__asm volatile ("dsb 0xF" ::: "memory");
/* Request system reset */
SCB_AIRCR = AIRCR_VECTKEY | AIRCR_SYSRESETREQ;
/* Wait for reset to take effect */
__asm volatile ("dsb 0xF" ::: "memory");
for (;;)
; /* Should never reach here */
}The VECTKEY field (0x05FA) is a safety mechanism — you must write this exact value or the write is ignored. This prevents accidental resets from stray memory writes.
Reading the Reset Status Register
After a reset, your firmware needs to know what caused it. The Reset and Clock Control (RCC) peripheral provides flags that indicate the reset source. Here is how to read and interpret them:
#define RCC_CSR (*(volatile uint32_t *)(0x40021000UL + 0x24))
/* Reset source flags (from RCC_CSR register description) */
#define RCC_CSR_LPWRRSTF (1U << 31) /* Low-power reset flag */
#define RCC_CSR_WWDGRSTF (1U << 30) /* Window watchdog reset flag */
#define RCC_CSR_IWDGRSTF (1U << 29) /* Independent watchdog reset flag */
#define RCC_CSR_SFTRSTF (1U << 28) /* Software reset flag */
#define RCC_CSR_PORRSTF (1U << 27) /* POR/PDR reset flag */
#define RCC_CSR_PINRSTF (1U << 26) /* NRST pin reset flag */
#define RCC_CSR_RMVF (1U << 24) /* Remove reset flags (write 1 to clear) */
typedef enum {
RESET_CAUSE_POWER_ON,
RESET_CAUSE_EXTERNAL_PIN,
RESET_CAUSE_SOFTWARE,
RESET_CAUSE_WATCHDOG_IWDG,
RESET_CAUSE_WATCHDOG_WWDG,
RESET_CAUSE_LOW_POWER,
RESET_CAUSE_UNKNOWN
} reset_cause_t;
reset_cause_t get_reset_cause(void)
{
uint32_t csr = RCC_CSR;
reset_cause_t cause = RESET_CAUSE_UNKNOWN;
/* Check flags in priority order (most specific first) */
if (csr & RCC_CSR_IWDGRSTF) {
cause = RESET_CAUSE_WATCHDOG_IWDG;
} else if (csr & RCC_CSR_WWDGRSTF) {
cause = RESET_CAUSE_WATCHDOG_WWDG;
} else if (csr & RCC_CSR_SFTRSTF) {
cause = RESET_CAUSE_SOFTWARE;
} else if (csr & RCC_CSR_PORRSTF) {
cause = RESET_CAUSE_POWER_ON;
} else if (csr & RCC_CSR_PINRSTF) {
cause = RESET_CAUSE_EXTERNAL_PIN;
} else if (csr & RCC_CSR_LPWRRSTF) {
cause = RESET_CAUSE_LOW_POWER;
}
/* Clear all reset flags so we can detect the next reset cause */
RCC_CSR |= RCC_CSR_RMVF;
return cause;
}
static const char *reset_cause_names[] = {
"Power-On Reset",
"External Pin Reset",
"Software Reset",
"Independent Watchdog Reset",
"Window Watchdog Reset",
"Low-Power Reset",
"Unknown"
};
/* Call this at the very beginning of main(), before any other initialization */
void log_reset_cause(void)
{
reset_cause_t cause = get_reset_cause();
uart_send_string("Reset cause: ");
uart_send_string(reset_cause_names[cause]);
uart_send_string("rn");
if (cause == RESET_CAUSE_WATCHDOG_IWDG ||
cause == RESET_CAUSE_WATCHDOG_WWDG) {
/* Watchdog reset — something went wrong in the previous run.
* You might want to:
* - Increment a crash counter in backup RAM / EEPROM
* - Enter a safe mode if crash count exceeds threshold
* - Log additional diagnostic info
*/
uart_send_string("WARNING: System recovered from watchdog reset!rn");
}
}The Boot Sequence After Reset
Understanding what happens between the reset and your main() function is essential for debugging startup problems and writing custom startup code. Here is the typical sequence for an ARM Cortex-M microcontroller:
- Hardware loads the Stack Pointer — The CPU reads the value at address 0x00000000 (or wherever the vector table starts) and loads it into the Main Stack Pointer (MSP). This is your initial stack.
- Hardware loads the Reset Vector — The CPU reads the value at address 0x00000004 (the reset handler address) and begins execution there.
- Reset handler runs (startup code) — This is typically assembly code provided by the chip vendor or your toolchain. It does the following:
- Copies initialized global variables from Flash to RAM (.data section)
- Zeros out uninitialized global variables (.bss section)
- Optionally initializes the FPU, configures the clock system
- Calls any C++ constructors (if using C++)
- Calls
main()
- main() executes — Your application code begins. By this point, the stack is set up, global variables are initialized, and the CPU is running from the default internal oscillator (usually 8 MHz).
Here is a simplified startup code in C that shows the .data and .bss initialization:
#include <stdint.h>
/* These symbols are defined by the linker script */
extern uint32_t _sidata; /* Start of .data in Flash (source) */
extern uint32_t _sdata; /* Start of .data in RAM (destination) */
extern uint32_t _edata; /* End of .data in RAM */
extern uint32_t _sbss; /* Start of .bss in RAM */
extern uint32_t _ebss; /* End of .bss in RAM */
extern int main(void);
void Reset_Handler(void)
{
uint32_t *src, *dst;
/* Copy .data section from Flash to RAM */
src = &_sidata;
dst = &_sdata;
while (dst < &_edata) {
*dst++ = *src++;
}
/* Zero-fill .bss section */
dst = &_sbss;
while (dst < &_ebss) {
*dst++ = 0;
}
/* Call main */
main();
/* If main returns, hang */
for (;;)
;
}Watchdog Best Practices
The watchdog timer is your most important reliability mechanism, but it is easy to use incorrectly. Here are the key rules:
Feed from the main loop only
Never feed the watchdog from a timer interrupt. If the main loop is stuck but interrupts still fire, the watchdog will never trip and the system stays hung. The whole point of the watchdog is to detect when your main application logic stops making progress.
/* CORRECT: Feed from main loop */
int main(void)
{
system_init();
watchdog_init();
while (1) {
process_inputs();
run_state_machine();
update_outputs();
watchdog_feed(); /* Only here — proves the entire loop ran */
}
}
/* WRONG: Do NOT feed from a timer ISR */
void TIM2_IRQHandler(void)
{
clear_timer_flag();
watchdog_feed(); /* BAD: This fires even if main loop is stuck! */
}Multi-task watchdog monitoring
In systems with multiple tasks or state machines, you need to ensure ALL critical tasks are running before feeding the watchdog. A common pattern:
#define TASK_SENSOR_BIT (1U << 0)
#define TASK_COMM_BIT (1U << 1)
#define TASK_CONTROL_BIT (1U << 2)
#define ALL_TASKS_ALIVE (TASK_SENSOR_BIT | TASK_COMM_BIT | TASK_CONTROL_BIT)
static volatile uint32_t task_alive_flags = 0;
/* Each task calls this to report it is still running */
void task_report_alive(uint32_t task_bit)
{
task_alive_flags |= task_bit;
}
/* Main supervisor checks all tasks before feeding watchdog */
void supervisor_check(void)
{
if ((task_alive_flags & ALL_TASKS_ALIVE) == ALL_TASKS_ALIVE) {
watchdog_feed();
task_alive_flags = 0; /* Reset for next cycle */
}
/* If any task has not reported, watchdog is NOT fed.
* If this persists, the watchdog will reset the system. */
}This pattern ensures that if ANY task hangs, the system resets. It is used in safety-critical systems like automotive and medical devices.
Real-World Example: Watchdog Recovery with Crash Counter
In production firmware, you often want to track how many times the watchdog has triggered. If it triggers too many times in a short period, the system should enter a safe mode instead of repeatedly crashing and resetting. Here is a complete implementation using backup registers (non-volatile across resets):
#include <stdint.h>
/* Backup register — survives reset but not power cycle */
#define BKP_BASE 0x40006C00UL
#define BKP_DR1 (*(volatile uint32_t *)(BKP_BASE + 0x04))
/* Enable backup domain access (required on many MCUs) */
#define RCC_APB1ENR (*(volatile uint32_t *)(0x40021000UL + 0x1C))
#define PWR_CR (*(volatile uint32_t *)(0x40007000UL + 0x00))
#define MAX_CRASH_COUNT 5
#define CRASH_MAGIC 0xDEAD0000UL
void enable_backup_domain(void)
{
RCC_APB1ENR |= (1U << 28) | (1U << 27); /* Enable PWR and BKP clocks */
PWR_CR |= (1U << 8); /* Disable backup domain write protection */
}
uint16_t get_crash_count(void)
{
uint32_t val = BKP_DR1;
if ((val & 0xFFFF0000UL) != CRASH_MAGIC) {
return 0; /* First boot or backup lost — no crashes recorded */
}
return (uint16_t)(val & 0xFFFF);
}
void set_crash_count(uint16_t count)
{
BKP_DR1 = CRASH_MAGIC | count;
}
void startup_safety_check(void)
{
enable_backup_domain();
reset_cause_t cause = get_reset_cause();
if (cause == RESET_CAUSE_WATCHDOG_IWDG ||
cause == RESET_CAUSE_WATCHDOG_WWDG) {
uint16_t crashes = get_crash_count() + 1;
set_crash_count(crashes);
if (crashes >= MAX_CRASH_COUNT) {
/* Too many crashes — enter safe mode */
uart_send_string("CRITICAL: Entering safe mode after ");
uart_send_number(crashes);
uart_send_string(" consecutive crashesrn");
enter_safe_mode(); /* Minimal operation, disable risky peripherals */
return;
}
uart_send_string("Watchdog recovery #");
uart_send_number(crashes);
uart_send_string("rn");
} else if (cause == RESET_CAUSE_POWER_ON) {
/* Clean boot — reset crash counter */
set_crash_count(0);
}
}
int main(void)
{
uart_init(8000000, 115200);
startup_safety_check();
/* Normal operation — if we run successfully for 30 seconds,
* clear the crash counter (we are stable) */
watchdog_init();
uint32_t stable_timer = 0;
while (1) {
process_application();
watchdog_feed();
stable_timer++;
if (stable_timer > 30000) { /* ~30 seconds of stable operation */
set_crash_count(0); /* We are stable, clear crash history */
stable_timer = 30001; /* Prevent overflow */
}
}
}Summary
Reset systems are a fundamental part of microcontroller design that most tutorials skip. Understanding the different reset sources, reading the reset cause register, and properly implementing a watchdog timer are essential skills for building reliable embedded systems.
Key points to remember:
- Always read and clear the reset cause flags at the start of
main() - Enable the watchdog timer in production firmware — it is your safety net
- Feed the watchdog from the main loop only, never from interrupts
- Implement crash counting to detect repeated failures
- Know the boot sequence so you can debug startup issues
- Microcontrollers: A Beginner’s Guide
- Introduction to Embedded Systems
- Registers in Microcontrollers
- Polling vs Interrupts in Embedded Systems
Next, read about Brownout Detection to understand how voltage drops interact with the reset system. Also see Power Supply and Voltage Regulators for designing robust power circuits, and Sleep Modes and Low-Power Techniques for understanding how resets interact with low-power modes.

Vivek Bhageria — Lead Firmware R&D Engineer, 12+ years. Ex-Bosch (automotive powertrain), MusicTribe (real-time audio), medical devices. M.Tech BITS Pilani. I write at NerdyElectronics — practical, register-level embedded systems for engineers who want to understand what’s actually happening under the hood.





