If you need to move a heavy physical load with variable speed using a microcontroller, the direct answer is this: use a 12V or 24V brushed DC gearmotor paired with a high-current MOSFET H-bridge (like the BTS7960), driven by an ESP32's LEDC peripheral at a 5kHz PWM frequency. This combination delivers maximum low-end torque, avoids the complexity of three-phase commutation, and keeps your component cost under $25.

Selecting the right hardware for PWM DC motor speed control is where most DIY builds fail. Undersized drivers melt, incorrect PWM frequencies cause deafening whine, and voltage sags from motor stalls constantly reboot your microcontroller. This guide cuts through the theory and gives you the exact sizing math, wiring schematics, and failure diagnostics to build a bulletproof motor drive system.

The Core Decision: Brushed vs. Brushless for PWM Control

Before wiring a single terminal, you must match the motor type to your load profile. While brushless and stepper motors have their place, treating them as interchangeable with brushed DC motors is a primary cause of project abandonment. Here is how they stack up for variable-speed embedded control.

Motor Type Torque Curve Control Needs Cost (100W class) Best Load Profile
Brushed DC (BDC) Maximum torque at 0 RPM (stall), drops linearly as speed increases. Simple 2-wire DC. Speed via PWM duty cycle. Direction via H-bridge. $8 - $15 Winches, conveyors, heavy carts, linear actuators.
Brushless DC (BLDC) Flatter torque curve, high efficiency at high RPM. Low torque at 0 RPM without FOC. 3-phase ESC. Requires Hall sensors or sensorless back-EMF zero-crossing detection. $25 - $45 Propellers, high-speed spindles, RC vehicles, cooling fans.
Stepper High holding torque at 0 RPM. Torque collapses rapidly above base speed. 4-wire bipolar. Requires step/direction pulse generation and microstepping drivers. $12 - $20 3D printers, CNC routers, precise rotary indexing.
The Verdict: For high-torque, low-to-medium speed applications (under 3000 RPM) where you just need to control speed via a simple PWM pin, the Brushed DC Gearmotor is the undisputed winner. It requires no complex commutation algorithms and delivers peak torque exactly when you need it: at startup.

Sizing the Motor and Driver: The 2.5x Stall Current Rule

