Blynk Joystick Controlled Tracked Rover with Wemos D1 Mini Pro

This is going to be a “work in progress” topic… mainly because I do things slowly, as clarity and energy allow, so if I wait until it is finished, well… we may be into a new millennium :wink:

I am using a Wemos D1 Mini Pro. I am using the Pro becasue it has an external antenna option… for better range, should I get adventurous.

Right now I just have the Wemos mounted on a battery shield, sitting on the rover’s battery, and wired ONLY to the motor controller without PWM control.

As this progresses (i.e. as I learn how to use some of the more nitpicky pins on the Wemos), I will wire in the servo controlled Low/High gear transmission and the transistor controlled LED headlamp… as well as PWM variable control of the motors.

This will task the Mini’s limited pins as it is, so not sure if I will have any pins left to connect the UNO as a secondary “slave” device or not (or even if it is necessary).

There will be the RX/TX pins, but I haven’t yet figured out if they are readily available, since I am using OTA for programming.

So much to learn :slight_smile:

I will update this topic with sketches, App QR, detailed pictures and build info as I go along. If you have any questions or wish to see something in more detail, let me know.

8 Likes

The basic code so far…

#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

#include <ESP8266mDNS.h>  // For OTA
#include <WiFiUdp.h>  // For OTA
#include <ArduinoOTA.h>  // For OTA

ADC_MODE(ADC_VCC);  // For monitoring of internal voltage levels

// Setup L298N Motor Controller Input Pins
int IN1 = D6;  // Motor 2
int IN2 = D5;  // Motor 2
int IN3 = D2;  // Motor 1
int IN4 = D1;  // Motor 1

int Flag = 1;
char incomingByte;

char auth[] = "xxxxxxxxxx";
char ssid[] = "xxxxxxxxxx";
char pass[] = "xxxxxxxxxx";

BlynkTimer timer;



void setup()
{
  // setup L298N Motor Controller Input Pins
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);

  Serial.begin(115200);

  Blynk.begin(auth, ssid, pass, "10.10.3.13", 8442);  // Set for Local Server

  timer.setInterval(1000L, VCCInput);

  ArduinoOTA.setHostname("Wemos D1 Mini Pro");  // For OTA
  ArduinoOTA.begin();  // For OTA
}



BLYNK_WRITE(V6)  // Joystick Widget Input
{
  Flag = 1;
  int y = param[1].asInt();
  int x = param[0].asInt();
  if (y > 750 && Flag == 1) {  // Forward
    Flag = 0;
    incomingByte = '1';
    ControlOutput();
  }
  if (y < 250 && Flag == 1) {  // Backward
    Flag = 0;
    incomingByte = '2';
    ControlOutput();
  }
  if (x < 250 && Flag == 1) {  // Left
    Flag = 0;
    incomingByte = '3';
    ControlOutput();
  }
  if (x > 750 && Flag == 1) {  // Right
    Flag = 0;
    incomingByte = '4';
    ControlOutput();
  }
  if (y == 512 && y == 512) {  // Full Stop
    incomingByte = '0';
    ControlOutput();
  }
}



// Button Widgets Input
BLYNK_WRITE(V2) {  // Forward
  if (param.asInt() == 1) {
    incomingByte = '1';
  } else {
    incomingByte = '0';
  }
  ControlOutput();
}
BLYNK_WRITE(V3) {  // Backward
  if (param.asInt() == 1) {
    incomingByte = '2';
  } else {
    incomingByte = '0';
  }
  ControlOutput();
}
BLYNK_WRITE(V4) {  // Left
  if (param.asInt() == 1) {
    incomingByte = '3';
  } else {
    incomingByte = '0';
  }
  ControlOutput();
}
BLYNK_WRITE(V5) {  // Right
  if (param.asInt() == 1) {
    incomingByte = '4';
  } else {
    incomingByte = '0';
  }
  ControlOutput();
}



//  Take inputs and direct motor control functions
void ControlOutput() {
  switch (incomingByte) {
    case '1': {
        motor1Forward();
        motor2Forward();
        Blynk.virtualWrite(V1, "Forward");
      }
      break;
    case '2': {
        motor1Backwards();
        motor2Backwards();
        Blynk.virtualWrite(V1, "Backward");
      }
      break;
    case '3': {
        motor1Backwards();
        motor2Forward();
        Blynk.virtualWrite(V1, "Spin Left");
      }
      break;
    case '4': {
        motor1Forward();
        motor2Backwards();
        Blynk.virtualWrite(V1, "Spin Right");
      }
      break;
    default: {
        Blynk.virtualWrite(V1, "Full Stop");
        motor1Stop();
        motor2Stop();
        Flag = 1;
      }
      break;
  }
}



