Coche RC con NodeMCU (ESP8266)

Introducción

Materiales utilizados

  • Coche RC
  • NodeMCU V1(ESP8266)
  • Puente H (L293D)
  • ProtoBoard y jumpers

Introducción

LA idea es convertir un coche RC para que sea manejado por un ESP8266, creo que es el primer coche controlado por websocket, al menos no he visto otro en lo que buscaba información para este proyecto.

El coche lo compre el mas barato que encontré y al desarmarlo tenia dos motores DC 5V uno para el impulso y otro para la dirección, mas adelante les explicare el problema que tuve para el control de la dirección.
Tuve mucho interés en el manejo de los websockets que son los sustitutos del GET y POST en HTTP y los mas idoneos para este tipo de comunicación el cual estan siempre conectados cliente/servidor
El coche se conectara a mi red WIFII y estará funcionando como server, a la espera de que se conecten los clientes, que podrán ser un movil, tablet, PC, por medio de un browser a través de HTML.

El HTML consistirá en 5 botones básicos arriba, abajo, izquierda, derecha, parada, a los que le doy algo de estilo con CSS, demostrando así el enorme potencial de estos modulos ESP8266.

Primera Demo

Pasos:

1-paso: Abrir el coche y quitar la parte de radiofrecuencia el mando tampoco nos serviría, lo pueden guardar para otro proyecto tanto el mando como el transmisor
2-paso: conectar los motores al puente H

Para mas información de que es un puente H:

http://panamahitek.com/el-puente-h-invirtiendo-el-sentido-de-giro-de-un-motor-con-arduino/

3-paso asignar pines del ESP8266 y conectar

const byte AcelMotor_pin = 14;  // Pin D5, to H-Brige "enable 1"
const byte Adelante_pin = 12;   // Pin D6, to H-Brige "input 1"
const byte Atras_pin = 13;      // Pin D7, to H-Brige "input 2"

const byte Derecha_pin = 4;     // Pin D2, to H-Brige "input 1"
const byte Izquierda_pin = 5;   // Pin D1, to H-Brige "input 2"
Asignacion de pin

 


 

 

Empezamos con la adaptación!

 

El codigo

Lo primero es instanciar al websocket y preparar el modulo como server

ESP8266WiFiMulti WiFiMulti;
ESP8266WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

