Skip to content

Chapter 16

Hongjayeong-02 edited this page Oct 5, 2026 · 1 revision

16. Independent Watchdog (IWDG)

Goal

STM32F439ZI의 Independent Watchdog Timer(IWDG) 를 직접 설정하고, software가 정상적으로 동작하는 동안에는 watchdog counter를 주기적으로 reload한다.

B1을 누르면 의도적으로 reload를 중단하여 약 4초 후 hardware reset이 발생하도록 만든다.

Normal Operation

main() ↓ IWDG Reload ↓ IWDG Reload ↓ IWDG Reload ↓ System keeps running

B1을 누르면:

B1 Press
↓
g_btn_press = 1
↓
Stop IWDG Reload
↓
Counter ↓↓↓
↓
0
↓
Hardware Reset
↓
main() Restart
↓
RCC->CSR.IWDGRSTF
↓
"IWDG reset" 확인

1. What is a Watchdog?

Embedded software가 다음과 같은 상태에 빠질 수 있다.

Infinite Loop
Deadlock
Bus Hang
Unexpected Software Fault

이때 software 자체가 이미 멈췄다면 software가 자기 자신을 복구시키기 어렵다.

Watchdog은 별도의 hardware counter를 계속 감소시키고:

정상 software
→ timeout 전에 counter reload

software failure → reload 없음 → counter = 0 → system reset

하도록 만든다.


2. Why Independent Watchdog?

IWDG는 STM32의 main system clock이 아니라 LSI 저속 내부 oscillator를 사용한다.

Main CPU Clock
X

LSI ↓ IWDG ↓ Independent Counter

따라서 main clock 쪽에 문제가 생겨도 watchdog은 독립적으로 동작할 수 있다.


3. Important Registers

Register 역할
IWDG->KR Watchdog key command
IWDG->PR Prescaler
IWDG->RLR Reload value
IWDG->SR Prescaler / reload update status
RCC->CSR Reset cause 확인

4. IWDG Key Register

IWDG는 일반적인 control bit 대신 특별한 key 값을 KR에 write해서 제어한다.

0xCCCC
→ IWDG Start

0x5555 → PR / RLR Write Access Enable

0xAAAA → Counter Reload

즉:

START
0xCCCC

CONFIG UNLOCK 0x5555

FEED / RELOAD 0xAAAA

로 기억하면 된다.


5. Timeout Calculation

공식:

Timeout
≈
Prescaler × (RLR + 1)
─────────────────────
LSI Clock

이번 실습:

LSI ≈ 32 kHz

Prescaler = /32

RLR = 0xFFF = 4095

따라서:

32 × (4095 + 1)
────────────────
32000

= 32 × 4096 ───────── 32000

≈ 4.096 sec

즉 약:

4.1 seconds

동안 reload가 없으면 reset이 발생한다.


6. Why /32?

처음 원서 설정은:

Prescaler = /4

였다.

계산:

4 × 4096
────────
32000

≈ 0.512 sec

즉 약 0.5초다.

실기에서는 이 값이 너무 짧아서 IWDG reset 직후 사용자가 버튼을 누르기도 전에 다시 timeout이 발생해 재reset loop가 발생했다.

중간에:

/256
→ 약 33초

도 시험했지만 너무 길었다.

최종:

/32
→ 약 4.1초

로 결정했다.


7. Practice Code

Project:

Inc/
├── gpio.h
├── gpio_exti.h
├── iwdg.h
└── uart.h

Src/ ├── gpio.c ├── gpio_exti.c ├── iwdg.c ├── uart.c └── main.c

Ch07 GPIO, Ch10 UART, Ch14 EXTI 코드를 재사용한다.


8. iwdg.h

#ifndef IWDG_H__
#define IWDG_H__

#include <stdint.h> #include "stm32f4xx.h"

/*

  • ============================================================
  • IWDG Reload Key
  • ============================================================
  • Writing 0xAAAA to IWDG->KR reloads the watchdog
  • counter from IWDG->RLR.
  • Application code uses this periodically to tell
  • the watchdog:
  • "software is still alive"
    

*/ #define IWDG_KEY_RELOAD 0x0000AAAAU

/*

  • Initialize and start Independent Watchdog. */ void iwdg_init(void);

#endif /* IWDG_H__ */


9. iwdg.c

