Built-in project · Arduino Uno R3

Joystick robot car with an L298N on an Arduino Uno

A thumb stick on A1 and A2 drives a 2WD car on an L298N like a tank: throttle plus steer on one wheel, minus on the other, and a press for slow mode. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 30 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $44. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Reading a joystick with two analog inputs
  • Mixing throttle and steer into left and right wheels
  • Using a dead band so the car does not creep
  • Driving two motors with an L298N and PWM
  • Toggling a mode with a button press
  • Difficulty: Intermediate
  • Time: About 30 minutes
  • Board: Arduino Uno R3
  • Parts: 5

How it works

The thumb stick is two potentiometers. The sketch turns the up and down axis (A1) into a throttle and the sideways axis (A2) into a steer, each from -255 to 255. Pushing up drives forward, and pushing sideways turns.

The two numbers are mixed like a tank: left wheel is throttle plus steer, right wheel is throttle minus steer. Full sideways with no throttle spins the car on the spot. Pressing the stick toggles a slow mode at half speed.

Parts list

5 parts, plus the jumper wires. They retail for about $44; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Arduino Uno R3 pin GNDGround
Motor driver module, L298N pin ENBArduino Uno R3 pin 10
Motor driver module, L298N pin IN4Arduino Uno R3 pin 9
Motor driver module, L298N pin IN3Arduino Uno R3 pin 8
Motor driver module, L298N pin IN2Arduino Uno R3 pin 7
Motor driver module, L298N pin IN1Arduino Uno R3 pin 6
Motor driver module, L298N pin ENAArduino Uno R3 pin 5
Thumb stick pin SWArduino Uno R3 pin A0
Thumb stick pin VRYArduino Uno R3 pin A1
Thumb stick pin VRXArduino Uno R3 pin A2
Robot car chassis, 2WD pin L+Motor driver module, L298N pin OUT1
Robot car chassis, 2WD pin L-Motor driver module, L298N pin OUT2
Motor driver module, L298N pin 12V7.4 V
Motor driver module, L298N pin GNDGround
Thumb stick pin VCCMotor driver module, L298N pin 5V
Robot car chassis, 2WD pin R+Motor driver module, L298N pin OUT3
Robot car chassis, 2WD pin R-Motor driver module, L298N pin OUT4
Thumb stick pin GNDGround

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Joystick robot car: a thumb stick steers a 2WD chassis on an L298N, the way
// a tank is driven. Push up to go, sideways to turn, up and sideways to curve.
//
//   Joystick  SW -> A0 (INPUT_PULLUP), VRY -> A1, VRX -> A2
//             VCC -> the L298N's 5V terminal, GND
//   L298N     ENA pin 5, IN1 pin 6, IN2 pin 7    left motor (ENA jumper off)
//             ENB pin 10, IN3 pin 8, IN4 pin 9   right motor (ENB jumper off)
//             12V from the 7.4 V pack, GND to the Uno's GND
//
// The stick is two potentiometers, so each axis reads 0 to 1023 with the
// middle about 512. Pushed up, VRY falls toward 0; pushed right, VRX rises
// toward 1023. The sketch turns them into a throttle and a steer from -255
// to 255, then mixes them into the two wheels:
//
//   left  = throttle + steer
//   right = throttle - steer
//
// Full right with no throttle spins the car on the spot. A real stick never
// rests exactly on 512, so anything within DEAD of the middle counts as
// centered, or the car creeps. Press the stick to toggle a slow mode at half
// speed, for tight spaces.
//
// Press Run, then drag the stick's cap.

const int ENA = 5;
const int IN1 = 6;
const int IN2 = 7;
const int ENB = 10;
const int IN3 = 8;
const int IN4 = 9;

const int STICK_SW = A0;  // an analog pin works as a digital input
const int STICK_Y = A1;
const int STICK_X = A2;

const int DEAD = 40;

bool slow = false;
bool wasPressed = false;
int lastLeft = 999;
int lastRight = 999;

// One side of the car, as a signed speed from -255 to 255. Zero brakes.
void side(int en, int a, int b, int s) {
  digitalWrite(a, s > 0 ? HIGH : LOW);
  digitalWrite(b, s < 0 ? HIGH : LOW);
  if (s < 0) s = -s;
  analogWrite(en, s == 0 ? 255 : s);
}

// One axis from 0..1023 to -255..255, with the dead band in the middle.
int axis(int reading) {
  int offset = reading - 512;
  if (abs(offset) <= DEAD) return 0;
  int s = map(abs(offset), DEAD, 511, 0, 255);
  if (s > 255) s = 255;
  return offset > 0 ? s : -s;
}

void setup() {
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(STICK_SW, INPUT_PULLUP);
  Serial.begin(9600);
  Serial.println("Joystick car: push up to drive, sideways to turn, press for slow");
}

