Introduction
In our last lesson, we took a deep dive into the microcontroller's clock system, learning how to configure the various sources and the PLL to set a precise and high-speed system clock. This "heartbeat" governs the execution speed of our entire application. Now that we've set the tempo, our next logical step is to create a way to measure time based on that tempo.
This lesson focuses on the SysTick timer, a fundamental component of the ARM Cortex-M core. You will learn how to use this simple but powerful timer to implement system-level timing and delays. We'll explore its architecture, understand how to configure its registers, and implement two distinct methods for creating delays: a simple but inefficient polling method and a more robust, interrupt-driven approach that forms the backbone of nearly all real-time systems. Mastering SysTick is a crucial step toward writing software that can perform tasks on a predictable schedule.
The SysTick Timer: Your System's Metronome
Every ARM Cortex-M processor, regardless of the manufacturer, includes a standard, built-in timer called SysTick. Think of it as a simple, reliable metronome integrated directly into the CPU core. Its primary purpose is to generate a periodic signal or interrupt, which can be used as a "system tick" for timekeeping.
The SysTick timer consists of a 24-bit down-counter. Here's how it operates:
- You load a starting value into its Reload Value Register (
LOAD). - You enable the timer.
- On each clock cycle, the Current Value Register (
VAL) decrements. - When the counter reaches zero, it triggers an event (sets a flag and can optionally generate an interrupt).
- On the very next clock cycle, it automatically reloads the value from the
LOADregister and starts counting down again.
This simple, repeating countdown is what provides our regular time base. The video below offers a concise overview of this mechanism and its common applications.
Lecture 12: System Timer (SysTick)
This video from the "Embedded Systems and Deep Learning" channel introduces the purpose of the SysTick timer and provides a clear, animated explanation of its down-counting nature.
Watch the first two sections, covering what SysTick is used for and how the 24-bit down counter works. Pay close attention to the relationship between the reload value, the counter, and the generation of the interrupt.
Clock Source and Architecture
A timer is only as accurate as its clock source. As you learned in the previous lesson, the STM32 has a complex clock distribution network. The SysTick timer receives its clock from the AHB bus clock (HCLK), either directly or through a divider.

The internal logic of the SysTick timer itself is quite straightforward, as illustrated below.

