Project Overview & Motor Driver Decision Path

Building a reliable 2WD rover requires more than just copying a sketch; it demands matching your motor driver to your power supply and mechanical load. The most common point of failure in beginner robotics is selecting a motor driver that starves the motors of voltage or lacks adequate logic protection. This guide targets the Arduino Uno R3 (ATmega328P) and provides a complete, non-blocking architecture for a 2WD differential drive robot.

Before wiring, you must choose a motor driver. Here is the decision matrix that terminates in our concrete pick for this build:

Driver IC Topology Voltage Drop Continuous Current Verdict
L298N BJT H-Bridge ~1.4V to 2.0V 2.0A per channel Reject: Wastes battery capacity as heat.
DRV8833 NMOS H-Bridge ~0.2V 1.5A per channel Good, but limited to 10.8V max VM.
TB6612FNG MOSFET H-Bridge ~0.5V 1.2A (3.2A peak) SELECT: Best balance of efficiency, 15V tolerance, and PWM frequency handling.
Decision Locked: We are using the TB6612FNG Dual Motor Driver Carrier. The L298N's massive voltage drop means a 6V motor running on a 7.4V LiPo will only see ~5.4V, resulting in sluggish torque. The TB6612FNG's MOSFET design delivers nearly the full battery voltage to the wheels.

Exact Parts List & Spec Sheet

Sourcing the exact variants below prevents the micro-USB power brownouts and gear-stripping issues common with generic kits. Expect to spend roughly $45-$60 total in 2026.

Component Exact Variant / Spec Why This Variant
Microcontroller Arduino Uno R3 (Rev3, DIP ATmega328P) DIP chip allows recovery if you fry the MCU; 5V logic matches driver.
Motor Driver TB6612FNG Carrier (Pololu #713 or equivalent) Includes necessary 0.1-inch header spacing and bypass capacitors.
Motors TT Gearmotors (1:48 ratio, 3-6V nominal) 1:48 provides ~200 RPM and enough torque for acrylic chassis on carpet.
Power Supply 2S LiPo Battery (7.4V, 1000mAh, 25C+, XT60) 25C rating prevents voltage sag during stall-current spikes (up to 2.5A).
Chassis 2WD Acrylic Baseplate with rear caster wheel Rear caster reduces friction compared to a fixed skid plate.

Pin Mapping & Wiring Procedure

The TB6612FNG requires separate logic and motor power rails, plus a critical Standby (STBY) pin that beginners often leave floating.

Arduino Uno R3 Pin TB6612FNG Pin Function
D5 (PWM)PWMASpeed control Motor A (Left)
D4AIN1Direction logic Motor A
D7AIN2Direction logic Motor A
D6 (PWM)PWMBSpeed control Motor B (Right)
D8BIN1Direction logic Motor B
D12BIN2Direction logic Motor B
D9STBYLogic enable (Must be HIGH to run)
GNDGNDCommon logic ground (CRITICAL)
5VVCCLogic power supply
N/A (Battery +)VMMotor power supply (7.4V LiPo)
N/A (Battery -)GNDCommon motor ground (Tie to Arduino GND)

Numbered Wiring Steps

  1. Establish Common Ground: Connect the LiPo negative terminal to the TB6612FNG GND, and run a jumper wire from that same GND pin to the Arduino Uno GND. Without this shared reference, the logic signals will float and the motors will twitch randomly.
  2. Wire Logic Power: Connect Arduino 5V to TB6612FNG VCC. Do not power VCC from the motor battery (VM) unless you are using a step-down buck converter.
  3. Connect Motor Outputs: Solder or screw your TT motor leads to AO1/AO2 (Left) and BO1/BO2 (Right). Polarity doesn't matter yet; we will fix reversed motors in software.
  4. Verify STBY: Ensure Arduino D9 is wired to the STBY pin. If STBY is LOW or floating, the internal MOSFETs remain in a high-impedance state, and the robot will not move regardless of your PWM signals.

Complete Compilable Code for Arduino Robot

This sketch targets the Arduino Uno R3. It uses a non-blocking millis() state machine to drive the robot in a square pattern. This architecture prevents the CPU from locking up during delays, allowing you to easily add sensor polling later without rewriting the movement logic.

// =========================================================
// 2WD Arduino Robot - TB6612FNG Non-Blocking Control
// Target Board: Arduino Uno R3 (ATmega328P)
// =========================================================

// --- Pin Definitions ---
#define PWMA 5   // Left Motor PWM
#define AIN1 4   // Left Motor Dir 1
#define AIN2 7   // Left Motor Dir 2
#define PWMB 6   // Right Motor PWM
#define BIN1 8   // Right Motor Dir 1
#define BIN2 12  // Right Motor Dir 2
#define STBY 9   // Standby (Active HIGH)

// --- Movement States ---
enum RobotState {
  STATE_FORWARD,
  STATE_TURN_RIGHT,
  STATE_IDLE
};

RobotState currentState = STATE_FORWARD;
unsigned long stateStartTime = 0;
const unsigned long MOVE_DURATION = 1500; // ms to drive straight
const unsigned long TURN_DURATION = 600; // ms to pivot

void setup() {
  // Initialize all motor control pins as outputs
  pinMode(PWMA, OUTPUT);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(PWMB, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);
  pinMode(STBY, OUTPUT);

  // Wake up the TB6612FNG
  digitalWrite(STBY, HIGH);
  
  // Start serial for debugging
  Serial.begin(115200);
  Serial.println("Robot initialized. Starting sequence.");
  stateStartTime = millis();
}

void loop() {
  unsigned long currentTime = millis();
  
  switch (currentState) {
    case STATE_FORWARD:
      forward(200); // 200/255 PWM duty cycle
      if (currentTime - stateStartTime >= MOVE_DURATION) {
        currentState = STATE_TURN_RIGHT;
        stateStartTime = currentTime;
      }
      break;
      
    case STATE_TURN_RIGHT:
      turnRight(180);
      if (currentTime - stateStartTime >= TURN_DURATION) {
        currentState = STATE_FORWARD;
        stateStartTime = currentTime;
      }
      break;
      
    case STATE_IDLE:
      stopMotors();
      break;
  }
}

// --- Motor Control Functions with Bounds Checking ---

void setMotor(int speed, bool reverse, int motorChannel) {
  // Error handling: Clamp PWM values to valid 0-255 range
  if (speed < 0) speed = 0;
  if (speed > 255) speed = 255;

  if (motorChannel == 1) { // Left Motor
    analogWrite(PWMA, speed);
    digitalWrite(AIN1, !reverse);
    digitalWrite(AIN2, reverse);
  } else {                 // Right Motor
    analogWrite(PWMB, speed);
    digitalWrite(BIN1, !reverse);
    digitalWrite(BIN2, reverse);
  }
}

void forward(int speed) {
  setMotor(speed, false, 1);
  setMotor(speed, false, 2);
}

void backward(int speed) {
  setMotor(speed, true, 1);
  setMotor(speed, true, 2);
}

void turnRight(int speed) {
  setMotor(speed, false, 1); // Left wheel forward
  setMotor(speed, true, 2);  // Right wheel backward
}

void turnLeft(int speed) {
  setMotor(speed, true, 1);  // Left wheel backward
  setMotor(speed, false, 2); // Right wheel forward
}

void stopMotors() {
  // Brake mode: short both motor terminals together
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 0);
  analogWrite(PWMB, 0);
}

