임베디드 학습 노트 목차

저전력 설계 — 배터리로 몇 년을 버티기

코인셀(CR2032)은 약 220mAh 다. 평균 10µA 를 쓰면 2.5년, 1mA 를 쓰면 9일이다. 100배 차이가 제품의 성패를 가른다.

그리고 저전력 설계는 "CPU 를 얼마나 오래 재우느냐" 하나로 요약된다.


평균 전류가 전부다

전체 시간 중 깨어 있는 시간의 비율을 듀티 사이클이라 한다. 저전력 설계는 결국 이 값을 낮추는 일이다.

배터리 수명 = 용량(mAh) / 평균 전류(mA)

평균 전류 = (활성 전류 × 활성 시간 + 대기 전류 × 대기 시간) / 전체 시간
예: 10초마다 100ms 동안 센서를 읽고 나머지는 잔다
  활성  20mA × 0.1s  = 2.0 mA·s
  대기  0.01mA × 9.9s = 0.099 mA·s
  평균  2.099 / 10 = 0.21mA  →  220mAh / 0.21 ≈ 1,047시간 ≈ 44일

활성 구간이 1%인데 전류의 95%를 쓴다. 그래서 두 방향이 있다 — 깨어 있는 시간을 줄이거나, 깨어 있을 때 더 빨리 끝내거나.

깨는 것 자체가 비용이다 — 짧은 주기에서 절전이 무너지는 이유

위 계산은 "활성 시간 = 작업 시간" 이라고 가정했다. 실제로는 잠에서 깨어 클럭이 안정될 때까지의 시간이 앞에 붙는다. Stop 모드 복귀는 보통 수 µs~수 ms 인데, 이 구간에도 활성에 가까운 전류가 흐른다.

작업이 2ms 인데 웨이크업이 5ms 라면 — 일보다 준비가 더 오래 걸린다. 주기별로 계산해 보자 (활성 20mA, Stop 10µA, 웨이크업 5ms + 작업 2ms).

측정 주기실효 활성 비율평균 전류CR2032(220mAh) 수명
10 ms70.0%14.00 mA0.7 일
100 ms7.0%1.41 mA6.5 일
1 s0.7%0.150 mA61 일
10 s0.1%0.024 mA382 일

주기를 10ms → 1s 로 늘리면 수명이 약 90배가 된다. 반대로 주기가 짧아질수록 "깨는 비용"이 전체를 지배해서, 어느 지점부터는 잠들지 않는 편이 오히려 나은 역전이 일어난다.

설계 규칙웨이크업 시간 ≪ 작업 시간 이 성립해야 절전 모드가 의미가 있다. 그렇지 않다면 세 가지를 검토한다:

  • 더 얕은 절전 모드를 쓴다(Sleep 은 복귀가 즉시지만 전류가 더 크다 — 교환 관계).
  • 여러 작업을 모아서 한 번에 처리한다(배칭). 10ms 마다 깨는 대신 100ms 마다 깨어 10개를 처리.
  • 주변장치가 CPU 없이 일하게 한다 — DMA·타이머·아날로그 워치독으로 CPU 를 안 깨우고 조건이 맞을 때만 인터럽트로 깨운다. 이게 가장 효과가 크다.

함정 — 데이터시트의 Stop 모드 전류(µA)만 보고 설계하면, 실측에서 훨씬 큰 값이 나온다. 웨이크업 구간, GPIO 가 뜬 상태로 남아 새는 전류, 외부 센서의 대기 전류가 데이터시트 숫자에 안 들어 있기 때문이다. 평균 전류는 반드시 실측한다.

활성 시간을 100ms → 50ms 로 줄이면 평균이 절반이 된다
대기 전류를 10µA → 2µA 로 줄여도 평균은 거의 안 변한다

어느 쪽이 이득인지는 비율이 정한다. 위 예에서는 활성 시간을 줄이는 것이 압도적이다. 반대로 하루에 한 번만 깨는 장치라면 대기 전류가 전부다.

저전력 모드

Run      CPU 실행 중                      수 mA ~ 수십 mA
Sleep    CPU 정지, 주변장치 동작           수 mA
Stop     클럭 대부분 정지, SRAM 유지       수 µA
Standby  거의 전부 off, SRAM 손실          1µA 미만
깨우는 것복귀 시간SRAM
Sleep아무 인터럽트즉시유지
StopEXTI·RTC 등 일부수 µs~수십 µs유지
Standby웨이크업 핀·RTC리셋과 동일손실
// Sleep — 가장 쉽고 가장 자주 쓴다
__WFI();                                    // 인터럽트가 올 때까지 잔다

// Stop
PWR->CR |= PWR_CR_LPDS;                     // 저전력 레귤레이터
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
SystemClock_Config();                        // ★ 깨어난 뒤 클럭을 다시 세운다

// Standby
PWR->CR |= PWR_CR_PDDS | PWR_CR_CWUF;
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();                                     // 여기서 돌아오지 않는다 — 리셋된다

