LED의 1초 점멸이나 센서의 1 ms 주기가 클록 설정을 바꾼 뒤 어긋난다면 SysTick에 넣은 값부터 확인하세요. 이 글은 프로세서 클록을 사용하는 반복 SysTick의 주기 계산을 다룹니다. SysTick_Config()의 인자는 밀리초나 LOAD 값이 아니라 두 인터럽트 사이의 클록 수입니다. Arm CMSIS SysTick 문서와 CMSIS 6.2.0 Cortex-M4 구현을 기준으로 설명합니다.
1. 클록 수와 LOAD를 구분하기
SysTick은 24비트 다운카운터입니다. 프로세서 클록 주파수를 f, 원하는 반복 주파수를 r이라고 하면 정확히 나누어지는 경우 클록 수 N = f / r, 레지스터 값 LOAD = N - 1입니다. 반복 주기는 N / f초입니다. CMSIS 기본 구현이 LOAD에 1을 빼서 쓰므로 호출 전에 다시 1을 빼면 안 됩니다.
| 프로세서 클록 | 목표 주파수 | N | LOAD | 판단 |
|---|---|---|---|---|
| 48 MHz | 1,000 Hz | 48,000 | 47,999 | 1 ms |
| 168 MHz | 1,000 Hz | 168,000 | 167,999 | 1 ms |
| 168 MHz | 1 Hz | 168,000,000 | 범위 초과 | 직접 설정 불가 |
| 32,768 Hz | 1,000 Hz | 정수로 정확히 표현 불가 | 정책 필요 | 단순 나눗셈은 1 ms가 아님 |
| 32,768 Hz | 1,024 Hz | 32 | 31 | 976.5625 µs |
168 MHz에서 가능한 최대 반복 주기는 16,777,216 / 168,000,000, 약 99.864 ms입니다. 1초 간격이 필요하면 1 ms SysTick을 1,000번 누적하거나 적합한 주변장치 타이머를 사용합니다. 이 누적 방식은 초기 기준 시각, 인터럽트 지연, 클록 오차까지 자동으로 해결하지는 않습니다.
2. 24비트 상한과 LOAD=0 함정
24비트 LOAD의 최대값은 0xFFFFFF입니다. 따라서 N의 최대값은 0x1000000 = 16,777,216입니다. LOAD의 상한을 그대로 N의 상한으로 쓰면 유효한 마지막 값을 하나 놓칩니다. N=0일 때는 빼기 연산의 언더플로 때문에 CMSIS 기본 구현이 오류를 반환합니다.
반대쪽 경계도 중요합니다. N=1이면 CMSIS 기본 구현의 범위 검사에는 통과하고 LOAD=0을 씁니다. 하지만 Arm SysTick Reload 설명은 반복 인터럽트가 1에서 0으로 내려가는 전이에서 발생하며 RELOAD=0은 이를 만들지 못한다고 설명합니다. 그래서 아래 계산 함수는 주기 인터럽트용으로 2 ≤ N ≤ 16,777,216을 요구합니다. 이것은 계산상 허용 범위이며, 높은 빈도의 인터럽트를 실제 CPU가 처리할 수 있다는 보장은 아닙니다.
32,768 / 1,000을 정수로 계산하면 32입니다. 이 값으로 설정하면 실제 주파수는 1,024 Hz입니다. 아래 예제는 이런 오차를 조용히 허용하지 않고 PLAN_INEXACT로 반환합니다. 제품에서 근사 주기나 분수 누적을 사용할지는 별도로 정해야 합니다.
3. PC에서 실행하는 C11 경계값 검사
아래 전체 코드를 systick_plan.c로 저장하세요. GCC가 있는 터미널에서 gcc -std=c11 -O2 -Wall -Wextra -Werror systick_plan.c -o systick_plan을 실행하고, Windows PowerShell에서는 ./systick_plan.exe, Linux에서는 ./systick_plan으로 실행합니다. assert를 검사하므로 -DNDEBUG를 추가하지 않습니다.
예상 출력은 다음과 같습니다.
이 예제는 0 입력, 포인터, N=0·1·2, 최대값과 최대값+1, 나누어떨어지지 않는 요청을 검사합니다. 추가로 클록·목표 주파수 각각 1~256의 65,536개 조합을 대상으로 N × r = f를 만족하는 정수 N을 따로 찾아 함수 결과와 대조합니다. 오류일 때 출력 인자를 바꾸지 않는지도 확인합니다. PC에서 검증하는 것은 계산과 입력 정책입니다. 실제 SysTick 레지스터, ISR 실행 시간, 저전력 모드 동작을 시뮬레이션한 결과는 아닙니다.
4. 베어메탈 펌웨어에 적용하기
기존 코드에 위의 enum plan_status와 make_plan()을 가져오고, 장치 전용 CMSIS 헤더를 포함한 초기화 코드에서 다음 순서를 적용합니다. 클록 설정을 마친 뒤 실행하는 베어메탈 코드 조각이며 main() 전체는 아닙니다.
CMSIS System and Clock Configuration은 실행 중 코어 클록이 바뀔 때 SystemCoreClockUpdate()로 변수를 갱신하도록 설명합니다. 이 함수는 변수를 갱신하며 기존 SysTick의 LOAD를 새 주기에 맞춰 자동 변경하지는 않습니다. 48 MHz에 맞춘 N=48,000을 유지한 채 168 MHz로 바꾸면 계산상 주기는 약 285.714 µs가 됩니다. 클록 전환 절차 안에서 타이머의 정지·재설정과 경과 시간 처리를 함께 설계해야 합니다.
CMSIS 기본 SysTick_Config()는 프로세서 클록 선택, 인터럽트 활성화와 SysTick 우선순위 설정까지 수행합니다. __Vendor_SysTickConfig=1이면 제조사 구현을 확인해야 합니다. 외부 기준 클록이나 제조사 분주 경로를 직접 선택한 코드에는 해당 경로의 실제 주파수를 사용하세요. FreeRTOS·HAL이 이미 SysTick을 관리하는 프로젝트에서는 포트와 타임베이스 설정을 따라야 하며 위 코드를 별도로 호출해 덮어쓰지 않습니다.
5. 보드에서 확인할 항목
- 실제 PLL·분주 설정과
SystemCoreClock이 일치하는지 확인합니다. 변수 값만 바꾸어도 하드웨어 클록이 바뀌지는 않습니다. - 초기화 직후
LOAD,CTRL.CLKSOURCE,CTRL.TICKINT,CTRL.ENABLE을 읽고 계획한 값과 비교합니다. 반환값이 오류이면 성공한 설정으로 취급하지 않습니다. - 인터럽트 핸들러에서 GPIO를 한 번 토글해 로직 애널라이저로 측정합니다. 1 kHz ISR마다 토글하면 인접 에지는 약 1 ms, 상승 에지끼리의 주기는 약 2 ms입니다. 핸들러에서는 오래 걸리는 출력이나 센서 통신을 피합니다.
- 장시간 인터럽트 마스킹과 높은 우선순위 ISR의 영향을 측정합니다. SysTick pending 상태 하나가 놓친 모든 주기를 개수대로 저장해 주지는 않습니다.
- 저전력 모드에서는 보드 매뉴얼의 SysTick 클록 지속 여부와 복귀 시 클록 설정을 확인합니다. 정밀한 장시간 계시가 필요하면 RTC 등 목적에 맞는 타이머도 검토합니다.
공식 문서는 2026년 10월 12일 확인했습니다. PC 검증과 실제 보드 측정은 구분해서 기록하고, 장치별 클록 경로와 OS의 타이머 소유권을 확인한 뒤 적용하세요.
댓글
첫 댓글을 남겨 보세요.