Repository navigation
Bare metal C Ch18 StandbyMode
한 줄 요약: B1 interrupt로 Standby에 진입하고 PA0(WKUP)을 wakeup source로 구성한다. Standby 진입은 검증했지만 PA0 wakeup은 현재 미검증 상태다.
Notion에서는 STM32 low-power mode를 Sleep / Stop / Standby의 세 단계로 정리했다.
Mode | 특징 -- | -- Sleep | Core 중심으로 정지 Stop | 더 깊은 저전력 Standby | 가장 깊은 저전력, wakeup 시 reset 흐름이번 실습은 Standby를 사용한다.
Standby 진입에는 서로 다른 계층의 설정이 함께 필요하다.
SCB->SCR.SLEEPDEEP
= Cortex-M core에게 deep sleep 요청
PWR->CR.PDDS
= STM32에게 deep sleep 중 Standby 선택
그 후:
__WFI();
로 sleep 진입을 요청한다.
Notion 기록에서는 Standby에서 깨어나면 이전 코드 위치로 이어서 실행하는 것이 아니라 reset과 같은 흐름으로 main()부터 다시 시작한다고 정리했다.
따라서 PWR->CSR의:
SBFWUF
flag를 이용해 reset/wakeup 원인을 확인한다.
PA0을 WKUP input으로 사용한다. 별도 pull-up/down 없이 외부 wiring으로 input state를 만든다.
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()
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를 전부 기록한다.
#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 */
#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->MODER &= ~(0x3UL << (LED_PIN * 2));
GPIOB->MODER |= (0x1UL << (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->MODER &= ~(0x3UL << (BTN_PIN * 2)); /* Input */
GPIOC->PUPDR &= ~(0x3UL << (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 */
}
#ifndef GPIO_EXTI_H__
#define GPIO_EXTI_H__
#include <stdint.h>
#include "stm32f4xx.h"
#define LINE13 (1U << 13)
void pc13_exti_init(void);
#endif
#include "gpio_exti.h"
#define GPIOCEN (1U << 2)
#define SYSCFGEN (1U << 14)
void pc13_exti_init(void)
{
__disable_irq();
RCC->AHB1ENR |= GPIOCEN;
GPIOC->MODER &= ~(1U << 26);
GPIOC->MODER &= ~(1U << 27);
RCC->APB2ENR |= SYSCFGEN;
/* EXTI13 -> PORTC */
SYSCFG->EXTICR[3] |= (1U << 5);
EXTI->IMR |= (1U << 13);
/* 이 보드는 B1이 active-HIGH라 Rising-edge (원서는 FTSR/active-LOW) */
EXTI->RTSR |= (1U << 13);
NVIC_EnableIRQ(EXTI15_10_IRQn);
__enable_irq();
}
#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__ */
#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->MODER &= ~(1U << 0);
GPIOA->MODER &= ~(1U << 1);
GPIOA->PUPDR &= ~(1U << 0);
GPIOA->PUPDR &= ~(1U << 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->CSR &= ~(1U << 8);
PWR->CR |= (1U << 2);
PWR->CSR |= (1U << 8);
set_power_mode(PWR_MODE_STANDBY);
SCB->SCR |= (1U << 2);
__WFI();
}
#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
#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->MODER &= ~(0x3UL << (8 * 2));
GPIOD->MODER |= (0x2UL << (8 * 2));
GPIOD->MODER &= ~(0x3UL << (9 * 2));
GPIOD->MODER |= (0x2UL << (9 * 2));
GPIOD->AFR[1] &= ~(0xFUL << 0);
GPIOD->AFR[1] |= (0x7UL << 0); /* PD8 -> AF7 (USART3_TX) */
GPIOD->AFR[1] &= ~(0xFUL << 4);
GPIOD->AFR[1] |= (0x7UL << 4); /* PD9 -> AF7 (USART3_RX) */
RCC->APB1ENR |= USART3EN;
uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);
USART3->CR1 = CR1_TE;
USART3->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);
}
/*
- 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 -> Standby 진입, WKUP -> 리셋)로만 이뤄짐 */
}
}
static void check_reset_source(void)
{
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
if ((PWR->CSR & PWR_CSR_SBF) == PWR_CSR_SBF)
{
PWR->CR |= PWR_CR_CSBF;
uart_write_string("System resume from Standby.....\r\n");
while (get_wakeup_pin_state() == 0) {}
}
if ((PWR->CSR & PWR_CSR_WUF) == PWR_CSR_WUF)
{
PWR->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();
}
}
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에서는 현재 소스를 수정하지 않고 그대로 기록한다.
Standby는
SLEEPDEEP + PDDS + WFI흐름으로 진입한다.Standby wakeup 이후 reset 흐름으로 main이 다시 시작된다.
Standby 진입은 검증했고 PA0 실제 wakeup은 미검증이다.