Stop 에서 깨어나면 클럭이 내부 저속으로 돌아가 있다. 다시 설정하지 않으면 통신 보율이 어긋나고 타이밍이 전부 틀어진다. 이걸 빠뜨리는 것이 가장 흔한 실수다.

Standby 는 SRAM 이 날아간다. 상태를 남기려면 백업 레지스터나 .noinit 영역, 또는 외부 저장이 필요하다.

// 어느 모드에서 깨어났는지 확인
if (PWR->CSR & PWR_CSR_SBF) {               // Standby 에서 복귀
    PWR->CR |= PWR_CR_CSBF;
    restore_from_backup();
}

슈퍼루프를 재우는 방법

// ✗ 바쁜 대기 — CPU 가 계속 돈다
while (1) {
    if (data_ready) process();
}

// ✓ 할 일이 없으면 잔다
while (1) {
    if (data_ready) { data_ready = false; process(); }

    __disable_irq();
    if (!data_ready && !any_task_due()) {     // 다시 확인하고
        __WFI();                               // 잔다 (WFI 는 인터럽트가 막혀도 깨어난다)
    }
    __enable_irq();
}

__disable_irq() 로 감싸는 이유가 중요하다. 확인과 __WFI() 사이에 인터럽트가 들어오면, 플래그를 세웠는데도 그 뒤에 잠들어 깰 기회를 놓친다(lost wakeup). 인터럽트를 막아 두면 __WFI 는 여전히 깨어나므로 이 구멍이 막힌다.

Cortex-M 의 WFI 는 PRIMASK 로 인터럽트가 막혀 있어도 '깨어난다'
→ ISR 은 안 돌지만 WFI 에서 빠져나와, enable 직후 처리된다

주변장치 클럭을 끈다

// 안 쓰는 것은 클럭을 끊는다 — 게이팅된 클럭은 전류를 거의 안 쓴다
RCC->AHB1ENR &= ~RCC_AHB1ENR_GPIOCEN;
RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN;

"켜 두면 그냥 전류를 먹는다." 개발 중에 다 켜 두고 그대로 출시하는 경우가 흔하다.

// 미사용 핀은 아날로그 모드 — 떠 있으면 문턱 근처에서 진동해 누설이 생긴다
for (int p = 0; p < 16; p++)
    if (!(used_mask & (1 << p))) GPIOA->MODER |= (0b11 << (p * 2));

떠 있는 입력 핀 하나가 수십 µA 를 먹을 수 있다. 핀이 많은 칩에서 이것만으로 대기 전류가 몇 배 차이 난다.

// 디버그 비트를 반드시 끈다 (앞 편에서 본 그것)
DBGMCU->CR &= ~(DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY);

"데이터시트는 2µA 인데 우리는 500µA 다" 의 1순위 원인이 이 비트다. 디버그 회로가 계속 살아 있기 때문이다.

클럭을 낮추는 것이 항상 이득은 아니다

동적 전력 ∝ 전압² × 주파수

주파수를 절반으로 낮추면 순간 전류는 절반이 되지만 일이 두 배 오래 걸린다. 총 에너지는 비슷하다.

168MHz 로 10ms 일하고 990ms 잔다   →  대개 이쪽이 낫다
 42MHz 로 40ms 일하고 960ms 잔다

"빨리 끝내고 깊이 자라(race to sleep)" 가 기본 전략이다. 깨어 있는 동안 주변장치도 함께 켜져 있으므로, 그 시간을 줄이는 것이 이득이다.

다만 전압을 함께 낮출 수 있으면 이야기가 다르다 — 전압은 제곱으로 들어가므로, 저주파에서 전압 스케일링을 하면 실질적 절감이 된다.

RTC 로 깨우기

// 10초마다 깨어나는 구성
RTC->WUTR = 10 - 1;                          // 웨이크업 타이머
RTC->CR |= RTC_CR_WUTE | RTC_CR_WUTIE;

// Stop 모드로 들어가면 RTC 만 살아서 10초 뒤 깨운다
enter_stop_mode();

RTC 는 32.768kHz 저속 크리스털로 돌아 수백 nA 만 쓴다. 그래서 Stop·Standby 에서도 시간을 세고 깨울 수 있다.

// 주기적 센서 읽기의 전형적 구조
while (1) {
    wake_peripherals();                       // 필요한 것만 켠다
    read_sensors();
    if (should_transmit()) send_data();        // 매번 보내지 않는다
    sleep_peripherals();                       // 다시 끈다
    enter_stop_until_rtc(10);                  // 10초 잔다
}

"매번 보내지 않는 것" 이 큰 절감이다. 무선 송신은 대개 가장 비싼 동작(수십~수백 mA)이라, 값이 변했을 때만 보내면 평균이 크게 준다.

무선이 지배한다

BLE 광고 1회       수 mA × 수 ms
LTE-M 접속·전송    100~200mA × 수 초    ← 이것 하나가 하루 예산을 먹는다
Wi-Fi 접속         200~300mA × 수 초

무선을 쓰는 순간 MCU 전류는 부차적이 된다. 그래서 설계가 "얼마나 자주 보낼 것인가" 로 옮겨 간다.

