Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Harnessing the ESP8266 in the Arduino Mega 2560+WiFi R3

5.00/5 (5 votes)
2 Nov 2020MIT9 min read 22.9K   283  
Turbocharge your device with a clever way to configure your board
Reverse the roles of the two CPUs on your Arduino board to make a faster, more capable device.

Introduction

Disclaimer: This process will overwrite the default firmware that ships with the WiFi module on this board. Once you overwrite, it is possible, but not easy to put back to factory, and the default library for communicating with this module "WiFiEsp" will no longer work with it. I've also read, but have not verified that there's a very limited number of times you can flash some of the more cheaply made ESP-01s so there is some risk but I don't think that's necessarily true for these Arduino and derivative boards.

Arduino

Recently, I published a brief article here on getting started with the Arduino Mega 2560+WiFi R3. In that article, I alluded to the fact that the onboard WiFi has its own CPU and that this CPU is significantly more powerful than the main ATMega2560 CPU on the Arduino board. It's so much more capable in fact, that even while handling the TCP/IP stack, it still has enough cycles left over to outperform the Mega.

With this article, I aim to show you how to tap the more powerful CPU and delegate to the less powerful CPU for I/O.

This project contains the Arduino IDE project ino files for both the ESP8266 demo code and the ATMega2560 demo code. Both must be uploaded to the board because both work together. The project also contains a C# utility program to convert an HTML file to a C style string. This is for convenience in editing the webserver code.

Conceptualizing this Mess

The Plan

On this particular Arduino board, there are two CPUs. In the typical configuration, the 8-bit ATMega2560 CPU operates at 16mhz and serves as the primary CPU. It communicates via serial UART to an ESP8266 which contains its own 32-bit Tensilica XDS 106Micro running at 80 MHz. Obviously, the latter outclasses the former in every way. If we run our code on this CPU, it will in theory execute faster than it will on the other.

The main restriction to trying to use the 106Micro CPU as the main processor is that the only I/O it has is one serial UART, and access to the WiFi and TCP/IP stack. That means its only way of communicating with the other CPU and consequently the rest of the Arduino board is through a single serial connection.

Now, if your application does a lot of WiFi and CPU based crunching but can survive with maximum of 115200 baud in terms of communicating with all the other I/O on the board, than this configuration will work well for you.

The main disadvantage is you need to write additional code and now you need to flash both the ESP8266 and the main Arduino.

The other disadvantage is your code gets more complicated.

Here the ESP8266 handles all the Internet stuff, and any of the main computations that need to happen, and if it needs access to other I/O, it must communicate with the ATMega2560 via its hardware serial connection. The ATMega2560 in turn will take serial data and use it to read or manipulate all of the I/O it controls, reporting back to the 106Micro CPU as necessary.

Advanced systems may also have the ATMega2560 do some coprocessing, especially if there's a lot of I/O to deal with.

This complicates coding it because it means you must make a serial protocol for communicating back and forth between the two CPUs.

On the other hand, it means your 8-bit 16mhz CPU is only in charge of I/O, and your 32-bit 80mhz CPU is now ready to do more intensive work.

Setting Up the Arduino IDE

To get this to work, we must set up the Arduino IDE to use this configuration. The first thing to do is add the ESP8266 "Board Managers" by going to File|Preferencess... and then finding "Additional Board Manager URLs" and adding the URL http://arduino.esp8266.com/stable/package_esp8266com_index.json verbatim. If there already is another URL in there, you can separate them by commas.

That will add some things under the Tools menu but it doesn't matter yet - we'll get there.

How to Approach Coding This - The Development Cycle

The dev cycle is a bit complicated by the necessity of flipping dip switches around depending on which CPU you are coding against. It might be helpful to design your serial protocol first and then code the probably simpler ATMega2560 side of it before then working on the 106Micro side of it. Anything you can do to limit the back and forth here is helpful as it reduces the number of times you have to play with dip switches.

Coding Against the ATMega2560 Half (Deployment Configuration)

