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. |
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) | PWMA | Speed control Motor A (Left) |
| D4 | AIN1 | Direction logic Motor A |
| D7 | AIN2 | Direction logic Motor A |
| D6 (PWM) | PWMB | Speed control Motor B (Right) |
| D8 | BIN1 | Direction logic Motor B |
| D12 | BIN2 | Direction logic Motor B |
| D9 | STBY | Logic enable (Must be HIGH to run) |
| GND | GND | Common logic ground (CRITICAL) |
| 5V | VCC | Logic power supply |
| N/A (Battery +) | VM | Motor power supply (7.4V LiPo) |
| N/A (Battery -) | GND | Common motor ground (Tie to Arduino GND) |
Numbered Wiring Steps
- 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.
- 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.
- 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.
- 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#defineblock at the top, or you placed the defines insidesetup()instead of the global scope. Move all#definestatements to the very top of the file. - Error:
expected ';' before '}'
Cause: Missing semicolon at the end of a function call inside theswitchstatement, or a missing closing brace on thesetMotorfunction. 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:
- Wire HC-SR04 VCC to Arduino 5V, GND to GND.
- Wire Trig to Arduino D2, Echo to Arduino D3.
- Because the Uno R3 is 5V tolerant, you can connect the Echo pin directly (unlike ESP32 builds which require a voltage divider).
- Add the
NewPinglibrary via the Library Manager to handle ultrasonic timeouts without blocking the CPU. - Insert a distance check inside the
STATE_FORWARDcase. Ifsonar.ping_cm() < 20, force a state transition toSTATE_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.






