Do you need to dim an LED smoothly, or accelerate a DC motor from standstill to a stable speed? PWM (Pulse Width Modulation) can do both using a digital signal that has only two states: high and low. After reading this article, you will understand how duty cycle determines average voltage and power, and how to select frequency and troubleshoot issues in real projects.
1. The Core of PWM: Expressing Control Quantity Through Time Ratio
The essence of PWM is very simple: within a fixed period, the longer the high-level duration, the larger the duty cycle.
Duty Cycle = High-Level Duration / Total Period × 100%
For example, with a period of 1ms (frequency 1kHz) and a high-level duration of 0.25ms, the duty cycle is 25%. The key point is that the output has only two states throughout: high and low. There is no true "intermediate voltage." However, many loads perceive exactly the average effect corresponding to this time ratio.
Source: playembedded.org explains the duty cycle definition as the ratio of ON time to total time; cnblogs.com's example code implements three brightness levels (25%, 50%, 75%) using 20 loops with 50μs software delays each.
2. LED Brightness: The Human Eye Acts as a Biological Low-Pass Filter
An LED itself has only two states: on and off. If controlled directly with a GPIO, it is either fully on or fully off. But by raising the switching speed to hundreds of hertz or above, the human eye's persistence of vision automatically "integrates" these rapid flickers. What you perceive is not flicker, but an average brightness determined by the duty cycle.
Source: h-cled.com points out that as long as the switching speed is fast enough (typically hundreds to thousands of hertz), the human eye feels no flicker and only sees continuous brightness changes; wiki.lckfb.com adds that most people feel no flicker at all above 80Hz refresh frequency.
Specifically:
- Duty cycle 25% → LED appears dim
- Duty cycle 50% → LED appears medium brightness
- Duty cycle 75% → LED appears bright
This relationship can be described by a more fundamental formula:
Average Voltage = Duty Cycle × High-Level Voltage
Source: arduino.nxez.com directly gives the formula "Output Voltage = (ON Time / Pulse Time) × Maximum Voltage"; ampheo.com verifies this with a 5V signal at 25% duty cycle (equivalent to approximately 1.25V); wevolver.com confirms that for a PWM waveform with 12V high level and 0V low level, the average voltage varies linearly with duty cycle.
This means that even if your GPIO only outputs 3.3V or 5V, by adjusting the duty cycle, you can make the LED present any brightness from nearly fully off to fully on.
3. Why PWM Is Much More Efficient Than Resistor Voltage Regulation
The traditional approach is to connect an adjustable resistor in series with the LED or motor, reducing the voltage across the load through voltage division. But the resistor directly wastes the excess electrical energy as heat.
PWM takes a completely different approach: the switching device is either fully on (minimal voltage drop, minimal loss) or fully off (zero current, zero loss). It does not waste energy in intermediate states; instead, it equivalently produces a lower average voltage through the time ratio of rapid switching.
Source: blog.csdn.net explains that PWM switching elements operate in "fully on or fully off" states, reducing linear-mode losses; electrolik-eg.com uses a product page to explain that "the motor is either fully on or fully off at any instant, and even at low speed settings, very little power is wasted as heat"; easyelecmodule.com directly contrasts the drawback of traditional series resistor speed control, which "converts excess electrical energy directly into waste heat through the resistor."
This is why PWM has almost replaced linear voltage regulation schemes in motor drives, LED dimming, switching power supplies, and other fields.
4. DC Motors: Inductance and Inertia Act as Integrators
The speed regulation mechanism for DC motors is similar to that of LEDs, but relies on different physical properties.
A motor's coil is essentially an inductor. A key property of inductors is that they resist sudden current changes — when the PWM signal transitions from high to low, the current in the coil does not immediately drop to zero; instead, it gradually decays through the freewheeling circuit. At the same time, the rotor's mechanical inertia prevents it from stopping instantly each time power is cut.
Source: playembedded.org points out that for brushed DC motors, the motor windings and mechanical inertia smooth out the switching actions, and speed is almost proportional to the supply voltage; cnblogs.com describes this process as "energy accumulates into average torque over many consecutive cycles"; 21ic.com's summary is the most intuitive — the motor is in a state of "seemingly stopped but not stopped, seemingly running at full speed but not full speed," with the average voltage within a cycle determining the speed.
The result is: the higher the duty cycle, the more average energy the motor receives within a period, and the faster it spins; the lower the duty cycle, the less average energy, and the slower it spins. This relationship is approximately linear within a certain range.
5. Frequency Selection: Engineering Trade-offs Between 6kHz and 20kHz
The PWM frequency directly affects two practical experiences: noise and switching losses.
- Frequency too low (e.g., a few hundred hertz): The motor's current ripple may be audible to human ears, manifesting as a "buzzing" whine; LEDs may also exhibit visible flicker under certain conditions.
- Frequency too high (e.g., over 100kHz): Each switching transition of the device generates losses; the higher the frequency, the greater the losses, and electromagnetic interference (EMI) also intensifies.
Source: developer.quectel.com and 21ic.com both suggest that "normal PWM frequency is best at 6–16kHz"; ariat-tech.com further explains the trade-off: low frequency leads to audible noise, while high frequency increases switching losses and heat generation.
In practical projects, DC motor PWM speed control typically selects a frequency between 6kHz and 20kHz — this keeps the noise outside the human hearing range while preventing excessive switching losses.
6. Practical Implementation: From LED Breathing Light to Motor Speed Control
After understanding the principles, here is a complete hands-on verification process.
6.1 Verify Duty Cycle with an LED
The simplest starting point is to observe brightness changes with an LED. Using Arduino as an example, the analogWrite() function accepts values from 0 to 255, corresponding to 0% to 100% duty cycle:
cpp const int ledPin = 9; // PWM-capable pin
void setup() { pinMode(ledPin, OUTPUT); }
void loop() { // Gradually brighten from off for (int duty = 0; duty <= 255; duty++) { analogWrite(ledPin, duty); delay(5); } // Gradually dim from brightest for (int duty = 255; duty >= 0; duty--) { analogWrite(ledPin, duty); delay(5); } }
Source: ampheo.com's Arduino LED gradient example directly uses this code, explaining that analogWrite(ledPin, 0) corresponds to 0% duty cycle and analogWrite(ledPin, 255) corresponds to full duty cycle.
If you use STM32, the hardware PWM configuration differs: the timer's prescaler (PSC) determines the counting frequency, the auto-reload register (ARR) determines the period, and the capture/compare register (CCR) determines the duty cycle. Modifying CCR is equivalent to modifying the duty cycle, without requiring the CPU to toggle the GPIO each time.
Source: cnblogs.com's STM32 example uses a 72MHz system clock, with PSC set to 72-1 and ARR set to 50-1, yielding a 20kHz PWM frequency; simply calling TIM_SetCompare1() to change the value of CCR1 adjusts the duty cycle.
6.2 Connect the DC Motor: A Driver Board Is Essential
Note: The PWM output pin must not be connected directly to the motor. GPIO pins cannot provide the startup current required by the motor, nor can they withstand the back-EMF voltage when the motor powers off.
The correct approach is to connect through a driver board (such as TB6612FNG or L298N):
STM32 PWM pin → Driver board logic input Driver board power output → Motor 12V battery → Driver board motor power supply Step-down module → STM32 and driver board logic power All modules share a common ground
Source: cnblogs.com details the wiring of TB6612FNG and L298N, emphasizing the importance of common grounding and the practical trap of "motor startup current must not loop through STM32's thin power wires."
6.3 Control Speed and Direction with a Percentage Value
At the code level, a signed percentage value can uniformly represent both speed and direction:
c #define MOTOR_PWM_PERIOD (50) // ARR + 1, i.e., total counts per period
void Motor_SetPercent(int16_t percent) { uint16_t compare;
// Clamping protection if (percent > 100) percent = 100; if (percent < -100) percent = -100;
// Clear first, then switch direction — avoid sudden reverse while rotor is still spinning TIM_SetCompare1(TIM2, 0);
if (percent >= 0) { Motor_SetDirection(1); // IN1=high, IN2=low compare = (uint16_t)percent; } else { Motor_SetDirection(0); // IN1=low, IN2=high compare = (uint16_t)(-percent); }
compare = (uint16_t)((compare * MOTOR_PWM_PERIOD) / 100); TIM_SetCompare1(TIM2, compare); }
Source: cnblogs.com provides this Motor_SetPercent() implementation and specifically emphasizes the necessity of clearing before switching direction — "when the rotor is still spinning forward, suddenly flipping the direction pins to reverse is equivalent to making the driver immediately apply a reverse torque, delivering a hard shock to the gears, driver chip, and power supply."
7. Verification and Troubleshooting
After completing the wiring and code, verify in the following order:
- Connect the LED first, without the motor: Observe brightness changes at 0%, 20%, 50%, and 80% duty cycle to confirm that brightness is positively correlated with duty cycle.
- Connect the driver board logic side: Confirm common grounding, and that TB6612's STBY or L298N's ENA is in the enabled state.
- Test the motor with no mechanical load: Start from 10% duty cycle and gradually increase, recording the minimum startup duty cycle. Note that many motors may not start immediately between 1% and 15% — this is a normal phenomenon caused by static friction, not a fault.
- Reverse test: First reduce to 0%, then switch direction. Do not jump directly from forward to reverse.
- Frequency test: Hold 50% duty cycle and vary the PWM frequency to distinguish between audible whine and jitter caused by low duty cycle.
Common Troubleshooting:
| Symptom | Possible Cause | Solution |
|---|---|---|
| LED brightness does not change with duty cycle | TIM2 clock not enabled, PA0 not configured as alternate push-pull, PWM channel not mapped to correct pin | Check RCC clock configuration and GPIO mode |
| Motor buzzes but does not spin | Duty cycle below startup threshold, PWM frequency in audible range | Increase duty cycle or raise frequency above 6kHz |
| Left and right wheels spin at different speeds under the same duty cycle | Individual motor differences, different driver board voltage drops, different mechanical loads | Measure actual speeds separately; do not assume duty cycle equals speed |
| Motor spins on its own after program download | Motor_Init() or main() does not properly halt the motor | Ensure Motor_Stop() is called immediately after initialization |
| STM32 resets when motor starts | Motor startup current pulls down supply voltage, no common ground | Check power wire gauge, add decoupling capacitors, ensure common ground |
Source: cnblogs.com's verification sequence and troubleshooting table synthesize multiple practical engineering experiences, including the non-linearity of startup thresholds, left-right wheel differences, and the importance of initialization shutdown.
8. Important Caveats and Boundaries
- Duty cycle does not equal speed: The PWM command describes "how long the drive is applied," but does not guarantee how fast the motor actually spins. Actual speed is also affected by load, supply voltage, driver board voltage drop, gear friction, and other factors.
- Low duty cycle may not spin: This is a normal phenomenon caused by static friction. Changing the motor, battery, or driver board may alter the startup threshold.
- Frequency is not the higher the better: Excessively high frequency increases switching losses and EMI. 6–20kHz is the recommended range that balances noise and losses.
- The motor must go through a driver board: Do not attempt to drive a motor directly with GPIO pins; this will damage the microcontroller.
Summary: PWM enables digital systems to precisely control analog loads by "outputting only high or low, yet controlling the average effect." It does not create a non-existent intermediate voltage; instead, it places high and low levels into a fixed period, expressing the control quantity through the time ratio. By mastering the three variables — duty cycle, frequency, and load characteristics — you can simultaneously handle both typical applications: LED dimming and DC motor speed control.