HomeAbout UsContact Us

Cortex-M SysTick Timer Configuration and Usage

By Jithin Tom
Published in Embedded Concepts
July 27, 2026
3 min read
Cortex-M SysTick Timer Configuration and Usage

Table Of Contents

01
SysTick Register Map
02
Clock Source Selection
03
Reload Value Calculation
04
Interrupt Configuration
05
Common Pitfalls
06
Free-Running Timestamp Mode
07
SysTick in Multi-Core Systems
08
Summary
09
Related Reading
10
References
11
Frequently Asked Questions

The Cortex-M SysTick timer is the backbone of timekeeping in almost every RTOS and bare-metal scheduler targeting ARM Cortex-M cores. It’s a 24-bit down-counter integrated directly into the NVIC, clocked from the core clock (optionally divided by 8), and tied to a dedicated exception vector (Exception #15). Despite its simplicity, misconfiguration of SysTick is a common source of subtle timing bugs — missed ticks, jitter, or complete scheduler failure.

This article walks through the SysTick register map, clock source selection, reload value calculation, interrupt setup, and common pitfalls when using SysTick for RTOS ticks or bare-metal periodic timing.

SysTick Register Map

The SysTick timer exposes four 32-bit registers in the System Control Space (SCS) at base address 0xE000E010:

+--------+---------------+-------------------+-------------------+
| Offset | Register | Description | Key Bits |
+========+===============+===================+===================+
| 0x00 | SYST_CSR | Control & Status | ENABLE (0) |
| | | | TICKINT (1) |
| | | | CLKSOURCE (2) |
| | | | COUNTFLAG (16) |
+--------+---------------+-------------------+-------------------+
| 0x04 | SYST_RVR | Reload Value | RELOAD[23:0] |
+--------+---------------+-------------------+-------------------+
| 0x08 | SYST_CVR | Current Value | CURRENT[23:0] |
+--------+---------------+-------------------+-------------------+
| 0x0C | SYST_CALIB | Calibration | TENMS[23:0] |
| | | | SKEW (30) |
| | | | NOREF (31) |
+--------+---------------+-------------------+-------------------+

Key register behaviors:

  • SYST_CVR reads clear COUNTFLAG — reading the current value register clears the COUNTFLAG bit in SYST_CSR. This matters if you poll COUNTFLAG for tick detection.
  • Writing any value to SYST_CVR clears it to zero — this also clears COUNTFLAG. Use this to reset the counter and synchronize.

Clock Source Selection

The CLKSOURCE bit in SYST_CSR selects the clock input:

CLKSOURCESourceFrequency
0External reference clock (typically core_clk / 8)Core clock ÷ 8
1Processor clock (core clock)Core clock

Always use CLKSOURCE = 1 (processor clock) unless you have a specific reason to use the divided reference. The reference clock frequency is implementation-defined and may not be exactly core_clk/8. Using the processor clock gives you deterministic, calculable timing.

// Use processor clock (core clock)
#define SYST_CSR_CLKSOURCE_Msk (1UL << 2)
SYST_CSR |= SYST_CSR_CLKSOURCE_Msk;

Reload Value Calculation

The reload value determines the period. SysTick counts down from RELOAD to 0, then reloads. The number of clock cycles per period is RELOAD + 1.

period_cycles = RELOAD + 1
RELOAD = (core_clock_hz / desired_freq_hz) - 1

Example: 1 kHz tick at 72 MHz

RELOAD = (72,000,000 / 1,000) - 1 = 71,999 = 0x0001193F

Example: 1 kHz tick at 168 MHz (STM32F4) with /8 prescaler

RELOAD = (168,000,000 / 8 / 1,000) - 1 = 20,999 = 0x0000520F
SYST_CSR |= SYST_CSR_CLKSOURCE_Msk; // Use core clock (don't divide)

Maximum reload value: 0x00FFFFFF (24-bit). At 72 MHz with /8 prescaler, max period ≈ 2.3 seconds.

Interrupt Configuration

Enable the SysTick exception by setting TICKINT (bit 1) in SYST_CSR:

#define SYST_CSR_ENABLE_Msk (1UL << 0)
#define SYST_CSR_TICKINT_Msk (1UL << 1)
#define SYST_CSR_CLKSOURCE_Msk (1UL << 2)
void systick_init(uint32_t reload) {
SYST_RVR = reload; // Set reload value
SYST_CVR = 0; // Clear current value
SYST_CSR = SYST_CSR_CLKSOURCE_Msk | // Processor clock
SYST_CSR_TICKINT_Msk | // Enable interrupt
SYST_CSR_ENABLE_Msk; // Enable counter
}

The SysTick exception handler is SysTick_Handler (Exception #15). In an RTOS context, this calls the tick increment function:

void SysTick_Handler(void) {
// RTOS tick hook
xTaskIncrementTick(); // FreeRTOS
// osSystickHandler(); // CMSIS-RTOS2
}

Priority: SysTick should typically run at the lowest priority (255 on Cortex-M3/M4/M7) to avoid preempting higher-priority interrupts. Configure via NVIC:

NVIC_SetPriority(SysTick_IRQn, 0xFF); // Lowest priority

Common Pitfalls

1. Off-by-one in Reload Value

Wrong: SYST_RVR = core_hz / tick_hz;
Right: SYST_RVR = (core_hz / tick_hz) - 1;

The counter counts from RELOAD down to 0 inclusive — that’s RELOAD + 1 cycles.

2. Forgetting to Clear SYST_CVR

If you don’t write to SYST_CVR before enabling, the counter starts from a random power-on value, causing a wildly incorrect first period.

SYST_CVR = 0; // Must clear before enabling

3. Using the Reference Clock Without Verification

The CLKSOURCE = 0 reference clock is not guaranteed to be core_clk/8. Some vendors implement it differently. Always verify in the device datasheet or use CLKSOURCE = 1.

4. COUNTFLAG Polling Race

If you poll COUNTFLAG in a loop, reading SYST_CVR clears it. This creates a race where you might miss a tick if an interrupt fires between the read and the check.

// Unreliable: reading CVR clears COUNTFLAG
while (!(SYST_CSR & (1 << 16))) { /* wait */ }
uint32_t val = SYST_CVR; // Clears COUNTFLAG!

Better: use the interrupt, or if polling, accept that reading CVR consumes the flag.

5. SysTick Priority Too High

Setting SysTick priority higher than application interrupts causes the RTOS tick to preempt critical sections, breaking atomic operations. Always use the lowest priority.

Free-Running Timestamp Mode

For timestamping without periodic interrupts, configure SysTick with maximum reload:

void systick_freerun_init(void) {
SYST_RVR = 0x00FFFFFF; // Max 24-bit value
SYST_CVR = 0;
SYST_CSR = SYST_CSR_CLKSOURCE_Msk | SYST_CSR_ENABLE_Msk; // No TICKINT
}
// Read timestamp (down-counter)
uint32_t timestamp_us(void) {
return (0x00FFFFFF - SYST_CVR) * (1_000_000 / (SystemCoreClock / 1_000_000));
}
// Or as up-counter
uint32_t timestamp_ticks(void) {
return 0x00FFFFFF - SYST_CVR;
}

This gives a ~2.3 second wraparound at 72 MHz (core clock). For longer periods, chain with a software counter in a lower-frequency timer.

SysTick in Multi-Core Systems

On dual-core Cortex-M devices (e.g., STM32H7), each core has its own SysTick timer. They are not synchronized by hardware. For cross-core time sync, use a shared hardware timer (TIM, LPTIM) or a hardware semaphore with a common timebase.

Summary

  • SysTick is a 24-bit down-counter at 0xE000E010 with four registers
  • Use CLKSOURCE = 1 (processor clock) for deterministic timing
  • RELOAD = (f_clk / f_tick) - 1 — don’t forget the -1
  • Clear SYST_CVR before enabling
  • Enable interrupt via TICKINT bit, set priority to lowest (0xFF)
  • Handler calls RTOS tick function (xTaskIncrementTick for FreeRTOS)
  • Avoid polling COUNTFLAG — reading CVR clears it
  • For timestamping, run free-running with max reload and no interrupt

References

  1. ARM, Cortex-M3/M4/M7 Technical Reference Manual, Section “SysTick Timer” (ARM DDI 0403/0439/0489)
  2. ARM, ARMv7-M Architecture Reference Manual, Section “System Timer (SysTick)” (ARM DDI 0403)
  3. STMicroelectronics, STM32F4xx Programming Manual, Section “SysTick Timer” (PM0214)
  4. FreeRTOS Kernel, Source/tasks.cxTaskIncrementTick implementation
  5. Joseph Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, 3rd ed., Chapter 11 “SysTick Timer”
  6. Michael Barr, Embedded Systems Dictionary, Entry “SysTick Timer” — timing reference

Frequently Asked Questions

What is the SysTick timer in Cortex-M processors?

The SysTick timer is a 24-bit down-counter built into every Cortex-M processor core. It runs on the core clock (or core clock divided by 8) and generates a dedicated exception (Exception #15) when it reaches zero, making it ideal for RTOS tick generation and periodic timekeeping.

Which registers configure the SysTick timer?

Four registers control SysTick: SYST_CSR (Control and Status), SYST_RVR (Reload Value), SYST_CVR (Current Value), and SYST_CALIB (Calibration). SYST_RVR sets the reload value, SYST_CVR reads the current count, and SYST_CSR enables the counter, selects the clock source, and enables the interrupt.

How do you configure SysTick for a 1 ms tick on a 72 MHz Cortex-M3?

With the core clock at 72 MHz and using the processor clock (not divided by 8), set SYST_RVR to 72000 - 1 = 71999. Enable the counter and interrupt by setting SYST_CSR to 0x07 (ENABLE | TICKINT | CLKSOURCE). The timer will count down from 71999 to 0 in exactly 1 ms.

Why does SysTick use a 24-bit counter instead of 32-bit?

The 24-bit width balances silicon area and maximum reload range. At 72 MHz with the /8 prescaler, the maximum period is ~2.3 seconds (2^24 / 9 MHz), which covers all practical RTOS tick intervals. A 32-bit counter would consume more die area for marginal benefit in Cortex-M's target applications.

Can SysTick be used for free-running timestamping instead of periodic interrupts?

Yes. Configure SysTick with the maximum reload value (0xFFFFFF) and read SYST_CVR directly for a free-running down-counter. For an up-counter, compute elapsed ticks as (reload - current). This provides a lightweight timestamp source without periodic interrupt overhead.

Tags

cortex-msysticktimerrtoscortex-m-systickperiodic-timerinterrupt

Share


Previous Article
Reducing Embedded Firmware Size with Linker Garbage Collection
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

ARM Cortex-M NVIC Interrupt Latency Optimization
ARM Cortex-M NVIC Interrupt Latency Optimization
July 13, 2026
4 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media