Build a robot that follows a line with Arduino.
This site's flagship build: a two-wheeled robot that reads a black line with IR sensors and steers itself along it, no remote control needed. We'll walk you through the parts, the wiring, the code, and the theory behind how it "thinks" with diagrams you can explore and a live simulator you can play with before you ever plug anything in.
An Arduino robot that steers itself along a taped line.
Two IR sensors stare down at the floor. When one of them loses the black line, the Arduino nudges that wheel to steer back over and over, dozens of times a second. That's the entire trick. Below is everything you need to build, wire, code, and calibrate one yourself.
Sense → decide → act, four times a beat
Click a state below to see exactly what the sensors are reading and what the wheels do about it.
Go Forward
Both sensors still see the black line.
L: LOW · R: LOWTurn Left
Left sensor slid off the line steer that side back onto it.
L: HIGH · R: LOWTurn Right
Right sensor slid off the line steer that side back onto it.
L: LOW · R: HIGHStop & Alert
Both sensors lost the line entirely the robot halts instead of wandering off.
L: HIGH · R: HIGHGo Forward
Both IR sensors are still reading LOW, meaning they're both hovering over the black line (a fresh sensor reads dark surfaces as "line detected"). The loop() function drives both motors forward at equal speed this is the state the robot spends most of its time in on a straight stretch of track.
What you'll need on the bench
A typical 2WD line-follower kit, sold pre-cut by several suppliers, includes all of this.
Arduino Uno (or clone)
The brain. Reads both sensors, runs the decision logic, and sends PWM signals to the motor driver.
L298N Motor Driver
An H-bridge module that lets the low-power Arduino pins switch the much higher current the drive motors need forward or reverse.
2× IR Line Sensors
Small modules with an IR LED and receiver, plus an onboard potentiometer to tune sensitivity. Output a simple HIGH/LOW digital signal.
2× DC Gear Motors + Wheels
Geared motors trade top speed for torque, which is what lets the robot make tight corrections without stalling.
Caster Wheel
A free-spinning third wheel at the front or back keeps the chassis balanced without adding a third drive motor.
Battery Pack + Chassis
A 4×AA or 2×18650 pack powers everything on board; an acrylic or wood plate holds it all together.
Assemble & wire the robot
Eight steps, check them off as you go hover any step to see it highlighted on the diagram.
Mount the gear motors
Screw both DC gear motors to the underside of the chassis, one per side, with the shafts pointing outward.
Fit wheels & the caster
Press a wheel onto each motor shaft, then bolt the free-spinning caster wheel at the opposite end for balance.
Fix the Arduino & L298N
Standoff-mount the Arduino Uno and the L298N driver board on top of the chassis, away from the wheels.
Wire the battery pack
Run the battery's positive/negative leads through an on/off switch into the L298N's power terminal, and a 5V line to the Arduino's VIN.
Wire the motors to the driver
Connect each motor's two leads to one OUT terminal pair on the L298N (left motor → OUT A, right motor → OUT B).
Wire driver control pins
Connect ENA/IN1/IN2/IN3/IN4/ENB on the L298N to six digital pins on the Arduino (see the connection table below).
Mount the IR sensors
Bolt both sensors low at the front of the chassis, close to the ground, angled straight down at the track.
Wire the sensors back
Run VCC to 5V, GND to ground, and each sensor's digital OUT pin to its own Arduino input pin.
Typical pin connection table
- D6 / D5 → L298N ENA / ENB (speed, via PWM)
- D8, D9, D10, D11 → L298N IN1, IN2, IN3, IN4 (direction)
- D4 → Left IR sensor OUT · D7 → Right IR sensor OUT
- 5V / GND → shared across the L298N, both sensors, and any buzzer or LED module
Tune the sensors before the first run
Each IR sensor module has a tiny potentiometer. Turn it with a screwdriver while watching its onboard LED.
Over a white / light surface
Most IR bounces back. The sensor's indicator LED should turn ON, and the pin reads LOW.
Over the black line
Dark surfaces absorb IR instead of reflecting it. The LED should turn OFF, and the pin reads HIGH.
Sweep the sensor slowly between white and black while turning the potentiometer, until the LED flips cleanly right at the edge of the tape not too early, not too late. Recalibrate any time you change the surface, lighting, or track material.
Play with the sensor logic
Flip each sensor between "white" and "black" to see exactly how the real loop() code below would react before you ever touch a soldering iron.
This mirrors the if / else if / else chain in loop() from the sketch below LOW means "still sees black", HIGH means "slid onto white".
Give it somewhere to drive
Start simple and work your way up to tougher layouts as your robot (and your code) improves.
Simple loop
A single closed curve with gentle bends and no crossings the right first track for a freshly-calibrated robot.
Elevation changes
Ramps or gradient changes test whether your motors have enough torque once the chassis tilts.
Sharp corners & gaps
Right-angle turns and small breaks in the tape push the sensor spacing and your decision logic to their limits.
A roll of matte black PVC tape (about 18mm wide) on a plain, light-colored floor works well and is easy to reposition while you're still tuning things.
When the robot won't cooperate
Nearly every line-follower problem traces back to one of these six things.
No movement Robot won't drive at all+
The Arduino may be powering on (lights up) while the motors stay dead. That split points straight at the motor power path rather than the code.
Logic bug Robot spins in one direction+
If it immediately spins in a circle instead of driving straight, one motor is very likely wired backwards relative to the other.
Wiring One sensor never changes+
A sensor reading the exact same value everywhere never flipping over the line usually isn't a code problem at all.
Mechanical Wanders off a straight line+
Sensors mounted too high, too far apart, or off-center relative to the wheels will make correction hunt back and forth even on straight tape.
Power Motors are weak or sluggish+
Underpowered or aging batteries can leave the Arduino running fine while the motors barely turn L298N modules also get warm under load and can throttle current.
Behavior Loses sharp corners+
A simple two-sensor, on/off design is built for gentle curves. Tight right-angle turns need either a slower approach speed or a smarter algorithm.
What is Arduino, actually?
Skip the acronyms here's what's really going on inside that little teal board.
A programmable brain
At its core, an Arduino is a circuit board built around a microcontroller a tiny chip you can program to notice things happening in the real world and react to them.
Senses in, action out
Wire in a sensor (light, motion, temperature) and the board can respond through outputs like LEDs, motors, or small displays. That loop sense, decide, act is the whole idea.
Open for anyone
Arduino is released under an open license, which is why brands like Adafruit and SparkFun sell compatible boards. Anything with "Arduino" in the name is an official one.
Several boards exist start with the Uno
Each model trades size, power, or shape for a different kind of project.
Arduino Uno
Not the newest or the flashiest, but the most widely documented board out there meaning endless tutorials when you get stuck. Our pick for a first board.
LilyPad
Round and sewable, designed to be stitched directly into fabric for wearable electronics and e-textile projects.
Pro Mini
A stripped-down, tiny-footprint board for projects where space is at a premium and you don't need every port.
Anatomy of the Uno
Hover any card or any marker on the board to see how they connect.
Reset button
Restarts whatever program is currently loaded.
AREF
Sets an external analog reference voltage.
GND (top)
Ground pin several live on the board, all equivalent.
Digital I/O 0–13
General-purpose pins that read or send digital signals.
PWM pins (~)
Digital pins marked with a tilde can fake an analog output.
USB port
Powers the board and uploads your programs.
TX / RX LEDs
Blink to show data being sent and received.
Microcontroller
The ATmega chip where your program actually lives.
Power LED
Lit whenever the board has power.
Voltage regulator
Keeps incoming voltage steady and safe.
DC barrel jack
Plug in an external power supply here.
3.3V pin
Supplies steady 3.3V power out to components.
5V pin
Supplies steady 5V power out to components.
GND (bottom)
More ground pins near the analog side.
Analog A0–A5
Reads a sensor signal and converts it to digital.
What you'll need on the bench
Nothing exotic most of it comes in a basic starter kit.
Core parts
Arduino Uno board · half-size breadboard · a handful of jumper wires · USB cable · a 5mm LED · a pushbutton switch · a 220Ω resistor · a 10kΩ resistor.
Handy tools (optional at first)
Needle-nose pliers · wire strippers · a precision screwdriver set · flush cutters · fine tweezers · a digital multimeter · a soldering iron for later projects.
Three ways to power the board
USB cable
The simplest option plug straight into your computer. This also uploads your code.
9V battery pack
Best when a project needs to move around untethered from a computer.
9V AC adapter
A wall-powered option for projects that stay in one place.
Prototyping without solder
A breadboard lets you build and rebuild a circuit freely before anything is permanent.
How the holes connect
Underneath the plastic, metal clips link holes together in strips rows connect horizontally along the power rails, and columns connect vertically in the main grid.
It needs power too
A breadboard has no power of its own. Jumper wires carry power and ground over from the Arduino, and also link components like resistors and switches into a working circuit.
Meet the Arduino IDE
Free to download from arduino.cc, this is where you'll write and send programs to your board.
Sketches
An Arduino program is called a sketch. The board holds exactly one at a time uploading a new one replaces the last.
setup()
Runs once, right when the board powers on. Perfect for one-time setup like defining a pin's mode.
loop()
Runs over and over, forever, until the board loses power. This is where the ongoing behavior of your project lives.
The interactive code guide
Click any highlighted word in the code to see exactly what it does.
// Line Follower reads two IR sensors, steers two motors
const int Left_A = 8, Left_B = 9, SpeedA = 6;
const int Right_A = 10, Right_B = 11, SpeedB = 5;
const int irSensorLeft = 4;
const int irSensorRight = 7;
const int RUNSPEED = 190;
void forward() {
digitalWrite(Left_A, LOW); digitalWrite(Left_B, HIGH);
digitalWrite(Right_A, LOW); digitalWrite(Right_B, HIGH);
}
void turnleft() {
digitalWrite(Left_A, HIGH); digitalWrite(Left_B, HIGH);
digitalWrite(Right_A, LOW); digitalWrite(Right_B, HIGH);
}
void turnright() {
digitalWrite(Left_A, LOW); digitalWrite(Left_B, HIGH);
digitalWrite(Right_A, HIGH);digitalWrite(Right_B, HIGH);
}
void stopmoving() {
digitalWrite(Left_A, HIGH); digitalWrite(Left_B, HIGH);
digitalWrite(Right_A, HIGH);digitalWrite(Right_B, HIGH);
}
void setupsetup()Configures every pin's direction once, then sets the motors spinning at a fixed base speed before the loop takes over steering.() {
pinMode(Left_A, OUTPUT); pinMode(Left_B, OUTPUT); pinMode(SpeedA, OUTPUT);
pinMode(Right_A, OUTPUT); pinMode(Right_B, OUTPUT); pinMode(SpeedB, OUTPUT);
pinMode(irSensorLeft, INPUTINPUTBoth sensors are read, not driven this tells the pin to listen rather than send voltage.);
pinMode(irSensorRight, INPUT);
analogWrite(SpeedA, RUNSPEED);
analogWrite(SpeedB, RUNSPEED);
forward();
}
void looploop()Runs continuously every pass re-reads both sensors and picks one of four actions based on what they see.() {
int left = digitalReaddigitalRead(pin)Returns HIGH or LOW. LOW means that sensor is still over the black line; HIGH means it has slid onto the lighter surface.(irSensorLeft);
int right = digitalRead(irSensorRight);
ifif / else if / elseExactly one of these four branches runs each pass this is the entire "brain" of the robot. (left == LOW && right == LOW) {
forward(); // both still on the line
} elseelse ifChecked only when the first condition was false. if (left == HIGH && right == LOW) {
turnleft(); // left sensor slid off steer left
} else if (left == LOW && right == HIGH) {
turnright(); // right sensor slid off steer right
} else {
stopmoving(); // both lost the line halt
}
}
This is a deliberately simple on/off version no PID, no speed ramping so the logic stays easy to trace with a multimeter or the Serial Monitor. Once it's reliably following a simple loop, that's your cue to start exploring proportional control.
// Blink turns an LED on and off, forever
void setupsetup()Runs exactly once, the moment your board powers on or resets. Used for anything you only need to configure a single time.() {
pinModepinMode(pin, mode)Tells a pin whether it should behave as an OUTPUT (sending signal out, like to an LED) or an INPUT (reading a signal in, like from a button).(LED_BUILTINLED_BUILTINA built-in shortcut name for pin 13 the pin wired to the board's onboard LED (and, in our project, the breadboard LED too)., OUTPUTOUTPUTA constant meaning "this pin will send voltage out," as opposed to reading it in.);
}
void looploop()Runs over and over, endlessly, right after setup() finishes. Anything that should keep happening like blinking belongs in here.() {
digitalWritedigitalWrite(pin, value)Sends either HIGH (on, ~5V) or LOW (off, 0V) to a pin. This is what actually switches the LED on.(LED_BUILTIN, HIGHHIGHRepresents "on" roughly 5 volts sent out of the pin.);
delaydelay(ms)Pauses the sketch for the given number of milliseconds 1000ms equals 1 second. Nothing else runs while it waits.(1000);
digitalWrite(LED_BUILTIN, LOWLOWRepresents "off" 0 volts sent out of the pin.);
delay(1000);
}
Click Verify in the IDE to compile and check for errors, then Upload to send it to the board. The onboard LEDs flash briefly while it uploads then your LED should blink once a second.
// Button LED follows the pushbutton state
const int buttonPin = 2;
const int ledPin = 13;
int buttonState = 0;
void setupsetup()Runs once here it configures one pin as output and one as input.() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUTINPUTTells the pin to read an incoming signal rather than send one out needed for buttons and sensors.);
}
void loop() {
buttonState = digitalReaddigitalRead(pin)Checks a pin and returns HIGH or LOW depending on whether it's currently receiving voltage this is how you read a button press.(buttonPin);
ifif (condition)Runs the code inside its braces only when the condition is true here, only while the button is pressed. (buttonState == HIGH) {
digitalWrite(ledPin, HIGH);
} elseelseRuns instead of the if-block, whenever the condition above was false here, whenever the button isn't pressed. {
digitalWrite(ledPin, LOW);
}
}
Same setup()/loop() shape as Blink the new idea here is digitalRead(), which lets your sketch make decisions based on the real world.
Blink an LED
The "hello world" of hardware. Six wiring steps check them off as you go.
Ground it
Run a jumper wire from a GND pin on the Arduino into the ground rail of the breadboard.
Bring in the signal
Run a second jumper wire from digital pin 13 into the main grid of the breadboard.
Place the resistor
Bridge a 220Ω resistor between that signal wire and an empty row for the LED.
Seat the LED
Place the LED's long leg (positive) in line with the resistor, and the short leg (negative) into the ground rail.
Double-check polarity
LEDs only work one way long leg to positive, short leg to negative.
Plug in
Connect the Uno to your computer with the USB cable. You're ready to upload code.
Common code errors, decoded
Every beginner hits these. Click one open to see the fix.
Compile error Missing semicolon+
C requires a semicolon at the end of most lines. Forget one, and the compiler gets confused about where that line ends.
Compile error Wrong capitalization+
Arduino's language is case-sensitive. digitalWrite and digitalwrite are treated as two completely different, unrelated words.
pinMode, digitalWrite, and digitalRead all use camelCase.Upload error Wrong board selected+
The IDE compiles slightly different code depending on which board you tell it you're using. Pick the wrong one and the upload will fail even if your sketch is perfect.
Upload error Wrong or missing COM port+
Your computer talks to the Arduino over a specific serial port. If the wrong one is selected or the board isn't showing up at all nothing can upload.
Compile error Mismatched braces+
Every opening { needs a matching closing }. Miss one often after copy-pasting or deleting code and the whole sketch fails to compile.
Logic bug Using = instead of ==+
This one won't throw an error at all which is what makes it sneaky. A single = assigns a value; == compares two values. Using the wrong one inside an if silently breaks your logic.
if (x = y) it should almost always be if (x == y).Simulate the sketch
No board? No problem play with a virtual LED to see what your code is telling it to do.
This mimics exactly what your real board does once blink.ino is uploaded and running.
Once blinking gets boring
Add a switch
Swap the fixed blink for a pushbutton-controlled LED see the button.ino tab above for the full sketch.
Try a shield
Shields stack on top of the board to add WiFi, Bluetooth, GPS, an LCD screen, or extra motor control with libraries already written for you.
Add a sensor
Distance, motion, light, humidity, and pressure sensors let your board start reacting to the room around it.
If the circuit itself isn't working
- Test the LED on its own first long leg to a battery's positive terminal, short leg to negative to confirm it isn't dead on arrival.
- Double-check the LED's orientation in the breadboard. Backwards is the single most common wiring mistake.
- Re-seat every jumper wire. A loose connection on a breadboard looks identical to a correct one.
- Confirm the resistor is actually bridging the signal line and the LED not sitting in an unconnected row.