Skip to content

Chapter 08

Hongjayeong-02 edited this page Oct 5, 2026 · 2 revisions

08. SysTick Timer

Goal

Ch02~07에서 사용했던 단순 loop delay 대신 Cortex-M4 내부의 SysTick timer를 이용해 millisecond 단위 delay를 만든다.

이번 실습:

System Clock = 16 MHz
        ↓
SysTick 16000 clocks
        ↓
1 ms
        ↓
500회 반복
        ↓
500 ms delay
        ↓
LD1 Toggle

핵심 register:

SysTick->CTRL
SysTick->LOAD
SysTick->VAL
SysTick->CALIB

1. SysTick

SysTick은 STM32 전용 peripheral이 아니라 ARM Cortex-M core 내부 timer다.

Cortex-M4 Core
│
├── NVIC
├── SysTick
├── Debug
└── CPU

따라서 GPIO나 USART처럼 ST가 만든 peripheral과 성격이 다르다.

SysTick
→ Cortex-M 공통

TIM2, TIM3 ... → STM32 Peripheral


2. SysTick Registers

Register 역할
SysTick->CTRL Timer enable, clock source, status
SysTick->LOAD Reload value
SysTick->VAL 현재 counter value
SysTick->CALIB Calibration information

SysTick은 24-bit down counter다.

동작:

LOAD
 ↓
15999
 ↓
15998
 ↓
...
 ↓
1
 ↓
0
 ↓
COUNTFLAG = 1
 ↓
Reload

3. CTRL Important Bits

이번 실습에서 사용하는 bit:

#define CTRL_ENABLE     (1UL << 0)
#define CTRL_CLKSRC     (1UL << 2)
#define CTRL_COUNTFLAG  (1UL << 16)

의미:

bit 0  ENABLE
       1 = SysTick counter enable

bit 1 TICKINT 1 = SysTick interrupt enable 이번 실습에서는 사용하지 않음

bit 2 CLKSOURCE 1 = Processor clock 사용

bit16 COUNTFLAG Counter가 0까지 내려가면 1

이번 실습은 interrupt 방식이 아니라:

COUNTFLAG Polling

방식이다.


4. 1 ms Reload Calculation

현재 Bare-Metal project의 core clock은:

16 MHz

이다.

즉:

16,000,000 clocks / second

1 ms는:

1 ms

0.001 second

따라서:

16,000,000 × 0.001

16,000 clocks

SysTick은 0까지 포함해서 count하므로:

LOAD = 16000 - 1
= 15999

를 사용한다.

#define ONE_MSEC_LOAD 16000UL

그리고 실제 register에는:

SysTick->LOAD = ONE_MSEC_LOAD - 1UL;

을 넣는다.


5. Why 16 MHz?

이 Bare-Metal project에서는 별도로 PLL을 설정하는 SystemClock_Config()를 호출하지 않는다.

따라서 clock은 reset 기본 HSI를 사용한다.

HSI
↓
16 MHz
↓
System Clock

여기서 중요한 점은 다른 STM32 실습에서 사용했던:

168 MHz

설정과 섞으면 안 된다는 것이다.

이 Bare-Metal Wiki 기준:

Core Clock = 16 MHz

이다.


6. Practice Code

Notion 원본에는 systick_msec_delay()와 main.c 핵심 코드가 기록되어 있다. 아래는 이를 실제 project에서 사용하기 쉽도록 systick.h / systick.c / main.c 구조로 정리한 것이다.

Project:

Inc/
├── gpio.h
└── systick.h

Src/ ├── gpio.c ├── systick.c └── main.c


systick.h

#ifndef SYSTICK_H__
#define SYSTICK_H__

#include <stdint.h>

/*

  • Delay execution for the requested number of milliseconds.
  • This implementation assumes:
  • Core Clock = 16 MHz
    
  • Example:
  • systick_msec_delay(500);
    
  • → approximately 500 ms delay
    

*/ void systick_msec_delay(uint32_t delay);

#endif


7. systick.c

#include "stm32f4xx.h"
#include "systick.h"

/*

  • ============================================================
  • SysTick CTRL Register Bit Definitions
  • ============================================================ */

/*

  • SysTick CTRL bit0 : ENABLE
  • 0 = Counter disabled
  • 1 = Counter enabled */ #define CTRL_ENABLE (1UL << 0)

/*

  • SysTick CTRL bit2 : CLKSOURCE
  • 0 = External/reference clock
  • 1 = Processor clock
  • This project uses the processor clock. */ #define CTRL_CLKSRC (1UL << 2)

/*

  • SysTick CTRL bit16 : COUNTFLAG
  • Hardware sets this flag when the SysTick counter
  • counts down to zero.
  • The delay routine polls this bit to detect
  • the completion of each 1 ms period. */ #define CTRL_COUNTFLAG (1UL << 16)

/*

  • ============================================================
  • SysTick Timing Definition
  • ============================================================ */