(This is also the configuration you will use when the device is deployed.)

  1. Ensure your dip switches are set such that 1-4 are ON and 5-8 are OFF.
  2. Ensure the TXD0/TXD3 switch is set to TXD3
  3. Ensure in the Arduino IDE under Tools|Board: it reads "Arduino Mega or Mega 2560" and under Tools|Processor: it reads "ATmega2560 (AT Mega)"
  4. Double check the above!

In your sketch, the main processor is connected to this one via Serial3. The USB COM port is Serial.

You'll want to read and write data to and from Serial3, setting I/O pins or routing to Serial or possibly even Serial1 and Serial2 as indicated by your protocol, which you've designed already. You have designed your protocol, right?

That's the main job of this processor, unless you also have coprocessing to do.

Note that above any time you upload your code while in this state the board will be reset and the new code will be run automatically. Coding this way with it should be familiar to you.

Coding Against the XDS 106Micro

  1. Ensure your dip switches are set such that 1-4 and 8 are OFF and 5-7 are ON.
  2. Ensure in the Arduino IDE under Tools|Board: it reads "Generic ESP8266 Module"
  3. Ensure Tools|Flash Size: is set to "4 MB (FS: 2MB OTA:~1019KB)"
  4. Double check it!

The code can be uploaded to the XDS 106Micro but it will not run while in this mode. There are two ways to get the code to run. Either switch it back to the deployment configuration listed previously, or set the dip switches such that 1-4 and 7-8 are OFF and 5-6 are ON. This will bridge the USB COM port directly to the XDS 106Micro's UART, and is good for getting debug spew out of it.

Obviously, coding against this one is a bit more involved because you have to flip a dip to get it to run. Unfortunately, this is simply part of the design.

The only way to test the whole system with both CPUs working together is to put it in the deployment configuration.

Coding this Mess

Communicating Between CPUs

One of the first things you're going to want to do is to develop a protocol for communicating between the ATmega2560 and the XDS 106Micro via serial at 115200 baud.

Let's consider a scenario where we want to read or write I/O pins from the XDS 106Micro processor. We'd do something like:

C++
Serial.write(op_write); // op_read or op_write
Serial.write(pin); 
Serial.write(value);

and then, if the opcode was op_read:

C++
Serial.write(op_read);
Serial.write(pin);
while(!Serial.available());
byte value = (byte)Serial.read(); 

You're going to need more opcodes for doing other things, too, but what they are depends on what you're doing. The above was just an example, and one that might be lower level being pin by pin than you would do in a real application.

The above used Serial because that code was for the XDS 106Micro processor and its single serial port.

On the other end - the ATmega2560 side of things we'll use Serial3 for the other side of this link since they are hardwired together.

The other main wrinkle is using with the Wifi capabilities of the ESP8266. Luckily, there's the ESP8266WiFi library which was specifically designed for use in scenarios like ours where we're running on the ESP8266's CPU itself. It's pretty easy to use, with information on getting started here.

The Master ESP8266

In this project, there is a simple webserver that is designed to run on the ESP8266. It allows you to read 3 of the pins on your board, and to set or clear the LED light using a series of checkboxes.

I shamelessly ripped the webserver from one of the built in examples under File|Examples and modified it for demonstration purposes.

The webserver will broadcast on http://esp8266.local if your network supports mDNS (multicast DNS) or otherwise the IP address is reported via the COM port.

Basically, what it does is it connects to the given network by SSID and then it sets up the webserver and the multicast domain (if supported by your network) in order to give you a form. The form presents the pins from the Arduino board as well as the main LED. You can set the LED one, but it can't be read. You can read the other pins but they can't be written - at least by default. You can edit the code to easily change this.

Here's the slightly abbreviated main handler code:

