내용요약
현상: MCU가 딥 슬립(Deep Sleep)에서 깨어난(Wake-up) 직후, 하드폴트(HardFault_Handler)혹은 비정상적인 동작.
원인: 고속 외부 클럭(HSE)이나 PLL이 안정화되기 전에, CPU가 내부 오실레이터(예: HSI)를 사용해 즉시 동작을 재개하기 때문임.
해결: 웨이크업 명령(WFI / WFE) 직후, 하드웨어 타임아웃 메커니즘을 포함한 하드웨어 플래그 기반의 방어적 폴링 루프(Polling loop)를 구현해야 함.
Deep Sleep Wake-up 직후 시스템 오동작 및 HardFault 발생 증상
MCU가 Deep Sleep(Stop/Standby) 모드에서 깨어날 때, 시스템이 무작위로 다운되거나 HardFault, BusFault를 유발하는 현상이 발생합니다.
특히 해외 포럼이나 구글 검색 시 개발자들이 자주 겪는 전형적인 에러 패턴은 다음과 같습니다.
- HardFault_Handler 진입 후 CFSR (Configurable Fault Status Register)에 NOCP (No Coprocessor Usage Fault) 또는 INVSTATE (Invalid State Usage Fault) 플래그가 셋(Set)되는 현상.
- Deep Sleep Wake-up 직후 호출된 첫 번째 주변장치(Peripheral) 초기화 함수에서 Memory Management Fault 발생.
- USART_SendData() 또는 SPI_Transmit() 호출 시 Baud Rate이 비정상적으로 틀어져 쓰레기 문자(Garbage Data)가 출력되는 현상.
이러한 증상은 디버거(J-Link/ST-Link)를 연결하고 스텝 오버(Step Over)로 한 줄씩 실행할 때는 정상 동작하다가, 프리 런(Free Run) 상태에서만 간헐적으로 재현되는 특징이 있어 추적이 까다롭습니다.
MCU 클록 시스템(HSE/PLL)과 Wake-up 시퀀스의 근본적인 원인 분석
근본 원인은 하드웨어 클록 스타트업 시간(Clock Startup Time)과 CPU 파이프라인(Pipeline) 속도의 차이에 있습니다.
대부분의 ARM Cortex-M 계열 MCU는 Deep Sleep 모드에 진입하면 전력 소모를 줄이기 위해 고속 외부 클록(HSE, High-Speed External)과 PLL(Phase-Locked Loop)을 자동으로 끕니다(Disable). 이후 인터럽트(Interrupt)가 발생하여 시스템이 깨어날 때(Wake-up), MCU는 안정화 시간이 따로 필요 없는 내부 고속 고주파 발진기(HSI, High-Speed Internal)를 임시 시스템 클록으로 잡고 즉시 깨어납니다.
문제는 WFI(Wait For Interrupt) 명령어가 해제되는 순간 발생합니다.
$$f_{CPU} \gg f_{HSE_Stable}$$
CPU는 HSI 클록으로 구동되자마자 WFI 바로 다음 라인의 C 코드를 바로 실행합니다. 만약 펌웨어 내부적으로 HSE와 PLL이 준비 완료(Ready)될 때까지 대기하지 않고 즉시 원래의 시스템 클록 설정을 복구하려 하거나, 시스템 주파수가 유효하지 않은 상태에서 주변장치 레지스터에 접근하면 버스 클록 타이밍이 뒤틀리게 됩니다. 결과적으로 CPU 파이프라인에 잘못된 Opcode가 로드되어 Instruction Fetch Fault 혹은 하드웨어 락업(Lockup) 상태로 빠지게 되는 것입니다.
무한 루프 및 HardFault를 유발하는 잘못된 Clock 복구 C 코드 예시 (Bad Case)
아래 코드는 저전력 모드 해제 후 하드웨어 안정화 상태를 검증하지 않고 즉시 레지스터를 제어하여 레지스터 폴링(Polling) 중 무한 루프에 갇히거나 HardFault를 유발하는 오류 예시입니다.
void LowPower_Wakeup_Handler_BAD(void)
{
/* 1. Resume flash access and voltage regulator settings */
PWR->CR1 &= ~PWR_CR1_LPR;
/* 2. Enable HSE and PLL without check */
RCC->CR |= RCC_CR_HSEON;
RCC->CR |= RCC_CR_PLLON;
/* 3. BAD: Switch system clock source to PLL immediately */
/* If PLL is not locked yet, the CPU will freeze or trigger a BusFault */
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* 4. Peripheral access right after unsafe clock switch */
/* This will crash because the peripheral clock is unstable */
UART_Log_Init();
}
완벽한 클록 안정화를 위한 방어적 하드웨어 타이웃 대기 루프 구현 (Good Case)
문제를 방지하기 위해서는 HSE 및 PLL 레지스터의 Ready 플래그를 반드시 확인해야 하며, 하드웨어 결함으로 인한 영구 락업을 방지하기 위해 Software Timeout Counter를 적용한 방어적 프로그래밍(Defensive Programming)이 필요합니다.
#define CLK_STARTUP_TIMEOUT ((uint32_t)0x000FFFFF)
uint8_t System_Clock_Restore_Safe(void)
{
uint32_t timeout_counter = 0;
/* 1. Enable High-Speed External Clock (HSE) */
RCC->CR |= RCC_CR_HSEON;
/* 2. Wait until HSE is fully stabilized with a defensive timeout loop */
while ((RCC->CR & RCC_CR_HSERDY) == 0)
{
timeout_counter++;
if (timeout_counter > CLK_STARTUP_TIMEOUT)
{
/* Handle HSE Startup Error (e.g., Crystal HW failure) */
return CLK_RESTORE_ERROR_HSE;
}
}
/* 3. Enable Phase-Locked Loop (PLL) */
RCC->CR |= RCC_CR_PLLON;
timeout_counter = 0;
/* 4. Wait until PLL is locked */
while ((RCC->CR & RCC_CR_PLLRDY) == 0)
{
timeout_counter++;
if (timeout_counter > CLK_STARTUP_TIMEOUT)
{
/* Handle PLL Lock Error */
return CLK_RESTORE_ERROR_PLL;
}
}
/* 5. Switch System Clock Source to PLL safely */
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* 6. Wait until PLL is verified as the system clock source */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
/* Safe polling for clock switch acknowledgement */
}
return CLK_RESTORE_SUCCESS;
}
void LowPower_Wakeup_Handler_GOOD(void)
{
/* Execute immediately after WFI instruction */
if (System_Clock_Restore_Safe() != CLK_RESTORE_SUCCESS)
{
/* Fallback: Run emergency diagnostic or Safe Mode with HSI clock */
Emergency_Fallback_Handler();
return;
}
/* Peripherals are now safe to access */
UART_Log_Init();
printf("System successfully resumed at full speed.\n");
}
핵심 수정 포인트
- 하드웨어 플래그 검증: RCC_CR_HSERDY 및 RCC_CR_PLLRDY 비트를 순차적으로 폴링하여 물리적인 오실레이터의 발진 상태를 보장합니다.
- 타임아웃 카운터 도입: 주파수 미치밀, 오실레이터 파손 등으로 플래그가 셋되지 않더라도 timeout_counter를 통해 무한 루프 방지 및 안전 무드(Emergency Fallback) 전환을 유도합니다.
- SWS(System Clock Switch Status) 체크: 클록 소스 변경 명령 후 레지스터 상태(RCC_CFGR_SWS)를 통해 하드웨어 스위칭이 완료되었음을 검증합니다.
저전력 웨이크업 디버깅 및 트러블슈팅 가이드
- 레지스터 플래그 강제 모니터링 (SFR View): Wake-up 직후 브레이크포인트(Breakpoint)를 잡았을 때 레지스터 뷰어에서 RCC_CR의 HSERDY/PLLRDY가 이미 1로 변해있다면 타이밍 이슈일 확률이 큽니다. 이 경우 코드를 수정하여 인터럽트 서비스 루틴(ISR) 내부가 아닌 메인 루프의 WFI 직후에 복구 루틴을 배치해야 합니다.
- MAP 파일 및 하드폴트 분석 (BFAR / MMFAR 활용): BusFault 유발 시 MCU 내부 레지스터인 BFAR (Bus Fault Address Register)을 확인하십시오. Wake-up 직후 접근했던 특정 주변장치(예: 0x40013800 - USART1)의 베이스 주소가 찍혀있다면, 해당 주변장치 클록 Enable(Peripheral Clock Enable) 명령이 클록 복구보다 먼저 실행되었음을 의미합니다.
- GPIO 토글을 이용한 오실로스코프 측정: 소프트웨어 디버거는 클록 타이밍을 왜곡시킵니다. WFI 바로 다음 라인에서 특정 GPIO를 HIGH로 만들고, 클록 안정화 루프가 끝난 직후 LOW로 떨어뜨리도록 코딩한 뒤, 오실로스코프 채널 1은 외부 크리스탈 핀, 채널 2는 GPIO 핀에 물려 HSE 스타트업 타임 마진을 눈으로 직접 확인하는 것이 가장 확실한 방법입니다.
'Troubleshooting' 카테고리의 다른 글
| [임베디드 저전력] Sleep/Stop 모드 진입 시 전류 소모 안 줄어드는 원인 (Pending Flag 클리어) (0) | 2026.06.20 |
|---|---|
| [GPIO 설정] 플로팅(Floating) 노이즈로 인한 핀 오동작과 내부 풀업/풀다운 저항 제어 (0) | 2026.06.19 |
| [MCU 설계] Peripheral 기능 오동작을 유발하는 클록(Clock) 및 GPIO 초기화 순서 오류 해결법 (0) | 2026.06.18 |
| [GPIO 디바운싱] 버튼 채터링(Contact Bounce)으로 인한 외부 인터럽트(EXTI) 중복 실행 소프트웨어 방어적 설계법 (0) | 2026.06.17 |
| [MCU 설계] 인터럽트 서비스 루틴(ISR) 내 delay/printf 사용 시 발생하는 지연 문제와 대책 (0) | 2026.06.15 |