SysTick 타이머란?
ARMv7-M Architecture Reference Manual 에서 SysTick의 정체에 대해서 찾을 수 있다.

SysTick은
- 메인 프로세서의 CLK을 사용하는 빠른 속도의 타이머
- SysTick Routine을 매 주기마다 실행하는 RTOS 타이머
- 연결된 CLK과 Counter 값에 따라 측정 시간을 변경할 수 있는 가변 타이머
등으로 활용할 수 있다.
ㅤ
ㅤ
코어에 있기 때문에, HCLK과 동기화되어 CLK을 받는다고 보면 됨.

ㅤ
SysTick은 왜 프로세서 내부에 있냐?
다른 타이머들(TIM1, TIM2 같은 녀석들)은 다 프로세서 외부에 페리퍼럴로서 존재하는데, 왜 SysTick은 내부로 들고왔는지가 궁금했다.
ㅤ
임베디드 시스템에서 OS의 스케쥴링이나 RTOS 등에서 시간 관리를 위해서는 주기적인 타이머 인터럽트가 필요하다.
ㅤ
만약 시스템 외부에 타이머가 있고, 이 타이머의 인터럽트를 이용한다면 다음의 과정을 거쳐야한다.
- 설정에 따라서 타이머 인터럽트가 발생!
- APB 버스를 통해서 인터럽트 신호를 전달
- APB → AHB 브릿지를 통과 (프로세서에게 가기위해)
- 프로세서 내 NVIC 에 도달
- CPU가 인터럽트를 처리
ㅤ
이 과정에서 여러가지 이슈가 발생해서 인터럽트가 처리되는 시점이 불규칙해질 수 있다.
- APB 버스 / AHB 버스를 이미 누가 쓰고있어서 기다려야 한다면?
- APB - AHB의 CLK 속도 차이 → CLK 변환을 위해 지연 발생 가능
- 기타 버스 문제로 인터럽트의 도달 시간이 불안정할 수 있다.
- 1ms 마다 뭔가 처리해야 하는데 1.05ms → 1.2ms → 1.1ms → 0.95ms 이렇게 타이머 간격이 들어오면 제대로 신호를 처리하기 어려움.
ㅤ
만약 이 대신에 주변장치로서가 아닌, 프로세서 내부에 타이머를 넣는다면 버스로 인해 발생하는 문제 없이 바로 처리가 가능하다.
- 타이머 인터럽트 발생!
- NVIC에 이를 알림
- CPU가 인터럽트를 처리
ㅤ
그렇다면 우선순위가 높은 Peripheral 타이머를 만들면 되지 않았나?
라는 생각이 들었다. 주변장치로 CPU에 빠르게 신호를 줄 수 있는 타이머를 만드는게 프로세서의 구조를 바꿔가면서 뭔가 처리하는 것보다 프로세서의 공간도 덜 차지하고 extension으로서 더 좋을 것 같은데..? 라는 생각을 했다.
ㅤ
그런데 이걸 하려면 AHB 같은 버스에다가 System Timer를 위한 우선순위 판단 로직을 설계 + ST 같은 칩 제조사에서 타이머를 구현(제조사마다 구현 방식 달라짐) + 버스를 타고 전달되는 타이머에 대한 검증 복잡 등의 이유로 구현 복잡도가 크게 올라간다. 이걸 사용하는 사람도 칩의 제조사마다 다르게 System 로직을 작성해야함!
ㅤ
그런데, 만약 타이머를 프로세서 내부에 넣는다고 하면 단순히 SysTick 타이머를 위한 레지스터 몇 개랑 카운터, 타이머 인터럽트 시 NVIC으로 신호를 전달하는 선만 연결해주면 된다. 모든 ARM 에서 동일하게 작동하는 코드가 되고 검증도 타이머 모듈에 대해서만 수행하면 되며 시스템 입장에서는 더욱 믿고 쓸 수 있는 타이머가 되기 때문에 오히려 이렇게 하는 편이 더 효율적이라고 판단한 듯 하다.
ㅤ
의외로 STM32F420에는 AHB와 연결된 타이머가 없다. SysTick이 있기 때문일까? 🤔🤔
ㅤ
SysTick의 목적

Cortex-M4 에서는 SysTick 타이머를 RTOS를 위해 사용하는 타이머라고 되어있다. 그치만 일부 상황에서는 카운터로도 활용할 수 있다고 되어있다. 타이머 기능이 필요하다면 굳이굳이 SysTick 타이머를 불러서 사용하지말고, 일반 TIM 들을 사용하자.
ㅤ
SysTick의 제어


