SysTick Peripheral — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 6, 2025
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
IntPrioritySetwill not work for SysTick; useHWREG(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 |