To use PWM for motor control in embedded projects, you never connect a microcontroller GPIO directly to a motor coil. Microcontrollers output logic-level signals (3.3V or 5V at <40mA), while motors demand high current and generate destructive back-EMF. You must use a motor driver (H-bridge or MOSFET array) rated for the motor's stall current, and feed it a Pulse Width Modulation (PWM) signal typically ranging from 1 kHz to 20 kHz. Selecting the correct PWM frequency and driver topology depends entirely on your specific motor type and mechanical load.

Motor Type Comparison and PWM Control Needs

Not all motors respond to PWM the same way. Applying a standard 1 kHz PWM signal to a coreless DC motor will destroy its commutator, while applying it to a stepper motor will result in violent resonance and missed steps. The table below maps common embedded motor types to their torque profiles, specific PWM demands, and appropriate driver silicon.

Motor Type Torque Curve Profile PWM Control Needs Typical Driver IC Driver Cost
Brushed DC (BDC) High starting torque, linear speed-to-voltage ratio. Single PWM pin for speed, 1x H-bridge for direction. 1 kHz - 16 kHz. DRV8871, TB6612FNG $1.50 - $3.00
Brushless DC (BLDC) Flat torque curve, high efficiency at high RPM. 3-phase commutation. Requires 3x PWM signals (or 6-step) and hall-sensor feedback. DRV10983, L6234 $4.00 - $9.00
Bipolar Stepper High holding torque, severe torque drop-off at speed. Dual H-bridge with current chopping (microstepping). PWM dictates current decay, not speed. TMC2209, A4988 $3.00 - $6.50
Coreless DC Extremely low inertia, fast transient response. High-frequency PWM (>20 kHz) mandatory to prevent coil overheating and brush arcing. DRV8212, DRV8876 $2.00 - $5.00

When deciding which motor type fits this load profile, look at the inertia and duty cycle. If you need precise positioning without an encoder, use a stepper. If you need high-speed continuous rotation with high efficiency, use a BLDC. For simple conveyors, winches, or wheels where cost and control simplicity matter most, a Brushed DC gearmotor is the correct choice.

Sizing the Driver: A Worked Load Example

The most common mistake in embedded motor design is sizing the driver based on the motor's rated continuous current. Motors draw exponentially more current when starting under load or when stalled. If your driver cannot handle the stall current, its internal MOSFETs will melt or trigger thermal shutdown.

The Sizing Rule of Thumb:
Driver Continuous Current ≥ 1.5 × Motor Continuous Load Current
Driver Peak Current ≥ Motor Stall Current

Worked Load Example

Suppose you are driving a 12V planetary gearmotor for a robotic arm joint. The datasheet states:

  • Nominal Voltage: 12V DC
  • Continuous Current: 2.5A
  • Stall Current: 14A

Applying the rule: You need a driver rated for at least 3.75A continuous (2.5 × 1.5) and a minimum of 14A peak. A common TB6612FNG (1.2A continuous, 3.2A peak) will instantly fail. Instead, you select a VNH5019 motor driver, which handles 12A continuous and 30A peak, providing a safe margin for the 14A stall spike.

Wiring and Terminal Identification

When wiring a standard H-bridge driver (like the VNH5019 or DRV8871 breakout) to your microcontroller and power supply, identify these terminals:

  • VM (Motor Supply): Connect to the main battery/power supply (e.g., 12V). Use thick gauge wire (e.g., 16 AWG).
  • VCC (Logic Supply): Connect to the microcontroller's 3.3V or 5V rail to power the driver's internal logic and optoisolators.
  • GND: Must be shared between the motor power supply, the driver board, and the microcontroller to establish a common reference.
  • OUT1 / OUT2: Connect directly to the motor terminals.
  • PWM / EN: Connect to a hardware PWM-capable GPIO on your microcontroller.
  • IN1 / IN2 (or DIR): Standard GPIOs for setting H-bridge polarity (forward/reverse).

For detailed hardware integration practices, refer to the Pololu Motor Controller Guide, which covers decoupling and flyback diode placement.

Failure Signatures: Hum, Overheat, and Stall

When a PWM-driven motor system fails, it rarely does so silently. The physical symptoms tell you exactly where the mismatch lies between your code, your driver, and your mechanical load.

Audible Whine or Hum