/*

  • Current core clock:
  • 16 MHz = 16,000,000 clocks/sec
    
  • Number of clocks in 1 ms:
  • 16,000,000 × 0.001
    
  • = 16,000 clocks
    
  • SysTick counts:
  • LOAD → ... → 1 → 0
    
  • Therefore the actual value written to LOAD is:
  • 16,000 - 1
    
  • = 15,999
    

*/ #define ONE_MSEC_LOAD 16000UL

/*

  • ============================================================
  • Millisecond Delay
  • ============================================================ */

void systick_msec_delay(uint32_t delay) { /* * -------------------------------------------------------- * Step 1. Set reload value * -------------------------------------------------------- * * ONE_MSEC_LOAD = 16000 * * LOAD = 15999 * * With a 16 MHz processor clock, * this produces one 1 ms counting period. */ SysTick->LOAD = ONE_MSEC_LOAD - 1UL;

/*
 * --------------------------------------------------------
 * Step 2. Clear current counter value
 * --------------------------------------------------------
 *
 * Writing to VAL clears the current SysTick counter.
 *
 * This ensures that the delay starts from
 * a known counter state.
 */
SysTick-&gt;VAL = 0UL;


/*
 * --------------------------------------------------------
 * Step 3. Select SysTick clock source
 * --------------------------------------------------------
 *
 * CTRL_CLKSRC = bit2 = 1
 *
 * Use processor clock as the SysTick source.
 *
 * Note:
 * TICKINT(bit1) is NOT enabled.
 *
 * Therefore this implementation does not use
 * SysTick interrupts.
 */
SysTick-&gt;CTRL = CTRL_CLKSRC;


/*
 * --------------------------------------------------------
 * Step 4. Enable SysTick
 * --------------------------------------------------------
 *
 * Set CTRL bit0.
 *
 * SysTick now starts counting down.
 */
SysTick-&gt;CTRL |= CTRL_ENABLE;


/*
 * --------------------------------------------------------
 * Step 5. Repeat 1 ms delay
 * --------------------------------------------------------
 *
 * Example:
 *
 *     delay = 500
 *
 * The outer for-loop repeats 500 times.
 *
 * Each iteration waits for one SysTick period:
 *
 *     1 ms × 500
 *     = 500 ms
 */
for (uint32_t i = 0; i &lt; delay; i++)
{
    /*
     * Wait until COUNTFLAG becomes 1.
     *
     * While counter has NOT yet reached zero:
     *
     *     COUNTFLAG = 0
     *
     * so CPU remains in this while-loop.
     *
     * Once counter reaches zero:
     *
     *     COUNTFLAG = 1
     *
     * and the while-loop terminates.
     *
     * This is polling.
     */
    while ((SysTick-&gt;CTRL &amp; CTRL_COUNTFLAG) == 0UL)
    {
        /*
         * Busy wait.
         *
         * CPU performs no other useful work here.
         */
    }
}


/*
 * --------------------------------------------------------
 * Step 6. Stop SysTick
 * --------------------------------------------------------
 *
 * Clear CTRL register.
 *
 * ENABLE = 0
 * CLKSOURCE = 0
 * TICKINT = 0
 *
 * SysTick stops after the requested delay.
 */
SysTick-&gt;CTRL = 0UL;

}


8. main.c

#include "gpio.h"
#include "systick.h"

int main(void) { /* * Initialize LD1 GPIO. * * NUCLEO-F439ZI: * * LD1 → PB0 * * gpio.c configures PB0 as output. */ led_init();

while (1)
{
    /*
     * Wait 500 ms using the Cortex-M4 SysTick timer.
     *
     * Internally:
     *
     *     1 ms period
     *     ×
     *     500 iterations
     *     =
     *     500 ms
     */
    systick_msec_delay(500);


    /*
     * Toggle PB0.
     *
     * GPIOB-&gt;ODR bit0:
     *
     *     0 → 1
     *     1 → 0
     *
     * Therefore LD1 changes state every 500 ms.
     */
    led_toggle();
}

}


9. Important Code Flow

systick_msec_delay(500):

LOAD = 15999
↓
VAL = 0
↓
CLKSOURCE = Core Clock
↓
ENABLE = 1
↓
Counter Starts
↓
15999 → ... → 0
↓
COUNTFLAG = 1
↓
1 ms complete
↓
Repeat 500 times
↓
500 ms complete
↓
CTRL = 0

Main:

led_init()
↓
500 ms delay
↓
LED toggle
↓
500 ms delay
↓
LED toggle
↓
...

10. Why for + while?

핵심 코드:

for (uint32_t i = 0; i < delay; i++)
{
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)
{
}
}

두 loop의 역할이 다르다.

while

1 ms가 끝났는가?

를 확인한다.

for

1 ms를 몇 번 기다릴 것인가?

를 결정한다.

예:

systick_msec_delay(500);

이면:

1 ms 완료 확인
×
500회

500 ms

가 된다.


