Simple WiFi RC Car

WiFi RC Car using ESP01 Rx/Tx as GPIOs

Simple WiFi RC Car

A cheap RC car is controlled using 4 pins: Forward, Backward, Left, Right. We can use the ESP-01 to control the car via WiFi. The problem is the ESP-01 has only 2 GPIO pins exposed. But wait, we can use the Rx and Tx as GPIOs.

Materials needed:
- Cheap RC car waiting to get WiFi connectivity
- ESP-01 WiFi module
- 8-pin socket for the ESP-01 module
- 2x 470R resistors
- 2x 47uF capacitors
- 3.3V 250mA LDO voltage regulator MCP1702T3302ECB
- 2-pin jumper + jumper header
- 1x 1N4148 diode
- S7V7F5 voltage regulator module
- wires
- protoboard
- soldering iron + solder
- Android phone with RoboRemo app installed

Introduction

If you have a car with steering servo and electronic speed controller (ESC), it is easier because you need only 2 GPIOs and you can use the GPIO0 and GPIO2 of the ESP-01. If your car uses 2 H Bridges, however, you need 4 GPIOs to control it.
In this project I use the Rx and Tx pins of the ESP-01 as GPIOs, so together with GPIO0 and GPIO2, I have 4 GPIOs. But there is a little problem because when you power on the ESP8266, it first runs the boot code (that checks if you want to upgrade the firmware) and it uses the UART, so for about 500ms the Tx pin will be HIGH.

esp8266-pinout.png



The GPIO0 and GPIO2 also must be pulled up (the pull-up resistor value must be small enough, because the 1k resistors from H Bridges are pulling down) with resistors for normal operation (not entering firmware upgrade mode), so you have 3 GPIOs that are HIGH for the first 500 ms.
The problem is that the H Bridge of the car does not like to have both inputs HIGH. It will result in short circuit and you may see and smell the smoke from transistors. To solve this, I added a 1N4148 diode that disables one input of the H bridge when the other one is active.

h-bridge-input-protection.jpg


The final schematic looks like this:

simple-wifi-rc-car-schematic.png

Steps:

1.
Upload the code to your ESP:

Lua version (NodeMCU):

wifi.setmode(wifi.SOFTAP)

cfg={}
cfg.ssid="mywifi"
cfg.pwd="qwerty123"

cfg.ip="192.168.0.1"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.0.1"

port = 9876

wifi.ap.setip(cfg)
wifi.ap.config(cfg)

function stringStarts(a,b)
    return string.sub(a,1,string.len(b))==b
end

function stringEnds(a,b)
   return b=='' or string.sub(a,-string.len(b))==b
end

f=3 --GPIO0
b=10 --GPIO1
l=4 --GPIO2
r=9 --GPIO3

function low(a)
  gpio.write(a,gpio.LOW)
end

function high(a)
  gpio.write(a,gpio.HIGH)
end

gpio.mode(f,gpio.OUTPUT)
low(f)
gpio.mode(b,gpio.OUTPUT)
low(b)
gpio.mode(l,gpio.OUTPUT)
low(l)
gpio.mode(r,gpio.OUTPUT)
low(r)



cmd = ""

function exeCmd(st)

    tmr.stop(1) -- clr contact lost tmr
   
    tmr.alarm(1,500,0,function() -- if cnt lost for 500ms
        low(f) low(b)
    end)

    if st=="f" then low(b) high(f) end
    if st=="b" then low(f) high(b) end
    if st=="s" then low(f) low(b) end

    if st=="l" then low(r) high(l) end
    if st=="r" then low(l) high(r) end
    if st=="c" then low(l) low(r) end

end


function receiveData(conn, data)
    cmd = cmd .. data

    local a, b = string.find(cmd, "\n", 1, true)   
    while a do
        exeCmd( string.sub(cmd, 1, a-1) )
        cmd = string.sub(cmd, a+1, string.len(cmd))
        a, b = string.find(cmd, "\n", 1, true)
    end 
end


srv=net.createServer(net.TCP, 28800) 
srv:listen(port,function(conn)
    
    conn:on("receive",receiveData)  
    
    tmr.stop(2)
    tmr.alarm(2,500,1,function() -- heartbeat
        conn:send("alive 1\n")
    end)

end)


Arduino IDE version (AP mode):

