Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 7

Implementing System Timers and Delays with SysTick

Recommended resources

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:

  1. You load a starting value into its Reload Value Register (LOAD).
  2. You enable the timer.
  3. On each clock cycle, the Current Value Register (VAL) decrements.
  4. When the counter reaches zero, it triggers an event (sets a flag and can optionally generate an interrupt).
  5. On the very next clock cycle, it automatically reloads the value from the LOAD register 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.

This diagram shows the clock paths in an STM32. Notice the "Cortex System Timer" is fed from the AHB Prescaler output, which is derived from the main SYSCLK. This directly links the timer's speed to the clock configuration we performed in the previous lesson.

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

This block diagram shows the internal logic of the SysTick timer. You can see the multiplexer for clock source selection (`CLKSOURCE`), the down-counter, the reload register, and the logic that generates the `COUNTFLAG` and the SysTick interrupt (`TICKINT`).

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.

[PDF] System Timer (SysTick)

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:

  1. Set the LOAD register for a 1ms interval.
  2. Enable the timer with the CPU clock source.
  3. Enter a loop that waits for the COUNTFLAG in the CTRL register to become 1.
  4. 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:

  1. Configure SysTick to generate an interrupt every 1ms. This involves setting the LOAD register and enabling the TICKINT bit in SysTick->CTRL.
  2. 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.
  3. Inside SysTick_Handler(), increment a global volatile integer. This variable now tracks the system uptime in milliseconds. The volatile keyword 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.
  4. Create a delay_ms function 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_Handler ISR, 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.