...

STM32 PWM Motor Control Guide: Timers, Duty Cycle, and H-Bridge Wiring

Learn how STM32 timers generate PWM signals, how to configure a channel with STM32CubeIDE, and how to wire that signal through an H-bridge to control a DC motor's speed and direction.

Every STM32 has hardware timers capable of generating PWM directly in silicon, with no CPU cycles spent toggling a pin. That makes it the right tool for motor speed control, LED dimming, and servo signals. This guide walks through generating a PWM signal on STM32 and wiring it through an H-bridge driver to control a DC motor.

How Timer PWM Works

An STM32 timer counts up from zero to a value called the auto-reload register (ARR), then resets and repeats. A second register, the capture-compare register (CCR), sets the point in that count where the output pin flips from high to low. The ratio of CCR to ARR is your duty cycle, and the timer’s clock frequency divided by ARR gives you the PWM frequency. For motor control, 20kHz is a common choice — high enough to be inaudible, low enough to keep switching losses low on most H-bridge ICs.

Configuring the Timer in STM32CubeIDE

In CubeMX, set the timer channel to PWM Generation mode, then calculate your prescaler and ARR to hit the target frequency:

PWM Frequency = Timer Clock / ((PSC + 1) * (ARR + 1))

// Example: 168MHz timer clock, targeting 20kHz
// PSC = 0, ARR = 8399  →  168,000,000 / 8400 = 20,000 Hz

Starting the PWM and Setting Duty Cycle

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

// Set duty cycle as a percentage of ARR
void set_duty_cycle(uint8_t percent) {
  uint32_t arr = __HAL_TIM_GET_AUTORELOAD(&htim3);
  uint32_t ccr = (arr * percent) / 100;
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, ccr);
}

Wiring to an H-Bridge

A single PWM pin controls speed, but you need an H-bridge to handle the current a motor draws and to reverse direction. With a common driver like the DRV8833 or L298N, the PWM output goes to one input pin while a second GPIO sets direction:

  • IN1 = PWM signal, IN2 = LOW — motor spins forward, speed set by duty cycle
  • IN1 = LOW, IN2 = PWM signal — motor spins in reverse
  • IN1 = LOW, IN2 = LOW — motor coasts to a stop
  • IN1 = HIGH, IN2 = HIGH — active braking, on drivers that support it

Always power the motor from a separate supply rail from the STM32’s logic supply, sharing only ground, and add a flyback diode or use a driver IC with one already built in — brushed DC motors generate voltage spikes when the field collapses that can damage GPIO pins if driven directly.

Direction Control in Code

void motor_forward(uint8_t speed_percent) {
  HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET);
  set_duty_cycle(speed_percent);
}

void motor_reverse(uint8_t speed_percent) {
  HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET);
  set_duty_cycle(speed_percent);
}

Ramping Instead of Stepping

Jumping straight to full duty cycle draws a large inrush current and can brown out a poorly decoupled supply. Ramp the duty cycle up over 100–200ms in a timer interrupt or a simple loop with delays, especially on boards where the motor driver shares a supply rail with other sensitive electronics.

Next Steps

Once open-loop speed control works, the natural next step is closing the loop with an encoder and a PID controller so the motor holds a target RPM regardless of load — a good follow-up project once this foundation is solid.

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.