PlatformIO

How to print 16-bit uint16_t as four hex digits in Arduino

When using Arduino, you often want to print the hex value of a 16-bit value such as a uint16_t, consisting of four hex digits. For example, if you have uint16_t val = 2022;, you intend to print 07E6.

In Arduino you can do that using Serial.printf() with %04x as format specifier.

Furthermore, you typically want to invert the byte order of the uint16_t using __builtin_bswap16()since it’s more inuitive to write the hex value MSB-first (big-endian) while most hardware platforms represent the uint16_t as LSB-first (little-endian):

Serial.printf("val = %04x\r\n", __builtin_bswap16(val));

When using printf, %x means to format the value as hex. 04 means to pad the value with 0s up to a length of 4 digits. If you would format 2022 using just %x, it would print 7E6  instead of 07E5. This is why you need to use %04x instead.

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

How to print byte as two hex digits in Arduino

In embedded programming, you often want to print the hex value of a byte, consisting of two hex digits. For example, if you have uint8_t val = 14;, you intend to print 0x0E.

In Arduino you can do that using Serial.printf() with %02x as format specifier:

Serial.printf("val = %02x\r\n", val);

When using printf, %x means to format the value as hex. 02 means to pad the value with 0s up to a length of 2 digits. If you would format 14 using just %x, it would print E  instead of 0E. This is why you need to use %02x.

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

Arduino I2C: Wire.endTransmission() before or after Wire.requestFrom() ?

The correct order to call Wire commands in Arduino is:

Wire.beginTransmission(MY_I2C_ADDR);
Wire.write(addr);
Wire.endTransmission();
Wire.requestFrom(MY_I2C_ADDR, 1); // Request one byte
delay(5); // Wait for data to be available
uint8_t value = Wire.read();

So you call Wire.endTransmission() after Wire.write() and call Wire.requestFrom() directly after Wire.endTransmission()

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

Teensy 4.x Quadrature Encoder minimal example in PlatformIO/Arduino

This example uses PlatformIO and the Teensy-4.x-Quad-Encoder-Library to implement a hardware quadrature encoder on pins 0 and 1. Remember that the teensy will be destroyed if you use 5V encoder signals. You can only use 3.3V signals!

#include <Arduino.h>
#include <QuadEncoder.h>

QuadEncoder encoder(1, 0, 1, 0);

void setup() {
    Serial.begin(115200);
    encoder.setInitConfig();
    encoder.init();
}

void loop() {
    int32_t position = encoder.read();
    // Compute position in mm and print it
    constexpr float mm_per_count = 0.0012;
    float mm = position * mm_per_count;
    Serial.printf("Position: %+3.4f (count %ld)\r\n", mm, position);
    // Print every 50ms
    delay(50);
}
[env:teensy41]
platform = teensy
board = teensy41
framework = arduino
monitor_speed = 115200
lib_deps =
    git+https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library

 

Posted by Uli Köhler in C/C++, Electronics, Embedded, PlatformIO, Teensy

How to add GitHub repository to PlatformIO lib_deps

Use git+https like this:

lib_deps =
    git+https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library

 

Posted by Uli Köhler in Electronics, Embedded, PlatformIO

How to set pin speed/alternate function in STM32 Arduino (PlatformIO)

You can use the STM32 HAL (STM32CubeMX) even when using Arduino as a framework for STM32 boards in PlatformIO:

GPIO_InitTypeDef pinInit = {
  .Pin = GPIO_PIN_8,
  .Mode = GPIO_MODE_AF_PP,
  .Pull = GPIO_NOPULL,
  .Speed = GPIO_SPEED_FREQ_VERY_HIGH,
  .Alternate = GPIO_AF1_TIM1
};
HAL_GPIO_Init(GPIOA, &pinInit);

Use

#include <stm32f4xx_hal_gpio.h>

in order to include the HAL functions.

Posted by Uli Köhler in Arduino, Electronics, PlatformIO, STM32

How to pass firewall using PlatformIO espota ArduinOTA upload

ArduinoOTA’s protocol tries to connect to the host which is trying to program the device on a randomly chosen port. This often leads to the packets being filtered in a firewall since no rule exists to pass the packet and they are not related to an existing connection.