// RC car (2 H Bridges) controller
// using an ESP8266 (As WiFi ACCESS POINT)
// and an Android phone with RoboRemo app

// Disclaimer: Don't use RoboRemo for life support systems
// or any other situations where system failure may affect
// user or environmental safety.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <Servo.h>

// config:

const char *ssid = "mywifi";  // You will connect your phone to this Access Point
const char *pw = "qwerty123"; // and this is the password
IPAddress ip(192, 168, 0, 1); // From RoboRemo app, connect to this IP
IPAddress netmask(255, 255, 255, 0);
const int port = 9876; // and this port


// ESP pins: GPIO 0, 1(Tx), 2, 3(Rx)

// ESP adapter 470R pull-up on GPIO 0 and 2
// GPIO 1 (Tx) also gets HIGH on startup => H bridge short circuit

int forward = 0;
int backward = 1;
int left = 2;
int right = 3;

// (Rx and Tx are used as GPIO)


WiFiServer server(port);
WiFiClient client;


char cmd[100]; // stores the command chars received from RoboRemo
int cmdIndex;
unsigned long lastCmdTime = 60000;
unsigned long aliveSentTime = 0;


boolean cmdStartsWith(const char *st) { // checks if cmd starts with st
  for(int i=0; ; i++) {
    if(st[i]==0) return true;
    if(cmd[i]==0) return false;
    if(cmd[i]!=st[i]) return false;;
  }
  return false;
}



// commands:
// f = forward
// b = backward
// s = stop

// l = left
// r = right
// c = center



void exeCmd() { // executes the command from cmd

  lastCmdTime = millis();
  
  if( cmdStartsWith("f") ) {
    digitalWrite(backward, 0);
    digitalWrite(forward, 1);
  }

  if( cmdStartsWith("b") ) {
    digitalWrite(forward, 0);
    digitalWrite(backward, 1);
  }

  if( cmdStartsWith("s") ) {
    digitalWrite(forward, 0);
    digitalWrite(backward, 0);
  }


  if( cmdStartsWith("l") ) {
    digitalWrite(right, 0);
    digitalWrite(left, 1);
  }

  if( cmdStartsWith("r") ) {
    digitalWrite(left, 0);
    digitalWrite(right, 1);
  }

  if( cmdStartsWith("c") ) {
    digitalWrite(right, 0);
    digitalWrite(left, 0);
  }

  
  
}



void setup() {


  pinMode(forward, OUTPUT);
  digitalWrite(forward, 0);

  pinMode(backward, OUTPUT);
  digitalWrite(backward, 0);

  pinMode(left, OUTPUT);
  digitalWrite(left, 0);

  pinMode(right, OUTPUT);
  digitalWrite(right, 0);

  delay(1000);

  
  cmdIndex = 0;

  WiFi.softAPConfig(ip, ip, netmask); // configure ip address for softAP 
  WiFi.softAP(ssid, pw); // configure ssid and password for softAP

  server.begin(); // start TCP server
}


void loop() {

  // if contact lost for more than half second
  if(millis() - lastCmdTime > 500) {
    // stop car:
    digitalWrite(forward, 0);
    digitalWrite(backward, 0);
  }
  
  if(!client.connected()) {
    client = server.available();
    return;
  }

  // here we have a connected client

  if(client.available()) {
    char c = (char)client.read(); // read char from client (RoboRemo app)

    if(c=='\n') { // if it is command ending
      cmd[cmdIndex] = 0;
      exeCmd();  // execute the command
      cmdIndex = 0; // reset the cmdIndex
    } else {      
      cmd[cmdIndex] = c; // add to the cmd buffer
      if(cmdIndex<99) cmdIndex++;
    }
  } 

  if(millis() - aliveSentTime > 500) { // every 500ms
    client.write("alive 1\n");
    // send the alibe signal, so the "connected" LED in RoboRemo will stay ON
    // (the LED must have the id set to "alive")
    
    aliveSentTime = millis();
    // if the connection is lost, the RoboRemo will not receive the alive signal anymore,
    // and the LED will turn off (because it has the "on timeout" set to 700 (ms) )
  }

}


Arduino IDE version (STA mode not tested):
Upload the code to your Arduino:

// RC car (2 H Bridges) controller
// using an ESP8266 (As WiFi Station - CONNECTS TO WIFI ROUTER)
// and an Android phone with RoboRemo app

