ESP8266/ESP32

ESPAsyncWebserver: How to run code in main loop thread

When working with ESPAsyncWebserver, you will soon encounter one of its most fundamental limitations: Its code will always run in interrupts, hence you can’t use delay() or any function that uses delay() or similar functions internally.

However, there’s a simple programming pattern that will enable you to execute code in the main thread if requested by the webserver. Note: In this example, we will only cover how to activate code in the main loop from the webserver, but the response will be sent before the code in the main loop is run – hence, the response is just {"status": "ok"} and it doesn’t depend on whatever is happening on the main loop.

The idea is to have a global flag:

bool request_serial_output = false;

which is set in the webserver handler code, whereas the main loop checks the flag repeatedly:

void loop() {
  if(request_serial_output) {
    request_serial_output = false; // Clear flag
    // TODO Add handling code here
  }
}

Full example

This example uses PlatformIO and should work on any ESP32 board.

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>

AsyncWebServer server(80);

bool request_serial_output = false;

void setup() { 
  Serial.begin(115200);
  // Connect Wifi, restart if not connecting
  // https://techoverflow.net/2021/01/21/how-to-fix-esp32-not-connecting-to-the-wifi-network/
  WiFi.begin("MyWifi", "mywifipassword");
  uint32_t notConnectedCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
      delay(100);
      Serial.println("Wifi connecting...");
      notConnectedCounter++;
      if(notConnectedCounter > 50) { // Reset board if not connected after 5s
          Serial.println("Resetting due to Wifi not connecting...");
          ESP.restart();
      }
  }
  Serial.print("Wifi connected, IP address: ");
  Serial.println(WiFi.localIP());

  // Initialize webserver URLs
  server.on("/api/test", HTTP_GET, [](AsyncWebServerRequest *request) {
      // Send request to main loop
      request_serial_output = true;
      // Send JSON "ok" response.
      // NOTE: The response will be sent BEFORE the main loop has finished
      // processing the request
      AsyncResponseStream *response = request->beginResponseStream("application/json");
      DynamicJsonDocument json(1024);
      json["status"] = "ok";
      serializeJson(json, *response);
      request->send(response);
  });

  // Start webserver
  server.begin();
}

