Friday, July 9, 2021

Universal code for ESP8266 and ESP32

For an index to all my stories click this text

You might stumble upon the same problem I encountered. You are building a project and are not sure wether an ESP8266 is sufficient or have to switch to an ESP32 later on.
This post is about building software which automatically detects on which ESP it runs and adjusts its parameters. Let me explain why I needed this.

Lately I have been involved in a large project. My Son in law asked me to assist in building an aquaponics system. He is doing the mechanics and I am to build the electronics.
As we are at this early stage not sure how many sensors and actuators we are going to use in the end.  I started the project with an ESP8266. We might need an ESP32 however, due to its extensive IO ports.

This gives me two possibilities. I can rewrite the code when we swicth from the ESP8266 to an ESP32, or I can structure the program from the beginning on to be ready for both controllers.

Naturally I opted for this last possibility and this is how to do that. I am using the Arduino IDE (C++) for programming the ESP's.

#if defined()

This is the magic command that makes it possible to write universal code.

When you write C++ code and upload it to the Arduino or ESP it is compiled. Hardcoded in this compiled code is included for which processor the code is. This way a program written for the ESP32 will not run on an ESP8266 and the other way roud. This makes perfect sense as both are from the same family however use different libraries and pinouts.

The question is how we can use that information. And as a matter of fact it is really easy.


#if defined(ESP8266)
  SOME ESP8266 CODE
#elif defined(ESP32)
  SOME ESP32 Code
#endif

Put this at the beginning of your program and these lines make sure that the code for the right processor is loaded.

How can we set this to work for us.

Lets start with something that looks simple and actually is aq bit more complex as you think. But let it not set you off as it will be clear in the end.

Let's start with a simple blink program.

#if defined(ESP8266)    
    int led = 05;  // GPIO D1 on ESP8266
#elif defined(ESP32)
    int led = 22; // GPIO22
#endif

void setup() 
{
pinMode(led, OUTPUT);   // Initialize the LED pin as an output
}

void loop() 
{
digitalWrite(led, LOW); // Turn the LED on
delay(1000);                // Wait for a second
digitalWrite(led, HIGH);// Turn the LED off
delay(1000);                // Wait for a second
}

And here you can see how it is done:

#if defined(ESP8266)    
    int led = 05;  // GPIO D1 on ESP8266
#elif defined(ESP32)
    int led = 22; // GPIO22
#endif

If the compiler notices that the program is for the ESP8266 the code in the #if will be compiled giving the led variable a value of 5 which is pin 5 also known as D1 on the ESP8266.
However if the compiler notices that the program is written for the ESP32 the code in the #elif will be compiled.

Hey, wait. We are not using the pin numbers usually when coding for the ESP8266. Normally you will use D2 (or another pin of your liking). Alas that will not work. If you would put that in your program the compiler would forget the D and use pin 2 which is at board number D4 (trust me, I've been there). So we need to make a refernce to the actual pin numbers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#if defined(ESP8266)    
    // Assign Arduino Friendly Names to GPIO pins
    #define D0 16
    #define D1 5
    #define D2 4
    #define D3 0
    #define D4 2
    #define D5 14
    #define D6 12
    #define D7 13
    int led = D2;  // IO port D2
#elif defined(ESP32)
    int led = 22;  // GPIO22
#endif

void setup() 
{
pinMode(led, OUTPUT);   // Initialize the LED pin as an output
}

void loop() 
{
digitalWrite(led, LOW); // Turn the LED on
delay(1000);            // Wait for a second
digitalWrite(led, HIGH);// Turn the LED off
delay(1000);            // Wait for a second
}

And there we are. We referenced the IO pins to the D-numbers on the board and now we can use the D-numbers like we are used to. I have tested this with a NodeMCU board and a Wemos-D1.




For reference I give you here the ESP8266 pin layout for the D1 and NodeMCU boards where you can see the difference between the actual pin numbers and the D-numbers on the board.



At the ESP32 side things are much easier. The D-numbers on the board actually correspond with the GPIO numbers. The Arduino IDE does not use the actual pin numbers which makes life a lot easier.

How about libraries

Indeed there is a difference in libraries for the ESP8266 and the ESP32. We can dissolve this in the same manner.

#if defined(ESP8266)    
    // Assign Arduino Friendly Names to GPIO pins
    #define D0 16
    #define D1 5
    #define D2 4
    #define D3 0
    #define D4 2
    #define D5 14
    #define D6 12
    #define D7 13
    int led = D2;
    #include <ESP8266WiFi.h>
    #include <ESP8266WebServer.h>
    ESP8266WebServer server(80);
#elif defined(ESP32)
    int led = 22;
    #include <WiFi.h>
    #include <WebServer.h>
    WebServer server(80);
#endif

Just look at this part of the beginning of a larger program.

If the program is going to be compiled for the ESP9266 then the compiler will use the ESP8266Wifi.h library and the ESP8266WebServer.h library and then automatically makes an instance for the webserver.

When the program is compiled for the ESP32 the Wifi.h and WebServer.h libraries are installed and again the right instance for the webserver is created.

If you are coding for both ESP versions this will make your life a lot easier.

Till next time

Luc Volders