diff --git a/docs/explanation/architecture/moonbase.md b/docs/explanation/architecture/moonbase.md index 14a3fb7bc..c05c3e453 100644 --- a/docs/explanation/architecture/moonbase.md +++ b/docs/explanation/architecture/moonbase.md @@ -32,7 +32,7 @@ Dual-OTA spends half the app area on a second copy of the firmware that is idle A 4 MB board has no choice, having room for one application and not two, and its app slot grows by a third in exchange. On a **16 MB** board the choice is deliberate rather than forced, and the freed 4 MB goes to the filesystem, 11 MB rather than 7. -Which boards use MoonBase is a per-variant decision recorded in `moondeck/build/build_esp32.py` rather than a property of flash size. Today the 4 MB classic, the S3-Zero and `esp32-16mb` use it, and it may become the default everywhere. +Which boards use MoonBase is a per-variant decision recorded in `moondeck/build/build_esp32.py` rather than a property of flash size; [the build system](../../reference/build-system.md) lists the variants that use it. It may become the default everywhere. ## The update cycle diff --git a/docs/reference/MIGRATING.md b/docs/reference/MIGRATING.md index 96750b5b2..c21540431 100644 --- a/docs/reference/MIGRATING.md +++ b/docs/reference/MIGRATING.md @@ -31,6 +31,12 @@ For a device still on old firmware (no Backup button yet), the [installer page]( ## v7.0.0 +### The Adafruit MatrixPortal S3 has its own firmware, `esp32s3-n8r2` + +**Action: *nothing*; to use the board's whole 8 MB, *install over USB* once with *Erase chip first* ticked.** + +A MatrixPortal ran the `esp32s3-zero` image, which keeps working on it but uses 4 MB of its 8. The `esp32s3-n8r2` image has a 3 MB app slot and 4 MB of filesystem. Its partition table differs from the Zero's, which an update over the air never rewrites, so moving takes one install over USB, and the device comes back unprovisioned: take a backup first and restore it after. + ### Network Send fills each universe with `channels_per_universe` channels exactly **Action: on a Network Send driving fixtures whose channels do not divide 510 over Art-Net or E1.31, such as RGBW strips (4) or a 13-channel moving head, set `channels_per_universe` to a multiple of the fixture's channels, 508 or 507, where the receiver needs each fixture inside one universe. RGB, RGBWW and 6-channel fixtures need nothing.** diff --git a/docs/reference/build-system.md b/docs/reference/build-system.md index 9eb88f6f1..2282024df 100644 --- a/docs/reference/build-system.md +++ b/docs/reference/build-system.md @@ -67,7 +67,7 @@ The Ethernet PHY and its pins are runtime settings. Each firmware carries the dr ## MoonBase and the partition layout -Some variants carry [MoonBase](../explanation/architecture/moonbase.md), the second boot image: `esp32`, `esp32-eth`, `esp32-wrover`, `esp32-pico`, `esp32-16mb`, `esp32s3-zero`, `esp32c3`, and `qemu`, which is emulated. On those, `flash_esp32.py` writes the whole layout in one pass: the app in the large `ota_0` slot, MoonBase in `factory`, and an otadata that boots the app. A network update writes the app and never the partition table, so a device on an older table adopts a new one only through a full serial flash. On the 4 MB classic that also moves the filesystem, and the device comes back unprovisioned. +Some variants carry [MoonBase](../explanation/architecture/moonbase.md), the second boot image: `esp32`, `esp32-eth`, `esp32-wrover`, `esp32-pico`, `esp32-16mb`, `esp32s3-zero`, `esp32s3-n8r2`, `esp32c3`, and `qemu`, which is emulated. On those, `flash_esp32.py` writes the whole layout in one pass: the app in the large `ota_0` slot, MoonBase in `factory`, and an otadata that boots the app. A network update writes the app and never the partition table, so a device on an older table adopts a new one only through a full serial flash. On the 4 MB classic that also moves the filesystem, and the device comes back unprovisioned. ## ESP-IDF version diff --git a/docs/reference/hardware/firmware-variants.md b/docs/reference/hardware/firmware-variants.md index 0a483237f..b9eb00915 100644 --- a/docs/reference/hardware/firmware-variants.md +++ b/docs/reference/hardware/firmware-variants.md @@ -12,6 +12,7 @@ Selected by `build_esp32.py --firmware `, reported by `SystemModule.firmwar | `esp32-eth` | classic | Ethernet only, WiFi excluded | | `esp32-16mb` | classic, 16 MB flash | WiFi and Ethernet | | `esp32s3-n16r8`, `esp32s3-n8r8` | S3 | WiFi and W5500 SPI Ethernet | +| `esp32s3-zero`, `esp32s3-n8r2` | S3 with quad PSRAM: 4 MB flash on the Zero, 8 MB on the N8R2 | WiFi only | | `esp32s3-n32r16` | S3 WROOM-2 N32R16V: 32 MB octal flash, 16 MB octal PSRAM | WiFi and W5500 SPI Ethernet; GPIO35 to 37 belong to the octal flash and PSRAM | | `esp32p4rev1-eth` | P4 rev 1 (Waveshare ESP32-P4-NANO, Waveshare ESP32-P4-ETH) | Ethernet only | | `esp32p4rev1-eth-wifi` | P4 rev 1 | Ethernet, plus WiFi via the on-board ESP32-C6 over esp_hosted | diff --git a/docs/reference/hardware/hub75-panels.md b/docs/reference/hardware/hub75-panels.md index 0602aec3d..a7786da4c 100644 --- a/docs/reference/hardware/hub75-panels.md +++ b/docs/reference/hardware/hub75-panels.md @@ -15,7 +15,7 @@ The chip needs an LCD_CAM or a Parlio block: an S3, a P4 or an S31. The classic Three boards have published pin maps, which the `board` select fills in: - **MoonHub75**, a Lilygo T7-S3 on a passive adapter. It also carries an INMP441 microphone socket on IO10/11/12, so an audio-reactive effect and a panel run together. -- **Adafruit MatrixPortal S3.** Three of its lines sit on GPIO 35 to 37, which the octal-PSRAM images hold, so it runs the `esp32s3-zero` image. +- **Adafruit MatrixPortal S3.** Three of its lines sit on GPIO 35 to 37, which the octal-PSRAM images hold, so it runs the `esp32s3-n8r2` image, built for its quad-PSRAM N8R2 part. - **Waveshare ESP32-S3 RGB Matrix**, SKU 34422. The Waveshare ESP32-S3-Matrix is a different product, with an onboard 8x8 matrix and no HUB75 connector. A published map hides the pin rows, since those lines are soldered; they stay bound and still drive the panel. The generic per-chip sets and Custom show them. A line on a pin the chip wires to flash or PSRAM is refused before init, with the line named. Free pins per chip: [GPIO usage](gpio-usage.md). diff --git a/esp32/partitions/esp32dev_8mb_moonbase.csv b/esp32/partitions/8mb_moonbase.csv similarity index 93% rename from esp32/partitions/esp32dev_8mb_moonbase.csv rename to esp32/partitions/8mb_moonbase.csv index 6517bc2d8..5abda9007 100644 --- a/esp32/partitions/esp32dev_8mb_moonbase.csv +++ b/esp32/partitions/8mb_moonbase.csv @@ -1,4 +1,4 @@ -# MoonBase on a classic ESP32 with 8 MB flash (the ESP32-PICO-V3-02's embedded part). +# Partition table for any 8 MB board WITH MoonBase (the classic ESP32-PICO-V3-02, the S3 N8R2). # # The same trade the 4 MB and 16 MB MoonBase tables make: dual-OTA spends half the app area on a # second copy of the firmware that sits idle except during an update, where MoonBase is a small diff --git a/esp32/sdkconfig.defaults b/esp32/sdkconfig.defaults index 6d35a70cf..1c85d9b0a 100644 --- a/esp32/sdkconfig.defaults +++ b/esp32/sdkconfig.defaults @@ -26,7 +26,6 @@ CONFIG_LOG_DEFAULT_LEVEL_INFO=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/esp32dev.csv" CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y -CONFIG_ESPTOOLPY_FLASHSIZE="4MB" # Task watchdog: PANIC on a wedged task so the device self-heals (reboot + backtrace) instead of hanging # SILENTLY forever: a silent hang looks dead and needs a physical power-cycle, the worst failure mode diff --git a/esp32/sdkconfig.defaults.esp32-pico b/esp32/sdkconfig.defaults.esp32-pico index 45189e2c9..90809932d 100644 --- a/esp32/sdkconfig.defaults.esp32-pico +++ b/esp32/sdkconfig.defaults.esp32-pico @@ -1,18 +1,12 @@ # ESP32-PICO-V3-02: classic ESP32 (Xtensa LX6) in a SiP package with 8 MB flash and 2 MB PSRAM # both EMBEDDED in the chip. The QuinLED Dig-Next-2 carries one. -# Append to sdkconfig.defaults + sdkconfig.defaults.eth (later fragment wins). +# Append to sdkconfig.defaults + sdkconfig.defaults.eth (later fragment wins); the 8 MB flash size +# and partition table come from the moonbase-8mb fragment. # # Its own variant rather than a reuse of `esp32` or `esp32-wrover` for two reasons: the flash is # 8 MB where the classic base sets 4, and the PSRAM must be on (the base has none) — a 4 MB image # on this part would waste half the flash, and the wrover image's 4 MB table would too. -# The 8 MB flash and its MoonBase partition table: a factory recovery image, ONE 3 MB app slot, and -# 4 MB of LittleFS. The 4 MB base table affords only 1856 KB slots and 256 KB of filesystem. -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE="8MB" -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/esp32dev_8mb_moonbase.csv" - # PSRAM: 2 MB, embedded in the SiP. QUAD mode, and there is nothing to select — on classic-ESP32 # silicon CONFIG_SPIRAM=y IS quad (CONFIG_SPIRAM_MODE_OCT exists only on the S3), which is the same # reason the WROVER fragment sets this one symbol and no mode. diff --git a/esp32/sdkconfig.defaults.esp32c3 b/esp32/sdkconfig.defaults.esp32c3 index 14da2e2ed..10fd1b993 100644 --- a/esp32/sdkconfig.defaults.esp32c3 +++ b/esp32/sdkconfig.defaults.esp32c3 @@ -1,11 +1,9 @@ # ESP32-C3: one RISC-V core at 160 MHz, 400 KB SRAM, no PSRAM, 4 MB flash, WiFi only. -# Append to sdkconfig.defaults (later fragment wins); the partition table comes from the moonbase-4mb fragment. +# Append to sdkconfig.defaults (later fragment wins); the flash size and partition table come from the moonbase-4mb fragment. # 160 MHz is the C3's top speed, where the base fragment asks for the 240 of the other chips. CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y - # MoonLive native codegen needs an executable heap, which ESP-IDF offers only with memory protection off: the same configuration as the S3 images. CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_HEAP_HAS_EXEC_HEAP=y diff --git a/esp32/sdkconfig.defaults.esp32s3-quad b/esp32/sdkconfig.defaults.esp32s3-quad new file mode 100644 index 000000000..a61cd54e8 --- /dev/null +++ b/esp32/sdkconfig.defaults.esp32s3-quad @@ -0,0 +1,25 @@ +# ESP32-S3 with QUAD PSRAM: the S3-Zero (N4R2) and the N8R2 (the Adafruit MatrixPortal S3 carries one). +# Append to sdkconfig.defaults (later fragment wins); the flash size and partition table come from the +# moonbase-4mb or moonbase-8mb fragment layered on top. +# +# The N8R8/N16R8 images set SPIRAM_MODE_OCT, and an octal-mode binary does not merely under-perform on +# quad hardware, it fails PSRAM init at boot. Quad also leaves GPIO 35 to 37 free, which the MatrixPortal +# routes to HUB75 lines. + +# Quad I/O flash reads, as on the N16R8, where reading code over two lines (DIO, the IDF default) cost 16% of the tick. +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y + +# PSRAM: 2 MB, QUAD mode. +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_QUAD=y +# At the 80 MHz the part is rated for rather than IDF's 40 MHz default. +CONFIG_SPIRAM_SPEED_80M=y + +# MoonLive native codegen needs an executable heap (allocExec -> MALLOC_CAP_EXEC IRAM). +# MALLOC_CAP_EXEC is gated behind CONFIG_HEAP_HAS_EXEC_HEAP, which IDF disables whenever +# memory protection (PMP/PMS W^X) is on. A JIT fundamentally needs writable-then-executable +# memory, so disable memprot (which enables the exec heap). Same configuration as the other +# S3 images; safety for scripted code is the staged bounds/watchdog checks, not the +# hardware W^X wall. +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n +CONFIG_HEAP_HAS_EXEC_HEAP=y diff --git a/esp32/sdkconfig.defaults.esp32s3-zero b/esp32/sdkconfig.defaults.esp32s3-zero deleted file mode 100644 index 2cde005b1..000000000 --- a/esp32/sdkconfig.defaults.esp32s3-zero +++ /dev/null @@ -1,36 +0,0 @@ -# ESP32-S3-Zero (Waveshare): 4 MB embedded flash, 2 MB embedded QUAD PSRAM. -# Append to sdkconfig.defaults (later fragment wins). -# -# The two things that make this its own variant rather than a reuse of the other S3 images: -# the flash is 4 MB where they assume 8 or 16, and the PSRAM is QUAD where they set -# SPIRAM_MODE_OCT. An octal-mode binary does not merely under-perform on quad hardware, it -# fails PSRAM init at boot, so neither existing S3 firmware can be flashed here. - -# The partition table comes from the moonbase-4mb fragment this variant layers on: a factory -# MoonBase recovery image plus ONE 2496 KB app slot, rather than the plain 4 MB table's two -# 1856 KB OTA slots. On a part this small that is the difference between ~690 KB of headroom -# and ~48 KB, and the S3 image is already ~1808 KB. The trade is the OTA slot pair: an update -# has no second app partition to roll back to, and MoonBase is the recovery path instead. -CONFIG_PARTITION_TABLE_CUSTOM=y - -# Flash size override (the ESP32-S3 default is 2 MB). -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y - -# Quad I/O flash reads, as on the N16R8, where reading code over two lines (DIO, the IDF default) cost 16% of the tick. -CONFIG_ESPTOOLPY_FLASHMODE_QIO=y - -# PSRAM: 2 MB embedded, QUAD mode. Not SPIRAM_MODE_OCT, which the N8R8/N16R8 images use for -# their external octal parts. -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MODE_QUAD=y -# At the 80 MHz the part is rated for rather than IDF's 40 MHz default. -CONFIG_SPIRAM_SPEED_80M=y - -# MoonLive native codegen needs an executable heap (allocExec -> MALLOC_CAP_EXEC IRAM). -# MALLOC_CAP_EXEC is gated behind CONFIG_HEAP_HAS_EXEC_HEAP, which IDF disables whenever -# memory protection (PMP/PMS W^X) is on. A JIT fundamentally needs writable-then-executable -# memory, so disable memprot (which enables the exec heap). Same configuration as the other -# S3 images; safety for scripted code is the staged bounds/watchdog checks, not the -# hardware W^X wall. -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n -CONFIG_HEAP_HAS_EXEC_HEAP=y diff --git a/esp32/sdkconfig.defaults.moonbase-4mb b/esp32/sdkconfig.defaults.moonbase-4mb index b2d0ffbe8..b7d0cc99f 100644 --- a/esp32/sdkconfig.defaults.moonbase-4mb +++ b/esp32/sdkconfig.defaults.moonbase-4mb @@ -1,3 +1,6 @@ +# MoonBase on a 4 MB board: the factory recovery image and ONE 2496 KB app slot, where dual-OTA +# affords two 1856 KB slots. The flash size is set here because the table spans exactly that much. +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/4mb_moonbase.csv" # No SD card backend on a 4 MB image: its flash is better spent on everything else, so SD ships only where there is more. diff --git a/esp32/sdkconfig.defaults.moonbase-8mb b/esp32/sdkconfig.defaults.moonbase-8mb new file mode 100644 index 000000000..8995d6fb4 --- /dev/null +++ b/esp32/sdkconfig.defaults.moonbase-8mb @@ -0,0 +1,4 @@ +# MoonBase on an 8 MB board: the factory recovery image, ONE 3 MB app slot, and 4 MB of LittleFS. +# The flash size is set here because the table spans exactly that much. +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/8mb_moonbase.csv" diff --git a/moondeck/build/build_esp32.py b/moondeck/build/build_esp32.py index 3f46ed832..49e6b5ca3 100644 --- a/moondeck/build/build_esp32.py +++ b/moondeck/build/build_esp32.py @@ -195,7 +195,7 @@ def check_idf_pin(idf_path: Path) -> None: "esp32-pico": { "chip": "esp32", "fragments": ["sdkconfig.defaults", "sdkconfig.defaults.eth", - "sdkconfig.defaults.esp32-pico"], + "sdkconfig.defaults.esp32-pico", "sdkconfig.defaults.moonbase-8mb"], "moonbase": True, # 8 MB: factory MoonBase + one app slot (see moonbase/) "eth_only": False, "description": "ESP32-PICO-V3-02 (classic ESP32 SiP: 8 MB embedded flash + 2 MB " @@ -256,7 +256,7 @@ def check_idf_pin(idf_path: Path) -> None: }, "esp32s3-zero": { "chip": "esp32s3", - "fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3-zero", + "fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3-quad", "sdkconfig.defaults.moonbase-4mb"], "moonbase": True, # 4 MB: factory MoonBase + one big app slot (see moonbase/) "eth_only": False, @@ -271,6 +271,21 @@ def check_idf_pin(idf_path: Path) -> None: # on a part that has 48 KB to spare. "panel_cards": False, }, + "esp32s3-n8r2": { + "chip": "esp32s3", + "fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3-quad", + "sdkconfig.defaults.moonbase-8mb"], + "moonbase": True, # 8 MB: factory MoonBase + one app slot (see moonbase/) + "eth_only": False, + "description": "ESP32-S3 (N8R2: 8 MB flash, 2 MB QUAD PSRAM) - WiFi only, no " + "Ethernet. Its own variant because the N8R8/N16R8 images set " + "SPIRAM_MODE_OCT, which fails PSRAM init on a quad part, and the " + "S3-Zero image uses half the flash (Adafruit MatrixPortal S3).", + "ships": True, + # No Ethernet fragment, as on the S3-Zero: the MatrixPortal breaks out no SPI header + # for a W5500. + "panel_cards": False, + }, "esp32c3": { "chip": "esp32c3", "fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32c3", diff --git a/mooninstaller/deviceModels.json b/mooninstaller/deviceModels.json index e47fe8d84..02faf2329 100644 --- a/mooninstaller/deviceModels.json +++ b/mooninstaller/deviceModels.json @@ -1830,6 +1830,83 @@ } } }, + { + "name": "Adafruit MatrixPortal S3", + "chip": "ESP32-S3", + "firmwares": [ + "esp32s3-n8r2" + ], + "url": "https://www.adafruit.com/product/5778", + "supported": [ + "LEDs", + "WiFi", + "Buttons", + "I2C" + ], + "planned": [ + "Accelerometer" + ], + "state": { + "System": { + "deviceModel": "Adafruit MatrixPortal S3", + "I2cBus": { + "type": "I2cBusModule", + "sda": 16, + "scl": 17 + } + }, + "Layouts": { + "$patch": "replace", + "Grid": { + "type": "GridLayout", + "width": 64, + "height": 32 + } + }, + "Effects": { + "$patch": "replace", + "Layer": { + "type": "Layer" + } + }, + "Drivers": { + "$patch": "replace", + "Hub75": { + "type": "Hub75Driver", + "board": "MatrixPortal S3", + "scanRate": "1/16" + } + }, + "Services": { + "$patch": "replace", + "Buttons": { + "type": "ButtonService", + "buttons": [ + { + "pin": 0, + "activeLow": true, + "target": "Control.switch1", + "kind": "toggle" + }, + { + "pin": 6, + "activeLow": true, + "target": "Control.fader1", + "kind": "delta", + "value": 16 + }, + { + "pin": 7, + "activeLow": true, + "target": "Control.fader1", + "kind": "delta", + "value": -16 + } + ] + } + } + } + }, { "name": "ESP32-C3 SuperMini", "chip": "ESP32-C3", diff --git a/mooninstaller/firmwares.json b/mooninstaller/firmwares.json index 262e73c8b..4e750040e 100644 --- a/mooninstaller/firmwares.json +++ b/mooninstaller/firmwares.json @@ -72,6 +72,14 @@ "ships": true, "description": "ESP32-S3-Zero (N4R2: 4 MB embedded flash, 2 MB embedded QUAD PSRAM) - WiFi only, no Ethernet. Its own variant because neither other S3 image can boot here: both assume 8/16 MB flash and set SPIRAM_MODE_OCT, and octal mode fails PSRAM init on this board's quad part. A thumbnail-sized board for small installations." }, + { + "name": "esp32s3-n8r2", + "platform": "esp32", + "chip": "esp32s3", + "eth_only": false, + "ships": true, + "description": "ESP32-S3 (N8R2: 8 MB flash, 2 MB QUAD PSRAM) - WiFi only, no Ethernet. Its own variant because the N8R8/N16R8 images set SPIRAM_MODE_OCT, which fails PSRAM init on a quad part, and the S3-Zero image uses half the flash (Adafruit MatrixPortal S3)." + }, { "name": "esp32c3", "platform": "esp32", diff --git a/test/unit/core/unit_PartitionTables.cpp b/test/unit/core/unit_PartitionTables.cpp index d7a9d8b7f..ce7d96f5d 100644 --- a/test/unit/core/unit_PartitionTables.cpp +++ b/test/unit/core/unit_PartitionTables.cpp @@ -31,7 +31,7 @@ uint32_t declaredFlashBytes(const std::string& file) { {"esp32dev.csv", 4u * 1024 * 1024}, {"4mb_moonbase.csv", 4u * 1024 * 1024}, {"wled_4mb_1mb_fs.csv", 4u * 1024 * 1024}, - {"esp32dev_8mb_moonbase.csv", 8u * 1024 * 1024}, + {"8mb_moonbase.csv", 8u * 1024 * 1024}, {"esp32s3_n8r8.csv", 8u * 1024 * 1024}, {"ota_16mb.csv", 16u * 1024 * 1024}, {"ota_16mb_moonbase.csv", 16u * 1024 * 1024},