← Embedded C 강의 목록으로
🔌
STM32
STM32 · 선수: 15강 · STM32 · 선수: 16강

16–17. SysTick 타이머와 정확한 지연 · 범용 타이머·PWM 출력

빈 루프 delay()는 시간이 부정확합니다. Cortex-M 코어에 내장된 SysTick 타이머로 해결합니다 — 모든 Cortex-M에 똑같이 들어 있는 24비트 카운터라 칩이 바뀌어도 같은 코드가 돕니다. 핵심 패턴은 "1ms마다 인터럽트 → 전역 밀리초 카운터 증가 → 그 카운터로 시간 측정"입니다. 이 한 패턴으로 정확한 지연·주기 실행·타임아웃을 모두 구현합니다. 부호 없는 뺄셈으로 래핑에 안전한 delay_ms도 익힙니다. 범용 타이머(TIM)는 채널·PWM·입력 캡처를 제공합니다. 가장 많이 쓰는 PWM은 켜진 시간의 비율(듀티)을 빠르게 조절해 LED 밝기나 모터 속도를 "아날로그처럼" 제어합니다. TIM2 채널1(PA0)로 1kHz PWM을 만들고 듀티를 조절합니다. 세 핵심 레지스터 PSC(분주)·ARR(주기)·CCR(듀티)의 관계와, APB1 타이머 클럭이 PCLK1의 2배가 되는 함정을 익힙니다.

SysTickdelay_ms인터럽트volatile밀리초래핑타이머PWMPSCARRCCR듀티
소요 시간
⏱ 약 1.5시간 + 약 1.5~2시간
난이도
📊 중급
선수 조건
🎯 15강
결과물
SysTick_Config로 1ms 인터럽트를 설정하고 volatile 밀리초 카운터로 래핑에 안전한 delay_ms를 만들어 정확한 시간을 측정할 수 있습니다. / PSC/ARR/CCR로 PWM 주파수와 듀티를 계산·설정하고, AF 푸시풀 핀으로 TIM2 채널1 PWM을 출력할 수 있습니다.

이 강의에서 배우는 것

  • 1SysTick의 구조(24비트 다운 카운터)와 동작을 이해한다
  • 2SysTick_Config()로 1ms 주기 인터럽트를 설정한다
  • 3SysTick_Handler에서 밀리초 카운터를 증가시킨다
  • 4부호 없는 뺄셈으로 래핑에 안전한 delay_ms()를 만든다
  • 5SysTick의 24비트 한계와 reload 계산을 안다
  • 6범용 타이머의 시간축(PSC·ARR)과 카운터 클럭을 계산한다
  • 7PWM 모드1의 동작(CNT vs CCR 비교)을 이해한다
  • 8TIM2 채널1로 PWM을 출력하도록 레지스터를 설정한다
  • 9CCR로 듀티를, ARR로 주파수를 조절한다
  • 10APB1 타이머 클럭이 PCLK1의 2배가 되는 규칙을 안다

16. SysTick 타이머와 정확한 지연

빈 루프 delay()는 시간이 부정확합니다. Cortex-M 코어에 내장된 SysTick 타이머로 해결합니다 — 모든 Cortex-M에 똑같이 들어 있는 24비트 카운터라 칩이 바뀌어도 같은 코드가 돕니다. 핵심 패턴은 "1ms마다 인터럽트 → 전역 밀리초 카운터 증가 → 그 카운터로 시간 측정"입니다. 이 한 패턴으로 정확한 지연·주기 실행·타임아웃을 모두 구현합니다. 부호 없는 뺄셈으로 래핑에 안전한 delay_ms도 익힙니다.

소개

SysTick은 reload 값에서 0까지 내려가고 0에 닿으면 다시 채워지며 인터럽트(SysTick exception)를 냅니다. 코어에 내장돼 어떤 Cortex-M에서도 같은 방식으로 씁니다.

핵심 개념

1) SysTick_Config 와 핸들러

c
SysTick_Config(SystemCoreClock / 1000u);   /* 1ms 마다 인터럽트 (72MHz→72000) */

static volatile uint32_t g_ms = 0;
void SysTick_Handler(void) { g_ms++; }   /* 핸들러 이름은 CMSIS 약속 */
⚠️

