Repository navigation
Chapter 16
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" 확인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
하도록 만든다.
IWDG는 STM32의 main system clock이 아니라 LSI 저속 내부 oscillator를 사용한다.
Main CPU Clock
X
LSI
↓
IWDG
↓
Independent Counter
따라서 main clock 쪽에 문제가 생겨도 watchdog은 독립적으로 동작할 수 있다.
| Register | 역할 |
|---|---|
| IWDG->KR | Watchdog key command |
| IWDG->PR | Prescaler |
| IWDG->RLR | Reload value |
| IWDG->SR | Prescaler / reload update status |
| RCC->CSR | Reset cause 확인 |
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
로 기억하면 된다.
공식:
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이 발생한다.
처음 원서 설정은:
Prescaler = /4였다.
계산:
4 × 4096
────────
32000
≈ 0.512 sec
즉 약 0.5초다.
실기에서는 이 값이 너무 짧아서 IWDG reset 직후 사용자가 버튼을 누르기도 전에 다시 timeout이 발생해 재reset loop가 발생했다.
중간에:
/256
→ 약 33초도 시험했지만 너무 길었다.
최종:
/32
→ 약 4.1초로 결정했다.
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 코드를 재사용한다.
#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__ */
#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->PR
-
IWDG->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->KR = IWDG_KEY_WR_ACCESS_ENABLE;
/*
* --------------------------------------------------------
* Step 3. Configure Prescaler
* --------------------------------------------------------
*
* PR = 0x3
*
* LSI clock division:
*
* /32
*/
IWDG->PR = IWDG_PRESCALER_32;
/*
* --------------------------------------------------------
* Step 4. Configure Reload Value
* --------------------------------------------------------
*
* RLR = 0xFFF
*
* Timeout estimate:
*
* (32 × 4096) / 32000
*
* ≈ 4.096 seconds
*/
IWDG->RLR = IWDG_RELOAD_VAL;
/*
* --------------------------------------------------------
* Step 5. Wait Until PR / RLR Update Completes
* --------------------------------------------------------
*
* IWDG->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->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
);와 동일하다.
다음 코드를 썼다고 해서:
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)
{
}를 사용한다.
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->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->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 < 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.
*/
}
}
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;
}
}
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;
}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();
}
}
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되지 않는다.
B1:
B1 Press
↓
PC13 Rising Edge
↓
EXTI13
↓
ISR
↓
g_btn_press = 1main:
g_btn_press == 1
↓
Skip Reload
↓
No 0xAAAA
↓
IWDG Counter
↓
0
↓
SYSTEM RESETReset이 발생하면 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.....만약:
RCC->CSR = RCC_CSR_RMVF;를 하지 않는다면 과거 reset 정보가 남을 수 있다.
그러면 나중에 다른 이유로 reset되어도:
Previous IWDG Flag
still present때문에 잘못된 reset cause를 판단할 수 있다.
따라서:
Read Cause
↓
Handle Cause
↓
Clear Cause Flags순서를 사용한다.
초기 설정:
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이 반복됐다.
/4
≈ 0.5 sec문제:
Too Short
→ repeated reset/256
≈ 33 sec문제:
Too Long
→ test takes too long/32
≈ 4.1 sec결과:
Long enough for user interaction
+
Short enough for practical test로 사용했다.
Button interrupt가 발생했다고 ISR 안에서:
UART
Delay
Complex Logic을 모두 수행하지 않는다.
현재 구조:
ISR
↓
g_btn_press = 1
↓
Returnmain:
Check flag
↓
Change application behavior이 방식은 ISR을 짧게 유지할 수 있다.
Ch14에서 바로 callback 안에서 UART를 실행했던 단순 구조보다 한 단계 더 실무적인 pattern이다.
실제 실습:
Build : PASS
Download : PASS
Run : PASSProgram size:
text = 1536 bytes
data = 0 bytes
bss = 2568 bytes경고 없이 build 완료.
정상 동작:
LD1 Toggle
LD1 Toggle
LD1 Toggle
...→ watchdog이 계속 reload되고 있음.
B1 press:
B1 Press
↓
LED normal activity stops
↓
approximately 4 sec
↓
System ResetReset 후:
LD1 ONUART:
RESET was caused by IWDG.....가 한 번 출력되는 것까지 확인했다.
다시 B1을 누르면 다음 cycle로 진행했다.
확인:
IWDG->PRExpected:
0x3
→ /32확인:
IWDG->RLRExpected:
0xFFF확인:
IWDG->SRconfiguration 완료 후:
PVU = 0
RVU = 0이어야 한다.
watchdog reset 후:
RCC->CSR에서:
IWDGRSTF = 1을 확인한다.
check_reset_source()가 처리한 뒤 reset flag가 clear되는지도 확인한다.
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....."이다.
LSI
↓
IWDG Prescaler /32
↓
12-bit Counter
↓
RLR = 0xFFF
↓
≈ 4.1 sec timeout정상 상태:
0xAAAA
↓
Reload
↓
System Alive오류 상태:
No Reload
↓
Counter = 0
↓
Hardware ResetIWDG는 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를 사용한다.