## Installing Micropython on the Super Mini ESP32-S3

## Context

Many years ago I bought a “bargain” bluetooth scale. I wanted a way to automatically log my weight, and the Xiaomi equivalent was more than twice as expensive at the time. The problem is that it came with a very basic and somewhat sketchy software that required signing up, and none of the typical apps (openscale, gadgetbridge…) supported it. I looked at how to reverse engineer it, but I did not have much time back then, and I wrote it off as a “future project”. Luckily, it had a screen, so I’ve been using it as a regular scale. I did not even bother to check the body composition metrics, because I got a very depressing reading of 35% body fat.

Flash forward to today, when I decided to finally work on it. In addition to trying nrfConnect, I installed the app to see the actual values.. It turns out the composition data was completely off!! When a reading starts, the app needs to tell the scale some basic data about you (age, height, sex). Unfortunately, there is no way (that I know of) to set a default user, so when the phone is not connected, it goes back to the wildly inaccurate reading.

So, I decided to connect one of my idle ESP32 boards to it, to interface with the scale through bluetooth (sending the right user data) and logging the results somewhere through WiFi.

## The problem

Installing the firmware was not too difficult, after I got the board to properly register.

    1
    2
    3
    4
    5
    6

    ❯ uv tool run esptool.py --port /dev/ttyACM4 erase-flash
    ...
    Chip type:          ESP32-S3 (QFN56) (revision v0.2)
    Features:           Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded Flash 4MB (XMC), Embedded PSRAM 2MB (AP_3v3)
    ...
    ❯ uv tool run esptool.py --port /dev/ttyACM4 --baud 460800 write_flash 0 ESP32_GENERIC_S3-20250415-v1.25.0.bin

But then, I kept getting this error on boot: `OSError: (-24579, 'ESP_ERR_FLASH_NOT_INITIALISED')`. And I could not copy any files or install any packages.

After some research, I narrowed the possibilities to:

- A wrong partition table, most likely due to the fact that my board only has 4MB of flash.
- Using an unsupported type of PSRAM (less likely)

## The solution

At this point, I could either try to compile the firmware with a custom partition (kind of tedious), or I could use the wonderful mp-image-tool-esp32 tool to check existing firmwares and modify partition sizes.

I confirmed my suspicion:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14

    ❯ uv tool run mp-image-tool-esp32  ~/Downloads/ESP32_GENERIC_S3-20250415-v1.25.0.bin
    Running mp-image-tool-esp32 v0.1.1 (Python 3.12.11).
    Opening /home/j/Downloads/ESP32_GENERIC_S3-20250415-v1.25.0.bin...
    Found esp32s3 firmware file (8MB flash).
                            Partition table (flash size: 8MB):
    ╭──────────┬──────┬─────────┬──────────┬──────────┬──────────┬───────┬───────────╮
    │ Name     │ Type │ SubType │   Offset │     Size │      End │ Flags │           │
    ├──────────┼──────┼─────────┼──────────┼──────────┼──────────┼───────┼───────────┤
    │ nvs      │ data │ nvs     │   0x9000 │   0x6000 │   0xf000 │   0x0 │ (24.0 kB) │
    │ phy_init │ data │ phy     │   0xf000 │   0x1000 │  0x10000 │   0x0 │  (4.0 kB) │
    │ factory  │ app  │ factory │  0x10000 │ 0x1f0000 │ 0x200000 │   0x0 │  (1.9 MB) │
    │ vfs      │ data │ fat     │ 0x200000 │ 0x600000 │ 0x800000 │   0x0 │  (6.0 MB) │
    ╰──────────┴──────┴─────────┴──────────┴──────────┴──────────┴───────┴───────────╯
    Micropython app fills 79.3% of factory partition (410 kB free)

    1
    2

    ❯ uv tool run mp-image-tool-esp32 -f 4MB --resize vfs=2MB ESP32_GENERIC_S3-20250415-v1.25.0.bin
    ❯ uv tool run esptool.py --port /dev/ttyACM4 --baud 460800 write_flash 0 ESP32_GENERIC_S3-20250415-v1.25.0-4MB-resize=vfs=2097152.bin

After that, I was able to install new packages with `mpremote` and `mip`:

    1

    uv tool run mpremote mip install aioble

We can check that we are fully utilizing the 2MB of PSRAM:

    1
    2

    ❯ uv tool run mpremote exec 'import gc;print(gc.mem_free(), "bytes")'
    2061456 bytes

If you see much less than that, you are probably using the wrong variant (try `SPIRAM_OCT`).