Key Takeaway: STM32 timer interrupts deliver microsecond-precision timing for stepper motors, servo control, and encoder reading — eliminating the jitter and missed steps that plague software-based delay loops in industrial motion control systems.
Table of Contents
- 1. Why Timer Interrupts Matter for Motor Control
- 2. STM32 Timer Architecture Overview
- 3. Timer Modes for Motion Control
- 4. Writing an Efficient Interrupt Service Routine
- 5. Practical Example: Stepper Motor Speed Control
- 6. Encoder Reading with Timer Input Capture
- 7. Best Practices for Industrial Applications
- 8. FAQ
1. Why Timer Interrupts Matter for Motor Control
When you call delay() or delayMicroseconds() in a bare-metal embedded program, the CPU sits idle — or worse, misses an incoming encoder pulse. In industrial motion control, even a few microseconds of jitter can cause stepper motor missed steps, PID control instability, or encoder position errors.
STM32 timer interrupts solve this by offloading timing to dedicated hardware peripherals. The timer counts independently of the main loop, and triggers an interrupt at precise intervals — typically within ±1 clock cycle of the target period. This is the foundation of reliable embedded systems for factory automation.
Key advantages of hardware timer interrupts over software timing:
- Zero jitter: Interrupt fires at exact clock-cycle precision regardless of main loop load
- Non-blocking: CPU continues executing main loop code between interrupts
- Multi-channel: One timer can drive multiple independent PWM outputs or capture inputs
- Event-driven: Timers can trigger DMA transfers, ADC conversions, or other peripherals without CPU intervention
2. STM32 Timer Architecture Overview
STM32 microcontrollers — particularly the STM32F4, STM32G4, and STM32H7 series commonly used in motor control applications — include multiple timer peripherals with different capabilities:
| Timer | Bits | Channels | Special Features |
|---|---|---|---|
| TIM1 / TIM8 | 16-bit | 4 | Advanced: Dead-time insertion, complementary outputs, break input |
| TIM2 / TIM5 | 32-bit | 4 | Long period counting — ideal for slow-speed encoders |
| TIM3 / TIM4 | 16-bit | 4 | General-purpose: PWM, input capture, encoder interface |
| TIM9–TIM14 | 16-bit | 2 | Reduced feature set — good for simple periodic interrupts |
For motor timing, TIM1 (advanced) is the first choice because it supports dead-time insertion — critical for H-bridge motor drivers where simultaneous high-side and low-side conduction would cause a short circuit.
3. Timer Modes for Motion Control
Up-Counting Mode
The simplest mode: the timer counts from 0 to ARR (auto-reload register), generates an update event, and resets. This is used for periodic interrupts — for example, triggering a PID calculation every 1 ms.
PWM Mode 1 and 2
PWM output is the primary method for controlling motor speed. In Mode 1, the output is active when the counter is below the capture/compare register (CCR) value. The PWM frequency and duty cycle are independently configurable.
One-Pulse Mode
The timer generates exactly one PWM pulse of configurable width — ideal for sending a precise step pulse to a stepper motor driver like the DRV8825. After the pulse completes, the timer stops until re-triggered.
Encoder Interface Mode
The hardware encoder mode decodes quadrature encoder signals (A and B channels) directly, counting up or down based on direction — no CPU intervention needed. This is available on TIM2, TIM3, TIM4, and TIM5.
4. Writing an Efficient Interrupt Service Routine
The golden rule for ISR performance: keep it short. Every microsecond spent in the ISR is a microsecond stolen from your main loop. Here is a well-structured timer interrupt handler for STM32 HAL:
// Global volatile flag — set in ISR, cleared in main loop
volatile bool timer_flag = false;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM1) {
timer_flag = true;
}
}
// In main loop
while (1) {
if (timer_flag) {
timer_flag = false;
// Run PID calculation, step motor, read sensors
run_control_loop();
}
// Other non-critical tasks here
}
For time-critical ISR code that must execute within the interrupt context (e.g., generating step pulses for a stepper motor at high speed), use __HAL_TIM_ENABLE_IT() and __HAL_TIM_DISABLE_IT() to dynamically manage interrupt priority. STM32 supports nested vectored interrupts — a higher-priority timer can preempt a lower-priority one.
5. Practical Example: Stepper Motor Speed Control
Controlling a NEMA 23 stepper motor with a DRV8825 driver using STM32 timer interrupts. The goal: generate precise step pulses at a variable frequency to control motor speed from 1 RPM to 600 RPM.
Step pulse frequency calculation:
- NEMA 23 with 200 steps/revolution (1.8° per step)
- DRV8825 at 32x microstepping = 6400 steps per revolution
- For 60 RPM: 60 × 6400 / 60 = 6400 pulses per second
- TIM1 ARR = (SystemCoreClock / Prescaler) / Target Frequency – 1
// TIM1 configured for step pulse generation
// SystemClock = 168 MHz, Prescaler = 168, PSC => 1 MHz tick
// For 1000 steps/sec: ARR = 1000000/1000 - 1 = 999
void set_stepper_speed(uint32_t steps_per_sec) {
if (steps_per_sec == 0) {
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);
return;
}
uint32_t arr_value = (1000000 / steps_per_sec) - 1;
__HAL_TIM_SET_AUTORELOAD(&htim1, arr_value);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, arr_value / 2); // 50% duty
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
}
This approach achieves step pulse timing accuracy within ±0.01% — far superior to any software-based approach. For acceleration/deceleration profiling (trapezoidal or S-curve), update the ARR value in a periodic timer ISR that runs at a lower frequency (e.g., 1 kHz).
6. Encoder Reading with Timer Input Capture
STM32’s hardware encoder interface on TIM3 or TIM4 automatically decodes quadrature encoder signals. Configure the timer in encoder mode:
// Encoder Mode 3: counting on both TI1 and TI2 edges
// Quadrature decoder — 4x resolution
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
// Read position
int32_t position = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
// Calculate speed using input capture on TIM4
float rpm = (float)encoder_delta * 60.0f /
((float)ENCODER_PPR * (float)overflows_in_period);
For high-resolution encoders (1000+ PPR), the 32-bit timers TIM2 or TIM5 are preferred because they prevent counter overflow during high-speed rotation.
7. Best Practices for Industrial Applications
Clock source selection: Always use the internal high-speed oscillator (HSI at 16 MHz or the PLL-derived system clock) rather than HSI/2 for maximum timer resolution. External oscillators (HSE) provide better accuracy for communication-heavy systems.
Interrupt priority management: Use NVIC priority grouping 4 (4 bits preemption, 0 bits sub-priority) for deterministic behavior. Motor control timers should have higher priority than communication interrupts.
Dead-time for H-bridges: Advanced timers (TIM1/TIM8) support programmable dead-time insertion between complementary outputs. Set DTG bits in the BDTR register to match your MOSFET switching characteristics — typically 0.5–2 µs for industrial motor drivers.
Watchdog integration: If the main control loop fails to execute within expected bounds, the independent watchdog (IWDG) should trigger a safe shutdown of motor outputs. Never rely on software watchdogs alone in safety-critical motor control systems.
DMA for high-speed data logging: Use DMA to stream timer values to a circular buffer for oscilloscope-style data capture — invaluable for debugging motion control systems without adding ISR overhead.
Frequently Asked Questions
What is the maximum interrupt frequency for STM32 timer interrupts?
STM32F4 timers can generate interrupts up to the system clock frequency (e.g., 168 MHz), but practical limits are around 1–10 MHz due to ISR execution time. For motor control, typical interrupt rates range from 1 kHz (PID loop) to 100 kHz (step pulse generation at maximum motor speed).
Can I use delay() inside a timer interrupt service routine?
No. Calling delay() inside an ISR blocks the interrupt vector and prevents other interrupts from firing. For timing within an ISR, use polling with a timer counter read (__HAL_TIM_GET_COUNTER()) or set a flag and handle the logic in the main loop.
Which STM32 timer is best for stepper motor control?
TIM1 (advanced timer) is the best choice because it provides: (1) complementary outputs with dead-time insertion for H-bridge safety, (2) break input for emergency motor shutdown, and (3) up to 4 channels for multi-axis control. For simpler applications, TIM3 or TIM4 work well for basic step pulse generation.
How do STM32 timer interrupts compare to Arduino timer interrupts?
STM32 timers offer significantly more capability: 16/32-bit resolution vs 8-bit on Arduino Uno, up to 14 timers vs 3, hardware encoder interface, dead-time insertion, and DMA triggers. STM32 also supports nested vectored interrupts with configurable priority levels, enabling real-time multi-task motor control.
Do I need external crystals for precise motor timing with STM32?
For most motor control applications, the STM32’s internal PLL-generated clock (derived from HSI or HSE) provides sufficient accuracy (±1%). External crystals are recommended when the STM32 must simultaneously handle CAN bus or Ethernet communication, where clock accuracy directly affects bit-timing tolerance.
Related Reading
- STM32 PWM Signal Generation for Motor Speed Control
- Arduino Servo Motor Control: Complete Library and PWM Guide
- STM32 vs Arduino for Industrial Embedded Systems
- UART Serial Communication between STM32 and Arduino
Sources
- STM32F4 Reference Manual — Timer Chapter (RM0090)
- STMicroelectronics — STM32 Timer Overview
- Trinamic — Stepper Motor Control Fundamentals
- Texas Instruments — DRV8825 Stepper Motor Driver Application Note
Disclosure: This post contains affiliate links to recommended development boards and motor drivers. If you purchase through these links, we may earn a commission at no extra cost to you.
