A ₹1,640 robot that drives a measured distance and turns a measured angle, so it can trace a 500 mm square. Slot-type encoders count wheel pulses, and the sketch turns ticks into millimetres and degrees. The test at the end measures how far the robot misses the start point, which is the honest measure of dead reckoning.
Scope: intermediate. This is a design worksheet with illustrative costs. It has not been bench-tested by this site. The tick table was computed by script for 65 mm wheels, 20-slot encoder discs and a 130 mm wheel track. Real wheels slip, tyres squash and the effective track is not exactly what you measured, so the numbers give a starting point that you correct by measurement. This is the same dead-reckoning idea as the odometry worksheet, applied to fixed moves.
How it works
One tick moves the wheel π × 65 / 20 = 10.21 mm. A 1,000 mm drive is therefore 97.9 ticks, which rounds to 98, giving 1,000.5 mm, so the rounding error is under a millimetre here. The coarse resolution matters more for short moves: 100 mm is 9.8 ticks, which rounds to 10 and gives 102.1 mm.
Turning on the spot, each wheel travels an arc of π × 130 × (angle / 360). A full 360° spin needs 40.0 ticks per wheel, so 90° needs 10.0 ticks per wheel, one wheel forward and the other back. While driving straight, the sketch counts ticks per wheel on interrupts and slows the leading wheel to keep the two counts equal.
Bill of materials
| Part | Qty | Unit | Total |
|---|---|---|---|
| Arduino Uno R3 (or compatible) | 1 | ₹550 | ₹550 |
| 2WD chassis kit with gear motors and 65 mm wheels | 1 | ₹450 | ₹450 |
| L298N motor driver board | 1 | ₹180 | ₹180 |
| Slot-type speed sensor module (LM393) with encoder disc | 2 | ₹90 | ₹180 |
| Battery holder with two 18650 cells | 1 | ₹210 | ₹210 |
| Switch | 1 | ₹20 | ₹20 |
| Jumper wires | 1 | ₹50 | ₹50 |
| Parts total | ₹1,640 | ||
Scroll the table sideways to see every column.
Prices checked Nov 2026. Shipping and tools are not included.
Prices are illustrative figures for Indian sellers in November 2026, before shipping, and have not been checked against current listings.
The numbers
| Distance | Ticks (10.21 mm each) | Distance after rounding to whole ticks |
|---|---|---|
| 100 mm | 9.8 ticks | 102.1 mm |
| 500 mm | 49.0 ticks | 500.3 mm |
| 1000 mm | 97.9 ticks | 1000.6 mm |
A 500 mm square side is 49.0 ticks, and each 90° turn is 10 ticks per wheel. If the effective track is 3 percent bigger than you measured, each turn is about 3 percent short, about 2.7°, which adds up to roughly 11° of error after four turns. Calibrate the turn first, by measuring one 360° spin.
Power and wiring
| Part | Connects to | Note |
|---|---|---|
| Left encoder OUT | D2 | Interrupt 0 |
| Right encoder OUT | D3 | Interrupt 1 |
| Encoder VCC and GND | 5V and GND | Check the module’s range |
| L298N ENA / IN1 / IN2 | D5 / D7 / D8 | Left motor |
| L298N ENB / IN3 / IN4 | D6 / D9 / D4 | Right motor |
The sketch
// Drive a distance and turn an angle: Uno + L298N + two slot encoders
// Design worksheet: calibrate MM_PER_TICK and TRACK_MM by measurement.
const int L_EN = 5, L_IN1 = 7, L_IN2 = 8;
const int R_EN = 6, R_IN3 = 9, R_IN4 = 4;
void drive(int left, int right) { // -255..255 per side
digitalWrite(L_IN1, left >= 0); digitalWrite(L_IN2, left < 0);
digitalWrite(R_IN3, right >= 0); digitalWrite(R_IN4, right < 0);
analogWrite(L_EN, constrain(abs(left), 0, 255));
analogWrite(R_EN, constrain(abs(right), 0, 255));
}
void motorsInit() {
pinMode(L_EN, OUTPUT); pinMode(L_IN1, OUTPUT); pinMode(L_IN2, OUTPUT);
pinMode(R_EN, OUTPUT); pinMode(R_IN3, OUTPUT); pinMode(R_IN4, OUTPUT);
}
const int ENC_L = 2, ENC_R = 3;
const float MM_PER_TICK = 10.21; // pi * 65 / 20
const float TRACK_MM = 130.0;
volatile long ticksL = 0, ticksR = 0;
void isrL() { ticksL++; }
void isrR() { ticksR++; }
void stopMotors() { drive(0, 0); delay(300); }
void driveMm(float mm, int speed) {
long target = (long)(mm / MM_PER_TICK + 0.5);
noInterrupts(); ticksL = 0; ticksR = 0; interrupts();
while (true) {
noInterrupts(); long l = ticksL, r = ticksR; interrupts();
if ((l + r) / 2 >= target) break;
int corr = constrain((int)(l - r) * 8, -40, 40); // slow the leading wheel
drive(speed - corr, speed + corr);
}
stopMotors();
}
void turnDeg(float deg, int speed) { // positive = clockwise
long target = (long)(PI * TRACK_MM * fabs(deg) / 360.0 / MM_PER_TICK + 0.5);
noInterrupts(); ticksL = 0; ticksR = 0; interrupts();
int s = deg > 0 ? 1 : -1;
while (true) {
noInterrupts(); long l = ticksL, r = ticksR; interrupts();
if ((l + r) / 2 >= target) break;
drive(s * speed, -s * speed);
}
stopMotors();
}
void setup() {
motorsInit();
pinMode(ENC_L, INPUT_PULLUP); pinMode(ENC_R, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENC_L), isrL, RISING);
attachInterrupt(digitalPinToInterrupt(ENC_R), isrR, RISING);
delay(3000);
for (int i = 0; i < 4; i++) { // 500 mm square
driveMm(500, 150);
turnDeg(90, 130);
}
}
void loop() {}The turn uses the same speed on both sides, so the two wheel counts may differ slightly; averaging them is a simple fix, not a precise one. Motors coast after power is cut, so the real stopping point is a tick or two beyond the target; lowering speed near the end, or subtracting a measured overshoot from the target, improves accuracy. The sketch has been proofread but not compiled on this site.
Test plan
- Encoder check: spin each wheel by hand for one turn and print the count; it should be 20 ticks per turn.
- Straight line: drive 1 m three times and measure the miss.
- Spin test: command a 360° turn, mark the start heading, and adjust TRACK_MM until it comes back to the mark.
- Square test: run the 500 mm square and measure the gap between start and finish.
- Surface test: repeat on carpet and a hard floor.
- Speed test: repeat at a lower speed and compare overshoot.
What usually goes wrong
| Symptom | Likely cause | Fix |
|---|---|---|
| Counts double or erratic | Contact bounce or noisy sensor | Adjust the module threshold and debounce in software |
| Drifts to one side | Wheels differ | Keep the tick correction loop and check motor wiring |
| Turns short | Effective track wider than measured | Raise or lower TRACK_MM from the spin test |
| Overshoots | Coasting after power off | Slow down near the target |
| Square does not close | Accumulated small errors | Calibrate the turn first; expect some gap |
Upgrades
- Speed ramp: ease in and out of each move.
- Gyro heading: add an MPU6050 for turns.
- Path list: store a list of moves and replay it.
- Odometry: combine with the odometry worksheet to track the pose.
Cost and parts notes
The ₹1,640 total is an illustration and does not include shipping. Use the cost calculator to compare sellers.
Common questions
How far is one encoder tick?
For a 65 mm wheel and 20 slots, about 10.2 mm.
Why does the square not close?
Small turn and distance errors add up; encoders reduce this but cannot remove it.
Why interrupts for the encoders?
They catch every pulse even while the loop does other work.
What is dead reckoning?
Working out position from measured movement alone, without an outside reference.
Last reviewed: November 2026. This is a design worksheet with illustrative costs. It has not been bench-tested by this site; library names, pins and limits must be checked against your board and the current library versions, and measurements from your build are welcome.