Skip to content

Bare metal C Ch18 StandbyMode

Hongjayeong-02 edited this page Sep 21, 2026 · 2 revisions

Ch18. Power Management — Standby + PA0 Wakeup

한 줄 요약: B1 interrupt로 Standby에 진입하고 PA0(WKUP)을 wakeup source로 구성한다. Standby 진입은 검증했지만 PA0 wakeup은 현재 미검증 상태다.

1. 핵심 이론

Notion에서는 STM32 low-power mode를 Sleep / Stop / Standby의 세 단계로 정리했다.

Mode | 특징 -- | -- Sleep | Core 중심으로 정지 Stop | 더 깊은 저전력 Standby | 가장 깊은 저전력, wakeup 시 reset 흐름

이번 실습은 Standby를 사용한다.

Cortex-M과 STM32 설정의 결합

Standby 진입에는 서로 다른 계층의 설정이 함께 필요하다.

SCB->SCR.SLEEPDEEP
= Cortex-M core에게 deep sleep 요청

PWR->CR.PDDS = STM32에게 deep sleep 중 Standby 선택

그 후:

__WFI();

로 sleep 진입을 요청한다.

Standby wakeup 이후

Notion 기록에서는 Standby에서 깨어나면 이전 코드 위치로 이어서 실행하는 것이 아니라 reset과 같은 흐름으로 main()부터 다시 시작한다고 정리했다.

따라서 PWR->CSR의:

  • SBF

  • WUF

flag를 이용해 reset/wakeup 원인을 확인한다.

Wakeup pin

PA0을 WKUP input으로 사용한다. 별도 pull-up/down 없이 외부 wiring으로 input state를 만든다.

2. 동작 흐름

Normal boot
│
├─ UART init
├─ LED init
├─ PA0 wakeup pin init
└─ reset source 확인
│
▼
B1 / EXTI13
│
▼
LED OFF
│
▼
Standby setup
│
├─ WUF clear
├─ WKUP enable
├─ PDDS
├─ SLEEPDEEP
└─ __WFI()
│
▼
Standby
│
PA0 wakeup
│
▼
Reset
│
▼
main()

3. 실제 프로젝트 구조


Ch18_pm/

├── Inc/gpio.h

├── Src/gpio.c

├── Inc/gpio_exti.h

├── Src/gpio_exti.c

├── Inc/standby_mode.h

├── Src/standby_mode.c

├── Inc/uart.h

├── Src/uart.c

├── Src/main.c

syscalls.c, sysmem.c, startup file, linker script, CubeIDE metadata/build output은 Ch04에서 다룬 공통 기반이므로 이 페이지에서는 반복하지 않는다. 아래에는 이 chapter 기능 구현에 직접 사용한 source/header를 전부 기록한다.

4. 전체 코드

Inc/gpio.h

#ifndef GPIO_H
#define GPIO_H

#include <stdbool.h>

/* ---- LED (PB0, LD1 녹색) ---- */ void led_init(void); void led_on(void); void led_off(void);

/* ---- Push Button (PC13, B1) ---- */ void button_init(void); bool get_btn_state(void);

void led_toggle(void); /* Ch08에서 추가 */

#endif /* GPIO_H */

Src/gpio.c

#include "stm32f4xx.h"
#include "gpio.h"

#define LED_PIN 0U /* PB0 = LD1 (녹색) / #define BTN_PIN 13U / PC13 = B1 */

void led_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

GPIOB-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (LED_PIN * 2));
GPIOB-&gt;MODER |=  (0x1UL &lt;&lt; (LED_PIN * 2));

}

void led_on(void) { GPIOB->BSRR = (1UL << LED_PIN); /* Set */ }

void led_off(void) { GPIOB->BSRR = (1UL << (LED_PIN + 16)); /* Reset */ }

void button_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;

GPIOC-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (BTN_PIN * 2));  /* Input */
GPIOC-&gt;PUPDR &amp;= ~(0x3UL &lt;&lt; (BTN_PIN * 2));  /* No pull (보드가 자체 처리) */

}

bool get_btn_state(void) { return (GPIOC->IDR & (1UL << BTN_PIN)) != 0U; /* active-HIGH */ }