// Functions to control L298N Motor Controller Input Pins
void motor1Forward() {
  digitalWrite(IN4, HIGH);
  digitalWrite(IN3, LOW);
}
void motor1Backwards() {
  digitalWrite(IN4, LOW);
  digitalWrite(IN3, HIGH);
}
void motor1Stop() {
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
}
void motor2Forward() {
  digitalWrite(IN2, HIGH);
  digitalWrite(IN1, LOW);
}
void motor2Backwards() {
  digitalWrite(IN2, LOW);
  digitalWrite(IN1, HIGH);
}
void motor2Stop() {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
}



void VCCInput() {  // Simple monitoring of internal voltage levels
  Blynk.virtualWrite(V0, ESP.getVcc() * 0.001);
}



void loop()
{
  Blynk.run();
  timer.run();
  ArduinoOTA.handle();  // For OTA
}

Hello sir nice project
I try to play the video nothing appear ?

I fixed the link… try again. And thank you!

Very nice!!!
Tesla will copy your vehicle for sure…!

( I really like it )

What chassis do you use?

It was the base from an old broken remote control “robot” toy called the RAD 1.0


It has gone through a few stages of development… before I scaled it back to its current slim state with easier access to the control circuitry.


3 Likes

So, a few updates… I now have it working entirely on Blynk and the Wemos D1 Mini Pro; Gear change, LED, Motion and even Video.

Right now the voltage display is the ESP’s internal voltage… next is to switch that over to the 12v battery level with a voltage divider.

Current code:

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

// For OTA:
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

ADC_MODE(ADC_VCC);  // For monitoring of internal voltage levels

// Setup L298N Motor Controller Input Pins:
int IN1 = D6;  // Motor 2
int IN2 = D5;  // Motor 2
int IN3 = D2;  // Motor 1
int IN4 = D1;  // Motor 1
int servoPin = 0;
int Flag = 1;
char incomingByte;

//Setup WiFi:
char auth[] = "xxxxx";
char ssid[] = "xxxxx";
char pass[] = "xxxxx";
char server[] = "blynk-cloud.com";
int port = 8080;

BlynkTimer timer;

Servo myservo;



void setup()
{
  // setup L298N Motor Controller Input Pins:
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(2, OUTPUT);

  // Set to LOW gear:
  myservo.attach(servoPin);
  myservo.write(0);
  timer.setInterval(1000L, StopServo);

  Blynk.begin(auth, ssid, pass, server, port);  // Set for Local Server

  timer.setInterval(1000L, VCCInput);  // Battery Check timer

  // For OTA:
  ArduinoOTA.setHostname("Gunnerator - Wemos D1 Mini Pro");
  ArduinoOTA.begin();
}



// Servo controlled gearing:
BLYNK_WRITE(V8) {
  if (param.asInt() == 1) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
    myservo.attach(servoPin);
    myservo.write(175); // High gear
    timer.setTimeout(1500L, StopServo);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
    myservo.attach(servoPin);
    myservo.write(0);  // Low gear
    timer.setTimeout(1500L, StopServo);
  }
}



// Headlamp Control:
BLYNK_WRITE(V9) {
  if (param.asInt() == 1) {
    digitalWrite(2, HIGH); // Headlamp ON
  } else {
    digitalWrite(2, LOW); // Headlamp OFF
  }
}



void StopServo() {
  myservo.detach();
}



// Joystick Widget Input:
BLYNK_WRITE(V6)
{
  Flag = 1;
  int y = param[1].asInt();
  int x = param[0].asInt();
  if (y > 750 && Flag == 1) {  // Forward
    Flag = 0;
    incomingByte = '1';
    ControlOutput();
  }
  if (y < 250 && Flag == 1) {  // Backward
    Flag = 0;
    incomingByte = '2';
    ControlOutput();
  }
  if (x < 250 && Flag == 1) {  // Left
    Flag = 0;
    incomingByte = '3';
    ControlOutput();
  }
  if (x > 750 && Flag == 1) {  // Right
    Flag = 0;
    incomingByte = '4';
    ControlOutput();
  }
  if (y == 512 && y == 512) {  // Full Stop
    incomingByte = '0';
    ControlOutput();
  }
}