Configuring the SysTick Timer
To control the timer, we primarily use three registers. These are part of the core peripherals, so their definitions are found in the main ARM Cortex-M documentation, not just the STM32-specific manuals.
The lecture slides below provide a clear summary of these essential registers.
These slides detail the four main SysTick registers. We will focus on the first three: Control and Status, Reload Value, and Current Value.
Please review the descriptions for the Control and Status Register (CTRL), the Reload Value Register (LOAD), and the Current Value Register (VAL). Pay special attention to the key bits in the CTRL register: ENABLE, TICKINT, CLKSOURCE, and the status bit COUNTFLAG.
To summarize the key registers:
SysTick->CTRL: The main control register. We use it to:ENABLE(Bit 0): Start or stop the timer.TICKINT(Bit 1): Enable or disable the interrupt when the counter reaches zero.CLKSOURCE(Bit 2): Select the clock source (HCLK or HCLK/8).COUNTFLAG(Bit 16): A read-only flag that is set to 1 when the counter reaches zero. It is cleared automatically on read.
SysTick->LOAD: A 24-bit register holding the value to be loaded into the counter when it reaches zero.SysTick->VAL: A 24-bit register holding the current counter value. Writing any value to it clears it to 0.
Calculating the Reload Value
The most important calculation is determining the value for the LOAD register to achieve a desired timing interval. The formula is:
The counter decrements from LOAD down to 0, which is LOAD + 1 total ticks. For example, to generate an interrupt every 1 millisecond (a frequency of 1000 Hz) with the 180 MHz system clock we configured in the last lesson:
This value, 179999, fits comfortably within the 24-bit range of the LOAD register (which has a maximum value of ).
Implementation 1: Blocking Delay with Polling
The simplest way to create a delay is to use SysTick in a polling mode. This method does not require interrupts. The process is as follows:
- Set the
LOADregister for a 1ms interval. - Enable the timer with the CPU clock source.
- Enter a loop that waits for the
COUNTFLAGin theCTRLregister to become 1. - Repeat this loop for the desired number of milliseconds.
This approach is called a "blocking" or "busy-wait" delay because the CPU is stuck inside the waiting loop, unable to perform any other tasks. While simple, it is very inefficient. The following video demonstrates this exact technique.
Bare metal STM32 #2: Using SysTick timer to write a delay function
This "Bare metal STM32" video provides a clear, step-by-step implementation of a blocking delay function using the polling method.
Watch from the calculation to see how the reload value is determined and how the control register is configured. Then, watch the code implementation, which shows how a while loop is used to poll the COUNTFLAG.
Here is what the core logic of a polling-based delay_ms function looks like:
void delay_ms_polling(uint32_t ms) {
// 1. Configure SysTick for a 1ms tick
// Assuming HCLK is 180 MHz
SysTick->LOAD = 180000 - 1; // 1ms = 180,000 cycles
SysTick->VAL = 0; // Clear current value register
SysTick->CTRL = (1 << 0) | (1 << 2); // Enable, Clock source = HCLK
for (uint32_t i = 0; i < ms; i++) {
// 2. Wait until the COUNTFLAG is set
while (!(SysTick->CTRL & (1 << 16)));
}
// 3. Disable SysTick
SysTick->CTRL = 0;
}
Implementation 2: Non-Blocking Time Base with Interrupts
A far more powerful and efficient method is to use SysTick's interrupt capability. Instead of having the CPU wait for a flag, we let the timer run in the background and notify the CPU via an interrupt at each interval. This frees the CPU to perform other work. This approach is fundamental to creating a "time base" for your system.
The following article explains this concept and provides a complete implementation.
Delay using the SysTick Timer on STM32 | STM32 Using Registers #3
This article by Mateusz Salamon explains how to create a system time base and then build a delay function on top of it using interrupts.
Start by reading the section on Time Base to understand the concept. Then, read how to calculate the ticks for a 1ms time base. Next, review the section on the SysTick interrupt, which shows the interrupt handler function. Finally, see how this is all brought together in the Delay function which uses the global Tick variable.
The process for the interrupt-driven method is:
- Configure SysTick to generate an interrupt every 1ms. This involves setting the
LOADregister and enabling theTICKINTbit inSysTick->CTRL. - Create an Interrupt Service Routine (ISR) named
SysTick_Handler(). This specific function name is defined by the ARM architecture and is automatically called when the SysTick interrupt occurs. - Inside
SysTick_Handler(), increment a globalvolatileinteger. This variable now tracks the system uptime in milliseconds. Thevolatilekeyword is crucial; it tells the compiler that this variable's value can change at any time (e.g., by an interrupt), preventing optimizations that might cause bugs. - Create a
delay_msfunction that uses this global tick counter to wait for the required duration.
Here is an example implementation:
// Global variable to store the number of milliseconds since startup
volatile uint32_t g_ticks = 0;
// This function is the ISR for the SysTick interrupt
void SysTick_Handler(void) {
g_ticks++;
}
// Initializes SysTick to generate a 1ms interrupt
void systick_init_1ms(void) {
// Assuming HCLK is 180 MHz
SysTick->LOAD = 180000 - 1;
SysTick->VAL = 0;
// Enable interrupt, Enable SysTick, Clock source = HCLK
SysTick->CTRL = (1 << 1) | (1 << 0) | (1 << 2);
}
// A blocking delay that uses the non-blocking tick counter
void delay_ms(uint32_t ms) {
uint32_t start_tick = g_ticks;
while ((g_ticks - start_tick) < ms);
}
int main(void) {
// ... clock configuration from previous lesson ...
systick_init_1ms();
while(1) {
// ... toggle an LED ...
delay_ms(500); // Wait for 500ms
}
}
Although our delay_ms function is still blocking, the underlying mechanism is not. The g_ticks variable is always being updated in the background, allowing other parts of a more complex application to check the time without stopping execution. This is the foundation for cooperative multitasking and real-time operating systems.
Conclusion
Today you've added a crucial tool to your embedded programming toolkit: the ability to measure and control time. We've seen how the SysTick timer, a standard feature of the ARM Cortex-M core, provides a reliable and configurable time base for our applications. Its behavior is directly tied to the system clock we configured previously, completing the fundamental setup of our processor.
Here are the key takeaways from this lesson:
- The SysTick timer is a simple 24-bit down-counter built into the ARM Cortex-M core, making it a portable solution for timekeeping.
- The timer's speed is derived from the system clock (HCLK), so its configuration depends on the clock setup from our last lesson.
- A polling-based delay is simple to implement by waiting for the
COUNTFLAG, but it is inefficient as it blocks the CPU. - An interrupt-driven approach is far more efficient. It establishes a system "tick" by updating a global counter in the
SysTick_HandlerISR, freeing the CPU to perform other tasks. This method is the cornerstone of real-time scheduling.
This lesson concludes our first module on ARM Cortex-M fundamentals. You now have a solid theoretical understanding of the processor's architecture, memory map, boot sequence, and its core timing mechanisms.
In our next lesson, we will begin Module 2 by finally reaching out to the physical world. We will learn how to configure General-Purpose Input/Output (GPIO) pins, the fundamental way a microcontroller interacts with external components like LEDs, buttons, and sensors.