Built-in project · Arduino Uno R3

Infrared remote rover on an Arduino Uno

A two-wheel robot driven from a 21-button infrared remote. Hold 2, 8, 4 or 6 to drive, + and - for speed, and it stops by itself the moment the remote goes quiet. The whole build, an Arduino Uno and 4 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 20 minutes to build and run. Runs in your browser. Free, and no account needed.

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

The circuit runs here once the page has loaded.

What you'll learn

  • Controllers
  • Motors
  • Sensors
  • Using the MX1508 dual motor driver
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 6

How it works

A two-wheel robot driven from a 21-button infrared remote. Hold 2, 8, 4 or 6 to drive, + and - for speed, and it stops by itself the moment the remote goes quiet.

Arduino Uno R3 runs the sketch below. The circuit around it: MX1508 dual motor driver, Robot car chassis, 2WD, Infrared receiver, VS1838B, Infrared remote, 21 buttons. pinMode sets A1_PIN as an output. analogWrite sends a PWM signal out on p2. Serial.begin opens the serial port at 115200 baud. millis reads the time since power-up, so the sketch can time things without stopping.

Parts list

6 parts, plus the jumper wires. They retail for about $42; 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
MX1508 dual motor driver pin IN4Arduino Uno R3 pin 10
MX1508 dual motor driver pin IN3Arduino Uno R3 pin 9
MX1508 dual motor driver pin IN2Arduino Uno R3 pin 6
MX1508 dual motor driver pin IN1Arduino Uno R3 pin 5
Infrared receiver, VS1838B pin OUTArduino Uno R3 pin 2
Infrared receiver, VS1838B pin VCCArduino Uno R3 pin 5V
MX1508 dual motor driver pin +6 V
MX1508 dual motor driver pin -Ground
Robot car chassis, 2WD pin L+MX1508 dual motor driver pin MA1
Robot car chassis, 2WD pin L-MX1508 dual motor driver pin MA2
Robot car chassis, 2WD pin R+MX1508 dual motor driver pin MB1
Robot car chassis, 2WD pin R-MX1508 dual motor driver pin MB2
Infrared receiver, VS1838B pin GNDGround
Infrared remote, 21 buttons pin IRInfrared receiver, VS1838B pin IR

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
// Infrared remote rover: drive a robot with the remote that came with the kit.
//
//   VS1838B receiver OUT on pin 2 (VCC to 5V, GND to ground)
//   MX1508: IN1 pin 5, IN2 pin 6 (left motor)   IN3 pin 9, IN4 pin 10 (right motor)
//
// The keys:
//
//   2  forward     8  back     4  turn left     6  turn right     5  stop
//   +  faster      -  slower   (three speeds: slow, medium, fast)
//
// # Hold to drive
//
// A remote sends one full frame when a button goes down and then a short
// "repeat" every 110 ms for as long as it stays down. The rover drives while
// frames keep arriving and stops 300 ms after the last one. That is a
// dead-man switch, or failsafe: if the remote's batteries die, or the robot
// drives out of range, or somebody walks in front of the receiver, the robot
// stops by itself instead of driving on until it hits a wall.
//
// # Frames and repeats
//
// IrReceiver::receive() waits up to 60 ms for a burst. A full frame carries a
// command byte, which the sketch acts on once. A repeat carries nothing but
// "still held", so it only refreshes the failsafe timer. The sketch has to tell
// them apart: treating every repeat as a new press is how volume buttons end
// up running away.
//
// The command bytes are the ones on the common 21-button handset (NEC
// protocol, address 0x00).

#include "mokxi_ir.h"

const int IR_PIN = 2;
const int A1_PIN = 5;
const int A2_PIN = 6;
const int B1_PIN = 9;
const int B2_PIN = 10;

const unsigned long FAILSAFE_MS = 300;

const uint8_t KEY_2 = 0x18;
const uint8_t KEY_8 = 0x52;
const uint8_t KEY_4 = 0x08;
const uint8_t KEY_6 = 0x5A;
const uint8_t KEY_5 = 0x1C;
const uint8_t KEY_PLUS = 0x15;
const uint8_t KEY_MINUS = 0x07;

const int SPEEDS[3] = {110, 170, 230};
const char *SPEED_NAMES[3] = {"slow", "medium", "fast"};

IrReceiver ir(IR_PIN);

int speedLevel = 1;
bool moving = false;
// What the left and right wheels are doing, as -1, 0 or 1.
int leftDir = 0;
int rightDir = 0;
unsigned long lastFrame = 0;

void motor(int p1, int p2, int speed) {
  if (speed >= 0) {
    analogWrite(p2, 0);
    analogWrite(p1, speed);
  } else {
    analogWrite(p1, 0);
    analogWrite(p2, -speed);
  }
}