// Button Widgets Input:
BLYNK_WRITE(V2) {  // Forward
  if (param.asInt() == 1) {
    incomingByte = '1';
  } else {
    incomingByte = '0';
  }
  Blynk.virtualWrite(V7, "Forward");
  ControlOutput();
}

BLYNK_WRITE(V3) {  // Backward
  if (param.asInt() == 1) {
    incomingByte = '2';
  } else {
    incomingByte = '0';
  }
  Blynk.virtualWrite(V7, "Backward");
  ControlOutput();
}

BLYNK_WRITE(V4) {  // Left
  if (param.asInt() == 1) {
    incomingByte = '3';
  } else {
    incomingByte = '0';
  }
  Blynk.virtualWrite(V7, "Spin Left");
  ControlOutput();
}

BLYNK_WRITE(V5) {  // Right
  if (param.asInt() == 1) {
    incomingByte = '4';
  } else {
    incomingByte = '0';
  }

  Blynk.virtualWrite(V7, "Spin Right");
  ControlOutput();
}



//  Take inputs and direct motor control functions:
void ControlOutput() {
  switch (incomingByte) {
    case '1': {
        motor1Forward();
        motor2Forward();
        Blynk.virtualWrite(V1, "Forward");
      }
      break;
    case '2': {
        motor1Backwards();
        motor2Backwards();
        Blynk.virtualWrite(V1, "Backward");

      }
      break;
    case '3': {
        motor1Backwards();
        motor2Forward();
        Blynk.virtualWrite(V1, "Spin Left");
      }
      break;
    case '4': {
        motor1Forward();
        motor2Backwards();
        Blynk.virtualWrite(V1, "Spin Right");
      }
      break;
    default: {
        Blynk.virtualWrite(V1, "Full Stop");
        Blynk.virtualWrite(V7, "Full Stop");
        motor1Stop();
        motor2Stop();
        Flag = 1;
      }
      break;
  }
}



// Functions to control L298N Motor Controller Input Pins:
void motor1Forward() {
  digitalWrite(IN4, HIGH);
  digitalWrite(IN3, LOW);
}
void motor1Backwards() {
  digitalWrite(IN4, LOW);
  digitalWrite(IN3, HIGH);
}
void motor1Stop() {
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
}
void motor2Forward() {
  digitalWrite(IN2, HIGH);
  digitalWrite(IN1, LOW);
}
void motor2Backwards() {
  digitalWrite(IN2, LOW);
  digitalWrite(IN1, HIGH);
}
void motor2Stop() {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
}



// Simple monitoring of internal voltage levels:
void VCCInput() {
  Blynk.virtualWrite(V0, ESP.getVcc() * 0.001);
}



void loop()
{
  Blynk.run();
  timer.run();
  ArduinoOTA.handle();  // For OTA
}
3 Likes

Since the Wemos is plugged into a battery board, at the time for easy velcro mounting in the rover, I added a little battery just to keep it running even if the 12v battery somehow runs low enough to cause brownouts.

1 Like

That is seriously the coolest Blynk robot yet!!

Camera and all! So awesome!

Next I hope we will see a roaming video and a range test? :smiley:

Thank you!

Already range and reliability far exceeds the previous bluetooth, but that’s of course a normal benefit of WiFi vs BT.

That was a very old Android phone… and extremely laggy… very much a >Mars Rover< :wink: type driving experience, wherein I guess my target based on what I see, drive a few seconds, then wait to see what it hit :stuck_out_tongue: (I will eventually add sensors for semi autonomous action).

1 Like

ADC pin must be floating for ESP.getVcc() to read correctly.

You can’t use this function with Wemos D1 or NodeMCU because ADC pin is connected to ground with 100 k Ohm resistor and you will always get a wrong reading. (< 3v)

voltage divider

The NodeMCU and the Wemos D1 mini have this network already in place

R2= 100 k ohms
R1= 220 k ohms

the Vout is connected to the ADC pin of the ESP-12 module and Vin is connected to the A0 pin on the NodeMCU or Wemos board.

instead I use this function for 3.3 V

void VCCInput() {  
     float sensorData = analogRead(A0); //reading the sensor on A0
    Blynk.virtualWrite(vPIN_VCCInput, (sensorData/1024) * 3.3);}

& this function for 5 V after adding a 180 k ohms to A0 pin

R2= 100 k ohms
R1=180 k + 220 k =400 k ohms

