Electronics

How to make Angular work with ESP32 SPIFFS / ESPAsyncWebserver

The main issue when using Angular web UIs is that the resulting files get too large, hence building the filesystem image will fail with SPIFFS_write error(-10001): File system is full.

Using these tips, I could get an Angular PrimeNG app to fit into a 4MB flash ESP32 module without any custom partition table and without any other crazy hacks! Even the fonts & PrimeNG icons fit into the SPIFFS easily, with a total of only 380 kB of the approximately 1.5 MB being consumed.

File compression

The number one most important tip is that you can just gzip -9 the files from the angular dist directory and ESPAsyncWebserver will automatically handle decompressing them!

This is my platformio.ini:

[env:esp32dev]
platform = espressif32
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
board = esp32dev
framework = arduino
board_build.filesystem = littlefs
lib_deps =
    esphome/AsyncTCP-esphome@^1.2.2
    esphome/ESPAsyncWebServer-esphome@^2.1.0
    [email protected]
upload_speed = 460800
monitor_speed = 115200

This is my angular build script:

#!/bin/sh
ng build --aot --build-optimizer --optimization --progress --output-hashing none
gzip -9 dist/**/*

This is where I tell ESPAsyncWebserver (note that you should use the esphome fork) to serve files statically:

server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");

Other tips

In order to make your life easier managing the data directory with both Angular files and other files, see How to link Angular project dist directory to PlatformIO SPIFFS data directory

You can use purgecss but compression works so well that it isn’t really worth both the risk of accidentally removing some CSS rules which you manually need to whitelist. Before discovering how well compression worked, I started to manually remove CSS rules from the PrimeNG theme file. This worked fine, but the SPIFFS still wasn’t small enough.

Often you can save space by deleting.

For example, primeicons.svg and primeicons.ttf are two different formats with the same content. Note that some (especially older, and some mobile) browsers don’t support all formats, hence it’s rather risky to remove them if you need to support multiple platforms.

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

How to fix SPIFFS_write error(-10001): File system is full.

Are you using Angular and seeing this error message? See How to make Angular work with ESP32 SPIFFS / ESPAsyncWebserver

The error message

SPIFFS_write error(-10001): File system is full.

should be mostly self-explanatory:

The files you are trying to put into the image exceed the maximum size of the filesystem image.

There are two possible solutions:

  • Decrease the size of the files, either by minifying files where this is possible, or removing some files altogether.
  • Increase the allowable size of the filesystem image e.g. by using a ESP32 module with more flash (e.g. 8MB instead of 4MB), and/or using a partition table with a larger file partition.
Posted by Uli Köhler in ESP8266/ESP32, PlatformIO

How to fix SPIFFS_write error(-10010): unknown

Problem:

When trying to build a SPIFFS image, you see an error message like

SPIFFS_write error(-10010): unknown
error adding file!
Error for adding content from www!
/www/Inter-Light.27083fa6375bb9ef.woff2

Solution:

SPIFFS_write error(-10010): unknown means that the filename is too long. SPIFFS only supports filenames with a maximum of 32 characters.

In our example, Inter-Light.27083fa6375bb9ef.woff2 is 32 characters long.

Hence, the solution is to shorten the filename of every file with a filename (including extension) of 32+ characters.

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

How to check if value exists in ArduinoJSON

Option 1: Use containsKey()

if(doc.containsKey("speed")) {
    float value = doc["speed"];
} else {
    // Speed does not exist
}

Option 2: Use default values

// If doc["speed"] does not exist, speed will be NaN
float speed = doc["speed"] | nanf(nullptr);
if(speed != nanf(nullptr)) {
   // TODO do something with speed
}

 

Posted by Uli Köhler in Arduino, Embedded

