The Core Challenge: Microcontrollers and Inductive Loads
Mastering Arduino DC motor control requires understanding a fundamental hardware limitation: microcontrollers cannot drive motors directly. The ATmega328P (the brain of the Arduino Uno) has an absolute maximum DC current limit of 40mA per I/O pin, with a recommended operating limit of 20mA. Even a small 3V hobby DC motor can draw 300mA under normal load and exceed 1.5A during a stall condition. Connecting a motor directly to an Arduino pin will instantly destroy the microcontroller's internal silicon.
To bridge this gap, we use an H-bridge motor driver. The H-bridge uses high-current transistors or MOSFETs to switch the motor's power supply based on low-current logic signals from the Arduino. Furthermore, because motors are inductive loads, they generate back-EMF (voltage spikes) when power is removed. Modern motor drivers integrate flyback diodes to safely route these spikes away from your logic circuits.
2026 Driver Selection Matrix: Moving Beyond the L298N
For years, the L298N was the default choice for hobbyists. However, its ancient bipolar Darlington transistor architecture results in a massive voltage drop and heat generation. For modern projects, MOSFET-based drivers are the standard. Below is a comparison of the most common drivers for Arduino DC motor control.
| Driver IC | Architecture | Continuous Current | Voltage Drop (V) | Typical Module Cost | Best Use Case |
|---|---|---|---|---|---|
| L298N | Bipolar Darlington | 2.0A | 1.4V - 2.0V | $4.00 - $8.00 | Legacy replacements, high-voltage (12V+) non-battery rigs |
| TB6612FNG | MOSFET | 1.2A (3.2A peak) | ~0.5V | $2.50 - $5.00 | Battery-powered robots, 3.3V logic systems (ESP32/RP2040) |
| DRV8871 | MOSFET | 3.6A | ~0.4V | $3.00 - $6.00 | High-torque single motor applications, 12V/24V systems |
Reference: For detailed thermal and electrical characteristics of bipolar vs. MOSFET bridges, consult the STMicroelectronics L298 Datasheet and the Texas Instruments DRV8871 Datasheet.
Step-by-Step Wiring Guide: TB6612FNG
We will use the TB6612FNG for this guide, as its low Rds(on) (on-state resistance) preserves battery life and its 3.3V logic compatibility makes it future-proof for modern microcontrollers.
1. Power Routing and Decoupling
The most common point of failure in Arduino DC motor control is inadequate power decoupling, leading to microcontroller brownouts. Motors create massive electrical noise.
- VMOT (Motor Power): Connect your external battery pack (e.g., 2S LiPo at 7.4V or 4x AA at 6V) to VMOT and the driver's Power GND.
- VCC (Logic Power): Connect the Arduino's 5V (or 3.3V) output to VCC. This powers the internal logic gates of the driver.
- Common Ground: You MUST connect the Arduino GND to the driver's Logic GND. Without a shared ground reference, the PWM signals will float, causing erratic motor behavior.
- Capacitors: Solder a 100nF (0.1µF) ceramic capacitor directly across the motor terminals to suppress high-frequency brush noise. Additionally, place a 470µF electrolytic capacitor across the VMOT and Power GND rails on your breadboard to act as a bulk energy reservoir during motor startup spikes.
2. Logic and PWM Connections
Each motor channel requires three Arduino pins: two for direction (AIN1, AIN2) and one for speed (PWMA).
- Connect AIN1 to Arduino Digital Pin 4.
- Connect AIN2 to Arduino Digital Pin 7.
- Connect PWMA to Arduino Digital Pin 5.
- Connect STBY (Standby) directly to Arduino 5V to keep the chip active.
Expert PWM Pin Selection: On the Arduino Uno (ATmega328P), Pins 5 and 6 operate at a default PWM frequency of 980Hz, while Pins 3, 9, 10, and 11 operate at 490Hz. Always use Pin 5 or 6 for motor speed control; the higher 980Hz frequency pushes the switching noise above the range of human hearing, eliminating the annoying high-pitched whine common in 490Hz motor circuits. See the Arduino analogWrite() Reference for timer mappings.
Arduino DC Motor Control Code Architecture
Below is a robust, non-blocking C++ implementation. It includes a soft-start ramp to prevent sudden current spikes from tripping the battery's internal protection circuit (BMS) or causing an Arduino voltage regulator brownout.
// Pin Definitions for TB6612FNG Channel A
const int PWMA = 5; // PWM Speed (980Hz)
const int AIN1 = 4; // Direction 1
const int AIN2 = 7; // Direction 2
const int STBY = 8; // Standby (Active HIGH)
void setup() {
pinMode(PWMA, OUTPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(STBY, OUTPUT);
digitalWrite(STBY, HIGH); // Enable the driver
}
void loop() {
// 1. Soft-Start Forward (Ramp up to 70% speed)
setMotorDirection(true); // Forward
rampSpeed(0, 180, 20); // Ramp from 0 to 180 over 20ms steps
delay(2000); // Run for 2 seconds
// 2. Active Braking (Fast Decay)
brakeMotor();
delay(1000);
// 3. Reverse at fixed speed
setMotorDirection(false); // Reverse
analogWrite(PWMA, 128); // 50% speed
delay(2000);
// 4. Coast to stop (Slow Decay)
coastMotor();
delay(3000);
}
void setMotorDirection(bool forward) {
digitalWrite(AIN1, forward ? HIGH : LOW);
digitalWrite(AIN2, forward ? LOW : HIGH);
}
void rampSpeed(int startSpeed, int endSpeed, int stepDelay) {
if (startSpeed < endSpeed) {
for (int i = startSpeed; i <= endSpeed; i++) {
analogWrite(PWMA, i);
delay(stepDelay);
}
} else {
for (int i = startSpeed; i >= endSpeed; i--) {
analogWrite(PWMA, i);
delay(stepDelay);
}
}
}
void brakeMotor() {
// Setting both logic pins HIGH shorts the motor terminals
// through the low-side MOSFETs, creating active electromagnetic braking.
digitalWrite(AIN1, HIGH);
digitalWrite(AIN2, HIGH);
analogWrite(PWMA, 255); // Full current for maximum braking torque
}
void coastMotor() {
// Setting both logic pins LOW disconnects the motor,
// allowing it to spin down naturally via friction.
digitalWrite(AIN1, LOW);
digitalWrite(AIN2, LOW);
analogWrite(PWMA, 0);
}
Advanced Edge Cases and Troubleshooting
When scaling up your Arduino DC motor control project, you will inevitably encounter electromagnetic interference (EMI) and logic faults. Here is how to diagnose and fix the most common edge cases.
1. The I2C Bus Lockup
Symptom: Your motor runs fine, but the moment it changes direction, your I2C sensors (like an MPU6050 or OLED display) freeze or return garbage data.
Cause: Brushed DC motors act as massive broadband RF antennas. The brush arcing generates EMI that couples into the unshielded I2C SDA/SCL wires, corrupting the data packets and causing the I2C hardware state machine in the ATmega328P to lock up.
Solution: Use twisted-pair wiring for your I2C lines. Add 4.7kΩ pull-up resistors directly at the sensor end (not just relying on internal microcontroller pull-ups). If the issue persists, route the motor power wires physically away from the I2C bus, or use ferrite beads on the motor leads.
2. Unexplained Arduino Resets
Symptom: The Arduino reboots randomly when the motor starts or hits a mechanical load.
Cause: Voltage sag. A motor drawing 1.5A at startup can pull the shared battery voltage down from 7.4V to 5.5V for a few milliseconds. If you are powering the Arduino via the Vin pin, the onboard linear regulator drops out, resetting the MCU.
Solution: Never power high-torque motors and the Arduino from the same raw battery line without massive bulk capacitance (1000µF+). Better yet, use a separate UBEC (Universal Battery Elimination Circuit) or a dedicated buck converter (like an LM2596 module set to 5V) to power the Arduino's 5V pin directly, bypassing the inefficient onboard linear regulator entirely.
3. Asymmetric Speeds in Forward vs. Reverse
Symptom: The motor spins faster in one direction than the other at the exact same PWM value.
Cause: This is rarely a code issue. It is usually caused by physical brush timing in cheap hobby motors, or a slight mismatch in the Rds(on) of the high-side vs. low-side MOSFETs in counterfeit driver chips.
Solution: Calibrate in software. Create a lookup table or a simple multiplier in your code to offset the PWM value for the slower direction by 5-10% to achieve symmetrical physical output.






