SysTick is a built-in timer in ARM Cortex-M cores that can provide precise timing for delay functions. Here's how to implement microsecond-level delays using SysTick on STM32.
1. SysTick Configuration
Basic Setup (Without Interrupt)
c
#include "stm32fxxx.h" // Replace with your specific header
void SysTick_Init(void) {
// Configure SysTick to count at CPU clock speed
SysTick->LOAD = (SystemCoreClock / 1000000) - 1; // 1µs per tick
SysTick->VAL = 0; // Clear current value
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | // Enable SysTick
SysTick_CTRL_CLKSOURCE_Msk; // Use processor clock
}
void delay_us(uint32_t microseconds) {
SysTick->VAL = 0; // Reset counter
while (microseconds--) {
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); // Wait for tick
}
}
Usage Example
c
int main(void) {
HAL_Init();
SystemClock_Config();
SysTick_Init();
while (1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggle LED
delay_us(500); // 500µs delay
}
}
2. Higher Precision with 24-bit Counter
SysTick is a 24-bit down-counter, so maximum delay per call is:
- At 168MHz: (2²⁴-1) / 168 ≈ 99.9ms
- For longer delays, use a loop.
Optimized Delay Function
c
void delay_us(uint32_t us) {
uint32_t start = SysTick->VAL;
uint32_t ticks = us * (SystemCoreClock / 1000000);
while (1) {
uint32_t now = SysTick->VAL;
if (now < start) { // Handle counter wrap-around
if (start - now >= ticks) break;
} else {
if ((SysTick->LOAD - now) + start >= ticks) break;
}
}
}
3. Using SysTick Interrupt (For Long Delays)
For delays >100ms, use an interrupt-based approach.
Interrupt Setup
c
volatile uint32_t us_delay;
void SysTick_Handler(void) {
if (us_delay) us_delay--;
}
void delay_us(uint32_t us) {
us_delay = us;
while (us_delay);
}
Initialization
c
void SysTick_Init(void) {
SysTick_Config(SystemCoreClock / 1000000); // 1µs interrupt
NVIC_SetPriority(SysTick_IRQn, 0); // Highest priority
}
4. Calibration for Accuracy
SysTick's actual resolution depends on:
- CPU clock speed (SystemCoreClock).
- Prescaler settings.
Calibration Steps
- Measure a known delay (e.g., GPIO toggle) with an oscilloscope.
- Adjust the SystemCoreClock / 1000000 divisor if needed.
5. Comparison with Other Methods
6. Troubleshooting
7. Advanced: Combining with DWT (Data Watchpoint Trace)
For even higher precision (nanosecond-level), use the DWT Cycle Counter:
c
#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004
void DWT_Init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
void delay_us(uint32_t us) {
uint32_t start = DWT->CYCCNT;
uint32_t cycles = us * (SystemCoreClock / 1000000);
while ((DWT->CYCCNT - start) < cycles);
}
Final Recommendation
- For µs delays: Use SysTick polling (simple and accurate).
- For ns delays: Use DWT cycle counter (if available).
- For long delays: Use SysTick interrupts.