16. SysTick 타이머와 정확한 지연
빈 루프 delay()는 시간이 부정확합니다. Cortex-M 코어에 내장된 SysTick 타이머로 해결합니다 — 모든 Cortex-M에 똑같이 들어 있는 24비트 카운터라 칩이 바뀌어도 같은 코드가 돕니다. 핵심 패턴은 "1ms마다 인터럽트 → 전역 밀리초 카운터 증가 → 그 카운터로 시간 측정"입니다. 이 한 패턴으로 정확한 지연·주기 실행·타임아웃을 모두 구현합니다. 부호 없는 뺄셈으로 래핑에 안전한 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 와 핸들러
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
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 틱을 세는 방식으로 만듭니다.
핵심 예제
#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)는 틱 카운팅으로 넘는다
과제
- delay_ms 없이 g_ms 만으로 "마지막 토글 후 200ms 경과 시 토글"하는 논블로킹 점멸 작성
- g_ms 로 1초마다 카운트를 증가시키는 주기 실행 만들기
- elapsed(now, start) 가 래핑 케이스에서 올바른지 pc_test 로 검산