void VCCInput() {  
     float sensorData = analogRead(A0); //reading the sensor on A0
    Blynk.virtualWrite(vPIN_VCCInput, (sensorData/1024) * 5);}

Old topic… and that was only for (attempted) simple monitoring of the Wemos power as it was running on battery. But even then was limited as the step up converter (not to mention the internal 5v-3.3v regulator circuit) would keep the voltage indication artificially high until just before shutdown, floating ADC or not.

End result, I never really used it anyhow… :wink:

… and I have long since switched to a voltage divider (basically added a 1.1M resistor in-line between the ADC and the battery+ to account for up to 14.8vdc) and direct 12v battery monitoring.

// Simple monitoring of internal voltage levels:
void VCCInput() {
  //Blynk.virtualWrite(V0, ESP.getVcc() * 0.001);
  Blynk.virtualWrite(V0, analogRead(A0)*0.0145);  // ADC range up to 14.8v
}

image

And I already knew about all the rest… was recently explaining it to another user :stuck_out_tongue_winking_eye:

1 Like

A post was split to a new topic: Need some help with battery reading

UPDATED CODE:

I updated my code to allow variable speed control with the Blynk Joystick Widget.

Enjoy.

/*
  Differential Motor with Speed Control using Blynk Joystick
  Using TB6612FNG Motor Controller (drop-in replacement for L298N, but with greater efficiency) 
  Gunner 2020

  Main variable speed control code from
  DroneBot Workshop 2017
  http://dronebotworkshop.com
*/

// For Blynk
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#define BLYNK_MSG_LIMIT 0
#define BLYNK_HEARTBEAT 5

// For Servo
#include <Servo.h>

// For OTA:
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>


BlynkTimer timer;

Servo myservo;

// Project Auth Token and WiFi settings
char auth[] = "xxxxxxxxxx";  // Set for Local Server
char ssid[] = "xxxxxxxxxx";
char pass[] = "xxxxxxxxxx";

// Motor A
int PWMA = 12;  // D6  // L298N = EnA
int AIN1 = 14;  // D5  // L298N = IN1
int AIN2 = 15;  // D8  // L298N = IN2

// Motor B
int PWMB = 13;  // D7  // L298N = EnB
int BIN1 = 5;  // D1  // L298N = IN3
int BIN2 = 4;  // D2  // L298N = IN4

// LED Headlamp
int HeadLamp = 2; // D4

// Gear Shift Servo
int servoPin = 0; // D3

// Motor Speed Values - Start at zero
int MotorSpeed1 = 0;
int MotorSpeed2 = 0;

// Joystick Values - Start at 512 (middle position)
int joyposVert = 512;
int joyposHorz = 512;



void setup() {
  // Set to LOW gear:
  myservo.attach(servoPin);
  myservo.write(0);
  timer.setTimeout(2000L, []() {  // Timer to Disengage Servo
    myservo.detach();
  });  // END Timer Function

  // Set all the motor and other control pins to outputs
  pinMode(PWMA, OUTPUT);
  pinMode(PWMB, OUTPUT);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);

  pinMode(HeadLamp,  OUTPUT);
  digitalWrite(HeadLamp, LOW); // Turn off headlamp

  // Start with motors disabled and direction forward
  // Motor A
  digitalWrite(PWMA, LOW);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  // Motor B
  digitalWrite(PWMB, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);

  // Log into Blynk Local Server
  Blynk.begin(auth, ssid, pass, "xxx.xxx.xxx.xxx", 8080);  // Set for Local Server
  Blynk.connect();

  Blynk.virtualWrite(V0, "Bootup");  // First Battery Message
  timer.setInterval(1000L, []() {  //  Battery Check timer
    Blynk.virtualWrite(V0, analogRead(A0) * 0.0145); // ADC range up to 14.8v
  });  // END Timer Function

  Blynk.virtualWrite(V10, BLYNK_VERSION);

  // Setup OTA programming
  ArduinoOTA.setHostname("Gunnerator");
  ArduinoOTA.begin();
}



BLYNK_CONNECTED() {
  Blynk.syncAll();
}



void loop() {
  Blynk.run();
  timer.run();
  ArduinoOTA.handle();
}



// Stop all motors if App disconnects
BLYNK_APP_DISCONNECTED() {
  MotorSpeed1 = 0;
  MotorSpeed2 = 0;
  analogWrite(PWMA, MotorSpeed1);
  analogWrite(PWMB, MotorSpeed2);
}



