임베디드 면접 용어 사전
주변장치·타이머PSC · ARR · CCR

PSC / ARR / CCR

타이머 분주값·자동 재적재값·비교값. 주기는 PSC와 ARR로, 듀티는 CCR로 정해진다.

타이머의 세 가지 핵심 레지스터. 주파수와 듀티를 결정한다.

  • PSC (Prescaler) — — 입력 클럭을 나눈다
  • ARR (Auto-Reload) — — 카운터가 여기까지 세고 0으로 돌아간다 → 주기
  • CCR (Capture/Compare)— 이 값에서 출력이 바뀐다 → 듀티

구조

타이머 클럭 ──[÷(PSC+1)]──► 카운터 클럭
                              │
                              ▼
              0 ─────────► ARR ──(오버플로 → 0으로)
                    ↑
                   CCR    ← 여기를 지날 때 출력 토글

계산식

  • 카운터 클럭 — = 타이머 클럭 / (PSC + 1)
  • PWM 주파수 — = 카운터 클럭 / (ARR + 1)
    • = 타이머 클럭 / ((PSC+1) × (ARR+1))
  • 듀티 사이클 — = (CCR + 1) / (ARR + 1) (모드에 따라 CCR/(ARR+1))

+1이 붙는 이유 — 카운터가 0부터 세기 때문이다. ARR=99면 0~99까지 100개를 센다.

예제 1 — 1kHz PWM, 듀티 25%

  • 타이머 클럭 84MHz
PSC = 83→ 카운터 클럭 = 84MHz / 84 = 1MHz(1μs 단위)
ARR = 999→ 주기 = 1000μs = 1ms → 1kHz
CCR = 250→ 듀티 = 250/1000 = 25%

PSC로 편리한 시간 단위를 만들고, ARR로 주기를 세는 것이 요령이다. 1MHz(1μs)나 10kHz(0.1ms)처럼 계산하기 쉬운 값을 고른다.

예제 2 — 1초 주기 인터럽트

84MHz / ((PSC+1) × (ARR+1)) = 1Hz (PSC+1) × (ARR+1) = 84,000,000

  • PSC = 8399 — → 84MHz / 8400 = 10kHz
  • ARR = 9999 — → 10kHz / 10000 = 1Hz ✓

16비트 타이머는 PSC와 ARR이 각각 최대 65535다. 큰 값이 필요하면 곱으로 분배해야 한다.

타이머 클럭을 잘못 알면 전부 틀린다 — 가장 흔한 실수

  • APB1 분주비가 1이면 — → 타이머 클럭 = APB1 클럭 APB1 분주비가 1이 아니면 → 타이머 클럭 = APB1 클럭 × 2 ★

SYSCLK 168MHz, APB1 분주 4 → PCLK1 = 42MHz

  • TIM2 클럭 = 84MHz (42가 아니다!)

★ 이것을 놓치면 주파수가 정확히 2배로 나온다. "계산은 맞는데 주파수가 두 배"라면 이것이다.

uint32_t timclk = HAL_RCC_GetPCLK1Freq();
if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1) timclk *= 2;

런타임에 계산하면 클럭 설정이 바뀌어도 안전하다.

프리로드 (섀도 레지스터)

htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

켜면 ARR·CCR에 쓴 값이 즉시 반영되지 않고 다음 주기 시작 시 적용된다.

  • 프리로드 없이 듀티를 바꾸면
  • 현재 카운터가 이미 새 CCR을 지나쳤을 수 있다
  • 그 주기에서 펄스가 사라지거나 비정상적으로 길어진다
  • 모터가 순간적으로 튄다

동적으로 듀티를 바꾸는 용도라면 반드시 켜야 한다.

관련 레지스터

  • CNT — — 현재 카운터 값 (읽기·쓰기 가능)
  • CR1 — — 제어 (활성화, 방향, 정렬 모드)
  • DIER — 인터럽트·DMA 활성화
  • SR — — 상태 플래그 (UIF: 업데이트, CCxIF: 비교 일치)
  • EGR — — 소프트웨어 이벤트 생성 (UG 비트로 강제 갱신)

설정 후 TIM->EGR = TIM_EGR_UG; 를 써서 프리로드 값을 즉시 반영시키는 것이 관례다.

인터럽트에서 플래그 클리어

void TIM2_IRQHandler(void) {
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;    // ★ &= ~ 가 아니라 = ~ 로 쓴다
        // …
    }
}

&= 를 쓰면 read-modify-write가 되어 그 사이에 발생한 다른 플래그를 지워 버릴 수 있다. 타이머 SR은 0을 써야 클리어되는 레지스터라 = ~FLAG 형태가 안전하다.

타이머의 다른 용도

입력 캡처 : 외부 신호의 에지에서 CNT 값을 CCR에 저장 → 주파수·펄스 폭 측정 엔코더 모드: A/B 상 입력으로 CNT가 자동 증감 → 회전 위치 측정 원샷 모드 : 한 번만 실행하고 정지 → 정밀 지연

  • 트리거 — : 다른 타이머나 ADC를 동기 구동

타이머로 ADC를 트리거하면 정확히 일정한 간격으로 샘플링할 수 있다. 소프트웨어로 하면 지터가 생겨 신호 처리 결과가 나빠진다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 주변장치·타이머 — GPIO(push-pull/open-drain)·PWM(PSC/ARR/CCR)·워치독·ADC·클럭