#include "iwdg.h"

/*

  • ============================================================
  • IWDG Key Values
  • ============================================================ */

/*

  • Start Independent Watchdog.
  • After this value is written,
  • the watchdog begins counting down. */ #define IWDG_KEY_ENABLE 0x0000CCCCU

/*

  • Unlock write access to:
  • IWDG-&gt;PR
    
  • IWDG-&gt;RLR
    

*/ #define IWDG_KEY_WR_ACCESS_ENABLE 0x00005555U

/*

  • ============================================================
  • Timeout Configuration
  • ============================================================ */

/*

  • PR = 0x3
  • Prescaler:
  • LSI / 32
    
  • Original book example used /4.
  • Final board test uses /32 because /4 produced
  • an approximately 0.5-second watchdog timeout,
  • which was too short for the reset test flow. */ #define IWDG_PRESCALER_32 0x00000003U

/*

  • Maximum 12-bit reload value.
  • 0xFFF
  • =
  • 4095 */ #define IWDG_RELOAD_VAL 0x00000FFFU

/*

  • Private helper.
  • Returns 1 when both prescaler and reload-value
  • updates have completed. */ static uint8_t isIwdg_ready(void);

/*

  • ============================================================
  • IWDG Initialization
  • ============================================================ */

void iwdg_init(void) { /* * -------------------------------------------------------- * Step 1. Start IWDG * -------------------------------------------------------- * * KR = 0xCCCC * * Once started, IWDG begins counting down. */ IWDG->KR = IWDG_KEY_ENABLE;

/*
 * --------------------------------------------------------
 * Step 2. Unlock PR / RLR Write Access
 * --------------------------------------------------------
 *
 * KR = 0x5555
 *
 * The prescaler and reload registers are protected
 * against accidental modification.
 */
IWDG-&gt;KR = IWDG_KEY_WR_ACCESS_ENABLE;


/*
 * --------------------------------------------------------
 * Step 3. Configure Prescaler
 * --------------------------------------------------------
 *
 * PR = 0x3
 *
 * LSI clock division:
 *
 *     /32
 */
IWDG-&gt;PR = IWDG_PRESCALER_32;


/*
 * --------------------------------------------------------
 * Step 4. Configure Reload Value
 * --------------------------------------------------------
 *
 * RLR = 0xFFF
 *
 * Timeout estimate:
 *
 *     (32 × 4096) / 32000
 *
 *     ≈ 4.096 seconds
 */
IWDG-&gt;RLR = IWDG_RELOAD_VAL;


/*
 * --------------------------------------------------------
 * Step 5. Wait Until PR / RLR Update Completes
 * --------------------------------------------------------
 *
 * IWDG-&gt;SR contains:
 *
 *     PVU
 *     RVU
 *
 * These flags are set while new values
 * are being transferred internally.
 *
 * Continue only when BOTH become zero.
 */
while (isIwdg_ready() != 1U)
{
}


/*
 * --------------------------------------------------------
 * Step 6. Reload Counter
 * --------------------------------------------------------
 *
 * KR = 0xAAAA
 *
 * Load the new RLR value into the internal
 * watchdog down-counter.
 *
 * From here the approximately 4.1-second
 * timeout is active.
 */
IWDG-&gt;KR = IWDG_KEY_RELOAD;

}

/*

  • ============================================================
  • Check IWDG Configuration Update Status
  • ============================================================ */

static uint8_t isIwdg_ready(void) { /* * IWDG_SR_PVU * * Prescaler Value Update * * IWDG_SR_RVU * * Reload Value Update * * If either bit is still set: * * configuration update is still in progress. * * If both are zero: * * PR / RLR configuration is ready. */ if (READ_BIT( IWDG->SR, IWDG_SR_PVU | IWDG_SR_RVU ) == 0U) { return 1U; }

return 0U;

}

원본 한 줄 형태:

return (uint8_t)(
(READ_BIT(IWDG->SR,
IWDG_SR_PVU | IWDG_SR_RVU) == 0U)
? 1U
: 0U
);

와 동일하다.


10. Why Wait for PVU / RVU?

다음 코드를 썼다고 해서:

IWDG->PR  = IWDG_PRESCALER_32;
IWDG->RLR = IWDG_RELOAD_VAL;

IWDG 내부 counter logic에 즉시 반영됐다고 가정하면 안 된다.