- CTRL 레지스터
- SysTick의 여러 제어를 위한 Bit 들을 가지고 있다.
- CLK Source 결정 (AHB or AHB/8)
- 인터럽트 사용 여부
- SysTick 활성화
- 타이머가 0이 되었는지 Flag. 지난번에 이 Flag를 읽었던 시점 이후에 타이머가 0이 되었다면 Set 된다.
- Reload Value 레지스터
- 타이머 카운터가 0이 되었을 때 카운터 레지스터에 들어가는 값
- 24Bit의 값을 담을 수 있다 (0x00000001 ~ 0x00FFFFFF)
- Current Value 레지스터
- 현재 카운터 레지스터의 값을 보여준다. (Current Value 레지스터에서 직접 값이 줄어드는게 아니다)
- Calibration 레지스터
- 읽기 전용 레지스터
- 일부 Calibration을 지원하는 프로세서에서, 10ms 를 하려면 몇 CLK을 Reload Value 레지스터에 넣어야하는지 알려준다.
ㅤ
만약 Calibration 레지스터에서 SKEW 값이
1이라면 “알아서 계산해서 만들어라” 라는 의미이다.
이럴때는 TEMMS 비트를 가져가 사용하면 안되고, 차라리SystemCoreClock같는 CMSIS 변수에서 값을 가져와 계산해주는게 좋다.
ㅤ
유의할 점
CTRL 레지스터에서 CLK Source를 선택할 때, AHB 또는 AHB/8을 선택할 수 있다. 이때 만약 타이밍의 정확도가 중요하다면 8배나 더 정밀한 AHB를 클럭으로 선택하는게 맞다. 다만 저전력으로 구동하는게 훨씬 더 중요하다거나 최대로 측정할 수 있는 시간이 더 길어야하는 상황이라면 AHB/8를 선택해주면 된다.
ㅤ

안전한 제어를 위해서 SysTick을 사용하는 방법까지 친절하게 안내가 되어있다. 사실 내가 생각하기에 Control 레지스터에서 SysTick Enable bit만 가장 나중에 하고 나머지 설정하는 것은 순서가 상관이 없을 것 같긴한데, 위 순서를 지켜서 작성해주어야 한다.
- Reload Value 레지스터 값을 작성할 것
- Current Value 레지스터를 초기화할 것
- Control 레지스터의 값을 작성할 것
ㅤ
순서 바꿔서 하면 진짜 안되나 팩트체크 해보기


→ 순서 바꿔서 해봤는데, 됨!
아마도 첫 사이클에서는 문제가 발생할 수도 있을 것 같은데, 타이머 자체가 너무 빨라서 문제가 있는 것처럼 보이지 않는 것으로 생각된다.
ㅤ
혹시나 몰라서 레지스터값도 까봤다. 확실한건, 디버그를 찍어서 메모리 값을 확인해보니 처음에 VAL 값을 0으로 초기화해주지 않으면 쓰레기 값을 담고 시작한다.
ㅤ

Document가 분명 Reset Value는 0x00000000 이라고 했을텐데 말이지,,, 이제는 ARM 문서도 믿을 수 없는 증거가 하나 추가되었다. 뭐든지 끝없이 의심해야한다.
ㅤ

ㅤ
Reload 레지스터에 넣어주는 값은 원하는 값보다 1 작은 값을 넣어주어야 한다. N CLK을 카운트하고싶다면 N-1 값을 Reload 제리스터에 넣어주어야 한다. 문서에도 그렇게 작성되어있음!

SysTick과 RTOS
그래서 이 SysTick이 RTOS와 연관이 있다는데, 어떤 연관이 있는지 살펴보고 넘어가야겠다. 모든 타이머를 주변장치로두고 이 SysTick만 내부로 끌고들어온게 뭔가 이유가 있지 않겠어?
ㅤ
RTOS의 핵심은 Task의 전환이다(라고 나는 생각한다.) 현재 들고있는 Task들을 동시에 관리하면서 제한시간 내에 각 Task를 완료하기. 이게 RTOS의 정수가 아닐까.
ㅤ
이를 위해서는 Task 전환이 필요하다. CPU에게 Task를 전환할 타이밍을 알려주는게 SysTick의 역할이다. RTOS에서는 가장 작은 시간의 단위로 Tick을 쓴다. 그리고 이 한 틱 한 틱을 측정하기 위해 사용하는 것이 바로 SysTick이다. 비유적으로 보자면 RTOS의 CLK 같은 존재가 아닐까.
ㅤ

SysTick에 대한 설명을 다시 한 번 읽어보면, RTOS의 Tick 타이머라는 말과 함께 매 주기마다 SysTick 루틴(SysTick_Handler)을 호출한다고 되어있다.
ㅤ
과연 이름부터 SysTimer가 아니라 SysTick 이였던 이유가 있었군. 나중에 RTOS할 때 SysTick 다시 만나면 반가울 것 같다 ㅋㅋㅋ
알고보니 ioc 파일에서 CLK Configuration 쪽에 가보면 Cortex System Timer에 적용해줄 수 있는 Prescaler가 따로 있다. 테스트를 해봤는데, 내가 이후에 코드로 SysTick의 CTRL 레지스터에 Source를 지정하면 해당 세팅으로 변경되는 것을 확인할 수 있었다. 요 Prescaler 값을 지정해주면 SysTick CTRL 레지스터로 바로 꽂힌다!