C++
void handleRoot() {
  // if the form parameters are specified
  // set them high, otherwise set them low
  // note we do this for all pins, not just
  // the LED. That's okay, it just doesn't
  // actually do anything for those pins
  String sza = server.arg("Pin1");
  if(0!=sza && 0!=sza[0])
    writePin(2,HIGH);
  else
    writePin(2,LOW);
  sza = server.arg("Pin2");
  if(0!=sza && 0!=sza[0])
    writePin(3,HIGH);
  else
    writePin(3,LOW);
  sza = server.arg("Pin3");
  if(0!=sza && 0!=sza[0])
    writePin(4,HIGH);
  else
    writePin(4,LOW);
  sza = server.arg("PinLED");
  if(0!=sza && 0!=sza[0])
    writePin(13,HIGH);
  else
    writePin(13,LOW);
  char sz[1024];
  sz[0]=0;
  // prepare the checked=\"checked\"
  // attribute for the relevant
  // checkboxes
  char su[] = "";
  char sc[] = " checked=\"checked\"";
  char* s1=(0!=readPin(2))?sc:su;
  char* s2=(0!=readPin(3))?sc:su;
  char* s3=(0!=readPin(4))?sc:su;
  char* s4=(0!=readPin(13))?sc:su;
  // omitted part of the string
  // below for display
  // don't copy/paste this code!
  sprintf(sz,"<!DOCTYPE html>\r\n\r\n<html...",s1,s2,s3,s4);
  server.send(200, "text/html", sz);
}

The Communication Protocol

Our protocol is simple and convenient. It forwards all serial data from the ESP8266 to the ATMega2560. When the ATMega2560 CPU receives it, it forwards it to the main serial port. The exception is if it receives a byte valued at 254 or 255. If it receives the former, it's a code to signal that a pin is to be read. What follows is a byte containing the pin's id. If it receives 255, it's a signal that a pin is to be written. The bytes that follow are one byte for the pin's id, and the next byte for the value. This reading and writing is basically the same as the code in the most recent two figures presented previously. It will only work with digital pins, there's no way to set pin direction from the server, and you can't work with analog pins or other serial UARTs using this implementation. You'll have to make something more elaborate for that.

Here are the two methods used by the ESP8266 webserver code to send I/O commands to the slave:

C++
int readPin(int pin) {
  Serial.write((byte)254);
  Serial.write((byte)pin);
  while(0==Serial.available());
  int ret = Serial.read();
  return ret;
}
int writePin(int pin,int value) {
  Serial.write((byte)255);
  Serial.write((byte)pin);
  Serial.write((byte)value);
}

Those methods are straightforward. The corollary is in the slave code that we cover next.

The Slaved ATMega2560

This CPU is simply responsible for forwarding serial output or setting and getting digital I/O pin values. That's all the code here does. As before, it forwards everything except bytes 254 or 255 which are escapes for reading and writing pins. Here's the code:

C++
void setup() {
  // initialize the serial ports
  Serial.begin(115200);
  Serial3.begin(115200);
}
void loop() {
  int pin;
  // wait for data
  while(0==Serial3.available());
  // get the next byte
  int b = (byte)Serial3.read();
  
  if(254==b) { // read
    // wait for data
    while(0==Serial3.available());
    // read the pin id
    pin = Serial3.read();
    // read the pin
    int val = digitalRead(pin);
    // write the result back to the other CPU
    Serial3.write((byte)(0==val?0:1));
  } else if(255==b) { // write 
    // wait for data
    while(0==Serial3.available());
    // read the pin id
    pin = Serial3.read();
    // wait for data
    while(0==Serial3.available());
    // read the value to set
    int val = Serial3.read();
    // set the pin
    digitalWrite(pin,0!=val?HIGH:LOW);
  } else // simply forward
    Serial.write((byte)b);
  return;
}

Points of Interest

The powerful ESP32 boards you can get now essentially got their start because of hobbyist hacking of the ESP8266 including projects like the above. While the board used here and consequently this technique is somewhat dated, they will help you gain some background on coding these devices.

History

  • 2nd November, 2020 - Initial submission

License

This article, along with any associated source code and files, is licensed under The MIT License