During the 3rd Corona Lockdown in Austria, I searched for a new private project and decided to test something new. So I tried myself in programming microcontrollers.
This post shares my first impressions, experiences, and ready-to-use code snippets for programming ESP8266. The associated code samples can be found on GitLab.

Find a Suitable Microcontroller

Since I’m deep into developing all sorts of web applications, I searched for a microcontroller with a built-in WiFi chip and decided to order the D1 mini ESP8266.

Install Sophisticated IDE

Arduino IDE seems fine for getting started or hacking around and reminds me of times when I started programming with a simple file editor with no modern IDE features. So it’s perfectly clear that a higher sophisticated IDE is required for efficient development.
I recommend taking a look at PlatformIO IDE for Visual Studio Code. To be honest, that was the first IDE I’ve tried after short research.
PlatformIO provides everything you need to code, build, upload, run and debug your microcontroller programs.

Create a Project

The most important step in creating a new project is to select the microcontroller which is used to program.

Create a new PlatformIO Project

After that PlatformIO set up the complete environment needed to build, upload and run the project on the selected microcontroller. It even autodetects COM port and so on.

Note: Important environment settings can be found in platformio.ini

Install Libraries with PlatformIO

Just use the library manager of PlatfromIO (Home-Button), search for the needed package or library, and add it to the project. Yep it’s that easy!

For instance: Search for “wifi” to find and install the library ESPAsyncWebServer-esphome

Start Programming

Basically, there are two important functions to know.

void setup() – The setup function runs once when the reset button is pressed or the microcontroller is powered on. It is obviously used for setting up things like WiFi connections or whatever so on.

void loop() – The loop function runs over and over again forever and executes the actual business logic. So try out your first example!

Note Keep in mind that microcontrollers are very limited in their resources. So make sure to develop efficiently 😉

‘Hello World’ for Microcontrollers

Check out the Blink – Hello World Example for microcontrollers or copy&paste the snippet below into main.cpp.
After build and upload the code to the ESP, the microcontroller’s built-in LED starts blinking while “Blink” is printed via Serial output.

NoteCheap USB cables sometimes cause problems communicating with the Microcontroller. So use a qualitative USB cable to prevent frustration.

#include <Arduino.h>

// Blink microcontroler's built in led 
void setup() {
  // Sets the data rate in bits per second (baud) for serial data transmission. 
  // For communicating with Serial Monitor, make sure to use the correct baud rate for your microcontroller. 
  Serial.begin(9600);

  Serial.println("Starting up..");

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  Serial.println("Blink");

  // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);   
  // wait 1000 ms
  delay(1000);                      
  digitalWrite(LED_BUILTIN, LOW);    
  delay(1000);                       
}

Note – delay(1000) is a very simple solution to implement a timeout. But it is a blocking implementation that causes side effects and issues! Use it very carefully and think of alternative implementations!
Example:

#define MAINDELAY 1000
...
lastIteration = millis();
void loop() {

    if((lastIteration + MAINDELAY) < millis()) {

       // Execute delayed code
       lastIteration = millis();
    }
}

Connect ESP8266 to WiFi

After a library that contains the Wifi implementation ESP8266WiFi (e.g. ESPAsyncWebServer-esphome) is installed, the microcontroller can be connected to any preferred WiFi.

#include <ESP8266WiFi.h>

void connectWifi() {
  WiFi.disconnect();
  WiFi.begin("SSID", "PASSWORD");
  WiFi.mode(WIFI_AP_STA);
  int wifiConnectTimeout = 10;
  while (wifiConnectTimeout > 0 && WiFi.status() != WL_CONNECTED) {
    Serial.println("");
    wifiConnectTimeout--;
    delay(1000);
    Serial.println("Connecting ... ");
  }
  if(WiFi.status() != WL_CONNECTED) {
    Serial.println("Failed to connect to Wifi");
  }
  else {
    Serial.println("WiFi connection Successful");
    Serial.println(WiFi.localIP()); 
  }
}

To connect to WiFi, the function needs to be called inside the setup() function. A ready-to-use implementation can be found on my GitLab Project and is tagged with v2.0.0.