// Main Joystick and Speed Control Function
BLYNK_WRITE(V6)  {  // Read the Joystick X and Y positions
  int joyposHorz = param[0].asInt();
  int joyposVert = param[1].asInt();

  // Determine if this is a forward or backward motion
  // Do this by reading the Vertical Value
  // Apply results to MotorSpeed and to Direction

  if (joyposVert < 460)  {  // This is Backward
    // Set Motor A backward
    digitalWrite(AIN1, LOW);
    digitalWrite(AIN2, HIGH);

    // Set Motor B backward
    digitalWrite(BIN1, LOW);
    digitalWrite(BIN2, HIGH);

    // Determine Motor Speeds
    // As we are going backwards we need to reverse readings
    joyposVert = joyposVert - 460; // This produces a negative number
    joyposVert = joyposVert * -1;  // Make the number positive

    MotorSpeed1 = map(joyposVert, 0, 460, 0, 1023);
    MotorSpeed2 = map(joyposVert, 0, 460, 0, 1023);

  }  else if (joyposVert > 564)  {  // This is Forward
    // Set Motor A forward
    digitalWrite(AIN1, HIGH);
    digitalWrite(AIN2, LOW);

    // Set Motor B forward
    digitalWrite(BIN1, HIGH);
    digitalWrite(BIN2, LOW);

    //Determine Motor Speeds
    MotorSpeed1 = map(joyposVert, 564, 1023, 0, 1023);
    MotorSpeed2 = map(joyposVert, 564, 1023, 0, 1023);

  }  else  {  // This is Stopped
    MotorSpeed1 = 0;
    MotorSpeed2 = 0;
  }

  // Now do the steering
  // The Horizontal position will "weigh" the motor speed
  // Values for each motor

  if (joyposHorz < 460)  {  // Move Left
    // As we are going left we need to reverse readings
    joyposHorz = joyposHorz - 460; // This produces a negative number
    joyposHorz = joyposHorz * -1;  // Make the number positive

    // Map the number to a value of 1023 maximum
    joyposHorz = map(joyposHorz, 0, 460, 0, 1023);

    MotorSpeed1 = MotorSpeed1 - joyposHorz;
    MotorSpeed2 = MotorSpeed2 + joyposHorz;

    // Don't exceed range of 0-1023 for motor speeds
    if (MotorSpeed1 < 0)MotorSpeed1 = 0;
    if (MotorSpeed2 > 1023)MotorSpeed2 = 1023;

  }  else if (joyposHorz > 564)  {  // Move Right
    // Map the number to a value of 1023 maximum
    joyposHorz = map(joyposHorz, 564, 1023, 0, 1023);

    MotorSpeed1 = MotorSpeed1 + joyposHorz;
    MotorSpeed2 = MotorSpeed2 - joyposHorz;

    // Don't exceed range of 0-1023 for motor speeds
    if (MotorSpeed1 > 1023)MotorSpeed1 = 1023;
    if (MotorSpeed2 < 0)MotorSpeed2 = 0;
  }


  // Adjust to prevent "buzzing" at very low speed
  if (MotorSpeed1 < 8)MotorSpeed1 = 0;
  if (MotorSpeed2 < 8)MotorSpeed2 = 0;

  // Set the motor speeds
  analogWrite(PWMA, MotorSpeed1);
  analogWrite(PWMB, MotorSpeed2);
}



// Servo controlled gearing:
BLYNK_WRITE(V8) {
  if (param.asInt() == 1) {
    digitalWrite(AIN1, LOW);
    digitalWrite(AIN2, LOW);
    digitalWrite(BIN1, LOW);
    digitalWrite(BIN2, LOW);
    myservo.attach(servoPin);
    myservo.write(175); // High gear
    timer.setTimeout(2000L, []() {  // Timer to Disengage Servo
      myservo.detach();
    });  // END Timer Function
  } else {
    digitalWrite(AIN1, LOW);
    digitalWrite(AIN2, LOW);
    digitalWrite(BIN1, LOW);
    digitalWrite(BIN2, LOW);
    myservo.attach(servoPin);
    myservo.write(0);  // Low gear
    timer.setTimeout(2000L, []() {  // Timer to Disengage Servo
      myservo.detach();
    });  // END Timer Function
  }
}



// Headlamp Control:
BLYNK_WRITE(V9) {
  if (param.asInt() == 1) {
    digitalWrite(2, HIGH); // Headlamp ON
  } else {
    digitalWrite(2, LOW); // Headlamp OFF
  }
}
2 Likes