DC motor speed control via PWM (Pulse Width Modulation) works by rapidly switching the supply voltage on and off. The motor's speed is proportional to the duty cycle (0-100%), while the PWM frequency (typically 1kHz to 20kHz) must be high enough to avoid audible whine but low enough to prevent excessive switching losses in the driver MOSFETs. If you are building an ESP32-based robotics or automation project, getting the driver sizing and PWM tuning right is the difference between a smooth, reliable system and melted silicon.
Motor Type Comparison & Load Profiling
Before writing a single line of PWM code, you must verify that a brushed DC motor is actually the right fit for your load profile. A common mistake in embedded projects is treating stepper, servo, and DC motors as interchangeable. They are not. Steppers excel at open-loop positional holding but drop torque rapidly at high RPMs. Servos offer closed-loop high-speed positioning but are costly and complex to scale. Brushed DC motors provide high starting torque and simple speed control, making them ideal for wheels, conveyors, and pumps where exact millimeter positioning is not required.
| Motor Type | Torque Curve | Control Needs | Typical Cost (NEMA 17 / 500W equiv) |
|---|---|---|---|
| Brushed DC | High starting torque, drops linearly with speed | Single H-bridge, 1x PWM pin, 2x direction pins | $15 - $40 |
| Brushless DC (BLDC) | Flat torque curve, high efficiency at high RPM | 3-phase ESC or FOC driver, Hall sensors/Back-EMF | $60 - $150 (incl. driver) |
| Stepper | Maximum at standstill, drops sharply above 1000 RPM | Step/Dir pulses, microstepping driver (e.g., TMC2209) | $20 - $50 |
| Servo (AC/DC) | Constant torque across rated speed range | Closed-loop feedback, dedicated industrial drive | $150 - $400+ |
If your application requires variable speed under varying loads without strict positional requirements, the brushed DC motor is your target. For deeper theory on how the PWM signal interacts with the motor's inductance, refer to the All About Circuits guide on PWM motor control.
Sizing the Driver and Wiring Terminals
The most frequent point of failure in DIY motor projects is undersizing the motor driver. A beginner might look at a motor's datasheet, see a "nominal current" of 3A, and select an L298N driver (rated for 2A continuous). Within minutes, the L298N will overheat and trigger its internal thermal shutdown.
Worked Load Example
Let’s size a driver for a 12V brushed DC gearmotor pulling a small conveyor belt. The datasheet lists a nominal running current of 3A at 12V.
- Measure Winding Resistance: Using a multimeter, we read 0.4 Ω across the motor terminals.
- Calculate Stall Current: I_stall = V_supply / R_winding = 12V / 0.4 Ω = 30A.
- Apply Safety Margin: 30A * 1.2 = 36A continuous rating required.
For this load, you need a heavy-duty MOSFET-based H-bridge like the BTS7960 (43A continuous, typically $12-$15) or an IBT-2 module. Avoid BJT-based drivers like the L298N for anything above 1.5A; they drop ~2V across their internal transistors, wasting power as heat.
Wiring and Terminal Identification
When wiring a high-current module like the BTS7960 to an ESP32, keep your logic and power grounds tied together, but keep the power traces thick (minimum 10 AWG for 30A+ loads).
- B+ / VCC (Motor): Connects directly to the positive terminal of your 12V/24V power supply. Do not power this from the ESP32's VIN pin.
- GND (Power): Connects to the power supply negative. Must share a common ground with the ESP32 GND.
- VCC (Logic): Connects to the ESP32 3.3V pin (powers the driver's optocouplers/logic ICs).
- R_EN / L_EN: Enable pins. Tie these to 3.3V or a GPIO to enable the H-bridge halves.
- RPWM / LPWM: The PWM input pins. Connect RPWM to an ESP32 GPIO for forward speed control, and LPWM for reverse.
- OUT1 / OUT2: The high-current outputs connecting directly to the DC motor terminals.
ESP32 Implementation and Failure Signatures
With the ESP32 Arduino Core v3.x, the legacy ledcSetup() and ledcAttachPin() functions have been deprecated in favor of the simplified ledcAttach() API. According to the official Espressif LEDC documentation, the LEDC (LED Control) peripheral is highly suited for generating the precise PWM signals required for motor control.
// ESP32 Arduino Core 3.x DC Motor PWM Control
const int RPWM_PIN = 18;
const int LPWM_PIN = 19;
const int PWM_FREQ = 2000; // 2kHz avoids audible whine
const int PWM_RES = 10; // 10-bit resolution (0-1023)
void setup() {
// Attach pins to LEDC channels with specified frequency and resolution
ledcAttach(RPWM_PIN, PWM_FREQ, PWM_RES);
ledcAttach(LPWM_PIN, PWM_FREQ, PWM_RES);
}
void loop() {
// Drive forward at 60% speed
ledcWrite(LPWM_PIN, 0); // Ensure reverse is off
ledcWrite(RPWM_PIN, 614); // 60% of 1023
delay(3000);
// Stop (coast)
ledcWrite(RPWM_PIN, 0);
delay(1000);
}
Recognizing Failure Signatures
Even with correct wiring, tuning DC motor speed control PWM requires observing physical feedback. Here is how to diagnose common issues on the bench:
- Audible Hum or Whine: If the motor or driver squeals, your PWM frequency is too low (typically below 500Hz), causing magnetostriction in the motor laminations and acoustic resonance. Fix: Increase the
PWM_FREQto 2000Hz - 5000Hz. - Driver Overheating (Idle or Low Speed): If the H-bridge gets too hot to touch even under light loads, you are likely switching at too high a frequency (e.g., >20kHz) without adequate gate drive voltage, causing the MOSFETs to linger in their linear (high-resistance) region. Fix: Drop frequency to 2kHz and verify your logic VCC is meeting the driver's minimum threshold (usually 4.5V+ for standard MOSFET gates; if using a 3.3V ESP32, ensure your driver has logic-level MOSFETs or onboard optocouplers).
- Stalling or Cogging at Low Speeds: Brushed DC motors lose torque proportionally with average voltage. At a 10% duty cycle, the motor may not have enough torque to overcome static friction. Fix: Use a higher voltage power supply (e.g., 24V instead of 12V) and run a lower duty cycle to push higher current pulses through the windings, or implement a closed-loop encoder with a PID controller.
DC Motor Speed Control PWM FAQ
Why does my DC motor squeal when using PWM speed control?
The squealing is caused by the physical vibration of the motor's internal windings and laminations (magnetostriction) when the PWM frequency falls within the human hearing range (20Hz to 20kHz). If your frequency is set to 500Hz, you will hear a distinct tone. To eliminate this, increase your ESP32 PWM frequency to at least 16kHz, pushing it above the range of human hearing, though you must ensure your motor driver's MOSFETs can handle the increased switching losses at that speed.
Can I use a standard RC ESC for brushed DC motor speed control PWM?
No. Standard Electronic Speed Controllers (ESCs) used in RC cars and drones are designed specifically for 3-phase Brushless DC (BLDC) motors. They rely on back-EMF zero-crossing detection or Hall effect sensors to commutate the three phases electronically. If you connect a single-phase brushed DC motor to an ESC, it will either throw a startup error, short-circuit the battery, or simply fail to spin. For brushed DC motors, you must use a dedicated H-bridge DC motor driver.
How do I maintain high torque at low speeds with PWM?
In a brushed DC motor, torque is strictly proportional to current, and current is dictated by the voltage applied across the winding resistance. A low PWM duty cycle results in a low average voltage, which limits your available torque. To maintain high torque at low speeds without burning out the motor, use a higher supply voltage (e.g., 24V or 48V) combined with a very low duty cycle. This allows the peak current during the "on" pulse to be high enough to generate strong magnetic fields, while the "off" time keeps the average thermal heating within safe limits. Alternatively, add a mechanical gear reduction to multiply your output torque.