Hardware 내부에서는:

CPU Register Write
↓
Update in progress
↓
PVU / RVU = 1
↓
Internal configuration updated
↓
PVU / RVU = 0

과정이 있을 수 있다.

그래서:

while (isIwdg_ready() != 1U)
{
}

를 사용한다.


11. main.c

Wiki correction 1

기존 실습 코드에는 uint8_t g_btn_press;로 기록되어 있었다.

이 변수는 ISR에서 write하고 main에서 read하므로 최종 Wiki에서는 compiler에게 비동기 변경 가능성을 명확히 알리기 위해 volatile을 사용한다.

#include <stdint.h>

#include "gpio.h" #include "gpio_exti.h" #include "iwdg.h" #include "uart.h"

/*

  • ============================================================
  • Button Event Flag
  • ============================================================
  • Modified by:
  • EXTI15_10_IRQHandler()
    
  • Read by:
  • main()
    
  • Because ISR and normal code share this variable,
  • volatile is used. */ volatile uint8_t g_btn_press = 0U;

/*

  • Check whether the previous reset
  • was caused by the IWDG. */ static void check_reset_source(void);

/*

  • Set the button-event flag.
  • Keep ISR-side processing short. */ static void exti_callback(void);

int main(void) { /* * -------------------------------------------------------- * Step 1. Initialize UART * -------------------------------------------------------- * * Used to report watchdog reset cause. */ uart_init();

/*
 * --------------------------------------------------------
 * Step 2. Initialize LD1
 * --------------------------------------------------------
 *
 * LD1 → PB0
 */
led_init();


/*
 * --------------------------------------------------------
 * Step 3. Initialize B1 EXTI
 * --------------------------------------------------------
 *
 * B1 → PC13
 *
 * Button press generates EXTI interrupt.
 */
pc13_exti_init();


/*
 * --------------------------------------------------------
 * Step 4. Check Previous Reset Cause
 * --------------------------------------------------------
 *
 * RCC-&gt;CSR retains information that tells us
 * why the MCU was reset.
 *
 * If IWDGRSTF is set:
 *
 *     previous reset was caused by watchdog.
 */
check_reset_source();


/*
 * --------------------------------------------------------
 * Step 5. Start IWDG
 * --------------------------------------------------------
 *
 * Final configuration:
 *
 *     Prescaler = /32
 *     RLR       = 0xFFF
 *     Timeout   ≈ 4.1 sec
 */
iwdg_init();


while (1)
{
    /*
     * ----------------------------------------------------
     * Normal Case
     * ----------------------------------------------------
     *
     * If B1 has NOT been pressed:
     *
     *     g_btn_press = 0
     *
     * continue feeding the watchdog.
     */
    if (g_btn_press != 1U)
    {
        /*
         * -----------------------------------------------
         * Feed the Watchdog
         * -----------------------------------------------
         *
         * KR = 0xAAAA
         *
         * Reload internal counter from RLR.
         *
         * This prevents the watchdog from reaching zero.
         */
        IWDG-&gt;KR = IWDG_KEY_RELOAD;


        /*
         * Toggle LED as a visible indication
         * that main() is still running normally.
         */
        led_toggle();


        /*
         * Intentional busy-wait delay.
         *
         * This delay must remain much shorter than
         * the approximately 4.1-second watchdog timeout.
         *
         * volatile prevents the compiler from simply
         * deleting an empty loop during optimization.
         */
        for (volatile int i = 0;
             i &lt; 90000;
             i++)
        {
        }
    }


    /*
     * ----------------------------------------------------
     * Failure Simulation
     * ----------------------------------------------------
     *
     * If B1 is pressed:
     *
     *     g_btn_press = 1
     *
     * The if block above is skipped.
     *
     * Therefore:
     *
     *     NO KR = 0xAAAA
     *
     * is written.
     *
     * The watchdog counter keeps decreasing:
     *
     *     ... → 3 → 2 → 1 → 0
     *
     * At zero:
     *
     *     Hardware Reset
     *
     * No software reset instruction is executed.
     */
}

}


12. check_reset_source()