전송 주기를 1분 → 10분 으로 늘리면 평균이 거의 1/10 이 된다
데이터를 모아서 한 번에 보내면 접속 오버헤드를 나눠 쓴다

측정하지 않으면 모른다

전류계로는 부족하다 — 순간 피크와 평균을 동시에 봐야 한다
  · 저항 + 오실로스코프    싸고 빠르다 (1Ω 을 직렬로, 전압을 본다)
  · 전용 전력 분석기        µA~수백 mA 를 동시에 (동적 범위가 넓다)
전형적인 파형

 20mA |    .--.                    .--.
      |    |  |                    |  |
 10uA +----'  '--------------------'  '---
      |<-->|
       활성 100ms      대기 9.9s

피크만 보고 놀라거나 평균만 보고 안심하면 안 된다. 배터리는 평균으로 소모되지만, 순간 피크가 크면 전압 강하로 리셋이 걸린다. 코인셀은 내부 저항이 커서 특히 그렇다.

// 측정을 위해 마커를 남긴다
PROBE_HI();  do_transmission();  PROBE_LO();
// 전류 파형과 프로브 신호를 같이 보면 어느 동작이 얼마를 먹는지 바로 보인다

앞 편의 GPIO 프로브를 전류 측정에 그대로 쓴다. 어느 구간이 비싼지가 눈으로 확인된다.

흔한 함정

증상원인
대기 전류가 데이터시트의 100배디버그 비트가 켜져 있다
Stop 에서 깨어난 뒤 통신이 깨진다클럭 재설정을 안 했다
가끔 깨어나지 못한다lost wakeup — 확인과 WFI 사이에 인터럽트
대기 전류가 들쭉날쭉하다떠 있는 입력 핀 · 외부 회로의 누설
배터리가 예상보다 빨리 준다순간 피크로 인한 전압 강하 → 유효 용량 감소
저온에서 수명이 급감배터리 내부 저항이 커진다 — 설계 여유가 필요
// 외부 회로도 본다 — MCU 만 재우면 소용없다
// 풀업 저항 하나가 3.3V/10kΩ = 330µA 를 계속 먹을 수 있다
// LED 표시등, 센서 상시 전원, 레귤레이터 정지 전류(Iq) 를 모두 계산에 넣는다

MCU 를 2µA 로 만들어도 레귤레이터가 50µA 를 먹으면 의미가 없다. 저전력 제품에서는 부품 선정이 코드보다 중요한 경우가 많다.


한눈에 정리

  • 평균 전류가 배터리 수명을 정한다 — 활성 1% 구간이 전류의 95%를 쓰는 것이 보통이다
  • 활성 시간을 줄일지 대기 전류를 줄일지는 비율이 정한다
  • Sleep/Stop/Standby — 복귀 시간과 SRAM 유지 여부가 다르다
  • Stop 에서 깨면 클럭을 다시 세워야 한다 — 안 하면 통신이 전부 어긋난다
  • __WFI 앞뒤를 disable_irq 로 감싼다 — lost wakeup 을 막는다. WFI 는 막혀 있어도 깨어난다
  • 안 쓰는 주변장치 클럭을 끄고, 미사용 핀은 아날로그로 — 떠 있는 핀 하나가 수십 µA
  • 디버그 비트가 대기 전류 이상의 1순위 원인
  • race to sleep — 빨리 끝내고 깊이 자는 편이 대개 낫다. 전압을 같이 낮출 수 있을 때만 저주파가 이득
  • 무선이 예산을 지배한다 — 설계가 "얼마나 자주 보낼 것인가" 로 옮겨 간다
  • 피크와 평균을 같이 본다 — 평균이 수명을, 피크가 리셋을 정한다
  • MCU 밖도 계산에 넣는다 — 레귤레이터 정지 전류·풀업·상시 전원 센서

꼬리질문 대비

  • "배터리 수명을 무엇으로 계산하나?" → 용량 ÷ 평균 전류. 평균 전류 = (활성×활성시간 + 대기×대기시간) / 전체
  • "주기를 짧게 하면 절전이 무너지는 이유는?" → 웨이크업 시간이 작업보다 길면 '깨는 비용'이 지배한다 — 10ms 주기면 실효 활성 70%
  • "CPU 를 안 깨우고 일을 시키려면?" → DMA·타이머·아날로그 워치독으로 조건이 맞을 때만 인터럽트를 낸다
  • "데이터시트 대기 전류대로 안 나오는 이유는?" → 웨이크업 구간·뜬 GPIO 누설·외부 센서 대기 전류가 빠져 있다. 평균 전류는 실측한다

출처 — STMicroelectronics, RM0090 STM32F4 Reference Manual §5 PWR(Sleep·Stop·Standby) · STMicroelectronics, AN4365 STM32F4 저전력 모드 활용 · Arm, Cortex-M4 Devices Generic User Guide(DUI 0553) §2.5 전력 관리(WFI·WFE·SCR) · Nordic Semiconductor, 무선 전력 소비 특성 문서

최적화 — 크기와 속도를 재고 줄인다신뢰성 — 현장에서 멈추지 않게