When you need to dim an LED, control a DC motor's speed, or simulate an analog voltage, Pulse Width Modulation (PWM) is your go-to tool. On the Arduino Uno R3 and Nano, the PWM pins are 3, 5, 6, 9, 10, and 11, easily identified by the tilde (~) symbol printed on the silkscreen next to the pin number. By default, these pins output a 5V square wave at either 490 Hz or 980 Hz with an 8-bit resolution (0-255 duty cycle).

However, simply calling analogWrite() only gets you so far. If you try to drive a 12V motor or a high-power LED strip directly from these pins, you will fry the ATmega328P microcontroller, which maxes out at 20mA per I/O pin. To use PWM pins on Arduino for real-world loads, you need to understand hardware timer conflicts, logic-level MOSFET selection, and proper gate driving. This guide gives you the exact pinout data, a robust MOSFET driver build, and the debugging steps to fix it when it fails.

The Complete Arduino PWM Pin Mapping & Timer Table

Not all PWM pins are created equal. Under the hood, the analogWrite() function configures the ATmega328P's hardware timers. Changing the frequency of one pin affects the other pins tied to that same timer. Furthermore, Timer0 is reserved for the millis() and delay() functions, so altering its frequency will break your timing code.

Bench Tip: If your project involves audio generation or driving a motor and you want to eliminate the audible 490 Hz whine, you need to push the frequency above 20 kHz. You can only do this safely on Timer1 (Pins 9 & 10) or Timer2 (Pins 3 & 11) using the TimerOne or TimerTwo libraries.
Table 1: Arduino Uno / Nano (ATmega328P) PWM Pin Specifications
Pin Hardware Timer Default Frequency Max Safe Frequency Notes & Conflicts
~3 Timer 2 (OC2B) 490 Hz ~31.3 kHz Safe to change frequency. Used by some IR libraries.
~5 Timer 0 (OC0B) 980 Hz ~62.5 kHz DO NOT CHANGE. Controls millis(), delay(), and Serial timing.
~6 Timer 0 (OC0A) 980 Hz ~62.5 kHz DO NOT CHANGE. Shares Timer 0 with Pin 5.
~9 Timer 1 (OC1A) 490 Hz ~31.3 kHz 16-bit timer. Best for high-res PWM. Conflicts with Servo.h.
~10 Timer 1 (OC1B) 490 Hz ~31.3 kHz Shares Timer 1 with Pin 9. Conflicts with Servo.h.
~11 Timer 2 (OC2A) 490 Hz ~31.3 kHz Shares Timer 2 with Pin 3. SPI SS pin (conflicts with SPI shields).

Source: Arduino Uno R3 Official Documentation

Project Build: High-Power PWM MOSFET Driver

To drive a 12V, 5A LED strip or a DC motor, we will use the Arduino's PWM output to switch a logic-level MOSFET. A common beginner mistake is buying an IRF520 MOSFET module. The IRF520 requires 10V on the gate to fully turn on; at the Arduino's 5V logic, it operates in its linear (high-resistance) region, overheating and failing to deliver full power. Instead, we use the IRLZ44N, a true logic-level MOSFET that fully enhances at a VGS of 5V.

Parts List & Materials

  • Microcontroller: Arduino Uno R3 (or compatible ATmega328P clone)
  • MOSFET: IRLZ44N (N-Channel, Logic-Level, TO-220 package)
  • Flyback Diode: 1N4007 (Required if driving an inductive load like a motor or relay)
  • Gate Resistor: 150Ω (Limits inrush current to the gate capacitor)
  • Pull-down Resistor: 10kΩ (Prevents floating gate state during Arduino boot)
  • Load: 12V LED Strip or 12V DC Motor
  • Power Supply: 12V DC, 5A+ switching power supply

Wiring Pinout Table

Component Pin Connects To Wire Color (Suggested) Function
Arduino Pin 9 150Ω Resistor (Leg 1) Yellow PWM Signal Output
150Ω Resistor (Leg 2) IRLZ44N Gate (Pin 1) Yellow Gate Drive
10kΩ Resistor Gate (Pin 1) to GND Black/White Pull-down (keeps MOSFET off at boot)
IRLZ44N Drain (Pin 2) Load Negative (-) Blue Low-side switching
IRLZ44N Source (Pin 3) Arduino GND & PSU GND Black Common Ground Reference
1N4007 Diode Across Load (+ to -) N/A Stripe faces Load (+). Flyback protection.

Assembly Steps

  1. De-energize: Ensure the 12V power supply is unplugged. Never wire high-current loads while the circuit is live.
  2. Build the Gate Network: Connect the 150Ω resistor between Arduino Pin 9 and the MOSFET Gate. Connect the 10kΩ resistor between the Gate and Ground. This prevents the load from turning on randomly while the Arduino bootloader runs.
  3. Wire the Load: Connect the 12V PSU positive directly to the Load positive. Connect the Load negative to the MOSFET Drain.
  4. Establish Common Ground: Connect the MOSFET Source, the Arduino GND pin, and the 12V PSU negative together. Without a common ground, the PWM signal has no reference and the MOSFET will not switch.
  5. Add Flyback Protection: If using a motor, place the 1N4007 diode in parallel with the motor terminals, with the cathode (silver stripe) pointing toward the 12V positive.

Compilable Code: Serial-Controlled PWM with Bounds Checking

This code targets the Arduino Uno R3. It sets up a serial listener that allows you to type a value from 0 to 255 into the Serial Monitor to adjust the PWM duty cycle. It includes robust error handling to catch out-of-bounds numbers and non-numeric strings, preventing erratic hardware behavior.