g_ms 에 volatile 필수. 인터럽트와 메인이 공유하므로 캐싱을 막아야 합니다. 핸들러 이름은 startup 벡터 테이블과 정확히 일치해야 합니다(SysTick_Handler).

2) 래핑에 안전한 delay_ms

c
static void delay_ms(uint32_t ms) {
    uint32_t start = g_ms;
    while ((g_ms - start) < ms) { }
}
/* 부호 없는 뺄셈 → g_ms 가 32비트 한계를 넘어 0으로 돌아가도 올바른 경과를 준다 */

3) 24비트 한계

SysTick은 24비트라 reload 최댓값 0xFFFFFF. 72MHz에서 한 주기 최대 ~233ms. 그보다 긴 주기는 1ms 틱을 세는 방식으로 만듭니다.

핵심 예제

c
#include "stm32f10x.h"
#define LED 13u
static volatile uint32_t g_ms = 0;
void SysTick_Handler(void){ g_ms++; }
static void delay_ms(uint32_t ms){ uint32_t s=g_ms; while((g_ms-s)<ms){} }
int main(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
    GPIOC->CRH &= ~(0xFu << ((LED-8u)*4u));
    GPIOC->CRH |=  (0x2u << ((LED-8u)*4u));
    SysTick_Config(SystemCoreClock / 1000u);   /* 1ms 틱 */
    while (1) { GPIOC->ODR ^= (1u << LED); delay_ms(500u); }  /* 정확히 500ms */
}

래핑 검산: start=0xFFFFFFFF, now=4 → elapsed=5. start=0xFFFFFF00, now=0x100 → elapsed=512. 카운터가 한 바퀴 돌아도 부호 없는 뺄셈이 올바른 경과를 줍니다.

자주 하는 실수

Q. g_ms 가 안 변하거나 최적화로 사라져요.

A. volatile 을 빠뜨렸습니다. 인터럽트 핸들러와 메인이 공유하는 변수는 반드시 volatile 로 선언하세요.

Q. SysTick_Handler 이름을 다르게 지었더니 인터럽트가 안 걸려요.

A. 핸들러 이름은 startup 벡터 테이블과 정확히 일치해야 합니다. CMSIS 표준 이름은 SysTick_Handler 입니다.

Q. 1초 주기를 SysTick 하나로 만들려는데 안 돼요.

A. 24비트 한계로 72MHz에서 한 주기 최대 ~233ms입니다. 긴 시간은 1ms 틱을 세는 방식(g_ms)으로 만드세요.

정리

  • SysTick은 모든 Cortex-M에 내장된 24비트 다운 카운터로 이식성이 좋다
  • SysTick_Config(SystemCoreClock/1000)으로 1ms 주기 인터럽트를 만든다
  • 핸들러에서 volatile 밀리초 카운터를 증가시킨다
  • delay_ms는 부호 없는 뺄셈으로 래핑에도 안전하다
  • 24비트 한계(72MHz에서 ~233ms)는 틱 카운팅으로 넘는다

과제

  1. delay_ms 없이 g_ms 만으로 "마지막 토글 후 200ms 경과 시 토글"하는 논블로킹 점멸 작성
  2. g_ms 로 1초마다 카운트를 증가시키는 주기 실행 만들기
  3. elapsed(now, start) 가 래핑 케이스에서 올바른지 pc_test 로 검산

17. 범용 타이머·PWM 출력

범용 타이머(TIM)는 채널·PWM·입력 캡처를 제공합니다. 가장 많이 쓰는 PWM은 켜진 시간의 비율(듀티)을 빠르게 조절해 LED 밝기나 모터 속도를 "아날로그처럼" 제어합니다. TIM2 채널1(PA0)로 1kHz PWM을 만들고 듀티를 조절합니다. 세 핵심 레지스터 PSC(분주)·ARR(주기)·CCR(듀티)의 관계와, APB1 타이머 클럭이 PCLK1의 2배가 되는 함정을 익힙니다.

소개

디지털 핀은 0/1만 내지만, 켜진 시간 비율을 빠르게 바꾸면 평균 전압이 달라져 아날로그처럼 동작합니다. 타이머 출력은 GPIO의 대체 기능(AF)이라 PA0를 AF 푸시풀(0xB)로 둬야 합니다.

