An H-bridge allows a microcontroller to control both the speed and direction of a DC motor by selectively switching the polarity of the voltage applied to the load. If you are building an Arduino H bridge circuit, the direct answer for the best modern hobbyist module is the TB6612FNG for motors under 1.2A, or the BTS7960 for high-current robotics. The classic L298N is largely obsolete for new designs due to its massive 2V internal voltage drop, but remains common in legacy kits.

This guide covers exact wiring, a complete C++ implementation with overcurrent fault handling, and the specific hardware traps that cause motor jitter or silent failures.

Choosing the Right Arduino H Bridge Module

Not all H-bridges are created equal. Older bipolar junction transistor (BJT) designs waste significant power as heat, while modern MOSFET-based drivers offer near-zero voltage drop and higher switching frequencies. Below is a data-dense comparison of the four most common modules you will encounter on the bench in 2026.

IC / Module Topology Continuous Current Voltage Drop (V) Logic Level Est. Price (2026)
L298N BJT (Darlington) 2.0A per channel ~2.0V (High loss) 5V TTL $3.00 - $5.00
TB6612FNG MOSFET 1.2A per channel ~0.5V (Efficient) 3.3V / 5V $5.00 - $8.00
DRV8871 MOSFET 3.6A (Single channel) ~0.4V 3.3V / 5V $4.00 - $6.00
BTS7960 MOSFET (Half-bridge x2) 43A (with heatsink) ~0.1V 5V TTL $12.00 - $18.00

Source: Manufacturer datasheets and distributor pricing (DigiKey/Mouser). For detailed TB6612FNG specifications, refer to the Pololu TB6612FNG carrier board documentation. For the DRV8871, see the Texas Instruments DRV8871 datasheet.

Hardware Wiring and Pin Mapping

For this build, we are targeting the Arduino Uno R3 (ATmega328P) paired with a TB6612FNG breakout board (SparkFun ROB-14451 or Pololu #713 equivalent) and an external ACS712-20A current sensor for hardware fault protection.

⚠️ CRITICAL PWM WARNING: Do not use Arduino Uno pins 5 or 6 for motor PWM. These pins are tied to Timer0, which handles the millis() and delay() functions. Changing the PWM frequency on these pins will break your timing logic. Always use pins 9, 10, or 11 (Timer1/Timer2) for motor control.

Parts List

  • 1x Arduino Uno R3 or Nano v3 (ATmega328P)
  • 1x TB6612FNG Dual Motor Driver Breakout
  • 1x ACS712-20A Current Sensor Module (for overcurrent protection)
  • 1x 12V DC Gearmotor (e.g., 100 RPM, 500mA nominal draw)
  • 1x 12V 2A DC Power Supply (barrel jack or terminal block)
  • Jumper wires (22 AWG stranded for logic, 18 AWG for motor power)

Pin Mapping Table

Arduino Uno Pin TB6612FNG Pin Function
D9 (PWM)PWMASpeed control Channel A
D7AIN1Direction control A1
D8AIN2Direction control A2
D10STBYStandby (Must be HIGH to operate)
A0 (Analog)ACS712 OUTCurrent sense feedback
5VVCCLogic power (3.3V or 5V)
GNDGNDCommon ground (CRITICAL)
N/A (Ext 12V)VMMotor power supply positive

Complete Arduino Code with Fault Handling

Hardware faults in motor control usually manifest as overcurrent events (stalls) or undervoltage brownouts. The code below targets the Arduino Uno R3 and includes a non-blocking current sense loop that shuts down the H-bridge if the motor stalls and exceeds 1.5A, preventing the TB6612FNG from overheating.

#include <Arduino.h>

// --- Pin Definitions ---
#define PIN_AIN1    7
#define PIN_AIN2    8
#define PIN_PWMA    9
#define PIN_STBY    10
#define PIN_I_SENSE A0

// --- System Constants ---
const float CURRENT_LIMIT_A = 1.5;       // Max safe continuous current
const float ACS712_SENSITIVITY = 0.100;  // 100mV/A for 20A module
const int ADC_OFFSET = 512;              // Zero-current ADC value (approx)
const unsigned long FAULT_COOLDOWN_MS = 3000;

// --- State Variables ---
bool systemFault = false;
unsigned long faultTimestamp = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PIN_AIN1, OUTPUT);
  pinMode(PIN_AIN2, OUTPUT);
  pinMode(PIN_PWMA, OUTPUT);
  pinMode(PIN_STBY, OUTPUT);
  
  // Initialize H-Bridge in safe state
  digitalWrite(PIN_STBY, LOW); // Hold in standby during config
  digitalWrite(PIN_AIN1, LOW);
  digitalWrite(PIN_AIN2, LOW);
  analogWrite(PIN_PWMA, 0);
  
  // Enable the TB6612FNG
  digitalWrite(PIN_STBY, HIGH);
  Serial.println("[SYS] Arduino H-Bridge initialized. STBY = HIGH.");
}