Note – I would recommend implementing a fallback implementation that creates a WiFi Access Point when the WiFi connection fails.

A WiFi Access Point can be created with a single line of code.
WiFi.softAP("AP_SSID", "AP_PASSWORD");
The IP-Adress of the access point can be printed using:
Serial.println(WiFi.softAPIP());

Create a Minimal Webserver

In the following code snippets, I provide an implementation of a simple async webserver that serves the first version of index.html.

Upload index.html to Flash Storage

Depending on the chosen microcontroller, the chip provides the possibility to store files inside the built-in flash storage.

PlatformIO directly supports uploading files to flash storage. Just define the preferred filesystem implementation inside your project’s platformio.ini using board_build.filesystem = littlefs.
Files located inside the data folder of the project’s root can be uploaded to the microcontroller using Upload Filesystem Image that PlatformIO provides under Project Tasks.

Upload Files to Flash Storage using PlatformIO

So create a sample index.html located in <project-root>/data/index.html and upload it to the microcontroller. After successful file upload, check if index.html is available.

For that purpose, all files inside the flash storage can be printed using the following code snippet.

#include <LittleFS.h>

void printAllStoredFilenames() {
    // Initialize LittleFS
    Serial.println("Found files on LittleFS");
    if(!LittleFS.begin()){
        Serial.println("Error on LittleFS begin");
        return;
    }
	
    Dir dir = LittleFS.openDir("/");
    while (dir.next()) {
        Serial.println(dir.fileName());
    }
}

Setup Webserver, Handle Web-Requests, and Serve index.html

When index.html is available on the microcontroller, we can focus on setting up the webserver. The library (ESPAsyncWebServer-esphome) used to create the WiFi connection in the previous example, contains already everything needed to create a simple webserver and handle requests.

With the example code below, a webserver can be started on port 80, which serves index.html from the built-in filesystem. The startWebserver() function needs to be called inside the setup() function. After the successful setup, the webserver listens for requests on port 80 and handles these asynchronous.

#include <LittleFS.h>

static AsyncWebServer server(80);

void startWebserver() {
    // Initialize LittleFS
    if(!LittleFS.begin()){
        Serial.println("Error on LittleFS begin");
        return;
    }

    // provide index.html
    server.on("/", HTTP_GET, [&](AsyncWebServerRequest *request) { 
      Serial.print("Received request / from client with IP: ");
      Serial.println(request->client()->remoteIP());
      request->send(LittleFS, "/index.html", String(), false); 
    });


    // start webserver
    server.begin();
    Serial.println("HTTP server started");
}

Further, POST-Requests can be handled by adding the library ArduinoJson and including some lines of code inside the startWebserver() implementation.

#include <ArduinoJson.h>
..
void startWebserver() {
    ...
    // example post request
    server.onRequestBody([&](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
      if (request->url() == "/postValue" && request->methodToString() == "POST") {
        DynamicJsonDocument doc(2048);
        deserializeJson(doc, data);
        String serializedBody;
        serializeJson(doc, serializedBody);
        Serial.print("Received request /postValue [REQUEST]\t\r\n");
        Serial.println(serializedBody);
        request->send(200);
      }
    });

A full and working example of the WiFi setup as well as the index.html serving Webserver can be found on my demo project.


Over The Air (OTA) Update

In my point of view, over the air update is a very cool and helpful feature that should be implemented in every microcontroller software.

Before OTA can be implemented, the library EasyOTA needs to be added to the project.

When the library is installed, OTA Update can be enabled by adding the following configuration lines inside your projecte’s platformio.ini.

[env:something]
...
...
upload_flags = --auth=${ota.esp_pw}    
upload_port = MICROCONTROLLER-IP 
build_flags = -DESP_NAME=\"${ota.esp_name}\" -DESP_PW=\"${ota.esp_pw}\"

[ota]
esp_name = devicename                      ;#only use lowercase
esp_pw = password4ota

To check for OTA updates, use #include <EasyOta.h> and add EasyOta.checkForUpload(); to the loop() function.