How to fix ESPAsyncWebserver undefined reference to `SHA1Init’

Problem:

When using ESPAsyncWebserver with websockets, you see the following error messages while linking:

Linking .pio/build/esp32dev/firmware.elf
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(AsyncWebSocket.cpp.o):(.literal._ZN22AsyncWebSocketResponseC2ERK6StringP14AsyncWebSocket+0x10): undefined reference to `SHA1Init'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(AsyncWebSocket.cpp.o):(.literal._ZN22AsyncWebSocketResponseC2ERK6StringP14AsyncWebSocket+0x18): undefined reference to `SHA1Update'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(AsyncWebSocket.cpp.o):(.literal._ZN22AsyncWebSocketResponseC2ERK6StringP14AsyncWebSocket+0x1c): undefined reference to `SHA1Final'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(AsyncWebSocket.cpp.o): in function `AsyncWebSocketResponse::AsyncWebSocketResponse(String const&, AsyncWebSocket*)':
/home/uli/dev/My-Firmware/.pio/libdeps/esp32dev/ESP Async WebServer/src/AsyncWebSocket.cpp:1269: undefined reference to `SHA1Init'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: /home/uli/dev/My-Firmware/.pio/libdeps/esp32dev/ESP Async WebServer/src/AsyncWebSocket.cpp:1270: undefined reference to `SHA1Update'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: /home/uli/dev/My-Firmware/.pio/libdeps/esp32dev/ESP Async WebServer/src/AsyncWebSocket.cpp:1271: undefined reference to `SHA1Final'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(WebAuthentication.cpp.o):(.literal._ZL6getMD5PhtPc+0x4): undefined reference to `mbedtls_md5_starts'
/home/uli/.platformio/packages/[email protected]+2021r2-patch3/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/esp32dev/lib67a/libESP Async WebServer.a(WebAuthentication.cpp.o): in function `getMD5(unsigned char*, unsigned short, char*)':
/home/uli/dev/My-Firmware/.pio/libdeps/esp32dev/ESP Async WebServer/src/WebAuthentication.cpp:73: undefined reference to `mbedtls_md5_starts'

Solution:

This is a bug in the ESPAsyncWebserver official library, which is not regularly maintained. But you can use use the esphome fork of ESPAsyncWebserver and their fork of AsyncTCP instead.

In platformio.ini, instead of

lib_deps =
    ESP Async [email protected]

use the esphome fork and AsyncTCP:

lib_deps =
    esphome/AsyncTCP-esphome@^1.2.2
    esphome/ESPAsyncWebServer-esphome@^2.1.0

After that, try to build / upload, the linking errors should be gone.

Full platformio.ini example

[env:esp32dev]
platform = espressif32
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
board = esp32dev
framework = arduino
lib_deps =
    esphome/AsyncTCP-esphome@^1.2.2
    esphome/ESPAsyncWebServer-esphome@^2.1.0
    [email protected]
upload_speed = 460800
monitor_speed = 115200

 

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

How to compute Ethernet CRC32 on ESP32 (0x04C11DB7)

The ESP32 ROM provides functionality for calculating CRC8, CRC16 & CRC32 checksums, but that functionality is poorly documented.

By educated trial and error and comparing with Arduino_CRC32 (which implements the Ethernet CRC32 algorithm with polynomial 0x04C11DB7 based on the pyCRC library) I found out how to compute the CRC32 checksum using the Ethernet polynomial.

First, include crc.h:

#include <esp32/rom/crc.h>

Now given a buffer with length length, use this code:

uint32_t romCRC = (~crc32_le((uint32_t)~(0xffffffff), (const uint8_t*)buffer, length))^0xffffffff;

Complete example code

This PlatformIO/Arduino code compares the result of the ESP32 ROM CRC32 functiolity by printing out both results on the serial port.

#include <Arduino.h>
#include <esp32/rom/crc.h>
#include <Arduino_CRC32.h>

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

void loop() {
    const char* data = "ABCDEFGHIJ";
  
    // Compute using ESP32 ROM CRC library
    uint32_t romCRC = (~crc32_le((uint32_t)~(0xffffffff), (const uint8_t*)data, 8))^0xffffffFF;

    // Compute using Arduino_CRC32 library (based on pyCRC)
    Arduino_CRC32 crc32;
    uint32_t libCRC = crc32.calc((uint8_t const *)data, 8);

    // Print out btoh v
    char crcBytes[4];
    memcpy(crcBytes, &romCRC, sizeof(uint32_t));
    Serial.printf("ROM CRC: %02X %02X %02X %02X\n", crcBytes[0], crcBytes[1], crcBytes[2], crcBytes[3]);

    memcpy(crcBytes, &libCRC, sizeof(uint32_t));
    Serial.printf("Lib CRC: %02X %02X %02X %02X\n", crcBytes[0], crcBytes[1], crcBytes[2], crcBytes[3]);
    Serial.println("\n");
    delay(500);
}
[env:esp32dev]
platform = espressif32
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    arduino-libraries/Arduino_CRC32@^1.0.0

Example output:

ROM CRC: 1C B6 DC 68
Lib CRC: 1C B6 DC 68

 

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

How to turn DL3021 off via SCPI/LXI

In order to turn off the DL3021 electronic load (as in: disable any constant current/resistance/voltage/power function) via LXI, run the following command:

:SOURCE:INPUT:STATE Off

PyVISA example

For more info, see PyVISA Rigol DL3021 via LXI (TCP SCPI) example

#!/usr/bin/env python3

rm = pyvisa.ResourceManager()
inst = rm.open_resource("TCPIP0::192.168.178.112::INSTR")
# Query if instrument is present
# Prints e.g. "RIGOL TECHNOLOGIES,DL3021,DL3A204800938,00.01.05.00.01"
print(inst.query("*IDN?"))

# Turn electronic load off
inst.write(":SOURCE:INPUT:STATE Off")

 

Posted by Uli Köhler in Analog, Electronics, Networking, Python

How to fix STM32CubeIDE: Error in final lauch sequence – Setup exceptions (debugger)

Problem:

While trying to start the STM32CubeIDE debugger, you see the following error message just after clicking Continue :

Error in final launch sequence

Setup exceptions
Setup exceptions

Solution:

This error occurs because you clicked Continue too early. The flashing process was still underway when you clicked Continue. Therefore, the solution is to wait until the stack trace shows main() and only then click Continue.

Posted by Uli Köhler in STM32

What is SYS_STARTUP_FN() on the ESP32?

SYS_STARTUP_FN() is being called at the end of call_start_cpu0() during the app startup process (after the bootloader has already finished executing).

It essentially just calls the function pointer g_startup_fn for the current core:

#define SYS_STARTUP_FN()  ((*g_startup_fn[(cpu_hal_get_core_id())])())
g_startup_fn, in turn, is defined to basically start_cpu0 and start_cpu1 in this rather convoluted but still conceptually easy code.

 

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

Where is esp_startup_start_app_common() started on the ESP32 (ESP-IDF)?

esp_startup_start_app_common() is called in start_cpu0_default(), which is located in components/esp_system/startup.c
Note that start_cpu0_default() is a weak alias for start_cpu0().

For version 4.4.1 of the ESP-IDF, this is the link to the location where it is called.

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

Where is esp_startup_start_app_common() started on the ESP32 (ESP-IDF)?

esp_startup_start_app_common() is called in esp_startup_start_app(), which is a function specific to the architecture (xtensa or riscv).

For version 4.4.1 of the ESP-IDF, this is the link to the location where it is called for the xtensa port.

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

Where is the bootloader_init() source code for the ESP32?

In the ESP32 bootloader_start.c, you can see that bootloader_init() is called during bootloader startup:

if (bootloader_init() != ESP_OK) {
    bootloader_reset();
}

The implementation of bootloader_init() is sightly harder to find because it is not located in components/bootloader but components/bootloader_support, within a specific platform-specific subdirectory, for example in components/bootloader_support/src/esp32s2/bootloader_esp32s2.c for the ESP32S2 – Github link to the v4.4.1 source code.

 

 

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

Where is main_task() started on the ESP32 (ESP-IDF)?

The FreeRTOS main taskmain_task() (which in turn starts app_main()) is  started in esp_startup_start_app_common():

portBASE_TYPE res = xTaskCreatePinnedToCore(&main_task, "main",
                                            ESP_TASK_MAIN_STACK, NULL,
                                            ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);

 

For version 4.4.1 of the ESP-IDF, this is the link to the location where it is called.

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

What are the default UART0 RX/TX pins for the ESP32S2?

As shown in the ESP32S2 datasheet:

  • U0TXD (UART 0 transmit) is GPIO43, which is pin 48 in the QFN56 package#
  • U0RXD is GPIO44, which is pin 49 in the QFN56 package
Posted by Uli Köhler in Embedded, ESP8266/ESP32

What is SPI flash manufacturer C2 / Device 2016

Flash manufacturer C2 is Macronix.

I have seen Manufacturer C2 / device ID 2016 via esptool.py flash_id in a Macronix MX25L3233FM1I-08Q 32-Mbit (4MByte) QSPI flash.

Posted by Uli Köhler in Electronics

What is SPI flash manufacturer 20 / Device ID 4016

I have seen Manufacturer 20, device 4016 via esptool.py flash_id in a SOICW-8 flash device in a ESP8266 module. It is labeled XMC and P50H32C

It is a 32Mbit (4Mbyte) flash device.

Posted by Uli Köhler in Embedded

Which pins are USB D+/D- on the ESP32S2?

As listed in the ESP32S2 datasheet,

  • GPIO19 is USB-D-
  • GPIO20 is USB-D+
Posted by Uli Köhler in Electronics, Embedded, ESP8266/ESP32

How to fix PlatformIO No such file or directory bootloader__80m.bin/bootloader__40m.bin

Problem:

While trying to upload/flash an ESP32(S2/S3) project using PlatformIO with a platformio.ini like this:

[env:esp32-s2-saola-1]
platform = espressif32
board = esp32-s2-saola-1
framework = arduino

you see an error message like

esptool write_flash: error: argument <address> <filename>: [Errno 2] No such file or directory: '/home/uli/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32s2/bin/bootloader__80m.bin'
*** [upload] Error 2

or

esptool write_flash: error: argument <address> <filename>: [Errno 2] No such file or directory: '/home/uli/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/bin/bootloader__40m.bin'

Solution:

This is a bug in version 2.0.4 of the arduino-espressif32 framework. In order to work around it, use version 2.0.3 until this bux is fixed. You can do this by appending

platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3

to platformio.ini. Full example:

[env:esp32dev]
platform = espressif32
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
board = esp32dev
framework = arduino

 

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

How to initialize struct sockaddr_in using initializer lists

This way of initializing a struct sockaddr_in uses modern C and avoids all the raw of days that should long be forgotten, at least for most applications. I use this approach successfully e.g. for ESP32 microcontrollers.

struct sockaddr_in server_addr = {
    .sin_family = AF_INET,
    .sin_port = htons(46118),
    .sin_addr = {.s_addr = htonl(INADDR_ANY)}
};

 

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