all
Chapter 6 of 20

SysTick Peripheral — Tiva C

Eslam El Hefny Apr 6, 2025 6 min read
30% done

SysTick Peripheral — Tiva C

Overview

SysTick is a 24-bit down-counting timer built directly into the ARM Cortex-M4 core. It sits in the Private Peripheral Bus and is available on every Cortex-M device, making SysTick-based code portable across vendors. It is primarily used for generating periodic time-base interrupts (RTOS tick) and simple blocking delays.


Beginner Level — What & Why

What is SysTick?

SysTick is like a kitchen countdown timer built into the CPU itself. You load a number, press start, and it counts down to zero. When it reaches zero it can fire an interrupt or simply reload and keep counting. You do not need to enable a separate peripheral clock — it is always there.

Real-World Analogy

Imagine a metronome in a music studio. You set the tempo (RELOAD value), and it ticks regularly. Each tick can either just be noted (polled) or it can trigger a specific action (interrupt).

What Problem Does It Solve?

  • Provides a precise, portable time base without using a General-Purpose Timer peripheral.
  • Enables RTOS schedulers to preempt tasks at regular intervals.
  • Provides simple blocking millisecond/microsecond delays.

Key Terms

Term Meaning
RELOAD Value loaded when counter reaches 0 — sets period
CURRENT Current count value — reads as 0 after reset
STCTRL Control register — enable, interrupt, clock source
SysCtlDelay TivaWare function using SysTick for 3-cycle burn loops
Tick period Time = RELOAD / f_cpu

Intermediate Level — How It Works

SysTick Registers

Register Address Description
STCTRL 0xE000E010 Control and Status (ENABLE, INTEN, CLK_SRC, COUNTFLAG)
STRELOAD 0xE000E014 Reload value (24-bit, 0–16,777,215)
STCURRENT 0xE000E018 Current count (write any value to clear to 0)
STCALIB 0xE000E01C Calibration (TENMS field, vendor-specific)

STCTRL bit fields:

Bit Name Description
0 ENABLE 1 = counter running
1 INTEN 1 = generate SysTick interrupt on underflow
2 CLK_SRC 1 = system clock, 0 = external reference clock
16 COUNTFLAG Set to 1 when count reaches 0 since last read

TivaWare API

#include "driverlib/systick.h"

/* Set period for 1 ms at 80 MHz: 80,000,000 / 1000 = 80,000 ticks */
SysTickPeriodSet(80000);  // max value = 16,777,216

/* Enable SysTick counter */
SysTickEnable();

/* Enable SysTick interrupt */
SysTickIntEnable();

/* Register ISR (or rely on startup_ccs.c weak declaration) */
SysTickIntRegister(SysTick_Handler);

/* Read current count */
uint32_t ui32Count = SysTickValueGet();

/* Polling-based 1 ms delay (no interrupt needed) */
SysTickPeriodSet(80000);
SysTickEnable();
while (!(HWREG(NVIC_ST_CTRL) & NVIC_ST_CTRL_COUNT));  // wait for COUNTFLAG

SysCtlDelay

SysCtlDelay(n) burns exactly 3n CPU cycles in a tight loop. It is implemented as a short assembly function:

/* Delay approximately n milliseconds at 80 MHz */
void delay_ms(uint32_t ms)
{
    /* 80,000,000 cycles/sec / 3 cycles per loop / 1000 ms = 26,666 loops/ms */
    SysCtlDelay((SysCtlClockGet() / 3000) * ms);
}

Advanced Level — Deep Dive

Bare-Metal SysTick Setup

#include "inc/hw_types.h"
#include "inc/hw_nvic.h"    // NVIC_ST_* definitions

void SysTick_InitPeriodic_1ms(void)
{
    /* Step 1: Disable SysTick while configuring */
    HWREG(NVIC_ST_CTRL) = 0;

    /* Step 2: Set RELOAD for 1 ms at 80 MHz
     * RELOAD = (f_cpu / desired_freq) - 1
     * = (80,000,000 / 1000) - 1 = 79,999 */
    HWREG(NVIC_ST_RELOAD) = 79999;

    /* Step 3: Clear current count by writing any value */
    HWREG(NVIC_ST_CURRENT) = 0;

    /* Step 4: Enable with system clock and interrupt */
    HWREG(NVIC_ST_CTRL) = NVIC_ST_CTRL_CLK_SRC |  // use system clock
                           NVIC_ST_CTRL_INTEN   |  // enable interrupt
                           NVIC_ST_CTRL_ENABLE;    // start counter
}