void loop() {
  // 1. Check for hardware faults
  float currentA = readMotorCurrent();
  if (currentA > CURRENT_LIMIT_A && !systemFault) {
    triggerFault(currentA);
  }
  
  // 2. Handle fault cooldown and recovery
  if (systemFault) {
    if (millis() - faultTimestamp >= FAULT_COOLDOWN_MS) {
      Serial.println("[SYS] Cooldown elapsed. Clearing fault.");
      systemFault = false;
      digitalWrite(PIN_STBY, HIGH);
    } else {
      return; // Skip motor control while in fault state
    }
  }
  
  // 3. Normal Motor Operation Sequence
  // Ramp up forward
  for (int pwm = 0; pwm <= 200; pwm += 10) {
    driveMotor(1, pwm);
    delay(50);
  }
  
  delay(1000); // Run at speed
  
  // Stop and reverse
  driveMotor(0, 0); 
  delay(500);
  driveMotor(-1, 150);
  delay(1500);
  
  // Brake
  driveMotor(0, 0);
  delay(2000);
}

// --- Helper Functions ---

void driveMotor(int direction, int speed) {
  speed = constrain(speed, 0, 255);
  if (direction > 0) {
    digitalWrite(PIN_AIN1, HIGH);
    digitalWrite(PIN_AIN2, LOW);
  } else if (direction < 0) {
    digitalWrite(PIN_AIN1, LOW);
    digitalWrite(PIN_AIN2, HIGH);
  } else {
    digitalWrite(PIN_AIN1, LOW);
    digitalWrite(PIN_AIN2, LOW);
  }
  analogWrite(PIN_PWMA, speed);
}

float readMotorCurrent() {
  // Oversample for noise reduction
  long adcSum = 0;
  for (int i = 0; i < 16; i++) {
    adcSum += analogRead(PIN_I_SENSE);
  }
  int adcAvg = adcSum / 16;
  return (adcAvg - ADC_OFFSET) * (5.0 / 1024.0) / ACS712_SENSITIVITY;
}

void triggerFault(float current) {
  systemFault = true;
  faultTimestamp = millis();
  
  // Hardware shutdown
  analogWrite(PIN_PWMA, 0);
  digitalWrite(PIN_STBY, LOW); // Force standby to cut power
  
  // Exact error string for serial debugging
  Serial.print("[FAULT] Overcurrent detected on Channel A: ");
  Serial.print(current, 2);
  Serial.println("A exceeds 1.5A limit. Shutting down PWM.");
}

Note: For more on how analogWrite() interacts with hardware timers, consult the official Arduino analogWrite() documentation.

Debugging: The First Three Things to Check

When your motor jitters, clicks, or refuses to spin, and your serial monitor outputs [FAULT] Overcurrent detected immediately upon startup, do not swap out the microcontroller. Embedded motor control failures are almost always power or grounding issues. Here are the first three things to check on the bench:

  1. Missing Common Ground: The Arduino GND, the TB6612FNG GND, the ACS712 GND, and the external 12V power supply GND must all be tied together. If the logic ground and motor ground are floating relative to each other, the H-bridge cannot interpret the 5V logic HIGH signals, resulting in erratic half-switching and immediate overcurrent faults.
  2. VCC vs. VM Confusion: The TB6612FNG has two separate power inputs. VCC powers the internal logic gates (connect to Arduino 5V). VM powers the actual motors (connect to external 12V). If you feed 12V into VCC, you will instantly fry the logic IC. If you feed 5V into VM, the motor will lack the torque to overcome static friction, triggering a stall/overcurrent fault.
  3. The STBY (Standby) Pin is Floating: Unlike the L298N, the TB6612FNG has a hardware standby pin. If left unconnected (floating), internal leakage can pull it LOW, putting the IC to sleep. Always explicitly wire STBY to a GPIO (as done in the code above) or tie it directly to VCC with a 10kΩ pull-up resistor if you want it permanently enabled.

Extending and Simplifying the Build

How to Simplify

If you only need to control a single motor and do not care about the complexity of dual-channel logic, swap the TB6612FNG for a DRV8871 breakout. The DRV8871 requires only two GPIO pins (IN1 and IN2) per motor. It handles direction and speed internally via PWM on the input pins, eliminating the need for separate Enable/PWM pins and Standby pins. It also accepts motor voltages up to 45V, making it ideal for 24V e-bike or scooter motors.

How to Extend

To move from basic open-loop timing to closed-loop robotics control, extend the build by adding quadrature encoders to the motor shaft. Wire the encoder A/B phases to the Arduino's hardware interrupt pins (D2 and D3 on the Uno). By calculating the delta between commanded PWM and actual encoder ticks, you can implement a PID controller. For high-resolution PWM that doesn't interfere with the Arduino's default 490Hz timer limits, integrate the TimerOne library to push the H-bridge switching frequency to 20kHz, moving the motor whine out of the human hearing range.