ESP-01

From RoboWiki
Jump to: navigation, search

ESP-01 is a nice module for making connections to Wifi network from microcontroller.

Esp-01.jpg

With the help of ESP-01 adapter module, ESP-01 can be connected to Arduino, because the adpater module contains the level-shifting logic so that Arduino 5V logic can be used to talk to ESP-01 and its ESP8266.

Esp-01-adapter-module.jpg

Since we'd like to use the serial port of Arduino for programming, we will connect ESP-01 to different pins and use the SoftwareSerial library. There is a small glitch however, at the high communication speed (ESP-8266 uses 115200 baud by default), Arduino with 16MHz crystal is struggling and there is too much noise on the communication line, see the Table 19-12. Examples of UBRRn Settings for Commonly Used Oscillator Frequencies in ATmega328P datasheet: at 115.2k, the error in timing is 3.5% (*10 for a single byte => already more than 1/3 of timing error => very likely to receive some incorrect bits). So the first thing we will do is to change the baud rate to 38400, which has a reasonable timing error of 0.2%.

Connecting ESP-01 to Arduino:

ESP-01    Arduino
  GND ..... GND
  VCC ..... 5V
  RX  ..... D2 (we will use D2 as Rx pin)
  TX  ..... D4 (we will use D4 as Tx pin)

notice that contrary to some other documents on-line, the Rx,Tx are already crossed further, so the pin labelled Rx on ESP-01 should connect to Rx on Arduino, and Tx on ESP-01 should connect to Tx on Arduino!

Before connecting the ESP-01 to Arduino, download the following simple testing program:

//modem.ino
#include <SoftwareSerial.h>

SoftwareSerial ss(2, 4);

void setup() {
  ss.begin(115200);         // these two lines will change
  Serial.begin(115200);     // to 38400 after the baud rate is configured
  Serial.println("modem USB");
}

void loop() {
  if (Serial.available())
  {
    char c = Serial.read();
    ss.write(c);
    Serial.write(c);
  }
  if (ss.available())
  {
    char c = ss.read();
    Serial.write(c);
  }
}

Open the serial monitor, and configure the speed to 115200, and "Both NL & CR". Enter text "AT" to the edit line on top, hit enter. The ESP-01 should respond OK. If not, something is wrong, needs to be fixed, and we cannot proceed further.

The list of AT commands (not all of them should work, as the firmware versions differ slightly) is described in the document: ESP 8266 AT Instruction Set (pdf).

If OK is received, let's change the baud rate to 38400 first:

AT+UART_DEF=38400,8,1,0,0

OK should follow, change the 115200 for 38400 in the program above at both places and download a new version to Arduino. In the serial monitor, remember to change the baud rate as well.

Next, we make sure we are in mode 1 (station mode = ESP can connect to an Access Point nearby, for instance a mobile phone hotspot with Internet connection).

AT+CWMODE_DEF?

to set the mode to 1:

AT+CWMODE_DEF=1

Next, you may want to check the list of networks that the ESP-01 see in its vicinity:

AT+CWLAP

(wait a bit to see the list)

To connect, you may want to enable WPS:

AT+WPS=1

and then connect:

AT+CWJAP_DEF="abc","0123456789" 

where "abc" is the SSID of the network, and "0123456789" is the password. This will save it to EEPROM memory so that after the power on, ESP will automatically connect. And even when the wifi is lost, it will automatically reconnect when it becomes available. So for instance, if you configure the module to automatically connect to your mobile phone hotspot, just turn it on anytime, and your Arduino is online on the Internet!

Now comes the fun part, we are going to connect to a remote server on the Internet.

First, prepare the server. This can be written in any programming language, for a simplicity, we will use Node.js:

// esp_server.js
'use strict';

const net = require('net');
const loginRequestPacket = Buffer.from('esp calling\r');
const loginResponsePacket = Buffer.from('welcome esp!');
const keepAlivePacket = Buffer.from('bim');

const MESSAGE_PACKET = 'm'.charCodeAt(0);
//...

var program_connected = 0;

function processPacket(packet, socket)
{
  if (packet.compare(loginRequestPacket) == 0)
  {
    program_connected = 1;
    sendPacket(socket, loginResponsePacket);
    console.log("program connected");
  }
  else if (packet[0] == MESSAGE_PACKET)
  {
    var msg = packet.slice(2);
    console.log("msg " + msg);
  }
  else console.log("unrecognized packet " + packet);
}

function sendPacket(socket, packet)
{
  if (!program_connected)
  {
      console.log("program not connected, will not send packet");
      return;
  }
  socket.write(packet);
}

function keepAlive(socket)
{
  sendPacket(socket, keepAlivePacket);
}