void loop() {
  if(request_serial_output) {
    request_serial_output = false; // Clear flag
    // Output serial
    Serial.println("First message. Now waiting one second...");
    // Wait 1s. You can't do that in the webserver handler!
    delay(1000);
    // Output another message on Serial
    Serial.println("Second message!");
  }
}
[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200
lib_deps =
    ESP Async [email protected]
    [email protected]

 

Posted by Uli Köhler in ESP8266/ESP32, PlatformIO

How to enter serial bootloader on the ESP32

In order to enter the serial bootloader on the ESP32, connect both GPIO0 and GPIO2 to GND while the ESP32 is starting up.

A typical hardware sequence to enter the bootloader is:

  1. Connect GPIO0 and GPIO2 to GND
  2. Connect the EN or RST pin to GND for at least 100ms, then connect to 3.3V

The second step will restart the ESP32.

Posted by Uli Köhler in ESP8266/ESP32

What is “Download Mode” on the ESP32?

The ESP-WROOM-32E datasheet Table 3 defines that when GPIO0 and GPIO2 are low during startup, you will enter Download mode without Download mode being defined anywhere in the datasheet:

Download mode is the factory-integrated ROM serial bootloader. By strapping GPIO0 and GPIO2 low at the same time, you can flash a new firmware using the serial port.

 

Posted by Uli Köhler in Embedded, ESP8266/ESP32

Test code for 8 Neopixel/WS2812B LEDs

This test code toggles 8 WS2812b LEDs with circulating colors and will test all three R/G/B LEDs in each LED plus their connection.. It is based on an ESP32 with a 74LV245 level shifter, but it will also work on other platforms supported by NeoPixelBus. In my configuration, the 3.3V signal output pin is on pin 13.

#include <NeoPixelBus.h>
#include <NeoPixelAnimator.h>

const uint16_t PixelCount = 8;
const uint8_t PixelPin = 13;

NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> strip(PixelCount, PixelPin);

void DrawPixels(uint32_t offset)
{
    strip.SetPixelColor((0 + offset) % 8, RgbColor(255, 0, 0));
    strip.SetPixelColor((1 + offset) % 8, RgbColor(0, 255, 0));
    strip.SetPixelColor((2 + offset) % 8, RgbColor(0, 0, 255));
    strip.SetPixelColor((3 + offset) % 8, RgbColor(255, 255, 0));
    strip.SetPixelColor((4 + offset) % 8, RgbColor(0, 255, 255));
    strip.SetPixelColor((5 + offset) % 8, RgbColor(255, 0, 255));
    strip.SetPixelColor((6 + offset) % 8, RgbColor(255, 255, 255));
    strip.SetPixelColor((7 + offset) % 8, RgbColor(0, 0, 0));
    strip.Show();
}

void setup()
{
    strip.Begin();
    strip.Show();
}

uint32_t loopCounter = 0;
void loop()
{
  DrawPixels(loopCounter);
  loopCounter++;
  delay(250);
}

PlatformIO config:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino
lib_deps =
     makuna/NeoPixelBus @ ^2.6.9

 

Posted by Uli Köhler in Electronics, ESP8266/ESP32, PlatformIO

How fast is analogRead() on the ESP8266?

An analogRead() call on the ESP8266 takes about 90-100 microseconds.

I tested this experimentally using a Wemos D1 Mini board, PlatformIO and this code:

uint32_t t0 = micros();
for (size_t i = 0; i < 1000; i++)
{
    analogRead(A0);
}
uint32_t t1 = micros();
Serial.print("1000 analogRead() calls took [us]: ");
Serial.println(t1-t0);

Output:

1000 analogRead() calls took [us]: 97168

Since 1000 analogRead() calls (including some overhead from the loop) took 97.168ms, one analogRead() call takes about 97.168us (+-0.02us).

Posted by Uli Köhler in Arduino, Electronics, ESP8266/ESP32, PlatformIO

How to enable Watchdog on ESP8266/ESP32 using PlatformIO

You can enable the watchdog on the ESP8266 or ESP32 using

ESP.wdtEnable(5000);

where 5000 is the number of milliseconds until the watchdog times out.

Call

ESP.wdgFeed();

periodically to reset the watchdog.

Posted by Uli Köhler in ESP8266/ESP32, PlatformIO

What does ESP8266 rst cause: 2 mean?

Sometimes you will see a message like

 ets Jan  8 2013,rst cause:2, boot mode:(3,6)

load 0x4010f000, len 3460, room 16 
tail 4
chksum 0xcc
load 0x3fff20b8, len 40, room 4 
tail 4
chksum 0xc9
csum 0xc9
v0007e100
~ld
7

on the ESP8266 serial line.

rst cause: 2 means that the ESP was restarted from the firmware using

ESP.restart();

Typically such a restart is intentional. Look for ESP.restart() calls in your firmware. It’s not straightforward to identify which ESP.restart() call caused the reset. I recommend to insert Serial.println() statements describing the reset cause before every call to ESP.restart(), for example:

Serial.println("Resetting due to Wifi not connecting...");
ESP.restart();

 

Posted by Uli Köhler in Electronics, ESP8266/ESP32, PlatformIO

How to fix ESP8266 PlatformIO error: WIFI_STA was not declared in this scope

Problem:

When compiling your PlatformIO firmware, you see an error message like

src/main.cpp: In function 'void setup()':
src/main.cpp:49:10: error: 'class WiFiClass' has no member named 'mode'
   49 |     WiFi.mode(WIFI_STA);
      |          ^~~~
src/main.cpp:49:15: error: 'WIFI_STA' was not declared in this scope
   49 |     WiFi.mode(WIFI_STA);
      |               ^~~~~~~~

Solution:

You included WiFi.h instead of the correct ESP8266WiFi.h. This will cause multiple issues like reboots on WiFi.begin() even if it compiles correctly.

Replace

#include <WiFi.h>

with

#include <ESP8266WiFi.h>

everywhere in your source code and does not support

WiFi.mode(WIFI_STA);

For more details, see our previous post on How to fix PlatformIO ESP8266 WiFi.h: No Such File or Directory

Posted by Uli Köhler in Electronics, ESP8266/ESP32, PlatformIO

How I fixed ESP8266 WiFi.begin() rst cause 4 on PlatformIO

I faced an issue with my PlatformIO ESP8266 firmware causing a rst cause 4 reset a few seconds after calling WiFi.begin():

ets Jan  8 2013,rst cause:4, boot mode:(3,2)

wdt reset
load 0x4010f000, len 3460, room 16 
tail 4
chksum 0xcc
load 0x3fff20b8, len 40, room 4 
tail 4
chksum 0xc9
csum 0xc9
v0007e660
~ld
7

and a subsequent reboot of the firmware. The issue repeated ad infinitum.

In my case, the issue was that I had listed WiFi as lib_deps dependency in platformio.ini:

lib_deps =
    WiFi

instead of including #include <ESP8266WiFi.h> instead of #include <WiFi.h>. See How to fix PlatformIO ESP8266 WiFi.h: No Such File or Directory for more details.

Posted by Uli Köhler in Electronics, ESP8266/ESP32, PlatformIO

How to install ArduinoOTA for ESP8266/ESP32 on PlatformIO (platformio.ini and lib_deps)

As the ESP Arduino framework already includes ArduinoOTA, there is no need to explicitly install or require ArduinoOTA.

You do not need to add anything to platformio.ini or lib_deps in order to use ArduinoOTA on the ESP8266 or ESP32.

In order to use ArduinoOTA, the minimum you need to to is to include it:

#include <ArduinoOTA.h>

then call

ArduinoOTA.begin();

in your setup() function and then call

ArduinoOTA.handle();

periodically (for example, in your loop() function).

For a more complete example, see:

Posted by Uli Köhler in ESP8266/ESP32, PlatformIO

Minimal PlatformIO ESP8266 ArduinoOTA example

This simple firmware will connect to Wifi and enable over-the-air update (OTA) using ArduinoOTA on any ESP8266 module. Use it as a starting point for your own firmware to enable OTA.

#include <Arduino.h>
#include <ArduinoOTA.h>
#include <ESP8266WiFi.h>

void setup() {
  Serial.begin(115200);
  /**
   * Connet to Wifi
   */
  WiFi.begin("MyWifiSSID", "MyWifiPassword");
  uint32_t notConnectedCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
      delay(100);
      Serial.println("Wifi connecting...");
      notConnectedCounter++;
      if(notConnectedCounter > 150) { // Reset board if not connected after 5s
          Serial.println("Resetting due to Wifi not connecting...");
          ESP.restart();
      }
  }
  Serial.print("Wifi connected, IP address: ");
  Serial.println(WiFi.localIP());
  /**
   * Enable OTA update
   */
  ArduinoOTA.begin();
}