// Disclaimer: Don't use RoboRemo for life support systems
// or any other situations where system failure may affect
// user or environmental safety.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <Servo.h>

// config:

const char *ssid = "yourssid";  // Your ROUTER SSID
const char *pw = "yourpw"; // and WIFI PASSWORD
IPAddress ip(192, 168, 1, 100); // Change the IP if needed. From RoboRemo app, connect to this IP
IPAddress netmask(255, 255, 255, 0);
const int port = 9876; // and this port


// ESP pins: GPIO 0, 1(Tx), 2, 3(Rx)

// ESP adapter 470R pull-up on GPIO 0 and 2
// GPIO 1 (Tx) also gets HIGH on startup => H bridge short circuit

int forward = 0;
int backward = 1;
int left = 2;
int right = 3;

// (Rx and Tx are used as GPIO)


WiFiServer server(port);
WiFiClient client;


char cmd[100]; // stores the command chars received from RoboRemo
int cmdIndex;
unsigned long lastCmdTime = 60000;
unsigned long aliveSentTime = 0;


boolean cmdStartsWith(const char *st) { // checks if cmd starts with st
  for(int i=0; ; i++) {
    if(st[i]==0) return true;
    if(cmd[i]==0) return false;
    if(cmd[i]!=st[i]) return false;;
  }
  return false;
}



// commands:
// f = forward
// b = backward
// s = stop

// l = left
// r = right
// c = center



void exeCmd() { // executes the command from cmd

  lastCmdTime = millis();
  
  if( cmdStartsWith("f") ) {
    digitalWrite(backward, 0);
    digitalWrite(forward, 1);
  }

  if( cmdStartsWith("b") ) {
    digitalWrite(forward, 0);
    digitalWrite(backward, 1);
  }

  if( cmdStartsWith("s") ) {
    digitalWrite(forward, 0);
    digitalWrite(backward, 0);
  }


  if( cmdStartsWith("l") ) {
    digitalWrite(right, 0);
    digitalWrite(left, 1);
  }

  if( cmdStartsWith("r") ) {
    digitalWrite(left, 0);
    digitalWrite(right, 1);
  }

  if( cmdStartsWith("c") ) {
    digitalWrite(right, 0);
    digitalWrite(left, 0);
  }
  
}




void setup() {


  pinMode(forward, OUTPUT);
  digitalWrite(forward, 0);

  pinMode(backward, OUTPUT);
  digitalWrite(backward, 0);

  pinMode(left, OUTPUT);
  digitalWrite(left, 0);

  pinMode(right, OUTPUT);
  digitalWrite(right, 0);

  delay(1000);

  
  cmdIndex = 0;


  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pw);

  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }

  server.begin(); // start TCP server
}


void loop() {

  // if contact lost for more than half second
  if(millis() - lastCmdTime > 500) {
    // stop car:
    digitalWrite(forward, 0);
    digitalWrite(backward, 0);
  }
  
  if(!client.connected()) {
    client = server.available();
    return;
  }

  // here we have a connected client

  if(client.available()) {
    char c = (char)client.read(); // read char from client (RoboRemo app)

    if(c=='\n') { // if it is command ending
      cmd[cmdIndex] = 0;
      exeCmd();  // execute the command
      cmdIndex = 0; // reset the cmdIndex
    } else {      
      cmd[cmdIndex] = c; // add to the cmd buffer
      if(cmdIndex<99) cmdIndex++;
    }
  } 

  if(millis() - aliveSentTime > 500) { // every 500ms
    client.write("alive 1\n");
    // send the alibe signal, so the "connected" LED in RoboRemo will stay ON
    // (the LED must have the id set to "alive")
    
    aliveSentTime = millis();
    // if the connection is lost, the RoboRemo will not receive the alive signal anymore,
    // and the LED will turn off (because it has the "on timeout" set to 700 (ms) )
  }

}

2.
Modify the car circuit according to the schematic.

3.
Turn ON the car.

4.
From Android WiFi settings connect to the ESP(ssid: mywifi, password: qwerty123).

5.
Download the RoboRemo interface file:
this one:

cheap wifi rc car.interface

or this one:

rc car.interface

Copy the interface file to your Android device, then import in RoboRemo using menu -> interface -> import.

6.
Open RoboRemo, connect to TCP 192.168.0.1:9876 and enjoy :)