void led_toggle(void) { GPIOB->ODR ^= (1UL << 0); /* PB0 = LD1 */ }

Inc/gpio_exti.h

#ifndef GPIO_EXTI_H__
#define GPIO_EXTI_H__

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

#define LINE13 (1U << 13)

void pc13_exti_init(void);

#endif

Src/gpio_exti.c

#include "gpio_exti.h"

#define GPIOCEN (1U << 2) #define SYSCFGEN (1U << 14)

void pc13_exti_init(void) { __disable_irq();

RCC-&gt;AHB1ENR |= GPIOCEN;

GPIOC-&gt;MODER &amp;= ~(1U &lt;&lt; 26);
GPIOC-&gt;MODER &amp;= ~(1U &lt;&lt; 27);

RCC-&gt;APB2ENR |= SYSCFGEN;

/* EXTI13 -&gt; PORTC */
SYSCFG-&gt;EXTICR[3] |= (1U &lt;&lt; 5);

EXTI-&gt;IMR |= (1U &lt;&lt; 13);

/* 이 보드는 B1이 active-HIGH라 Rising-edge (원서는 FTSR/active-LOW) */
EXTI-&gt;RTSR |= (1U &lt;&lt; 13);

NVIC_EnableIRQ(EXTI15_10_IRQn);

__enable_irq();

}

Inc/standby_mode.h

#ifndef STANDBY_MODE_H__
#define STANDBY_MODE_H__

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

uint32_t get_wakeup_pin_state(void); void wakeup_pin_init(void); void standby_wakeup_pin_setup(void);

#endif /* STANDBY_MODE_H__ */

Src/standby_mode.c

#include "standby_mode.h"

#define PWR_MODE_STANDBY (PWR_CR_PDDS) #define WK_PIN (1U << 0)

static void set_power_mode(uint32_t pwr_mode);

void wakeup_pin_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

GPIOA-&gt;MODER &amp;= ~(1U &lt;&lt; 0);
GPIOA-&gt;MODER &amp;= ~(1U &lt;&lt; 1);

GPIOA-&gt;PUPDR &amp;= ~(1U &lt;&lt; 0);
GPIOA-&gt;PUPDR &amp;= ~(1U &lt;&lt; 1);

}

uint32_t get_wakeup_pin_state(void) { return ((GPIOA->IDR & WK_PIN) == WK_PIN); }

static void set_power_mode(uint32_t pwr_mode) { MODIFY_REG(PWR->CR, (PWR_CR_PDDS | PWR_CR_LPDS | PWR_CR_FPDS | PWR_CR_LPLVDS | PWR_CR_MRLVDS), pwr_mode); }

void standby_wakeup_pin_setup(void) { while (get_wakeup_pin_state() == 0) {}

PWR-&gt;CSR &amp;= ~(1U &lt;&lt; 8);
PWR-&gt;CR  |= (1U &lt;&lt; 2);
PWR-&gt;CSR |= (1U &lt;&lt; 8);

set_power_mode(PWR_MODE_STANDBY);

SCB-&gt;SCR |= (1U &lt;&lt; 2);

__WFI();

}

Inc/uart.h

#ifndef UART_H
#define UART_H

#include "stm32f4xx.h"

void uart_init(void); void uart_write_string(const char *s); int __io_putchar(int ch);

#endif

Src/uart.c

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

#define GPIODEN (1U << 3) #define USART3EN (1U << 18)

#define DBG_UART_BAUDRATE 115200U #define SYS_FREQ 16000000U #define APB1_CLK SYS_FREQ

#define CR1_TE (1U << 3) #define CR1_UE (1U << 13) #define SR_TXE (1U << 7)

static void uart_write(int ch); static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate); static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate);

int __io_putchar(int ch) { uart_write(ch); return ch; }

void uart_init(void) { RCC->AHB1ENR |= GPIODEN;

GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (9 * 2));

GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 0);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 0);   /* PD8 -&gt; AF7 (USART3_TX) */
GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 4);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 4);   /* PD9 -&gt; AF7 (USART3_RX) */

RCC-&gt;APB1ENR |= USART3EN;

uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);

USART3-&gt;CR1 = CR1_TE;
USART3-&gt;CR1 |= CR1_UE;

}