Implementing a Millisecond Tick Counter

static volatile uint32_t g_ui32SysTickMs = 0;

void SysTick_Handler(void)
{
    g_ui32SysTickMs++;  // increments every 1 ms
}

/* Returns elapsed milliseconds since startup */
uint32_t GetTickMs(void)
{
    return g_ui32SysTickMs;
}

/* Non-blocking delay using tick counter */
void DelayMs(uint32_t ms)
{
    uint32_t start = GetTickMs();
    while ((GetTickMs() - start) < ms);
}

Calculating RELOAD for Any Frequency

RELOAD = (SystemClock / DesiredFrequency) - 1

1 Hz (1 second) at 80 MHz: RELOAD = 80,000,000 - 1 = 79,999,999
  (exceeds 24-bit max of 16,777,215 — NOT possible!)

1000 Hz (1 ms) at 80 MHz: RELOAD = 80,000 - 1 = 79,999  (OK)

10000 Hz (0.1 ms) at 80 MHz: RELOAD = 8,000 - 1 = 7,999  (OK)

For periods longer than ~209 ms at 80 MHz, use a General-Purpose Timer instead.

SysTick vs General-Purpose Timers

Feature SysTick GPTM
Bit width 24-bit 16 or 32-bit
Max period @ 80 MHz ~209 ms ~53 s (32-bit)
Number available 1 6
Peripheral clock needed No Yes
Portability All Cortex-M TI-specific
Capture mode No Yes

Gotchas

  • RELOAD = 0 is invalid — the counter will never fire.
  • Writing STCURRENT does not change STRELOAD — it only clears the current count.
  • SysTick priority is set via the SYSPRI3 register (SCB), not the standard NVIC priority registers. TivaWare’s IntPrioritySet will not work for SysTick; use HWREG(NVIC_SYS_PRI3) directly or the dedicated function.
  • At high interrupt rates (>100 kHz), ISR overhead may consume significant CPU time.

Step-by-Step Example

/*
 * systick_demo.c
 * Uses SysTick at 1 kHz to create millisecond-accurate LED blink
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 */

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/systick.h"
#include "driverlib/interrupt.h"

/* Global millisecond tick counter incremented by SysTick ISR */
volatile uint32_t g_ui32TickMs = 0;

/* SysTick ISR — fires every 1 ms */
void SysTick_Handler(void)
{
    g_ui32TickMs++;
}

/* Returns current tick count */
static inline uint32_t Millis(void)
{
    return g_ui32TickMs;
}

/* Blocking delay in milliseconds */
void DelayMs(uint32_t ui32Ms)
{
    uint32_t ui32Start = Millis();
    while ((Millis() - ui32Start) < ui32Ms);
}

int main(void)
{
    /* Step 1: Configure 80 MHz system clock */
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    /* Step 2: Enable Port F for LEDs */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
                          GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);

    /* Step 3: Configure SysTick for 1 ms period
     * Period = clock / frequency = 80,000,000 / 1,000 = 80,000 */
    SysTickPeriodSet(80000);

    /* Step 4: Enable SysTick interrupt */
    SysTickIntEnable();

    /* Step 5: Start the SysTick counter */
    SysTickEnable();

    /* Step 6: Enable global interrupts */
    IntMasterEnable();

    /* Application loop: LED pattern timed in milliseconds */
    while (1)
    {
        /* Red ON 200 ms */
        GPIOPinWrite(GPIO_PORTF_BASE,
                     GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1);
        DelayMs(200);

        /* Green ON 200 ms */
        GPIOPinWrite(GPIO_PORTF_BASE,
                     GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_3);
        DelayMs(200);

        /* Blue ON 200 ms */
        GPIOPinWrite(GPIO_PORTF_BASE,
                     GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_2);
        DelayMs(200);

        /* All OFF 400 ms */
        GPIOPinWrite(GPIO_PORTF_BASE,
                     GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0);
        DelayMs(400);
    }
}

Summary

Key Point Details
Counter width 24-bit, counts down
Clock source System clock (80 MHz)
1 ms RELOAD 79,999 at 80 MHz
Max period ~209 ms at 80 MHz
STCTRL bits ENABLE (0), INTEN (1), CLK_SRC (2)
COUNTFLAG Set when counter reaches 0, cleared on read
Interrupt name SysTick_Handler
TivaWare API SysTickPeriodSet, SysTickEnable, SysTickIntEnable

Next Chapter

ADC in Tiva C

Share: