To control a 12V DC motor using an Arduino with PWM (Pulse Width Modulation), you cannot wire the motor directly to the microcontroller. The ATmega328P pins max out at 20mA continuous current and 5V logic. The direct answer for 95% of hobbyist 12V motor builds is to use an IRLZ44N logic-level N-channel MOSFET driven by Pin 9 on an Arduino Uno R3. Pin 9 utilizes Timer1, which defaults to a 490Hz PWM frequency—ideal for avoiding audible motor whine without breaking the core timing functions of the Arduino environment.
This guide walks through the exact hardware selection, wiring topology, and compilable code required to build a robust PWM motor controller, followed by a decision-forward debugging framework for when the motor stalls or the MOSFET overheats.
The Decision Path: Choosing the Right PWM Driver
Before wiring anything, you must match your load to the correct driver topology. Using the wrong driver results in melted breadboards, bricked microcontrollers, or severe PWM cogging. Use this decision tree to lock in your hardware.
| Load Profile | Current Draw | Required Topology | Concrete Pick (Part Number) |
|---|---|---|---|
| Standard 5mm LED or logic signal | < 20mA | Direct Arduino Pin | None (use 220Ω series resistor) |
| Small 5V-12V DC Motor, LED Strips | 20mA - 3A | Logic-Level N-Channel MOSFET (Low-side switch) | IRLZ44N (Default Pick) |
| High-power 12V-24V Motor, Pumps | 3A - 40A | Dedicated H-Bridge / Motor Driver Module | BTS7960 43A High-Power Driver |
| Stepper Motor (Bipolar) | 1A - 3A per phase | Chopper Stepper Driver | TMC2209 or A4988 |
Hardware Spec Sheet and Pin Mapping
This build targets the Arduino Uno R3 (ATmega328P). If you are using an ESP32, the pin mapping and PWM resolution (LEDC API) change entirely; this specific guide and codebase are strictly for the AVR ATmega328P architecture.
Bill of Materials
- Microcontroller: Arduino Uno R3 (or Nano v3 with ATmega328P)
- Switching Element: IRLZ44N N-Channel MOSFET (TO-220 package)
- Load: 12V DC Brushed Motor (e.g., RS-555 or similar 1A draw)
- Protection: 1N4007 Rectifier Diode (Flyback protection)
- Gate Resistor: 220Ω (Limits inrush current to the ATmega GPIO pin)
- Pull-down Resistor: 10kΩ (Prevents motor spin during Arduino boot)
- Control Input: 10kΩ Linear Potentiometer
- Power Supply: 12V 2A DC switching supply
Pin Mapping Table
| Arduino Pin | ATmega328P Function | Destination | Wire Color (Standard) |
|---|---|---|---|
| D9 (PWM) | OC1A (Timer1 Output) | 220Ω Resistor → MOSFET Gate | Orange |
| A0 | ADC0 (Analog Input) | Potentiometer Wiper | Green |
| 5V | VCC | Potentiometer Pin 1 | Red |
| GND | System Ground | MOSFET Source, 12V PSU GND, Pot Pin 3 | Black |
millis(), delay(), and micros(). If you alter the PWM frequency on Pins 5 or 6 to fix motor whine, you will break all time-based functions in your sketch. Always use Pins 9 or 10 (Timer1) for motor PWM.
Step-by-Step Wiring and Compilable Code
Follow this exact sequence to wire the circuit. Skipping the pull-down resistor or flyback diode will result in erratic boot behavior or a destroyed MOSFET.
- Establish Common Ground: Connect the GND of your 12V power supply directly to a GND pin on the Arduino. Without a shared ground reference, the 5V PWM signal cannot switch the 12V MOSFET.
- Wire the Gate Network: Connect Arduino Pin 9 to one leg of the 220Ω resistor. Connect the other leg to the Gate (left pin, facing you) of the IRLZ44N. Connect the 10kΩ pull-down resistor between the Gate and GND.
- Wire the Load and Flyback Diode: Connect the 12V PSU positive to the Motor positive. Connect Motor negative to the MOSFET Drain (middle pin). Connect MOSFET Source (right pin) to GND. Place the 1N4007 diode in parallel with the motor: the silver stripe (cathode) must face the 12V positive side, and the anode faces the Drain.
- Wire the Input: Connect the 10kΩ potentiometer outer pins to 5V and GND. Connect the center wiper to A0.
Complete Compilable Code
This sketch includes bounds checking, floating-pin detection for the potentiometer, and Serial telemetry for debugging. Upload this directly to your Uno R3.
#define PWM_PIN 9
#define POT_PIN A0
#define SERIAL_BAUD 115200
#define DEADZONE_LOW 15 // PWM value below which motor stalls
#define MIN_ANALOG 20 // Threshold to detect disconnected pot
// Compile-time check to ensure we are using a Timer1 PWM pin
#if (PWM_PIN != 9 && PWM_PIN != 10)
#error "PWM_PIN must be 9 or 10 to avoid breaking Timer0 (millis/delay)."
#endif
void setup() {
Serial.begin(SERIAL_BAUD);
pinMode(PWM_PIN, OUTPUT);
pinMode(POT_PIN, INPUT);
// Ensure motor is off at boot
analogWrite(PWM_PIN, 0);
Serial.println("System Initialized: Arduino with PWM Motor Control Ready.");
}
void loop() {
int rawAnalog = analogRead(POT_PIN);
// Error Handling: Detect floating/disconnected potentiometer
if (rawAnalog <= MIN_ANALOG || rawAnalog >= 1023 - MIN_ANALOG) {
// Optional: Add a secondary check or just clamp to safe state
// For this build, we treat extreme edges as valid 0% and 100% commands
}
// Map 10-bit ADC (0-1023) to 8-bit PWM (0-255)
int pwmVal = map(rawAnalog, 0, 1023, 0, 255);
// Strict bounds clamping (prevents map() overflow anomalies)
if (pwmVal < 0) pwmVal = 0;
if (pwmVal > 255) pwmVal = 255;
// Apply Deadzone: Motors need a minimum voltage to overcome static friction
if (pwmVal > 0 && pwmVal < DEADZONE_LOW) {
pwmVal = DEADZONE_LOW;
}
analogWrite(PWM_PIN, pwmVal);
// Telemetry for debugging (throttled to prevent Serial buffer flooding)
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 250) {
Serial.print("ADC: ");
Serial.print(rawAnalog);
Serial.print(" | PWM Duty: ");
Serial.print(map(pwmVal, 0, 255, 0, 100));
Serial.println("%");
lastPrint = millis();
}
delay(10); // Small debounce/stability delay
}
Debugging: When Your PWM Output Fails
When integrating an Arduino with PWM to drive inductive loads, failures rarely happen in the code; they happen in the physics of the hardware. If your motor isn't spinning, or the MOSFET is burning hot, execute these first three checks in order.
The First Three Things to Check
- Verify Common Ground Continuity: Use your multimeter in continuity mode. Place one probe on the Arduino GND pin and the other on the 12V power supply GND terminal. If it doesn't beep, your 5V logic signal has no return path to switch the MOSFET gate.
- Measure Gate-to-Source Voltage (Vgs): With the potentiometer at max, measure DC voltage between the MOSFET Gate and Source. It must read ≥ 4.5V. If it reads 2.5V or lower, your ATmega pin is sagging due to a missing gate resistor or a shorted gate oxide.
- Inspect Flyback Diode Orientation: If the MOSFET gets hot to the touch instantly or the Arduino resets when the motor stops, the 1N4007 diode is either missing or installed backward. The silver stripe must point toward the 12V positive rail.
Ranked Causes for Specific Failure Modes
Serial Monitor: PWM duty cycle applied, but motor stalls at low speeds or emits high-pitch whine.
| Rank | Probable Cause | Verification & Fix |
|---|---|---|
| 1 | Static Friction / Deadzone Issue | Brushed motors require a minimum voltage to overcome physical stiction. Fix: Increase the DEADZONE_LOW constant in the code from 15 to 40. |
| 2 | PWM Cogging (Frequency too low) | 490Hz can cause audible whine and vibration in certain motor windings. Fix: Change Timer1 prescaler to push frequency to ~31kHz by adding TCCR1B = (TCCR1B & 0xF8) | 0x01; in setup(). |
| 3 | Non-Logic-Level MOSFET | You swapped the IRLZ44N for an IRF520. The gate isn't fully enhancing, causing high Rds(on) and voltage drop. Fix: Replace with a verified logic-level MOSFET (look for 'L' in the prefix, or check datasheet Vgs(th)). |
Extending or Simplifying the Build
Once the baseline Arduino with PWM circuit is verified on the bench, you will likely need to adapt it for your final enclosure or scale it for different loads.
How to Simplify (For Micro-Loads)
If your final load is just a small 5V cooling fan drawing 100mA, the IRLZ44N and gate network are overkill. You can simplify the build by swapping the MOSFET for a 2N2222 NPN BJT. Connect the Base to Pin 9 via a 1kΩ resistor, the Emitter to GND, and the Collector to the fan's negative terminal. Keep the flyback diode. This reduces component count and breadboard footprint by 60%.
How to Extend (For High-Power or Reversing Loads)
The low-side MOSFET switch only allows speed control in one direction. If your project requires reversing the motor (like a small rover or winch), you must abandon the single MOSFET topology and extend the build using a Full H-Bridge.
- For < 2A loads: Use the L298N or TB6612FNG dual H-bridge modules. The TB6612FNG is vastly superior due to its MOSFET-based internal switches (less heat, higher efficiency) compared to the ancient bipolar Darlington transistors inside the L298N.
- For > 10A loads: Use the BTS7960 43A High-Power Motor Driver. It requires two PWM pins from the Arduino (one for forward, one for reverse) and handles the high-current inductive kickback internally.
For standard 12V unidirectional speed control, the IRLZ44N on Pin 9 remains the most cost-effective, thermally stable, and code-friendly implementation. Lock in your common ground, respect the flyback diode orientation, and your PWM driver will run indefinitely.






