Pulse width modulation on the Arduino Uno R3 (ATmega328P) is handled by hardware timers on digital pins 3, 5, 6, 9, 10, and 11, outputting a default 490 Hz (or 976 Hz on pins 5 and 6) square wave via the analogWrite(pin, duty) function. While calling analogWrite() is trivial, driving real-world loads like 12V LED strips, DC motors, or heating elements requires understanding timer allocations, gate capacitance, and library conflicts. This guide provides the exact pinout tables, a robust MOSFET driver circuit, and the debugging frameworks needed when your PWM signal mysteriously fails.
The Core Specs: Arduino Uno R3 PWM Pinout and Timer Table
Not all digital pins on the Uno R3 support hardware PWM, and the ones that do are tied to specific internal timers. Modifying a timer's prescaler to change the PWM frequency will affect every other pin sharing that timer—and potentially break core Arduino timing functions. Below is the definitive reference table for the Uno R3 (ATmega328P) PWM architecture.
| Pin | Timer | Default Freq | Mode | Critical Notes & Conflicts |
|---|---|---|---|---|
| 3 | Timer 2 (8-bit) | 490.20 Hz | Phase Correct | Safe for Servo.h and tone(). Shares timer with Pin 11. |
| 5 | Timer 0 (8-bit) | 976.56 Hz | Fast | WARNING: Do NOT alter Timer 0 prescaler. It controls millis(), delay(), and micros(). |
| 6 | Timer 0 (8-bit) | 976.56 Hz | Fast | Same as Pin 5. Avoid frequency modification. |
| 9 | Timer 1 (16-bit) | 490.20 Hz | Phase Correct | Hijacked by Servo.h. Best pin for high-resolution (10-bit+) custom PWM. |
| 10 | Timer 1 (16-bit) | 490.20 Hz | Phase Correct | Hijacked by Servo.h. Shares Timer 1 with Pin 9. |
| 11 | Timer 2 (8-bit) | 490.20 Hz | Phase Correct | Shares Timer 2 with Pin 3. Also used by the onboard SPI bus (MOSI). |
For a deeper look at how the ATmega328P hardware timers generate these waves, refer to the official Arduino analogWrite() documentation and the SparkFun PWM tutorial.
Project Build: High-Power 12V PWM LED Dimmer
Microcontroller GPIO pins can only source about 20mA safely. To dim a 12V LED strip drawing 2A, we use an N-channel logic-level MOSFET as a low-side switch. The IRLZ44N is ideal here because its gate threshold voltage (Vgs(th)) is low enough to fully turn on with the Uno's 5V logic.
Parts List
- MCU: Arduino Uno R3 (ATmega328P variant)
- Switch: IRLZ44N N-Channel MOSFET (TO-220 package)
- Gate Resistor: 220Ω metal film (limits inrush current to the gate capacitor)
- Pull-down Resistor: 10kΩ carbon film (prevents floating gate during MCU boot)
- Load: 12V LED Strip (or DC motor with flyback diode)
- Power: 12V 5A DC switching power supply
Pin Mapping & Wiring Table
| Component Node | Connects To | Wire Color (Typical) | Purpose |
|---|---|---|---|
| Arduino Pin 9 | 220Ω Resistor (Input) | Yellow | PWM signal delivery |
| 220Ω Resistor (Output) | IRLZ44N Gate (Pin 1) | Yellow | Protects AVR pin from gate capacitance spike |
| 10kΩ Resistor | Gate (Pin 1) to GND | Black/Red | Bleeds gate charge, keeps MOSFET off at boot |
| IRLZ44N Source (Pin 3) | Common GND (Uno + 12V PSU) | Black | Completes the low-side circuit |
| IRLZ44N Drain (Pin 2) | LED Strip Negative (-) | White | Switches the load ground path |
| 12V PSU (+) | LED Strip Positive (+) | Red | Main power delivery |
When the Arduino resets or boots, its GPIO pins float (high impedance) before
setup() runs. Without the 10kΩ pull-down resistor, ambient noise can charge the MOSFET gate, partially turning it on. This puts the MOSFET in its linear (resistive) region, causing it to dissipate massive heat and potentially melt the TO-220 package or catch fire. Always use a pull-down.
Compilable Code: Non-Blocking PWM Fade with Telemetry
This code targets the Arduino Uno R3. It generates a smooth, non-blocking triangular fade (breathing effect) on Pin 9. Unlike beginner tutorials that use delay(), this uses millis() to maintain a responsive loop, allowing you to add serial commands or sensor reads without interrupting the PWM timing.
// Target Board: Arduino Uno R3 (ATmega328P)
// Pin 9 uses Timer 1 (16-bit), default 490 Hz Phase Correct PWM
#define PWM_PIN 9
#define PWM_RESOLUTION 255
#define FADE_INTERVAL_MS 15 // Time between duty cycle steps
unsigned long previousMillis = 0;
int currentDuty = 0;
int fadeDirection = 1; // 1 = fading up, -1 = fading down
void setup() {
Serial.begin(115200);
// Configure pin as output
pinMode(PWM_PIN, OUTPUT);
// Ensure pin starts LOW to prevent visual glitch on boot
digitalWrite(PWM_PIN, LOW);
Serial.println(F("PWM LED Dimmer Initialized on Pin 9"));
Serial.println(F("Target: 490Hz Phase Correct PWM"));
}
void loop() {
unsigned long currentMillis = millis();
// Non-blocking timer check
if (currentMillis - previousMillis >= FADE_INTERVAL_MS) {
previousMillis = currentMillis;
// Update duty cycle
currentDuty += fadeDirection;
// Boundary checks and direction reversal
if (currentDuty >= PWM_RESOLUTION) {
currentDuty = PWM_RESOLUTION;
fadeDirection = -1;
} else if (currentDuty <= 0) {
currentDuty = 0;
fadeDirection = 1;
}
// Apply PWM signal
analogWrite(PWM_PIN, currentDuty);
// Telemetry (throttled to avoid serial buffer flooding)
if (currentDuty % 50 == 0) {
Serial.print(F("Duty Cycle: "));
Serial.print(currentDuty);
Serial.print(F(" ("));
Serial.print((currentDuty * 100) / PWM_RESOLUTION);
Serial.println(F("%)"));
}
}
// Add other non-blocking tasks here (e.g., button debouncing, sensor reads)
}
Debugging PWM: Why Your Signal is Failing
When a PWM circuit fails, the issue is rarely the analogWrite() function itself. It is almost always a timer conflict or a hardware wiring flaw. If your load isn't responding, run through these diagnostics.
The First Three Things to Check
- Verify the Pin is Actually PWM-Capable: Look at the silkscreen on your Uno R3. PWM pins are marked with a tilde (
~). If you callanalogWrite(8, 128), Pin 8 will simply output a steady 5V HIGH (since 128 > 0), acting as a digitaldigitalWrite(HIGH). It will not dim. - Check for Timer Hijacking (The Servo.h Conflict): If you include
<Servo.h>and instantiate a Servo object, the library takes exclusive control of Timer 1. Runtime Failure Signature: Pins 9 and 10 will immediately stop outputting a square wave and will instead output a steady 5V DC or 0V, completely ignoring youranalogWrite()duty cycle values. Move your PWM load to Pin 3 or 11 (Timer 2) if you must use servos. - Measure the Gate Voltage with a Multimeter: Set your DMM to DC Volts. Probe the MOSFET gate relative to ground while the code runs. If you read a fluctuating voltage between 0V and 5V, the MCU is working, and your MOSFET is likely blown or wired backward (Drain/Source swapped). If you read a steady 0V or 5V, your timer is hijacked or you are on the wrong pin.
If you attempt to use
tone() and Servo.h simultaneously, the Arduino IDE will halt compilation and throw this exact error:libraries/Servo/src/avr/Servo.cpp:112: multiple definition of `__vector_11'Both libraries attempt to attach an Interrupt Service Routine (ISR) to Timer 1. You cannot use them together on an Uno R3 without modifying the library source code to reassign timers.
Extending and Simplifying the Build
Depending on your application, the default 490 Hz frequency might cause audible whining in DC motors or visible flicker on camera sensors. Here is how to adapt the circuit and code.
Extending: Pushing Timer 1 to 31 kHz (Ultrasonic/Silent)
To eliminate motor whine or LED flicker on high-speed cameras, you can push the PWM frequency above human hearing (20 kHz). By altering the Timer 1 prescaler directly via the TCCR1B register, you can achieve ~31.3 kHz on Pins 9 and 10.
Add this single line to the very end of your setup() function:
// Set Timer 1 prescaler to 1 (No prescaling)
// Math: 16MHz / (510 * 1) = 31,372 Hz
TCCR1B = (TCCR1B & 0b11111000) | 0x01;
Note: This only affects Pins 9 and 10. Do not apply this to Timer 0 (Pins 5/6) or your millis() timing will run 64 times faster than reality.
Simplifying: The ULN2803 Darlington Array
If you are switching loads under 500mA and want to avoid wiring discrete MOSFETs, gate resistors, and pull-downs, swap the IRLZ44N for a ULN2803A Darlington transistor array IC.
- Pros: No external resistors needed. Connects directly to the Uno pin. Includes built-in flyback diodes for inductive loads (relays, small motors).
- Cons: High voltage drop (~1V to 1.5V across the Darlington pair). At 500mA, the chip will dissipate ~0.75W and get hot. It is strictly for low-current, simplified prototyping, not high-efficiency LED strips.
Mastering pulse width modulation on the Arduino requires looking past the analogWrite() abstraction. By respecting timer allocations, protecting your MCU from gate capacitance inrush, and selecting the right logic-level MOSFET, you can reliably scale a 5V microcontroller signal to control hundreds of watts of external hardware.