void apply() {
  int speed = SPEEDS[speedLevel];
  motor(A1_PIN, A2_PIN, leftDir * speed);
  motor(B1_PIN, B2_PIN, rightDir * speed);
  moving = leftDir != 0 || rightDir != 0;
}

void go(const char *what, int left, int right) {
  leftDir = left;
  rightDir = right;
  Serial.print(what);
  Serial.print(" (");
  Serial.print(SPEED_NAMES[speedLevel]);
  Serial.println(")");
  apply();
}

void setSpeed(int level) {
  if (level < 0) level = 0;
  if (level > 2) level = 2;
  speedLevel = level;
  Serial.print("speed: ");
  Serial.println(SPEED_NAMES[speedLevel]);
  apply();
}

void setup() {
  pinMode(A1_PIN, OUTPUT);
  pinMode(A2_PIN, OUTPUT);
  pinMode(B1_PIN, OUTPUT);
  pinMode(B2_PIN, OUTPUT);
  Serial.begin(115200);
  ir.begin();
  Serial.println("Mokxi Arduino Uno: infrared remote rover");
  Serial.println("ready: hold 2 8 4 6 to drive, + and - for speed");
}

void loop() {
  IrFrame frame = ir.receive(60);
  if (frame.valid) {
    lastFrame = millis();
    if (!frame.repeated) {
      switch (frame.command) {
        case KEY_2: go("forward", 1, 1); break;
        case KEY_8: go("back", -1, -1); break;
        case KEY_4: go("turning left", -1, 1); break;
        case KEY_6: go("turning right", 1, -1); break;
        case KEY_5: go("stop", 0, 0); break;
        case KEY_PLUS: setSpeed(speedLevel + 1); break;
        case KEY_MINUS: setSpeed(speedLevel - 1); break;
        default: break;
      }
    }
  }

  // Quiet for too long: the button was let go, or the signal is lost.
  if (moving && millis() - lastFrame > FAILSAFE_MS) {
    leftDir = 0;
    rightDir = 0;
    apply();
    Serial.println("remote quiet: stopped");
  }
}

Code walkthrough

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

setup sketch.ino

Runs once when the board starts. It gets the pins and libraries ready.

void setup() {
  pinMode(A1_PIN, OUTPUT);
  pinMode(A2_PIN, OUTPUT);
  pinMode(B1_PIN, OUTPUT);
  pinMode(B2_PIN, OUTPUT);
  Serial.begin(115200);
  ir.begin();
  Serial.println("Mokxi Arduino Uno: infrared remote rover");
  Serial.println("ready: hold 2 8 4 6 to drive, + and - for speed");
}

loop sketch.ino

Runs over and over for as long as the board has power. This is where the project does its work.

void loop() {
  IrFrame frame = ir.receive(60);
  if (frame.valid) {
    lastFrame = millis();
    if (!frame.repeated) {
      switch (frame.command) {
        case KEY_2: go("forward", 1, 1); break;
        case KEY_8: go("back", -1, -1); break;
        case KEY_4: go("turning left", -1, 1); break;
        case KEY_6: go("turning right", 1, -1); break;
        case KEY_5: go("stop", 0, 0); break;
        case KEY_PLUS: setSpeed(speedLevel + 1); break;
        case KEY_MINUS: setSpeed(speedLevel - 1); break;
        default: break;
      }
    }
  }

  // Quiet for too long: the button was let go, or the signal is lost.
  if (moving && millis() - lastFrame > FAILSAFE_MS) {
    leftDir = 0;
    rightDir = 0;
    apply();
    Serial.println("remote quiet: stopped");
  }
}

Troubleshooting

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

  • The motor does not turn or gets weak. MX1508 dual motor driver
    Supply 2 V to 9.6 V on VCC and share ground with the controller. Use PWM on one input and low on the other per direction. Mind the current per channel.
  • The motor does not stop as expected. MX1508 dual motor driver
    Both inputs low coasts, both high brakes on some drivers. Check what your board does. Add a capacitor across the supply for motor noise.
  • 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.
  • No signal is seen, or the module gets hot, because the three legs are in the wrong order. Infrared receiver
    On the VS1838B, with the dome facing you, the legs are usually OUT, GND, VCC from left to right. Check your module's marking before powering it. Supply is 2.7 to 5.5 V.
  • A plain LED blinking at the receiver is never detected. Infrared receiver
    The receiver only responds to light modulated at about 38 kHz. Send with a library such as IRremote, which makes the carrier. OUT idles high and goes low during a burst, so decode that inverted signal.
  • Button codes come out wrong or do not match another sketch. Infrared remote
    Remotes differ. Print the raw codes for your buttons first, then compare against those values. A repeat while you hold a button is a special repeat code, not a new press.
  • Nothing is received at all. Infrared remote
    Check the receiver pin order of OUT, GND and VCC, which varies by module. Use the pin your IR library expects. Keep the receiver away from bright light and aim the remote at it.

Change it and keep it

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