You could create a firewall rule to pass all traffic from the ESP8266/ESP32 to the programming host, but that is extremely insecure since it allows a hacked IoT device to hack your devices.

In order to fix it, add a fixed host port in platformio.ini using

upload_flags = --host_port=55910

and add these firewall rules:

allow from <programming host> to <IoT device> port 55910 TCP
allow from <IoT device> to <programming host> port 55190 TCP

Complete platformio.ini example:

[env:d1_mini_ota]
extends = env:d1_mini
upload_protocol = espota
upload_port = 192.168.178.25
upload_flags = --host_port=55910

 

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

How to use ESP32 as USB-to-UART converter in PlatformIO

The ESP32 can easily be used as USB-to-UART converter. Note that the ESP32 does not feature an USB interface by itself and ESP32 boards with an onboard USB connector just use an USB-to-UART converter (this is a separate chip on the board). Hence, from the ESP32 perspective, it

You can map UART TX and RX to any GPIO pin on the ESP32. While there are very, very slight performance advantages to using a set of predefined pins, this does not really matter in practice. In this example, we will use pin GPIO2 for UART RX and pin GPIO4 for UART TX.

Basically, the code is just copying bytes between Serial2 and Serial (connected to USB):

// Copy bytes incoming via PC serial
while (Serial.available() > 0) {
  Serial2.write(Serial.read());
}
// Copy bytes incoming via UART serial
while (Serial2.available() > 0) {
  Serial.write(Serial2.read());
}

Full example

#include <Arduino.h>

#define UART_RX_PIN 2 // GPIO2
#define UART_TX_PIN 4 // GPIO4

void setup() {
  // Serial connects to the computer
  Serial.begin(115200);
  // Serial2 is the hardware UART port that connects to external circuitry
  Serial2.begin(115200, SERIAL_8N1,
                UART_RX_PIN,
                UART_TX_PIN);
}

void loop() {
  // Copy byte incoming via PC serial
  while (Serial.available() > 0) {
    Serial2.write(Serial.read());
  }
  // Copy bytes incoming via UART serial
  while (Serial2.available() > 0) {
    Serial.write(Serial2.read());
  }
}

Regarding platformio.ini, we just need to set the monitor_speed to match the value in Serial.begin(115200);:

[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200

 

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

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

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 I fixed PlatformIO Arduino

Problem:

When uploading an Atmel AVR firmware like for the Arduino Uno using PlatformIO, you see an error message like

Auto-detected: /dev/ttyACM0
Uploading .pio/build/uno/firmware.hex
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 4 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 5 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 6 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 7 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 8 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 9 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0x00

avrdude done.  Thank you.

*** [upload] Error 1

or

Auto-detected: /dev/ttyUSB0
Uploading .pio/build/uno/firmware.hex
avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 4 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 5 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 6 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 7 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 8 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 9 of 10: not in sync: resp=0x00
avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0x00

avrdude done.  Thank you.

*** [upload] Error 1

Solution

This issue occurs if:

  • Either there is no bootloader on the board or the bootloader is damaged or
  • The wrong upload speed for this bootloader is selected

Note that  you can flash a new/updated bootloader using the Arduino IDE, overwriting the old / defective bootloader. You can do that using an AVR programmer, or you can do it using another Arduino. This will make working with the board much easier in general, but it will most likely fix your programming issues. In case you flash a new bootloader, the correct upload_speed setting in platformio.ini will typically be upload_speed = 115200.

In case you want to work with the bootloader currently present on the board, you need to select the correct upload_speed setting.

Depending on the age of the bootloader on the board, it will typically have either 115200, 57600 or 19200 baud speed.

In order to fix the issue, edit platformio.ini and add

upload_speed = 115200

then restart the upload. In case this doesn’t work, try

upload_speed = 57600

and

upload_speed = 19200

Rarely, the following setting work:

upload_speed = 9600

or

upload_speed = 230400

In case none of these settings work at all, either you are facing a hardware defect or you need to flash a new bootloader onto the board. I recommend to always try to flash a bootloader first before throwing away the board.

Posted by Uli Köhler in Arduino, Electronics, Embedded, 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