Iniciamos la coneccion a mi red WIFI  e iniciamos el servidor.

 WiFiMulti.addAP(ssid, password);
 while(WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
   
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (mdns.begin("espWebSock", WiFi.localIP())) {
    Serial.println("MDNS responder started");
    mdns.addService("http", "tcp", 80);
    mdns.addService("ws", "tcp", 81);
  }
  else {
    Serial.println("MDNS.begin failed");
  }
  Serial.print("Connect to http://espWebSock.local or http://");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

  server.begin();

  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

Una ves conectado a la red wifi y haya  dado una IP al servidor, empezamos a configurar lo el front end para ser visualizado por el cliente.

<body onload="javascript:start();">
<div id="div1">
<h1 id="text">RC CONTROL</h1>
<input id="textField1" type="text" value="" align="right" size="13"/><br>
<input id="display"    type="text" value="" align="right" size="13"/><br>
</div>

<div id="div2"><b>POWER</b></div>
<div id="div3">
<div id="ledstatus"><b>Estado</b></div>
<button id="ledon"    type="button" onclick="buttonclick(this);">>></button> 
<button id="ledoff"   type="button" onclick="buttonclick(this);">FRENO</button>
<button id="reversa"  type="button" onclick="buttonclick(this);">>></button>
<button id="izquierda"type="button" onclick="buttonclick(this);"><<</button>
<button id="derecha"  type="button" onclick="buttonclick(this);">>></button>
<button id="O"        type="button" onclick="buttonclick(this);">ON</button>
<input id="p"      type="range" min="0" max="255" step="5";>
</div>
</body>
</html>

Como podrán observar el código HTML es muy básico y al cargar la pagina pues se ejecuta el java script que es quien se encarga la lógica de programación. el código CSS pues no viene al tema, al final del proyecto pondré el código completo y podrán descargarlo.

Ahora el código de enlace como lo llamo entre la interacción del cliente con el coche RC

La idea es que al cargar la pagina la funcion start() se ejecuta y crea el socket con el cliente y el coche RC queda a la espera de instrucciones por medio de eventos, en el vídeo explicare mejor esta parte y podrán ver los mensajes cliente/servidor

function start() {
  websock = new WebSocket('ws://' + window.location.hostname + ':81/');
  websock.onopen =   function(evt){console.log('RC ready to drive'); };
  websock.onclose =  function(evt){console.log('RC connection close'); };
  websock.onerror =  function(evt){console.log(evt); };
  websock.onmessage =function(evt){console.log(evt);
    
  
    var e = document.getElementById('ledstatus');
    output = document.getElementById("display");
    output_text = document.getElementById("textField1");
     
       
      if (evt.data === 'ledon') {
      e.style.color = 'red';
      output_text.style.color ='red';
      var state= "Acelerando.."
      output_text.value=state;
             
    }
    else if (evt.data === 'ledoff') {
      e.style.color = 'black';
      //canvas.fillText(state,10,50);
      output_text.style.color ='black';
      var state= "Freno"
      output_text.value=state;
    }
      else if (evt.data === 'reversa') {
      e.style.color = 'black';
      output_text.style.color ='black';
      var state= "Reversa"
      output_text.value=state;
      }
      else if (evt.data === 'izquierda') {
      e.style.color = 'black';
      output_text.style.color ='black';
      var state= "Izquierda"
      output_text.value=state;        
    }
    else if (evt.data === 'derecha') {
      e.style.color = 'black';
      output_text.style.color ='black';
      var state= "Derecha"
      output_text.value=state;        
    }
    else if (evt.data > "100") {
      e.style.color = 'black';
      output.style.color ='black';
      var state= "Velocidad ";
      output.value=(state + evt.data);        
    }
    else {
      console.log('unknown event');
      document.getElementById("text").value = "";
    }
   
  };
}
function buttonclick(e) {
  websock.send(e.id);
 }

Una ves podemos interpretar las instrucciones del cliente pues hacemos andar el coche RC.

Esta parte es muy arduino.

Como había comentado antes el coche queda a la espera de instrucciones por medio de eventos pues el código para interactuar con el coche esta aquí.

Lo que inicia el evento es un mensaje tipo texto, que es el que indica la instrucción “Izquierda” pues el pone a 0 el pin D6 y a HIGH el pin 7 lo que hace que el motor de dirección gire a la izquierda, y así con las demás instrucciones.

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length)
{
  
  Serial.printf("webSocketEvent(%d, %d, ...)\r\n", num, type);
  switch(type) {
    case WStype_DISCONNECTED:
      Serial.printf("[%u] Disconnected!\r\n", num);
      break;
    case WStype_CONNECTED:
      {
        IPAddress ip = webSocket.remoteIP(num);
        Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\r\n", num, ip[0], ip[1], ip[2], ip[3], payload);
      
      } 
      break;
    case WStype_TEXT:
    {
      Serial.printf("[%u] get Text: %s\r\n", num, payload);

      String text = String((char *) &payload[0]);
      if(text=="ledon"){
        analogWrite(AcelMotor_pin, acel);  //Pin 5
        acel=acel+10;
        Serial.print("Velocidad ");
        Serial.println(acel);
        analogWrite (Adelante_pin, 1023);   // Pin D6
        analogWrite (Atras_pin,0);          //Pin D7
        String temp =String(acel); // convierte to string
        webSocket.sendTXT(num,temp);
                      
            }
      if(text=="ledoff"){
        Serial.println("break");  
        analogWrite (Adelante_pin, 0);     // Pin D6
        analogWrite (Atras_pin,0);         //Pin D7
            //webSocket.sendTXT(num, "break", 5);
           // analogWrite(LEDPIN, 0);
            acel=500;
            }
       if(text=="reversa"){
        Serial.println("reversa");
        analogWrite(AcelMotor_pin, acel);
        acel=acel+10;
        Serial.println(acel);
        analogWrite (Adelante_pin,0);     // Pin D6
        analogWrite (Atras_pin,1023);     //Pin D7
        webSocket.sendTXT(num, "reversa", length);
            }  
       if(text=="izquierda"){
        flag = true;
        analogWrite(Izquierda_pin, 0); //Pin 2
        analogWrite(Derecha_pin, 1023);      //Pin 1
        Serial.println("izquierda");
           webSocket.sendTXT(num, "girar a la izquierda", 20);
            } 
       if(text=="derecha"){
        flag = true;
        analogWrite(Izquierda_pin, 1023);    //Pin 2
        analogWrite(Derecha_pin, 0);   //Pin 1
        Serial.println("izquierda");
        
          webSocket.sendTXT(num, "girar a la derecha", 18);
            }                         
    

Conclusiones

Estoy muy satisfecho con los resultados la verdad que aprendí mucho, este proyecto es fácil de hacer y económico en comparación con lo que se puede aprender ya que toca electrónica, microprocesadores, html, css, javascript etc….

El problema que tuve que la dirección es que estoy trabajando con un motor dc con escobillas por lo que al inyectar alimentación este gira bruscamente hacia un lado impidiendo una conducción fina, como mejora sustituiré el motor de dirección por un servo motor que es mas adecuado para la dirección en un coche RC

El código no esta depurado por lo que tiene algunas librerías de mas y algunas variable tienen nombre sin sentido, cuando finalice el proyecto del todo ya les dejare un código mas limpio.

Cualquier duda o comentario estoy a su orden.

 

Conectando ESP8266 con Arduino nano “I2C” 2da parte

Introducción:

En este proyecto utilizaremos:
1- 1 NODEMCU (ESP12E).
2- 1 Arduino Nano
3- 2 DS18B20
4- breadboard
5- Cables de conexion

Introducción

En este ocasión presento como es la transferencia de datos mediante el protocolo I2C, entre un ESP8266 como maestro y un Arduino Nano como esclavo pero transfiriendo los datos de dos sensores de temperatura DS18B20, este es un sensor que contiene una dirección única que permite identificarlo individualmente, lo que lo hace propicio para el uso del protocolo OnWire que permite enviar y recibir datos utilizando un solo cable.

Click aquí para ver la primera parte

Para Saber sobre el sensor DS1820 click aqui

El código

Presentare la codificación del esclavo ya que la del maestro(ESP8266) es básicamente  la misma que en la primera parte.

En este vídeo explico su funcionamiento:

1- Añadimos las librerías relacionadas con el sensor
OneWire y DallarTemperture

2- Definimos el pin 2 del arduino nano
#One_WIRE_BUS

3- Instanciamos y asociamos el pin 2 del arduino al protocolo OneWire y la librería DallasTemperarute
OneWire oneWire(ONE_WIRE_BUS)
DallasTemperature sensors(&oneWire)

#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2
// Setup a oneWire instance to communicate with any OneWire devices 
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
 
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

4- Definimos la direccion del esclavo y asignamos los nombres a las variables

const byte MY_ADDRESS = 42;
int Temp1,Temp2;
// various commands we might get

enum {
    CMD_ID = 1,
    CMD_READ_Temp1  = 2,
    CMD_READ_Temp2 = 3
    };

5- iniciamos los sensores

 sensors.begin();

6- Creamos uns subrutina que lee las medidas del los sensores.

void sendSensorTemp ()
  {
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  Serial.print(" Requesting temperatures...");
  sensors.requestTemperatures(); // Send the command to get temperatures
  Serial.println("DONE");
  
  Temp1 = sensors.getTempCByIndex(0);
  Temp2 = sensors.getTempCByIndex(1);
  delay(500);
  
  }  // end of sendSensor

8- llamamos la subrutina infinitamente en el loop

void loop() 
  {
   sendSensorTemp ();
  }  // end of loop

A continucion los codigos completos tanto del master como el esclavo

/Master
/*These examples illustrate how a master can request data from a slave).
First, the master, which asks the slave for information:
*/
#include <Wire.h>

const int SLAVE_ADDRESS = 42;
int Temp1,Temp2;
// various commands we might send
enum {
    CMD_ID = 1,
    CMD_READ_Temp1  = 2,
    CMD_READ_Temp2 = 3
    };

void sendCommand (const byte cmd, const int responseSize)
  {
  Wire.beginTransmission (SLAVE_ADDRESS);
  Wire.write (cmd);
  //Wire.endTransmission ();
  if (Wire.endTransmission () == 0)
  {
  Serial.println("Data Sent OK");  
  }
  else{
    Serial.println("Error Sending Data");
    }
  Wire.requestFrom (SLAVE_ADDRESS, responseSize);  
  }  // end of sendCommand
  
void setup ()
  {
  Wire.begin (0,2);   
  Serial.begin (115200);  // start serial for output
  
  sendCommand (CMD_ID, 1);
  
  if (Wire.available ())
    {
    Serial.println("");  
    Serial.print ("Slave is ID: ");
    Serial.println (Wire.read (), DEC);
    }
  else
    Serial.println ("No response to ID request");
  
  }  // end of setup

void loop()
  {
  
  
  sendCommand (CMD_READ_Temp1, 1);
  Temp1 = Wire.read ();
  Serial.print ("Value of Temp1: ");
  Serial.println (Temp1);

  sendCommand (CMD_READ_Temp2, 1);
  Temp2= Wire.read ();
  Serial.print ("Value of Temp2: ");
  Serial.println (Temp2);

  delay (500);   
  }  // end of loop
Master
// Slave
/*It has to be able to reply to any request without even knowing the address of the device 
 making the request.
It does this by setting up an interrupt handler (by calling Wire.onRequest).
This gets called any time a master wants a response.
We also need the Wire.onReceive handler, to get the initial "command" - that is,
we need to know what data is wanted.
The example above assumes a single-byte command, which we save in the variable "command".
Then when the requestEvent handler is called, we check what the most-recent command was,
and send an appropriate response.
Warning - because of the way the Wire library is written, the requestEvent handler can 
only (successfully) do a single send. The reason is that each attempt to send a reply resets
the internal buffer back to the start. Thus in your requestEvent, if you need to send multiple bytes,
you should assemble them into a temporary buffer, and then send that buffer using a single Wire.write. 
For example:
*/
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2
// Setup a oneWire instance to communicate with any OneWire devices 
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
 
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

const byte MY_ADDRESS = 42;
int Temp1,Temp2;
// various commands we might get

enum {
    CMD_ID = 1,
    CMD_READ_Temp1  = 2,
    CMD_READ_Temp2 = 3
    };

char command;

void setup() 
  {
  command = 0;
  
  Serial.begin(115200);
  Serial.println("Dallas Temperature IC Control Library Demo");

  // Start up the library
  sensors.begin();
  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);  // interrupt handler for incoming messages
  Wire.onRequest (requestEvent);  // interrupt handler for when data is wanted

  }  // end of setup

void sendSensorTemp ()
  {
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  Serial.print(" Requesting temperatures...");
  sensors.requestTemperatures(); // Send the command to get temperatures
  Serial.println("DONE");
  
  Temp1 = sensors.getTempCByIndex(0);
  Temp2 = sensors.getTempCByIndex(1);
  delay(500);
  
  }  // end of sendSensor

void loop() 
  {
   sendSensorTemp ();
  }  // end of loop

void receiveEvent (int howMany)
  {
  command = Wire.read ();  // remember command for when we get request
  } // end of receiveEvent


void requestEvent ()
  {
  switch (command)
     {
     
    case CMD_ID:      Wire.write (0x55); break;   // send our ID 
    case CMD_READ_Temp1: Wire.write(Temp1); break;  // send Temp1 value
    case CMD_READ_Temp2: Wire.write(Temp2); break;   // send Temp2 value
    
     }  // end of switch
  
  }  // end of requestEvent
ESCLAVO