Debugging: The First Three Things to Check

When your robot fails to move or behaves erratically, do not immediately rewrite the code. Hardware and power faults account for 90% of embedded robotics failures. Follow this diagnostic sequence:

1. Hardware: The Common Ground & STBY Check

Symptom: Motors twitch violently or do not spin, but the Arduino power LED is on.
Fix: Use your multimeter in continuity mode. Check resistance between the Arduino GND pin and the TB6612FNG GND pin (should read < 1 ohm). Next, measure the voltage on the STBY pin relative to GND. It must read ~5V. If it reads 0V or floats, the driver is in standby mode.

2. Power: Battery Voltage Sag Under Load

Symptom: Robot moves fine on a desk, but resets or stops when placed on carpet.
Fix: TT motors draw up to 1.2A each at stall. If your LiPo has a low C-rating (e.g., 10C on a 500mAh pack), the voltage will sag below the Arduino Uno's brownout threshold (~4.3V), triggering a hardware reset. Measure battery voltage at the VM terminal while physically blocking the wheels. If it drops below 6.0V, upgrade to a higher capacity or higher C-rating LiPo.

3. Code: Exact Compiler Error Strings

If the code fails to upload, check these exact error strings in the Arduino IDE console:

  • Error: 'PWMA' was not declared in this scope
    Cause: You copied the functions but missed the #define block at the top, or you placed the defines inside setup() instead of the global scope. Move all #define statements to the very top of the file.
  • Error: expected ';' before '}'
    Cause: Missing semicolon at the end of a function call inside the switch statement, or a missing closing brace on the setMotor function. Check the line number indicated by the compiler and look at the line immediately preceding it.

How to Extend or Simplify the Build

Depending on your skill level and project timeline, you can scale this architecture up or down.

Simplifying the Build (For Absolute Beginners)

If the millis() state machine is too complex, strip it down to blocking delays. Replace the entire loop() function with this simplified sequence:

void loop() {
  forward(200);
  delay(1500);
  turnRight(180);
  delay(600);
}

Trade-off: This is much easier to read, but the Arduino cannot read sensors or process serial commands while delay() is executing.

Extending the Build (Adding Obstacle Avoidance)

To convert this into an autonomous rover, add an HC-SR04 Ultrasonic Sensor.
Concrete Integration Plan:

  1. Wire HC-SR04 VCC to Arduino 5V, GND to GND.
  2. Wire Trig to Arduino D2, Echo to Arduino D3.
  3. Because the Uno R3 is 5V tolerant, you can connect the Echo pin directly (unlike ESP32 builds which require a voltage divider).
  4. Add the NewPing library via the Library Manager to handle ultrasonic timeouts without blocking the CPU.
  5. Insert a distance check inside the STATE_FORWARD case. If sonar.ping_cm() < 20, force a state transition to STATE_TURN_RIGHT.

For detailed timing diagrams and electrical characteristics of the motor driver, refer to the Pololu TB6612FNG carrier documentation. For core language syntax and analogWrite() frequency limits on the ATmega328P, consult the official Arduino Language Reference.