/*
 * High-Power PWM MOSFET Controller
 * Target Board: Arduino Uno R3 (ATmega328P)
 * PWM Pin: 9 (Timer1, 490Hz default)
 */

const int PWM_OUTPUT_PIN = 9;
const int BAUD_RATE = 9600;

void setup() {
  Serial.begin(BAUD_RATE);
  
  // Configure the PWM pin as an output
  pinMode(PWM_OUTPUT_PIN, OUTPUT);
  
  // Ensure the load is OFF at startup
  analogWrite(PWM_OUTPUT_PIN, 0);
  
  Serial.println("PWM Controller Ready.");
  Serial.println("Enter a duty cycle value (0-255) and press Enter.");
}

void loop() {
  if (Serial.available() > 0) {
    // Read the incoming string until newline character
    String input = Serial.readStringUntil('\n');
    input.trim(); // Remove leading/trailing whitespace and carriage returns
    
    // Error Handling: Check if string is empty
    if (input.length() == 0) {
      return;
    }

    // Error Handling: Check if the first character is a digit or a negative sign
    bool isValidFormat = isDigit(input.charAt(0)) || (input.charAt(0) == '-' && input.length() > 1);
    
    if (isValidFormat) {
      int pwmValue = input.toInt();
      
      // Bounds checking for 8-bit PWM resolution
      if (pwmValue >= 0 && pwmValue <= 255) {
        analogWrite(PWM_OUTPUT_PIN, pwmValue);
        Serial.print("SUCCESS: PWM duty cycle set to ");
        Serial.print(pwmValue);
        Serial.print(" (");
        Serial.print((pwmValue / 255.0) * 100, 1);
        Serial.println("%)");
      } else {
        Serial.print("ERROR: Value ");
        Serial.print(pwmValue);
        Serial.println(" is out of bounds. Must be between 0 and 255.");
      }
    } else {
      Serial.print("ERROR: Invalid input '");
      Serial.print(input);
      Serial.println("'. Please enter an integer (e.g., 128).");
    }
  }
}

Debugging: The First Three Things to Check When PWM Fails

When your motor doesn't spin or your LED strip stays dark, don't immediately rewrite your code. Hardware and timer conflicts are the usual culprits. Here is the exact decision path for debugging PWM pins on Arduino.

Warning: If your MOSFET is burning hot to the touch, you are likely operating it in the linear region. Verify you are using a logic-level MOSFET (IRLZ44N, IRLB8721) and not a standard power MOSFET (IRF520, IRF540).

1. Check for Timer and Library Conflicts

Symptom: analogWrite() on Pin 9 or 10 outputs a constant HIGH or LOW, or your PWM suddenly stops working after adding a new component.
Cause: You included the Servo.h library. The standard Arduino Servo library hijacks Timer1 to generate its 50Hz pulse train. This completely disables hardware PWM on Pins 9 and 10.
Fix: Move your PWM output to Pin 3 or 11 (Timer2), or use the PCA9685 I2C PWM driver board if you need to run servos and high-speed PWM simultaneously.

2. Verify You Aren't Using a Non-PWM Pin

Symptom: The load turns fully ON when you pass a value > 127, and fully OFF when you pass < 128. There is no fading or speed control.
Cause: You called analogWrite(8, value). Pin 8 is a standard digital I/O pin. According to the official Arduino analogWrite() documentation, calling this function on a non-PWM pin simply sets the pin HIGH if the value is 128 or greater, and LOW otherwise.
Fix: Move your signal wire to a pin marked with a ~ (3, 5, 6, 9, 10, or 11 on the Uno).

3. Inspect the Gate Pull-Down and Common Ground

Symptom: The load turns on at full power the moment you plug the Arduino into USB, before the code even finishes booting. Alternatively, the MOSFET switches erratically.
Cause: A floating gate or a missing common ground. During the bootloader sequence, Arduino I/O pins are high-impedance (floating). Electromagnetic noise can induce enough voltage on the gate to partially turn on the MOSFET. Furthermore, if the 12V PSU ground and Arduino GND are not tied together, the 5V PWM signal has no return path.
Fix: Ensure the 10kΩ pull-down resistor is physically soldered or firmly seated between the Gate and Source/GND. Verify continuity between the Arduino GND pin and the 12V PSU negative terminal using a multimeter (should read < 1Ω).

Extending and Simplifying the Build

How to Extend (Increase Power and Frequency)

  • Higher Current Loads (10A+): The IRLZ44N handles up to 47A continuous (with a massive heatsink), but for high-current motor starts, parallel two MOSFETs or upgrade to an IRLB3034 (rated for 195A pulsed, extremely low RDS(on) at 5V VGS).
  • Ultrasonic Frequencies: If 490 Hz causes audible whine in your DC motor or visible flicker in high-speed camera recordings, install the TimerOne library. Add Timer1.initialize(50); (for 20kHz) and Timer1.pwm(9, 512); (where 512 is 50% duty cycle on a 10-bit scale) to your setup. See All About Circuits' guide on MOSFET switching for more on high-frequency gate drive requirements.

How to Simplify (Reduce Component Count)

  • Use a Pre-Built PWM Shield: If you don't want to wire discrete gate resistors and flyback diodes, purchase an Arduino Motor Shield (Rev3). It uses an L298P dual H-bridge IC, handling the flyback diodes and logic-level shifting on the PCB. It is limited to 2A per channel, making it perfect for small N20 or 775 motors, but unsuitable for high-power LED strips.
  • Potentiometer Input: Strip out the Serial code and wire a 10kΩ potentiometer to Analog Pin A0. Read it with analogRead(A0), map the 0-1023 value down to 0-255 using the map() function, and feed it directly to analogWrite(). This creates a standalone, code-free hardware dimmer.