SysTick 써보기 실습!
UART를 활용해서 0.5초마다 . 을 터미널에 출력하는 예제. UART는 USART3을 활용해서 Init 시켜주었다.
ㅤ
Polling 방식
void main()
{
...
// AHB CLK 16MHz 기준
// Core 의 클럭 (AHB)를 가져오기 + 이걸 8로 나눈 값을 LOAD에서 사용
uint32_t scaled_CLK = SystemCoreClock >> 3;
// LOAD 초기화
SysTick->LOAD &= ~(0xFFFFFF);
// scaled_CLK을 그대로 사용하면 1초
// scaled_CLK을 2로 나눠서 0.5초 간격으로 타이머가 찍히도록 LOAD 레지스터 값 설정
SysTick->LOAD |= scaled_CLK >> 1;
// VAL 초기화
SysTick->VAL &= ~(0xFFFFFF);
// CTRL 초기화
SysTick->CTRL &= ~(0x7);
// Interrupt 사용 안함
// SysTick->CTRL |= 0x2;
// SysTick 활성화
SysTick->CTRL |= 0x1;
while (1)
{
// 폴링 방식으로 구현
if(SysTick->CTRL >> 16 & 0x1) {
while(((USART3->SR >> 7) & 0x1) == 0);
USART3->DR = '.';
}
}
}
Interrupt 방식
void main()
{
...
// AHB CLK 16MHz 기준
// Core 의 클럭 (AHB)를 가져오기 + 이걸 8로 나눈 값을 LOAD에서 사용
uint32_t scaled_CLK = SystemCoreClock >> 3;
// LOAD 초기화
SysTick->LOAD &= ~(0xFFFFFF);
// scaled_CLK을 그대로 사용하면 1초
// scaled_CLK을 2로 나눠서 0.5초 간격으로 타이머가 찍히도록 LOAD 레지스터 값 설정
SysTick->LOAD |= scaled_CLK >> 1;
// VAL 초기화
SysTick->VAL &= ~(0xFFFFFF);
// CTRL 초기화
SysTick->CTRL &= ~(0x7);
// Interrupt 사용
SysTick->CTRL |= 0x2;
// SysTick 활성화
SysTick->CTRL |= 0x1;
while (1)
{
}
}
// stm32f4xx_it.c
// SysTick 카운터 값이 0이 되어서 인터럽트가 발생할 때 호출되는 함수
void SysTick_Handler(void)
{
while(((USART3->SR >> 7) & 0x1) == 0);
USART3->DR = '.';
}
ㅤ
그래서 Handler랑 Polling이랑 둘 중에 누가 더 빠르게 반응하냐?
에 대해서 궁금해서 한 번 파형을 측정해봤다. 당연히 Handler가 빠를것이라고 생각했는데, 과연 그게 맞을지 어디 한 번 보자구.
ㅤ
코드는 다음과 같이 구성했다.
1) 모든 레지스터 SET
2) 500ms 정도로 SysTick 타이머를 구동 + 인터럽트 허용
3-1) 인터럽트가 호출하면 PD6을 Toggle (갈색 Channel 1)
3-2) while 문에서 Polling으로 인터럽트 감지하면 PD7을 Toggle (빨간색 Channel 2)
실행 결과는 아래그림과 같이 나왔다.
- 검은색 Channel 0은 비교군
- 황토색 Channel 1은 폴링 방식
- 빨간색 Channel 2는 인터럽트 방식



처음에 봤을 때는 “동시에 동작하는구만” 이라고 생각했는데, 완전 자세히 당겨보니깐 인터럽트를 사용한 방식이 폴링 방식보다 약 3~4마이크로초 정도 (== 0.003밀리초 == 0.000003초) 정도 빠른 것을 확인할 수 있었다. 아마도 while 문에 작업중인 Task가 더 많다거나 여러 인터럽트가 혼잡하게 날아다니는 비상상황을 꾸려봤다면 인터럽트쪽이 훨씬 더 빨랐을지도 모르겠다.
사실 인터럽트가 훨씬 더 빠르기를 기대했는데, 4마이크로초가 튀어나와서 ‘에개’ 라는 생각이 절로 들었다. 4마이크로초요??
ㅤ
'Embedded System > MCU' 카테고리의 다른 글
| [MCU] STM32F429ZIT6의 타이머들 (0) | 2025.11.07 |
|---|---|
| [MCU] Basic Timer (2) | 2025.11.07 |
| [MCU] Push-Pull과 Open-Drain의 장단점? (0) | 2025.11.02 |
| [MCU] 풀업, 풀다운 저항은 MOSFET인가? (0) | 2025.11.02 |
| [MCU] V=IR과 기본 임피던스부터 풀업과 풀다운, 푸시풀과 오픈드레인까지 (0) | 2025.11.02 |