var program_socket;
var connection = net.createServer((socket) => {

        console.log("connection");
        program_socket = socket;
        socket.setNoDelay(true);
        var bytesOfPacketRead = 0;
        var pinger;
        socket.on('data', (data) => {
                        console.log("data");
                        processPacket(data, socket);
        });

        socket.on('close', (data) => { console.log('closed connection'); program_connected = 0; });
        socket.on('end', (data) => { console.log('program disconnects');
                                     program_connected = 0; });
        socket.on('error', (data) => { console.log('error on socket'); });
        pinger = setInterval(function() { if (program_connected) keepAlive(socket); else clearInterval(pinger); }, 60000);
     });


connection.listen(9933, '147.175.115.30');
connection.on('listening', () => { console.log('listnening...')});

Notice, there is a small problem here - the packets do not have to travel over the network without being split into multiple consecutive packets. So it would be cleaner to receive all the characters into a buffer and extract packets from there as the data arrive... For short packets as we use in our example, it will most likely never happen.

We need a server that is on a publicly available IP address... or at least at the same local network as the wifi network that ESP has connecte to. Then we start the script on that server:

node esp_server.js

We also need to determine the IP address of the server, for instance using the ifconfig command:

~/esp01$ ifconfig
enp7s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 147.175.115.30  netmask 255.255.255.0  broadcast 147.175.115.255
        inet6 fe80::218:71ff:fe78:1e7c  prefixlen 64  scopeid 0x20<link>
...

as we can see from the output, the IP address is 147.175.115.30. We chose the port 9933, but you can change it in the code above, if needed. The rest is simple, you can use the example aruduino program above, or you can add some more features, for example:

 // esp01.ino
 #include <SoftwareSerial.h>
 
 SoftwareSerial ss(2, 4); 

 void setup() {
   ss.begin(38400);
   Serial.begin(38400);
   Serial.println("modem USB");
 }
 
 void send_packet(char *packet)
 {
   int len = strlen(packet);
   ss.print("AT+CIPSEND=");
   ss.print(len);
   ss.write(13);
   ss.write(10);
   while(1) { if (ss.available()) { if (ss.read() == '>') break; } }
   ss.print(packet);
 }
 
 char cmd[20];
 
 void connect_to_wifi()
 {
   ss.print("AT+CWJAP_DEF=\"my_SSID\",\"my_passwd\"");  // replace with your SSID and passwd
   ss.write(13);
   ss.write(10);
 }
 
 void connect_to_server()
 {
   ss.print("AT+CIPSTART=\"TCP\",\"147.175.115.30\",9933");
   ss.write(13);
   ss.write(10);
 }
 
 void process_special_command()
 {
   if (cmd[0] == 'w')  connect_to_wifi();
   else if (cmd[0] == 'c')  connect_to_server();
   else if (cmd[0] == 's')  send_packet(cmd + 2); //"s packet_data[EOL]"
   else Serial.println("# ???");
 }
 
 void special_command()
 {
   int cmd_index = 0;
   char c = '#';
   while (1) {
     if (Serial.available())
     {
       c = Serial.read();
       if (c != 10) cmd[cmd_index++] = c;
       else break;
     }
   }
   cmd[cmd_index] = 0;
   Serial.print("cmd: '");
   Serial.print(cmd);
   Serial.println("'");
   process_special_command();
 } 
 
 void loop() {
   if (Serial.available())
   {
     char c = Serial.read();
     if (c == '#') special_command();
     else
     {
       ss.write(c);
       Serial.write(c);
     }
   }
   if (ss.available())
   {
     char c = ss.read();
     Serial.write(c);
   }
 }

Now the session in the Arduino Serial Monitor may look like this:

#w

(special command w enters the wifi connection information automatically)

cmd: 'w'
AT+CWJAP_DEF="my_ISSD","my password"
CLOSED
WIFI DISCONNECT
WIFI CONNECTED
WIFI GOT IP

OK

(we got disconnect, because it was already connected. When running the first time, it will just connect)

#c

(special command c connects to the remote server at the IP and PORT that is in the code - you could improve the program to make it possible to change it, and even store it to Arduino's EEPROM)

cmd: 'c'
AT+CIPSTART="TCP","147.175.115.30",9933

CONNECT

OK

Now we can send the login packet to the server:

#s esp calling
cmd: 's esp calling'
 
Recv 12 bytes 

SEND OK

+IPD,12:welcome esp!

in the last line, we see that the server responded - we could monitor for the +IPD message, intercept it and react to the data in the packet...

Now to send a message in our simple funny protocol:

#s m hello

(sends a message 'hello')

cmd: 's m hello'

Recv 8 bytes

SEND OK

And here is the corresponding console output on the server:

connection
data
program connected
data
msg hello