static void check_reset_source(void)
{
/*
* --------------------------------------------------------
* Check IWDG Reset Flag
* --------------------------------------------------------
*
* RCC_CSR_IWDGRSTF:
*
*     1
*
* means the previous system reset was
* generated by the Independent Watchdog.
/
if ((RCC->CSR & RCC_CSR_IWDGRSTF)
== RCC_CSR_IWDGRSTF)
{
/
* ----------------------------------------------------
* Clear Reset Flags
* ----------------------------------------------------
*
* RMVF:
*
*     Remove Reset Flags
*
* If this is not cleared, a later reset could
* incorrectly appear to have been caused by IWDG.
*/
RCC->CSR = RCC_CSR_RMVF;
    /*
     * Turn LD1 ON so the watchdog-reset state
     * is visually obvious.
     */
    led_on();


    /*
     * Report reset cause through UART.
     */
    uart_write_string(
        "RESET was caused by IWDG.....\r\n"
    );


    /*
     * ----------------------------------------------------
     * Wait for User Confirmation
     * ----------------------------------------------------
     *
     * After an IWDG reset, wait until B1 is
     * pressed again before proceeding.
     *
     * This allows the reset message to be observed.
     *
     * IMPORTANT:
     *
     * The practical experiment showed that the
     * watchdog remains relevant through this sequence,
     * which is why an excessively short timeout such as
     * approximately 0.5 seconds caused repeated resets.
     */
    while (g_btn_press != 1U)
    {
    }


    /*
     * Consume the button event before
     * entering the next watchdog cycle.
     */
    g_btn_press = 0U;
}

}


13. EXTI Callback

static void exti_callback(void)
{
/*
* Do only the minimum work inside the ISR path.
*
* The main loop decides what action to take
* after seeing this flag.
/
g_btn_press = 1U;
}

14. EXTI Handler

Wiki correction 2

기존 실습 기록:

EXTI->PR |= LINE13;

Ch14에서 정리했듯 EXTI->PR은 W1C register이므로 최종 Wiki에서는 직접 assignment를 사용한다.

void EXTI15_10_IRQHandler(void)
{
/
* EXTI10 ~ EXTI15 share one IRQ handler.
*
* Confirm that EXTI13 caused this interrupt.
/
if ((EXTI->PR & LINE13) != 0U)
{
/
* Clear EXTI13 pending bit.
*
* PR is W1C:
*
*     Write 1 → Clear
*/
EXTI->PR = LINE13;
    /*
     * Set button event flag.
     */
    exti_callback();
}

}


15. Normal Runtime Flow

Power On
↓
UART Init
↓
LED Init
↓
EXTI Init
↓
Check Reset Cause
↓
IWDG Init
↓
┌───────────────────────┐
│ g_btn_press == 0      │
│                       │
│ KR = 0xAAAA           │
│ LED Toggle            │
│ Delay                 │
│                       │
│ Repeat                │
└───────────────────────┘

Watchdog counter는 매 loop마다:

Reload
↓
0xFFF
↓
Counting...
↓
Reload
↓
0xFFF

가 반복된다.

따라서 reset되지 않는다.


16. Failure Simulation Flow

B1:

B1 Press
↓
PC13 Rising Edge
↓
EXTI13
↓
ISR
↓
g_btn_press = 1

main:

g_btn_press == 1
↓
Skip Reload
↓
No 0xAAAA
↓
IWDG Counter
↓
0
↓
SYSTEM RESET

17. Reset Cause Detection

Reset이 발생하면 CPU는 다시:

Reset_Handler
↓
Startup
↓
main()

부터 실행한다.

그러나 RCC->CSR에는 reset cause flag가 남아 있어서:

RCC->CSR & RCC_CSR_IWDGRSTF

로 직전 reset 원인을 확인할 수 있다.

흐름:

IWDG Timeout
↓
Reset
↓
main()
↓
check_reset_source()
↓
IWDGRSTF == 1
↓
UART:
RESET was caused by IWDG.....

18. Why Clear Reset Flags?

만약:

RCC->CSR = RCC_CSR_RMVF;

를 하지 않는다면 과거 reset 정보가 남을 수 있다.

그러면 나중에 다른 이유로 reset되어도:

Previous IWDG Flag
still present

때문에 잘못된 reset cause를 판단할 수 있다.

따라서:

Read Cause
↓
Handle Cause
↓
Clear Cause Flags

순서를 사용한다.


19. 실기에서 발견한 문제

초기 설정:

Prescaler = /4
Timeout ≈ 0.5 sec