static void uart_write(int ch) { while (!(USART3->SR & SR_TXE)) { } USART3->DR = (ch & 0xFF); }

void uart_write_string(const char *s) { while (*s) uart_write((int)*s++); }

static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate) { return (uint16_t)((periph_clk + (baudrate / 2U)) / baudrate); }

static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate) { USART3->BRR = compute_uart_bd(periph_clk, baudrate); }

Src/main.c

/*
  • Ch18 - Power Management: Standby 모드 + PA0(WKUP) 웨이크업
  • B1(PC13)을 누르면 EXTI 인터럽트로 standby_wakeup_pin_setup()이
  • 호출되어 보드가 Standby 모드(가장 깊은 저전력 모드)로 들어간다.
  • PA0(WKUP)에 배선된 신호가 Low->High로 바뀌면 보드가 깨어나는데,
  • Standby에서 깨어나는 건 완전한 리셋과 동일해서 main()이 처음부터
  • 다시 실행된다 — 그래서 check_reset_source()가 PWR->CSR의 SBF
  • 비트로 "방금 Standby에서 깨어났다"를 판별한다.
  • LD1(PB0)은 배선 불필요. 정상 동작 중엔 켜져 있다가 Standby 진입
  • 직전에 꺼지고, 웨이크업(리셋) 후 다시 켜진다. */ #include "gpio.h" #include "gpio_exti.h" #include "standby_mode.h" #include "uart.h"

uint8_t g_btn_press;

static void check_reset_source(void);

int main(void) { uart_init(); led_init(); wakeup_pin_init();

/* 리셋 원인 확인 (Standby에서 깨어난 것인지 아닌지) */
check_reset_source();

/* 정상 동작 중임을 LED로 표시 */
led_on();

/* B1 버튼으로 Standby 진입을 트리거하기 위한 EXTI 설정 */
pc13_exti_init();

while (1)
{
    /* 모든 동작은 인터럽트(B1 -&gt; Standby 진입, WKUP -&gt; 리셋)로만 이뤄짐 */
}

}

static void check_reset_source(void) { RCC->APB1ENR |= RCC_APB1ENR_PWREN;

if ((PWR-&gt;CSR &amp; PWR_CSR_SBF) == PWR_CSR_SBF)
{
    PWR-&gt;CR |= PWR_CR_CSBF;
    uart_write_string("System resume from Standby.....\r\n");

    while (get_wakeup_pin_state() == 0) {}
}

if ((PWR-&gt;CSR &amp; PWR_CSR_WUF) == PWR_CSR_WUF)
{
    PWR-&gt;CR |= PWR_CR_CWUF;
}

}

static void exti_callback(void) { led_off(); standby_wakeup_pin_setup(); }

void EXTI15_10_IRQHandler(void) { if ((EXTI->PR & LINE13) != 0) { EXTI->PR |= LINE13; exti_callback(); } }

5. 실습 결과

Notion 기록 기준:

  • B1 → EXTI interrupt 확인

  • LED off 후 Standby 진입 확인

  • Standby 진입 이후 UART가 응답하지 않는 상태 확인

  • PA0(WKUP) 실제 wakeup은 jumper wire가 없어 미검증

text = 1660 bytes
data = 0 bytes
bss  = 2560 bytes
warning 없음

현재 설명과 실제 코드 사이 확인할 점

Notion 설명에는 standby_wakeup_pin_setup() 시작 부분을 "PA0이 Low로 안정될 때까지 대기"라고 적어두었지만, 현재 ZIP의 실제 코드는:

while (get_wakeup_pin_state() == 0) {}

이다. 이 조건은 PA0이 0인 동안 계속 대기하고 1이 되면 빠져나온다.
PA0 wakeup 자체가 아직 미검증 상태이므로, 실제 jumper test를 할 때 초기 pin 상태와 이 polling 조건을 함께 재확인할 필요가 있다. 이 Wiki에서는 현재 소스를 수정하지 않고 그대로 기록한다.

6. 핵심 정리

  • Standby는 SLEEPDEEP + PDDS + WFI 흐름으로 진입한다.

  • Standby wakeup 이후 reset 흐름으로 main이 다시 시작된다.

  • Standby 진입은 검증했고 PA0 실제 wakeup은 미검증이다.

Clone this wiki locally