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
Callout Tip: The Floating Gate Hazard
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

  1. Verify the Pin is Actually PWM-Capable: Look at the silkscreen on your Uno R3. PWM pins are marked with a tilde (~). If you call analogWrite(8, 128), Pin 8 will simply output a steady 5V HIGH (since 128 > 0), acting as a digital digitalWrite(HIGH). It will not dim.
  2. 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 your analogWrite() duty cycle values. Move your PWM load to Pin 3 or 11 (Timer 2) if you must use servos.
  3. 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.
Exact Error String: tone() vs Servo.h
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.