void loop() {
  // Check for over the air update request and (if present) flash it
  ArduinoOTA.handle();
}
[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 115200

[env:d1_mini_ota]
extends = env:d1_mini
upload_protocol = espota
upload_port = 192.168.178.166

For more details on how the two targets (d1_mini and d1_mini_ota) work, see How to handle both OTA and serial upload in platformio.ini. Note that you need to enter the ESP8266’s IP address or mDNS name in order for OTA to work.

The firmware will print the Wifi IP address on the serial port, so you can use PlatformIO’s Monitor feature to view it.

Posted by Uli Köhler in Electronics, Embedded, ESP8266/ESP32, PlatformIO

How to handle both OTA and serial upload in platformio.ini

When you’re writing a PlatformIO firmware that can be uploaded using both ArduinoOTA and over the serial port, I recommend this platformio.ini setup:

Start with your normal config for serial upload:

[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 115200

And now add a new OTA target that extends the serial config, effectively inheriting the complete build configuration including the lib_deps:

[env:d1_mini_ota]
extends = env:d1_mini
upload_protocol = espota
upload_port = 192.168.178.166

In upload_port you need to enter either the IP address or the mDNS name of the board that shall be flashed. The IP address we entered (192.168.178.166) is just an example.

Also note that depending on the name of your original build target, change

extends = env:d1_mini

to the name of your original target (including env:)

Complete platformio.ini example

[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 115200

[env:d1_mini_ota]
extends = env:d1_mini
upload_protocol = espota
upload_port = 10.9.1.106

 

Posted by Uli Köhler in Electronics, ESP8266/ESP32, PlatformIO

How to fix PlatformIO ESP8266 ArduinoOTA error: stopAll is not a member of WiFiUDP

Problem:

When compiling your PlatformIO firmware, you see an error message like

/home/uli/.platformio/packages/framework-arduinoespressif8266/libraries/ArduinoOTA/ArduinoOTA.cpp: In member function 'void ArduinoOTAClass::_runUpdate()':
/home/uli/.platformio/packages/framework-arduinoespressif8266/libraries/ArduinoOTA/ArduinoOTA.cpp:268:12: error: 'stopAll' is not a member of 'WiFiUDP'
  268 |   WiFiUDP::stopAll();
      |            ^~~~~~~

Solution:

Remove WiFi from the lib_deps secton of your platform.ini. Before the fix:

lib_deps =
    ESP Async [email protected]
    [email protected]
    WiFi

After the fix:

lib_deps =
    ESP Async [email protected]
    [email protected]

Now check your source code and replace any

#include <WiFi.h>

by

#include <ESP8266WiFi.h>

in order to prevent the WiFi.h: No Such File or Directory  issue as outlined in How to fix PlatformIO ESP8266 WiFi.h: No Such File or Directory

Now you need to completely remove the .pio folder from your project directory in order to ensure a clean build:

rm -rf .pio

After that, recompile your firmware.

Posted by Uli Köhler in Arduino, Electronics, ESP8266/ESP32, PlatformIO

How to fix PlatformIO ESP8266 WiFi.h: No Such File or Directory

Problem:

When compiling your PlatformIO firmware, you see an error message like

src/main.cpp:2:10: fatal error: WiFi.h: No such file or directory

**************************************************************
* Looking for WiFi.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:WiFi.h"
* Web  > https://platformio.org/lib/search?query=header:WiFi.h
*
**************************************************************

    2 | #include <WiFi.h>

Solution:

Instead of

#include <WiFi.h>

use

#include <ESP8266WiFi.h>

Now delete the .pio folder from the project directory to ensure a clean build by using:

rm -rf .pio

In case that doesn’t help, see our article on how to fix this issue by adding the WiFi library to the dependencies: How to fix PlatformIO WiFi.h: No Such File or Directory

Posted by Uli Köhler in Arduino, Electronics, ESP8266/ESP32, PlatformIO

ESP32 JTAG pinout

The ESP32 uses the following pins for JTAG:

  • GPIO12: TDI
  • GPIO13: TCK
  • GPIO14: TMS
  • GPIO15: TDO

Source: ESP32 reference manual, section 4.10 IO_MUX Pad List

Posted by Uli Köhler in ESP8266/ESP32

What is the default PlatformIO / Arduino ESP32 TIMER_BASE_CLK?

On PlatformIO / Arduino, by default the TIMER_BASE_CLK is 80 MHz (the maximum frequency the ESP32 can run at).

If you want to verify this yourself, use this firmware:

#include <Arduino.h>

void setup() {
    Serial.begin(115200);
    Serial.println(getCpuFrequencyMhz());
}

void loop() {
}

 

Posted by Uli Köhler in Arduino, Electronics, Embedded, ESP8266/ESP32, PlatformIO

What is the default PlatformIO / Arduino ESP32 CPU clock frequency?

On PlatformIO / Arduino, by default the ESP32 clock frequency is 80 MHz (the default 240 MHzCPU frequency divided by 4)

If you want to verify this yourself, use this firmware:

#include <Arduino.h>

void setup() {
    Serial.begin(115200);
    Serial.println(TIMER_BASE_CLK);
}

void loop() {
}

 

Posted by Uli Köhler in Arduino, Electronics, Embedded, ESP8266/ESP32, PlatformIO

How to fix PlatformIO serial monitor scrambled output

Problem:

When using the Monitor function of platformIO, you see strange characters instead of strings being printed, for example:

)�
�␜ܠ��J��1��1!y��!���!��

Solution:

This issue almost always appears due to the Monitor function using the wrong UART speed. You can see from the log in our screenshot above:

--- Miniterm on /dev/ttyUSB0  9600,8,N,1 ---

that PlatformIO is using 9600 baud in this case – but your microcontroller is sending data at a faster speed (or, rarely at a slower speed).

Most firmwares using serial IO use 115200 baud, so that’s what I’d recommend to try first, but if that doesn’t work, look out for config options named baud rate or similar, or for lines of code like

Serial.begin(57600);

in the firmware.

In order to change the Monitor UART speed, open platformio.ini and add

monitor_speed = 115200

Full platformio.ini example for ESP32:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

After that, restart the Monitor function.

Posted by Uli Köhler in Arduino, Electronics, Embedded, ESP8266/ESP32, PlatformIO, STM32

How to fix ESP32 PlatformIO error: ‘LED_BUILTIN’ was not declared in this scope

Problem:

When trying to compile your ESP32 firmware using PlatformIO, you see this error message:

src/main.cpp: In function 'void setup()':
src/main.cpp:22:13: error: 'LED_BUILTIN' was not declared in this scope
     pinMode(LED_BUILTIN, OUTPUT);

Solution:

Important: Some ESP32 boards such as the ESP32-DevKitC have no builtin LED at all ! Either connect an external LED or find another method of doing whatever you are intending to do.

On most ESP32 boards that do have a builtin LED, the LED is connected to pin 2 – however, PlatformIO does not define LED_BUILTIN p. In order to fix the issue, define LED_BUILTIN yourself by using

#define LED_BUILTIN 2

at the top of every file where you see this error.

Posted by Uli Köhler in Electronics, Embedded, ESP8266/ESP32, PlatformIO