All modules
Beginner20 min read

DC Motor Control

H-bridges, PWM, encoders, and driver selection. The three questions every motor choice has to answer.

How PWM controls speed

DC motors run on voltage. Lower voltage = slower speed. Rather than waste power through a resistor, Pulse Width Modulation (PWM) switches the motor on and off at high frequency — typically 1–20kHz. The motor's inductance averages the pulses into an effective voltage. 50% duty cycle ≈ half speed.

# RP2350: set motor speed via PWM (MicroPython)
from machine import Pin, PWM
pwm_l = PWM(Pin(4)) # left motor enable
pwm_l.freq(1000) # 1kHz PWM
def speed(pct): # 0-100
pwm_l.duty_u16(int(pct / 100 * 65535))
speed(75) # 75% duty cycle

H-bridge fundamentals

A motor needs to spin in both directions. An H-bridge switches four transistors (or MOSFETs) around the motor — flip which pair is active, and current flows in reverse. The bridge protects your MCU from back-EMF and handles the current the motor draws (which your MCU pins cannot).

Never connect a motor directly to a GPIO pin. Even tiny motors pull 200mA+; MCU pins are typically rated 10–40mA. You will damage the MCU.

Driver comparison: L298N vs Cytron MDD10A

L298NCytron MDD10A
TechnologyBipolar transistorMOSFET (lower Rdson)
Voltage range5–46V6–24V
Current per ch2A continuous10A continuous
Efficiency~60-70%~95%
Heat at 1AGets warm (heatsink recommended)Barely warm
Logic level5V (3.3V works)3.3V / 5V
Price~$7~$30
Best forRP2350 builds, light loadsRPi5, Jetson, heavy loads

Rule of thumb: L298N for anything under 1A average. Cytron MDD10A for 12V mobile platforms — the efficiency difference alone extends runtime by 15–20%.

Encoders: why you need them

Without encoders, you're running open-loop — telling the motor to run at 50% PWM and hoping both wheels go the same speed. They won't. Floors aren't flat. Motors aren't matched. Add encoders and you get:

  • Closed-loop PID speed control: both wheels turn at the same RPM, straight lines are straight
  • Odometry: integrate encoder counts over time to estimate position (dead-reckoning)
  • Stall detection: if encoder stops ticking while motor is powered, you're stuck
  • Wheel slip detection: encoder velocity diverges from expected — surface or load issue
# Quadrature encoder read on RP2350 (PIO state machine)
# PIO handles counting — no CPU cycles wasted
from encoder import Encoder # custom PIO lib
enc = Encoder(pin_a=14, pin_b=15)
while True:
ticks = enc.value() # + forward, - reverse
rpm = ticks_to_rpm(ticks, gear_ratio=30)
TIP — Current headroom sizing

Size your driver for 3× stall current, minimum. Our N20 motors stall at 0.8A — that's why the L298N (2A) works with margin. The Cytron MDD10A (10A) gives 12× headroom on the same motors. More headroom = cooler driver = longer life.