Symptom: The motor emits a high-pitched whine that changes pitch as you adjust the speed in code.
Cause: Your PWM frequency is set too low, typically between 50 Hz and 1 kHz. The motor coils and laminations are physically vibrating at the switching frequency.
Fix: Increase the PWM frequency to at least 16 kHz (above human hearing) or 20 kHz. In ESP32 Arduino code, this means changing the frequency parameter in your setup function.

Driver Overheat and Thermal Shutdown

Symptom: The motor runs for 30 seconds, stops abruptly, and the driver IC is too hot to touch. It resumes after cooling down.
Cause: This is either a continuous current overload (you exceeded the 1.5x safety margin) or excessive switching losses. If you push PWM frequency above 30 kHz on a driver with high gate-charge MOSFETs and no heatsink, the energy lost turning the transistors on and off generates massive heat.
Fix: Add a heatsink, lower the PWM frequency to the 16-20 kHz sweet spot, or upgrade to a driver with lower $R_{DS(on)}$ MOSFETs.

Microcontroller Brownout and Stall Jitter

Symptom: The ESP32 or Arduino randomly reboots, or the motor stutters and jitters when starting under load.
Cause: Motor inrush current or back-EMF spikes are pulling down the shared 5V/3.3V rail, causing the microcontroller to brownout. Alternatively, long, unshielded PWM wires are acting as antennas, injecting noise into the logic lines.
Fix: Separate the logic and motor power supplies. Use a dedicated BEC (Battery Eliminator Circuit) or LDO for the microcontroller. Place a 100μF electrolytic capacitor across the motor driver's VM and GND terminals, and a 100nF ceramic capacitor directly across the motor brushes.

ESP32 PWM Wiring and Code Implementation

The ESP32-WROOM-32 features a dedicated LED Control (LEDC) peripheral that generates highly stable hardware PWM, freeing the CPU from timing interrupts. For modern ESP32 Arduino Core (v3.x and newer), the legacy ledcSetup functions are deprecated in favor of the simplified ledcAttach API.

Below is a complete, copy-pasteable implementation for a brushed DC motor using a standard H-bridge. It includes a soft-start ramp to prevent inrush current from tripping your power supply's over-current protection.

#include <Arduino.h>

// ESP32-WROOM-32 Pin Definitions
const int PWM_PIN = 18;       // GPIO 18 (supports high-speed PWM)
const int DIR_PIN = 19;       // GPIO 19 for H-Bridge direction
const int STBY_PIN = 21;      // GPIO 21 for driver standby/enable

// PWM Parameters
const uint32_t PWM_FREQ = 16000; // 16 kHz (above audible range to prevent whine)
const uint8_t PWM_RES = 10;      // 10-bit resolution (0-1023 duty cycle)

void setup() {
  Serial.begin(115200);
  
  pinMode(DIR_PIN, OUTPUT);
  pinMode(STBY_PIN, OUTPUT);
  
  digitalWrite(STBY_PIN, LOW); // Keep driver in standby during initialization
  digitalWrite(DIR_PIN, HIGH); // Set forward direction

  // Initialize LEDC PWM (Arduino Core v3.x syntax)
  if (!ledcAttach(PWM_PIN, PWM_FREQ, PWM_RES)) {
    Serial.println("LEDC PWM attach failed! Check pin capabilities.");
    while(1); // Halt execution
  }
  
  ledcWrite(PWM_PIN, 0); // Ensure 0% duty cycle before enabling driver
  digitalWrite(STBY_PIN, HIGH); // Enable motor driver
  Serial.println("Motor driver enabled. Starting soft-ramp.");
}

void loop() {
  // Soft-start ramp to prevent inrush brownout
  // Ramps from 0 to ~78% duty cycle (800/1023)
  for (int duty = 0; duty <= 800; duty += 20) {
    ledcWrite(PWM_PIN, duty);
    delay(50); // 50ms step delay
  }
  
  delay(3000); // Run at target speed for 3 seconds

  // Soft-stop ramp to safely dissipate kinetic energy
  for (int duty = 800; duty >= 0; duty -= 20) {
    ledcWrite(PWM_PIN, duty);
    delay(50);
  }
  
  delay(2000); // Wait before next cycle
}

For projects requiring regenerative braking, synchronized multi-phase control, or hardware fault handling, investigate the ESP32's MCPWM (Motor Control PWM) peripheral, which offers advanced features like dead-time insertion and trip-zone fault clearing that the standard LEDC peripheral lacks. Always verify your driver's datasheet for maximum logic-level switching speeds before pushing PWM frequencies beyond 20 kHz.