11. COUNTFLAG

Counter가 zero에 도달하면:

CTRL[16] = 1

즉:

COUNTFLAG = 1

이 된다.

코드는:

while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)
{
}

로 이를 polling한다.

Notion 실습 기록에서는 COUNTFLAG를 읽으면 다음 주기 확인을 위해 flag가 clear되는 동작을 이용했다.


12. Why VAL = 0?

SysTick->VAL = 0UL;

은 단순히 C variable에 0을 저장하는 개념과 조금 다르다.

SysTick의 VAL에 write하면 current counter가 clear된다.

즉:

Previous Counter State
↓
VAL Write
↓
Counter Clear
↓
Known Starting State

를 만든다.

따라서 이전 SysTick 사용 상태에 영향을 받지 않고 새로운 delay를 시작할 수 있다.


13. Why No TICKINT?

CTRL bit1은:

TICKINT

이다.

이를 enable하면 counter가 zero가 되었을 때 SysTick exception을 발생시킬 수 있다.

하지만 현재 코드는:

while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)

로 직접 기다린다.

따라서:

SysTick Interrupt
X

SysTick Polling O

이다.


14. Busy Waiting

현재 delay에서는 CPU가:

while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)
{
}

안에서 계속 기다린다.

즉 500ms 동안 CPU는 다른 application 작업을 하지 않는다.

CPU
↓
Wait
↓
Wait
↓
Wait
↓
Timer Complete

이를:

Busy Waiting

이라고 한다.

간단한 실습에는 적합하지만 복잡한 embedded system에서는:

Interrupt
RTOS Tick
State Machine
Non-blocking Timer

등의 방법을 사용할 수 있다.


15. SysTick vs Software Delay

Ch02에서 사용한 delay:

while (count--)
{
asm("nop");
}

는 정확도가 compiler와 instruction execution에 영향을 받는다.

Software Loop Delay

Compiler Optimization CPU Instruction Source Code ↓ Delay 변화 가능

SysTick:

Hardware Counter
↓
Clock 기준으로 Count

이므로 timing 기준이 훨씬 명확하다.


16. SysTick vs TIM

SysTick:

ARM Cortex-M Core
1개
기본적인 system tick/delay 용도
24-bit down counter

General Purpose Timer:

STM32 Peripheral
여러 개 존재
Prescaler
Auto Reload
PWM
Input Capture
Output Compare
Interrupt
...

다음 Ch09에서는:

TIM2

를 직접 사용한다.


17. Build Result

기존 실습에서 기록한 결과:

text = 764 bytes
data = 0 bytes
bss  = 2560 bytes

Warning 없이 build가 완료되었다.


18. Verification

1. Build

make clean
make

확인:

Build PASS

기록된 size:

text = 764
data = 0
bss  = 2560

2. Debugger — LOAD

systick_msec_delay()에 breakpoint를 걸고:

SysTick->LOAD = ONE_MSEC_LOAD - 1UL;

실행 후:

SysTick->LOAD

15999

인지 확인한다.


3. Debugger — VAL

timer 실행 중:

SysTick->VAL

을 확인한다.

값이:

15999
↓
...
↓
0

방향으로 감소하는 것을 확인할 수 있다.

SysTick은 down counter이기 때문이다.


4. Debugger — CTRL

확인할 bit:

CTRL bit0
ENABLE

CTRL bit2 CLKSOURCE

CTRL bit16 COUNTFLAG

Timer 실행 중:

ENABLE    = 1
CLKSOURCE = 1

이어야 한다.


5. Board

Expected:

LD1 state change
every 500 ms

즉 LED 전체 ON/OFF cycle은:

ON  500 ms
OFF 500 ms

Total period ≈ 1 second

이다.


19. Screenshots to Add

images/ch08_build.png
images/ch08_systick_load.png
images/ch08_systick_val.png
images/ch08_systick_ctrl.png
images/ch08_led.gif

가장 중요한 증거:

LOAD = 15999
+
VAL Down Counting
+
LD1 500 ms Toggle

20. Key Points

16 MHz
↓
16000 clocks
↓
1 ms
↓
COUNTFLAG
↓
500 repetitions
↓
500 ms
  • SysTick은 STM32 peripheral이 아니라 Cortex-M core timer다.

  • SysTick은 24-bit down counter다.

  • LOAD는 reload value를 설정한다.

  • VAL은 current counter와 관련된다.

  • CTRL에서 clock source와 counter enable을 제어한다.

  • COUNTFLAG로 counter가 zero에 도달했는지 확인한다.

  • 16MHz에서 1ms는 16,000 clock이다.

  • 따라서 LOAD = 15999를 사용한다.

  • 현재 구현은 interrupt가 아닌 polling 방식이다.

  • systick_msec_delay(500)은 1ms period를 500회 기다린다.

  • 이 Bare-Metal project의 clock 기준은 168MHz가 아니라 16MHz다.

Clone this wiki locally