핵심 개념

1) 시간축 — PSC·ARR·CCR

c
counter_clk = TIMxCLK / (PSC + 1)
pwm_freq    = counter_clk / (ARR + 1)
duty(%)     = CCR * 100 / (ARR + 1)
/* PSC=71 → 72MHz/72 = 1MHz, ARR=999 → 1kHz, CCR=250 → 25% */
⚠️

타이머 클럭 함정: TIM2~4는 APB1 버스인데, APB1 프리스케일러가 1이 아니면 타이머 클럭은 PCLK1의 2배가 됩니다. 표준 72MHz 설정에서 PCLK1=36MHz지만 TIM2 클럭은 72MHz입니다.

2) PWM 모드1과 설정 순서

PWM 모드1: CNT < CCR 동안 출력 High. 설정 순서는 클럭 → 핀(AF) → 시간축(PSC/ARR) → 듀티(CCR) → 모드(CCMR1 OC1M=110+OC1PE) → 출력(CCER CC1E) → UG(강제 로드) → CEN(시작).

c
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;  /* PWM1(110) */
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;  TIM2->CCER |= TIM_CCER_CC1E;
TIM2->CR1 |= TIM_CR1_ARPE;  TIM2->EGR |= TIM_EGR_UG;  TIM2->CR1 |= TIM_CR1_CEN;

핵심 예제

c
#include "stm32f10x.h"
int main(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    GPIOA->CRL &= ~0xFu; GPIOA->CRL |= 0xBu;   /* PA0 = AF 푸시풀 */
    TIM2->PSC = 71u; TIM2->ARR = 999u; TIM2->CCR1 = 250u;  /* 1kHz, 25% */
    TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE;
    TIM2->CCER |= TIM_CCER_CC1E;
    TIM2->CR1 |= TIM_CR1_ARPE; TIM2->EGR |= TIM_EGR_UG; TIM2->CR1 |= TIM_CR1_CEN;
    while (1) { /* CCR1 을 바꾸면 듀티가 즉시 바뀐다 */ }
}

계산 검산: PSC=71,ARR=999,CCR=250 → 1000Hz·25%. CCR=500 → 50%. ARR=499 → 2000Hz. Logic Analyzer에 PORTA.0을 추가하면 듀티 비율을 확인할 수 있습니다.

자주 하는 실수

Q. 핀에서 PWM이 안 나와요.

A. ① PA0를 AF 푸시풀(0xB)로 설정, ② CCER의 CC1E로 출력 켜기, ③ CR1의 CEN으로 카운터 시작 — 세 가지를 확인하세요.

Q. 주파수가 계산의 절반/2배로 나와요.

A. 타이머 클럭을 잘못 잡았습니다. APB1 프리스케일러가 1이 아니면 TIM2 클럭은 PCLK1의 2배(72MHz)입니다. 36MHz로 계산하면 2배 어긋납니다.

Q. 설정을 바꿨는데 첫 주기에 반영이 안 돼요.

A. EGR의 UG 비트로 업데이트 이벤트를 강제하면 PSC/ARR/CCR이 즉시 로드됩니다. 빠뜨리면 다음 업데이트까지 옛 값으로 돕니다.

정리

  • 타이머: PSC로 카운터 클럭, ARR로 주기, CCR로 듀티를 정한다
  • PWM 모드1은 CNT < CCR 동안 High → 듀티 = CCR/(ARR+1)
  • 타이머 출력은 AF 핀(PA0=0xB)으로 내보낸다
  • 설정 순서: 클럭 → 핀 → 시간축 → 모드/출력 → UG → CEN
  • APB1 타이머 클럭은 PCLK1의 2배(72MHz)임에 주의

과제

  1. 듀티를 0→100%로 서서히 올렸다 내리는 LED 호흡(breathing) 효과 구현
  2. PWM 주파수를 500Hz로 바꾸려면 PSC/ARR을 어떻게 할지 계산
  3. CCR ≥ ARR+1 이면 출력이 항상 High(100%)임을 확인
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드(과제·정답 포함)는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.

댓글은 회원만 쓸 수 있습니다.

광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침