void loop() {
  bool pressed = digitalRead(STICK_SW) == LOW;
  if (pressed && !wasPressed) {
    slow = !slow;
    Serial.println(slow ? "slow mode" : "full speed");
  }
  wasPressed = pressed;

  int throttle = -axis(analogRead(STICK_Y));  // up is forward
  int steer = axis(analogRead(STICK_X));
  int left = constrain(throttle + steer, -255, 255);
  int right = constrain(throttle - steer, -255, 255);
  if (slow) {
    left /= 2;
    right /= 2;
  }
  side(ENA, IN1, IN2, left);
  side(ENB, IN3, IN4, right);

  if (abs(left - lastLeft) > 8 || abs(right - lastRight) > 8) {
    lastLeft = left;
    lastRight = right;
    Serial.print("left ");
    Serial.print(left);
    Serial.print("  right ");
    Serial.println(right);
  }
  delay(20);
}

Code walkthrough

The key functions of the sketch, and what each one does.

axis sketch.ino

Turns one stick reading (0 to 1023) into a speed from -255 to 255. Anything within DEAD of the middle at 512 counts as zero, because a real stick never rests exactly there. Outside the dead band it scales up to full speed at the ends.

// One axis from 0..1023 to -255..255, with the dead band in the middle.
int axis(int reading) {
  int offset = reading - 512;
  if (abs(offset) <= DEAD) return 0;
  int s = map(abs(offset), DEAD, 511, 0, 255);
  if (s > 255) s = 255;
  return offset > 0 ? s : -s;
}

side sketch.ino

Drives one side of the car from a signed speed. A positive speed sets one direction pin high, a negative speed sets the other, and the size goes to the enable pin as PWM. A speed of zero sets both direction pins low and enables the pin fully, which brakes.

// One side of the car, as a signed speed from -255 to 255. Zero brakes.
void side(int en, int a, int b, int s) {
  digitalWrite(a, s > 0 ? HIGH : LOW);
  digitalWrite(b, s < 0 ? HIGH : LOW);
  if (s < 0) s = -s;
  analogWrite(en, s == 0 ? 255 : s);
}

loop sketch.ino

Watches the stick button to toggle slow mode, then reads both axes. Up is forward, so the Y reading is negated for the throttle. It mixes the wheels, halves them in slow mode, drives both sides, and prints speeds when they change by more than 8.

void loop() {
  bool pressed = digitalRead(STICK_SW) == LOW;
  if (pressed && !wasPressed) {
    slow = !slow;
    Serial.println(slow ? "slow mode" : "full speed");
  }
  wasPressed = pressed;

  int throttle = -axis(analogRead(STICK_Y));  // up is forward
  int steer = axis(analogRead(STICK_X));
  int left = constrain(throttle + steer, -255, 255);
  int right = constrain(throttle - steer, -255, 255);
  if (slow) {
    left /= 2;
    right /= 2;
  }
  side(ENA, IN1, IN2, left);
  side(ENB, IN3, IN4, right);

  if (abs(left - lastLeft) > 8 || abs(right - lastRight) > 8) {
    lastLeft = left;
    lastRight = right;
    Serial.print("left ");
    Serial.print(left);
    Serial.print("  right ");
    Serial.println(right);
  }
  delay(20);
  // ... 1 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The car drives backward when you push up This project
    Check the stick wiring: VRY goes to A1. If a motor still spins the wrong way, swap that motor's two wires on the L298N.
  • The car creeps with the stick at rest This project
    Raise DEAD so a bit more of the middle counts as centered.
  • Motors do not respond to speed This project
    Take the ENA and ENB jumpers off the L298N so pins 5 and 10 control the speed.
  • The motors do not run or run weakly. Motor driver module
    Check the power jumpers and supply. Motor voltage goes on the +12V terminal, and ground must join the Arduino's ground. The driver drops about 2 V, so use a supply above the motor voltage.
  • Speed control with analogWrite does nothing. Motor driver module
    Remove the ENA and ENB jumpers and connect those pins to PWM pins. With the jumpers on, the channel is always at full speed.
  • The car drives in circles or goes backwards. Robot car chassis
    Motor polarity decides the direction. Swap the two wires on a motor that spins the wrong way. For a straight run, the motors are never identical, so trim the speed on one side.
  • The motors do not run, or the board resets when they start. Robot car chassis
    Motors draw far more current than a pin can give. Use a motor driver such as an L298N or L293D. Power the motors from a separate battery pack and join its ground with the Arduino ground.
  • The axis reads about half scale at rest instead of exactly zero, and the values drift a little, so the cursor or motor creeps. Thumb stick
    Center is about half of the ADC range (about 512 on a 10 bit Arduino). Read it at startup and subtract that, then add a dead zone of a few counts.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.