이었다.

B1을 눌러 watchdog reset을 발생시켰더니 terminal에서:

RESET was caused by IWDG.....
RESET was caused by IWDG.....
RESET was caused by IWDG.....
...

처럼 빠르게 반복되는 현상이 발생했다.

즉:

IWDG Reset
↓
main Restart
↓
Reset Cause Message
↓
Wait for User
↓
Before User Can Respond
↓
IWDG Timeout Again
↓
Reset

이 반복됐다.


20. Timeout Tuning

First

/4
≈ 0.5 sec

문제:

Too Short
→ repeated reset

Second

/256
≈ 33 sec

문제:

Too Long
→ test takes too long

Final

/32
≈ 4.1 sec

결과:

Long enough for user interaction
+
Short enough for practical test

로 사용했다.


21. Why ISR Only Sets a Flag?

Button interrupt가 발생했다고 ISR 안에서:

UART
Delay
Complex Logic

을 모두 수행하지 않는다.

현재 구조:

ISR
↓
g_btn_press = 1
↓
Return

main:

Check flag
↓
Change application behavior

이 방식은 ISR을 짧게 유지할 수 있다.

Ch14에서 바로 callback 안에서 UART를 실행했던 단순 구조보다 한 단계 더 실무적인 pattern이다.


22. Build Result

실제 실습:

Build    : PASS
Download : PASS
Run      : PASS

Program size:

text = 1536 bytes
data = 0 bytes
bss  = 2568 bytes

경고 없이 build 완료.


23. Board Verification

정상 동작:

LD1 Toggle
LD1 Toggle
LD1 Toggle
...

→ watchdog이 계속 reload되고 있음.

B1 press:

B1 Press
↓
LED normal activity stops
↓
approximately 4 sec
↓
System Reset

Reset 후:

LD1 ON

UART:

RESET was caused by IWDG.....

가 한 번 출력되는 것까지 확인했다.

다시 B1을 누르면 다음 cycle로 진행했다.


24. Debugger Verification

IWDG Prescaler

확인:

IWDG->PR

Expected:

0x3
→ /32

Reload Value

확인:

IWDG->RLR

Expected:

0xFFF

Status

확인:

IWDG->SR

configuration 완료 후:

PVU = 0
RVU = 0

이어야 한다.


Reset Cause

watchdog reset 후:

RCC->CSR

에서:

IWDGRSTF = 1

을 확인한다.

check_reset_source()가 처리한 뒤 reset flag가 clear되는지도 확인한다.


25. Screenshots to Add

images/ch16_build.png
images/ch16_iwdg_registers.png
images/ch16_normal_led.gif
images/ch16_before_timeout.png
images/ch16_reset_uart.png
images/ch16_reset_flag.png

가장 중요한 evidence:

B1 Press
↓
~4 sec wait
↓
System Reset
↓
RCC IWDGRSTF
↓
"RESET was caused by IWDG....."

이다.


26. Key Points

LSI
↓
IWDG Prescaler /32
↓
12-bit Counter
↓
RLR = 0xFFF
↓
≈ 4.1 sec timeout

정상 상태:

0xAAAA
↓
Reload
↓
System Alive

오류 상태:

No Reload
↓
Counter = 0
↓
Hardware Reset
  • IWDG는 LSI를 사용하는 독립 watchdog이다.

  • 0xCCCC는 IWDG start key다.

  • 0x5555는 PR/RLR write access enable key다.

  • 0xAAAA는 watchdog reload key다.

  • 이번 실습은 /32, RLR=0xFFF를 사용해 약 4.1초 timeout을 구성했다.

  • PR/RLR 설정 후 PVU/RVU가 clear될 때까지 기다린다.

  • 정상 software는 timeout 전에 주기적으로 0xAAAA를 write한다.

  • B1을 누르면 reload를 중지해 의도적으로 watchdog reset을 발생시킨다.

  • RCC->CSR.IWDGRSTF로 직전 reset 원인을 확인한다.

  • 확인 후 RMVF로 reset cause flags를 clear한다.

  • 실제 board에서 watchdog timeout → hardware reset → reset cause UART 출력까지 검증 완료했다.

  • 최종 Wiki에서는 ISR 공유 변수에 volatile, EXTI W1C clear에 direct assignment를 사용한다.

Clone this wiki locally