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

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

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

SysTickdelay_ms인터럽트volatile밀리초래핑
소요 시간
약 1.5시간
난이도
📊 중급
선수 조건
🎯 15강
결과물
SysTick_Config로 1ms 인터럽트를 설정하고 volatile 밀리초 카운터로 래핑에 안전한 delay_ms를 만들어 정확한 시간을 측정할 수 있습니다.

이 강의에서 배우는 것

  • 1SysTick의 구조(24비트 다운 카운터)와 동작을 이해한다
  • 2SysTick_Config()로 1ms 주기 인터럽트를 설정한다
  • 3SysTick_Handler에서 밀리초 카운터를 증가시킨다
  • 4부호 없는 뺄셈으로 래핑에 안전한 delay_ms()를 만든다
  • 5SysTick의 24비트 한계와 reload 계산을 안다

소개

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 로 검산
예제 코드 / 강의 자료

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

GitHub에서 보기 ↗