The most common mistake in motor control is sizing the driver based on the motor's continuous current rating. When a DC motor starts from a dead stop, or hits a physical obstruction, it draws stall current. This is simply the supply voltage divided by the motor's internal winding resistance (Ohm's Law). Stall current is typically 5x to 8x higher than continuous running current.

If your driver cannot handle the stall current, the internal MOSFETs will thermally runaway and short out, often taking your microcontroller with them.

Worked Load Example: Motorized Winch

Let's size a system for a small DIY winch pulling a 50 lb (222 N) load on a drum with a 2-inch (0.05m) radius.

  1. Calculate Required Torque: Torque = Force × Radius = 222 N × 0.05 m = 11.1 Nm.
  2. Select Motor: A standard 12V 775-size DC gearmotor (100 RPM output) rated for 15 Nm continuous torque fits perfectly. Its datasheet lists a continuous current draw of 8A.
  3. Find Stall Current: The datasheet specifies a stall current of 32A.
  4. Apply the 2.5x Sizing Rule: To ensure the driver survives repeated startups and momentary jams without triggering thermal shutdown, multiply the continuous current by 2.5 (or simply ensure the driver's peak rating exceeds the stall current). 8A × 2.5 = 20A minimum continuous driver rating. The 32A stall means we need a driver rated for at least 35A peak.

This math immediately eliminates the ubiquitous L298N (2A max) and the TB6612FNG (1.2A continuous). We need a high-power MOSFET H-bridge.

Wiring and Terminal Identification for High-Power DC Drives

For the 32A stall requirement identified above, the BTS7960 43A Motor Driver is the benchmark choice for makers. It utilizes two Infineon BTS7960 half-bridge ICs, offering built-in freewheeling diodes, under-voltage lockout, and over-temperature shutdown.

Unlike small logic-level drivers, high-power boards require strict separation between logic and motor power to prevent ground bounce from resetting your ESP32.

BTS7960 Pinout and Wiring Rules

  • B+ and B-: Main motor power (12V or 24V). Wire this directly to your battery or high-current power supply using minimum 10 AWG silicone wire.
  • M+ and M-: Motor output terminals. Use ring terminals and crimp them; do not rely on friction or loose screw clamps for 30A+ loads.
  • VCC and GND: Logic power (5V). Connect VCC to the ESP32's 5V output (or a dedicated 5V buck converter). Connect GND to the ESP32's GND. Crucial: The logic ground must be tied to the motor ground, but do so at a single star-ground point to avoid ground loops.
  • R_EN and L_EN: Right and Left Enable pins. Both must be pulled HIGH (to 5V) for the driver to operate. You can jumper these directly to the VCC pin on the board.
  • R_PWM and L_PWM: PWM inputs. R_PWM controls forward speed; L_PWM controls reverse speed. Connect R_PWM to your ESP32 GPIO. If you only need one direction of rotation, connect L_PWM directly to GND.
  • R_IS and L_IS: Current sense analog outputs. These output a voltage proportional to the motor current (typically 1V per 10A). Connect to an ESP32 ADC pin (e.g., GPIO 34) if you want to implement software over-current protection.
Safety Caveat: The BTS7960 board's screw terminals are notorious for loosening under high-vibration, high-heat conditions. For any load exceeding 15A continuous, solder heavy-gauge pigtails directly to the PCB pads or apply dielectric grease and re-torque the screws after the first 10 minutes of operation.

Failure Signatures: Decoding Hum, Overheat, and Stall

When a PWM DC motor drive fails, it rarely does so silently. Here is how to decode the physical symptoms and fix the root cause.

1. The High-Pitch Hum (Audible Whine)

Symptom: The motor emits a loud, piercing whine that changes pitch with the PWM duty cycle, especially at low speeds.
Cause: Your PWM frequency is set in the audible range (typically 50Hz to 2kHz). The motor windings and ceramic bypass capacitors act as piezoelectric speakers.
Fix: Increase the PWM frequency to 5kHz or 10kHz. This moves the switching noise above the most sensitive range of human hearing and reduces iron core losses in the motor. On the ESP32 (Arduino Core v3.x), use the ledcAttach() function to set this explicitly.

2. Driver Overheat at Low Speeds

Symptom: The BTS7960 heatsink becomes untouchable within seconds, but only when running the motor at 20-40% duty cycle.
Cause: You are driving the MOSFET gates with insufficient voltage, causing them to operate in the linear (ohmic) region rather than fully saturating. Alternatively, the PWM frequency is too high for the gate driver's current capability, causing excessive switching losses.
Fix: Ensure your logic VCC is a stable 5V (not 3.3V). The BTS7960 requires a minimum of 4.5V on the logic rail to fully enhance the internal N-channel MOSFETs. Keep PWM frequency at or below 10kHz for this specific board.

3. Microcontroller Brownout on Stall

Symptom: The ESP32 randomly reboots or freezes the moment the motor encounters a heavy load or starts from a dead stop.
Cause: The motor stall current causes a massive voltage sag on the main power rail. Because the logic and motor share a power ecosystem, the 5V buck converter drops out, resetting the ESP32.
Fix: Add a bulk electrolytic capacitor (e.g., 3300µF, 25V) directly across the B+ and B- terminals on the motor driver to absorb transient current spikes. Power the ESP32 from a completely separate 5V buck converter (like an LM2596) wired directly to the battery, bypassing the motor driver's logic regulator.

ESP32 PWM Code Implementation (Arduino Core v3.x)

Here is the exact code to implement a 5kHz PWM signal, avoiding the audible whine and utilizing the modern ESP32 API:

const int motorForwardPin = 18;
const int motorReversePin = 19;

void setup() {
  // Attach pins to LEDC peripheral: 5000Hz frequency, 8-bit resolution (0-255)
  ledcAttach(motorForwardPin, 5000, 8);
  ledcAttach(motorReversePin, 5000, 8);
  
  // Ensure reverse is off
  ledcWrite(motorReversePin, 0);
}

void loop() {
  // Ramp up to 75% speed (191 / 255)
  ledcWrite(motorForwardPin, 191);
  delay(3000);
  
  // Stop
  ledcWrite(motorForwardPin, 0);
  delay(1000);
}

The Final Decision Tree: Pick Your Exact Motor and Driver

Stop guessing. Use this decision matrix to select the exact hardware for your specific load profile. We terminate every path with a concrete, purchasable part number.

Load Profile & Current Recommended Driver Recommended Motor Power Supply Requirement
Light Load
(Wheels on indoor robot, small fan)
Continuous < 1.2A, Stall < 3A
TB6612FNG
(Dual 1.2A continuous, 3.2A peak)
130-size or 200-size 6V Brushed DC Motor 4x AA NiMH or 2S LiPo (7.4V) with 5V buck for logic.
Medium Load
(Outdoor rover, small linear actuator)
Continuous 2A - 10A, Stall < 20A
Pololu VNH5019
(Single 12A continuous, 30A peak)
12V 300W Planetary Gearmotor (e.g., 60 RPM output) 12V 10A Sealed Lead Acid (SLA) or 3S LiFePO4 pack.
Heavy Load (Default Pick)
(Winch, heavy cart, motorized gate)
Continuous 10A - 25A, Stall up to 40A
BTS7960 43A Module
(Single channel, 43A peak limit)
12V 775 Brushed DC Gearmotor
(e.g., 100 RPM, 15 Nm torque)
12V 30A+ Switching Power Supply or deep cycle AGM battery + 3300µF bulk cap.

For 90% of heavy-duty maker projects requiring variable speed, the 12V 775 Gearmotor paired with the BTS7960 driver is the definitive, most cost-effective choice. It provides massive low-end torque, survives the inevitable stalls of DIY mechanical linkages, and interfaces cleanly with an ESP32's 3.3V logic (provided the 5V logic rail is stable). Buy the 775 motor with a minimum 50:1 planetary gearbox reduction to ensure you have the torque to start the load under PWM without drawing excessive current.