Repository navigation
Chapter 08
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->CALIBSysTick은 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
| 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이번 실습에서 사용하는 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방식이다.
현재 Bare-Metal project의 core clock은:
16 MHz이다.
즉:
16,000,000 clocks / second1 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;을 넣는다.
이 Bare-Metal project에서는 별도로 PLL을 설정하는 SystemClock_Config()를 호출하지 않는다.
따라서 clock은 reset 기본 HSI를 사용한다.
HSI
↓
16 MHz
↓
System Clock여기서 중요한 점은 다른 STM32 실습에서 사용했던:
168 MHz설정과 섞으면 안 된다는 것이다.
이 Bare-Metal Wiki 기준:
Core Clock = 16 MHz이다.
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
#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
#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->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->CTRL = CTRL_CLKSRC;
/*
* --------------------------------------------------------
* Step 4. Enable SysTick
* --------------------------------------------------------
*
* Set CTRL bit0.
*
* SysTick now starts counting down.
*/
SysTick->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 < 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->CTRL & 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->CTRL = 0UL;
}
#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->ODR bit0:
*
* 0 → 1
* 1 → 0
*
* Therefore LD1 changes state every 500 ms.
*/
led_toggle();
}
}
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 = 0Main:
led_init()
↓
500 ms delay
↓
LED toggle
↓
500 ms delay
↓
LED toggle
↓
...핵심 코드:
for (uint32_t i = 0; i < delay; i++)
{
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)
{
}
}두 loop의 역할이 다르다.
1 ms가 끝났는가?를 확인한다.
1 ms를 몇 번 기다릴 것인가?를 결정한다.
예:
systick_msec_delay(500);이면:
1 ms 완료 확인
×
500회
500 ms
가 된다.
Counter가 zero에 도달하면:
CTRL[16] = 1즉:
COUNTFLAG = 1이 된다.
코드는:
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)
{
}로 이를 polling한다.
Notion 실습 기록에서는 COUNTFLAG를 읽으면 다음 주기 확인을 위해 flag가 clear되는 동작을 이용했다.
SysTick->VAL = 0UL;은 단순히 C variable에 0을 저장하는 개념과 조금 다르다.
SysTick의 VAL에 write하면 current counter가 clear된다.
즉:
Previous Counter State
↓
VAL Write
↓
Counter Clear
↓
Known Starting State를 만든다.
따라서 이전 SysTick 사용 상태에 영향을 받지 않고 새로운 delay를 시작할 수 있다.
CTRL bit1은:
TICKINT이다.
이를 enable하면 counter가 zero가 되었을 때 SysTick exception을 발생시킬 수 있다.
하지만 현재 코드는:
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL)로 직접 기다린다.
따라서:
SysTick Interrupt
X
SysTick Polling
O
이다.
현재 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등의 방법을 사용할 수 있다.
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 기준이 훨씬 명확하다.
SysTick:
ARM Cortex-M Core
1개
기본적인 system tick/delay 용도
24-bit down counterGeneral Purpose Timer:
STM32 Peripheral
여러 개 존재
Prescaler
Auto Reload
PWM
Input Capture
Output Compare
Interrupt
...다음 Ch09에서는:
TIM2를 직접 사용한다.
기존 실습에서 기록한 결과:
text = 764 bytes
data = 0 bytes
bss = 2560 bytesWarning 없이 build가 완료되었다.
make clean
make확인:
Build PASS기록된 size:
text = 764
data = 0
bss = 2560systick_msec_delay()에 breakpoint를 걸고:
SysTick->LOAD = ONE_MSEC_LOAD - 1UL;실행 후:
SysTick->LOAD
15999
인지 확인한다.
timer 실행 중:
SysTick->VAL을 확인한다.
값이:
15999
↓
...
↓
0방향으로 감소하는 것을 확인할 수 있다.
SysTick은 down counter이기 때문이다.
확인할 bit:
CTRL bit0
ENABLE
CTRL bit2
CLKSOURCE
CTRL bit16
COUNTFLAG
Timer 실행 중:
ENABLE = 1
CLKSOURCE = 1이어야 한다.
Expected:
LD1 state change
every 500 ms즉 LED 전체 ON/OFF cycle은:
ON 500 ms
OFF 500 ms
Total period
≈ 1 second
이다.
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 Toggle16 MHz
↓
16000 clocks
↓
1 ms
↓
COUNTFLAG
↓
500 repetitions
↓
500 msSysTick은 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다.