Learning Goals 5 min
Cluster B ends with a chassis. You take everything from L03-05 to L03-09 — DC motors, H-bridge, PWM, signed speed — and bolt it to two wheels with a battery on top. Today you build the manual robot platform that we'll automate (with sensors, with Bluetooth, with WiFi) for the rest of L3. By the end of this lesson you will:
- Wire a 2WD robot chassis: two motors on the L298N, battery pack on the chassis, Arduino on top, no trailing wires.
- Drive both motors as a unified vehicle: forward, back, spin left (left wheel reverse, right wheel forward), spin right, gentle turn (one wheel slower than the other), and stop.
- Control it with four push buttons (forward, back, left, right) using non-blocking timing, a clean state machine, and the
motor.hhelper from L03-09.
Warm-Up 10 min
Lay out the parts on your desk and identify each:
- 2WD chassis: two motor mounts, two wheels with rubber tyres, one caster wheel, a flat plate with holes.
- Two DC gearmotors (the yellow plastic "TT" motors with M3 mounting holes).
- L298N module (from yesterday — already wired).
- 4 × AA battery pack + holder + cells.
- Arduino UNO.
- 4 push buttons (or a small breadboard with buttons).
- Jumper wires, including a few long ones for the 4-button breadboard so the "remote" can be off-chassis.
Plan the layout before you screw anything
Five-minute planning question: where does each part sit on the chassis?
- Battery: heaviest part. Mount low and centred so the chassis doesn't tip.
- L298N: hot. Don't bury under the battery.
- Arduino: needs the USB cable to be accessible for re-upload.
- Buttons: on a separate breadboard, off the chassis, connected by long jumper wires. The chassis would be hard to drive if you had to chase it to press buttons.
New Concept · Differential drive 20 min
How a two-wheel robot turns
Two driven wheels + one caster. The robot moves where the powered wheels push it. The math is simple:
| Left motor | Right motor | Vehicle motion |
|---|---|---|
| +200 | +200 | Drive straight forward |
| −200 | −200 | Drive straight backward |
| +200 | −200 | Spin right in place (left forward, right reverse) |
| −200 | +200 | Spin left in place |
| +200 | +100 | Gentle right turn (right wheel slower) |
| +100 | +200 | Gentle left turn (left wheel slower) |
| 0 | 0 | Stop (brake) |
This is called differential drive — speed and direction are differences between the two wheels. It's the simplest viable robot drive system; everything else (Ackermann steering, omniwheels) is more complex.
Convention: which motor is "left"?
Sit the chassis on the table with the caster at the back, facing "forward". The motor on your left side is Motor A; on your right is Motor B. Pick a convention and stick to it — if "forward" on Motor A makes the wheel spin backward when looking from outside, swap the two motor leads at OUT1/OUT2 (don't fix it in software — fix it in wiring so "positive duty = wheel rolls forward" is true for both).
Speed mismatch and trim
Two "identical" gearmotors are never actually identical. Drive both at +200 and you'll likely see the robot drift left or right by 5°–15° over a metre of travel. Two fixes:
- Software trim: subtract a few duty points from the faster motor in code. Easy.
- Encoder feedback (L04-27): measure actual wheel speed and close the loop. Proper.
For today, software trim is plenty.
Architecture of today's sketch
We'll build the robot in four layers, bottom to top:
- Motor driver:
motor.hfrom L03-09. Signed speed → L298N pins. - Vehicle: helper functions
driveForward,driveBackward,spinLeft,spinRight,stopVehicle. Each one sets both motor speeds. - Input: four buttons, polled each loop with debouncing.
- Control: while a button is held, run the matching vehicle action. While none are held,
stopVehicle.
Each layer is small and replaceable: in L03-25 we swap the "input" layer for Bluetooth commands; in L03-32 we swap it for HTTP commands. The motor and vehicle layers stay identical.
Worked Example · Build the four-button manual rover 30 min
Step 1 — assemble the chassis
- Mount the two motors to the chassis plate with the supplied M3 screws.
- Push the wheels onto the motor shafts.
- Fit the caster at the back.
- Tape (or screw) the battery pack flat on top of the plate, centred.
- Tape (or velcro) the L298N to the chassis, away from the battery.
- Tape the Arduino on top so the USB port is on one side, accessible.
Step 2 — wire the motors
| Wire | From | To |
|---|---|---|
| Battery + (red) | 4 × AA pack | L298N VS |
| Battery − (black) | 4 × AA pack | L298N GND |
| Common ground | L298N GND | Arduino GND |
| +5V (logic supply) | L298N +5V (5V enable jumper installed) | Arduino 5V (or skip if powering Arduino over USB) |
| Left motor | OUT1 / OUT2 | Motor A's two leads |
| Right motor | OUT3 / OUT4 | Motor B's two leads |
| IN1, IN2, ENA | Arduino D9, D8, D5 | L298N IN1, IN2, ENA |
| IN3, IN4, ENB | Arduino D7, D6, D3 | L298N IN3, IN4, ENB |
Step 3 — buttons on a remote breadboard
Use the breadboard from L01 with four push buttons. Wire each between an Arduino pin and GND, using INPUT_PULLUP. Bring long jumpers from the breadboard to D10 (forward), D11 (back), D12 (left), D13 (right). When the robot moves, the breadboard stays in your hand.
Step 4 — the sketch
// L03-10 · Two-wheel test bed
// Four buttons drive a 2WD robot: forward, back, spin-left, spin-right.
// No button → stop. Pin map: D9 D8 D5 = motor A; D7 D6 D3 = motor B.
#include "motor.h"
// --- pins ---
const Motor motorL = {9, 8, 5}; // IN1, IN2, ENA -- left
const Motor motorR = {7, 6, 3}; // IN3, IN4, ENB -- right
const int BTN_FWD = 10;
const int BTN_BACK = 11;
const int BTN_LEFT = 12;
const int BTN_RIGHT = 13;
// --- tuning ---
const int DRIVE_DUTY = 200;
const int SPIN_DUTY = 180;
const int TRIM_LEFT = 0; // increase if robot drifts right; decrease if drifts left
const int TRIM_RIGHT = 0;
// --- vehicle actions ---
void driveForward() { motorSpeed(motorL, DRIVE_DUTY + TRIM_LEFT); motorSpeed(motorR, DRIVE_DUTY + TRIM_RIGHT); }
void driveBackward() { motorSpeed(motorL, -DRIVE_DUTY - TRIM_LEFT); motorSpeed(motorR, -DRIVE_DUTY - TRIM_RIGHT); }
void spinLeft() { motorSpeed(motorL, -SPIN_DUTY); motorSpeed(motorR, SPIN_DUTY); }
void spinRight() { motorSpeed(motorL, SPIN_DUTY); motorSpeed(motorR, -SPIN_DUTY); }
void stopVehicle() { motorSpeed(motorL, 0); motorSpeed(motorR, 0); }
void setup() {
motorInit(motorL);
motorInit(motorR);
pinMode(BTN_FWD, INPUT_PULLUP);
pinMode(BTN_BACK, INPUT_PULLUP);
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
stopVehicle();
Serial.begin(9600);
Serial.println("# 2WD test bed armed.");
}
void loop() {
bool f = (digitalRead(BTN_FWD) == LOW);
bool b = (digitalRead(BTN_BACK) == LOW);
bool l = (digitalRead(BTN_LEFT) == LOW);
bool r = (digitalRead(BTN_RIGHT) == LOW);
if (f && !b) driveForward();
else if (b && !f) driveBackward();
else if (l && !r) spinLeft();
else if (r && !l) spinRight();
else stopVehicle();
}Step 5 — first roll (wheels off the floor!)
Hold the chassis up. Press FWD — both wheels should turn so the chassis would drive forward. If one spins backwards, swap its leads at the L298N. Press BACK — both reverse. LEFT / RIGHT — the wheels spin opposite directions, chassis would rotate.
Step 6 — put it down and drive
Set the chassis on the floor with the USB cable trailing. Press FWD. The robot rolls. If it veers left, the right motor is slower — bump up TRIM_RIGHT by 5 or 10 in the sketch and re-upload. Iterate until it tracks straight for a metre.
Step 7 — cut the cord (optional)
Unplug the USB cable. Power the Arduino from the L298N's +5V output (with the 5V enable jumper installed). The robot is now untethered. Test it on a clear bit of floor.
Try It Yourself 15 min
Goal: Add a fifth "turbo" button that, while held with the forward button, drives both wheels at duty 255 instead of DRIVE_DUTY.
Hint
bool turbo = (digitalRead(BTN_TURBO) == LOW);
int duty = turbo ? 255 : DRIVE_DUTY;
// then use 'duty' inside driveForward() / driveBackward()You'll need to refactor the action functions to accept a duty parameter, or pass it via a global. Either is fine for now.
Goal: Replace the spin-in-place behaviour with a gentle turn: when forward + left are both held, the chassis arcs left (left wheel slow, right wheel fast). Same for forward + right. Sets the stage for arcade-style driving.
Hint
if (f && l) { motorSpeed(motorL, 80); motorSpeed(motorR, DRIVE_DUTY); }
else if (f && r) { motorSpeed(motorL, DRIVE_DUTY); motorSpeed(motorR, 80); }
else if (f) driveForward();
// ... etcThe lower duty for the inside wheel should still be above the dead band, or it won't actually turn — it'll just slow down on one side. Experiment with the value.
Goal: Replace one of the spin buttons with a 3-second auto-square: when pressed, the chassis drives forward 1 s, spins right 0.5 s, drives forward 1 s, spins right 0.5 s, repeat 4 times. Returns roughly to its starting position. Use non-blocking timing.
Hint
You'll need a state machine. States: IDLE (default), FWD_LEG (currently driving forward), TURN_LEG (currently spinning). On each entry into the auto-square mode, start at FWD_LEG with a 1 s timer. When timer expires, advance to TURN_LEG with a 0.5 s timer, and a leg counter. After 4 legs return to IDLE.
This is essentially how every "canned trick" in a toy robot is implemented. Real-life version: how a Roomba does its "spiral pattern". We'll build a sensor-driven follower in L03-28, but the state-machine pattern is the same.
Mini-Challenge · Ship the rover 10 min
- Trim it straight. Test on a smooth floor over 1 m of forward drive. Tune
TRIM_LEFTandTRIM_RIGHTuntil the chassis tracks within 5 cm of straight. - Tape down the wires. Every loose wire is a battery-pack-fell-off-during-a-turn waiting to happen.
- Add a power switch. An inline switch on the battery pack's + lead lets you stop the robot without unplugging anything. Cheap insurance.
- Photograph the wiring from above and add it to your engineering notebook.
- Video a 30-second demo: forward, back, both turns, gentle arc, autonomous square (if you did the stretch).
Ship-ready test:
- A classmate can drive the robot through a simple obstacle course without crashing.
- Battery lasts at least 10 minutes of continuous driving.
- Nothing falls off when you spin in place at full duty.
If yes to all three, you've shipped a working manual robot — the foundation for all the L3 builds that follow. Cluster B is done.
Recap 5 min
A 2-wheel robot is differential drive: two motors, both signed, with a free-rolling caster at the back. Forward = both positive; reverse = both negative; spin = one positive, one negative; gentle turn = one slower than the other; stop = both zero. The architecture is layered — motor driver, vehicle helpers, input layer, control logic — so the input can be swapped from buttons to Bluetooth (L03-25 / L03-28), WiFi (L03-32) or sensors (L04-23 onwards) without touching the lower layers. Two practical points: trim software-mismatch of nominally-identical motors, and keep the USB cable attached until you trust the build. Cluster C starts tomorrow with the third motor family — steppers.
- Chassis
- The mechanical frame of a robot — the flat plate, the motor mounts, the wheel and caster fittings. Determines weight, balance and what fits on top.
- Differential drive
- A two-wheel drive system where steering comes from differences in the two wheel speeds. Simplest viable robot drive; no separate steering mechanism.
- Caster
- A free-rolling third wheel (or ball) at the back of a differential-drive chassis. Holds up the back without affecting steering.
- Trim
- A small offset applied to one motor's speed to compensate for it being slower than its partner. Software fix for hardware variation.
- Tracking
- How straight a vehicle drives when commanded straight. Good tracking = drives within ~5° of the intended heading over a metre.
- Layered architecture
- Splitting code into thin layers (driver, vehicle, input, control) so each layer can be replaced without affecting the others. The pattern that makes the same chassis serve a button rover, a Bluetooth car, a wall-following bot, and a line follower over the rest of L3.
- Tether
- The USB cable while testing — limits range but keeps re-upload and serial-monitor available. Cut only when the robot can be trusted alone.
- Power switch
- An in-line switch on the battery's + lead. Lets you kill the robot instantly without unplugging anything. Essential for sanity during development.
Homework 5 min
- Drive the rover. Practise. Get used to how it feels. Note in your notebook any control behaviour you wish were different — those are your wish-list items for L03-25 (Bluetooth) and L03-44 (Bluetooth Robot Car v2 — Build).
- Record video. 30 seconds, demonstrating: drive forward 1 m, stop, drive back to start, spin 360° in place, drive a small loop. This is your "Cluster B portfolio" clip — bring it to the L3 recap.
- Read ahead. Tomorrow is L03-11 (Stepper Anatomy) — completely different motor type. No build, just theory. Skim the lesson title list in the L3 page so you know what's coming.
Bring back next class:
- Your driving rover, fully working.
- Your 30-second video.
- A 28BYJ-48 stepper + ULN2003 driver board — both come together in a small clear-plastic bag in most kits. Tomorrow we wire them.