diff --git a/content/post/2025-micropython-esp32.md b/content/post/2025-micropython-esp32.md new file mode 100644 index 0000000..1f26591 --- /dev/null +++ b/content/post/2025-micropython-esp32.md @@ -0,0 +1,93 @@ +--- +title: "Installing Micropython on the Super Mini ESP32-S3" +description: +date: 2025-07-20T02:30:43+02:00 +image: +math: +license: +hidden: false +comments: true +draft: false +--- + +## 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. + +``` +❯ 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](https://github.com/glenn20/mp-image-tool-esp32) tool to check existing firmwares and modify partition sizes. + +I confirmed my suspicion: + +``` +❯ 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) +``` + + +``` +❯ 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`: + +``` +uv tool run mpremote mip install aioble +``` + +We can check that we are fully utilizing the 2MB of PSRAM: + +``` +❯ 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`).