ESP-IDF platform support for BlitzMax NG embedded applications.
esp32.mod lets bmk build, upload, and run BlitzMax applications on supported
ESP32 devices. It provides ESP32 implementations of the portable Embedded.*
APIs alongside target-specific modules for applications that need more direct
control of the hardware.
The currently validated targets use ESP-IDF 6.1:
| Target | Architecture | Validated board profile | Notes |
|---|---|---|---|
| ESP32 | Xtensa | esp32 |
Generic original ESP32 profile |
| ESP32-S3 | Xtensa | baguette_s3 |
8 MiB flash, MicroSD slot, no PSRAM |
| ESP32-S3 | Xtensa | esp32s3_44pin_n16r8 |
16 MiB flash, 8 MiB PSRAM, dual USB-C |
| ESP32-S3 | Xtensa | xiao_esp32s3_plus |
16 MiB flash, 8 MiB PSRAM, native USB, no onboard SD slot |
| ESP32-C3 | 32-bit RISC-V | baguette_c3 |
4 MiB flash, no SD slot or PSRAM |
| ESP32-C6 | 32-bit RISC-V | esp32_c6_supermini |
4 MiB flash, native USB, no PSRAM |
| ESP32-C6 | 32-bit RISC-V | xiao_esp32c6 |
4 MiB flash, native USB, no PSRAM |
| ESP32-C5 | 32-bit RISC-V | xiao_esp32c5 |
8 MiB flash, 8 MiB PSRAM, native USB |
| ESP32-H2 | 32-bit RISC-V | waveshare_h2_dev_kit_n4 |
4 MiB flash, native USB and CH343 UART bridge, no Wi-Fi or PSRAM |
| ESP32-P4 | 32-bit RISC-V | m5stack_stamp_p4 |
16 MiB flash, 32 MiB PSRAM, native USB, no onboard radio |
| ESP32-S31 | 32-bit RISC-V | esp32_s31_korvo_1 |
16 MiB flash, 16 MiB PSRAM, UART bridge, MicroSD slot; ESP-IDF preview target |
The Baguette S3, ESP32-S3 44-pin N16R8, XIAO ESP32-S3 Plus, Baguette C3, ESP32-C6 SuperMini, XIAO ESP32-C6, XIAO ESP32-C5, Waveshare H2 Dev Kit N4, M5Stack Stamp-P4, and ESP32-S31-Korvo-1 profiles have been tested on their respective physical boards. Additional generic profiles describe other ESP-IDF target families for inspection and future validation; their presence does not by itself mean that the complete BlitzMax implementation has been tested on that target.
- A BlitzMax NG installation containing the ESP32-enabled
bmkand compiler. esp32.modinstalled asmod/esp32.mod.- The matching
embedded.mod,blitzmax.mod,brl.mod,pub.mod, andrandom.modversions. - ESP-IDF 6.1 and its target toolchains.
- A data-capable USB connection for one-command upload.
bmk locates ESP-IDF using, in order, the esp32.idf option in custom.bmk,
the IDF_PATH environment variable, or the newest recognized installation
beneath ~/.espressif.
Create hello.bmx:
SuperStrict
Framework BRL.StandardIO
Print "Hello from BlitzMax on ESP32"Build and upload it to a connected Baguette S3:
bmk makeapp -a -r -board baguette_s3 -heap 64k -x hello.bmxFor a Baguette C3, select its board profile instead:
bmk makeapp -a -r -board baguette_c3 -heap 64k -x hello.bmxThe ESP32-C6 SuperMini likewise has its own board profile:
bmk makeapp -a -r -board esp32_c6_supermini -heap 64k -x hello.bmxFor the Seeed Studio XIAO ESP32-C6, use -board xiao_esp32c6. Its profile
identifies the D0–D10 header pins, onboard LED on GPIO15, and the GPIO3/GPIO14
antenna switch. The switch defaults to the ceramic antenna; selecting the
external antenna requires enabling the switch with GPIO3 low, then setting
GPIO14 high. Consult bmk boardinfo -board xiao_esp32c6 before assigning pins.
For the Seeed Studio XIAO ESP32-C5, use -board xiao_esp32c5. The profile
maps its D0–D10 header, default I²C/SPI/UART pins, active-low user LED, and
battery-voltage sensing circuit. The onboard 8 MiB PSRAM can hold the managed
heap, for example with -heap-region psram -heap 1m. The battery divider is
off until GPIO26 is driven high; read it through GPIO6, then disable it to
avoid idle drain. Consult bmk boardinfo -board xiao_esp32c5 before assigning
pins, especially the boot-strapping pins.
For the Waveshare ESP32-H2 Dev Kit N4, use -board waveshare_h2_dev_kit_n4.
The single USB-C connection exposes both the H2's native USB Serial/JTAG port
and an onboard CH343 UART bridge. Select the native USB port for bmk -x and
the console (on macOS, it appears as a cu.usbmodem device with USB vendor ID
303A). After upload, press the board's Reset button to start the new app.
If a subsequent upload cannot connect through native USB, press Reset and
retry. The CH343 bridge is a separate serial port, not the default console.
The H2 supports Bluetooth LE and IEEE 802.15.4, but not Wi-Fi.
For the M5Stack Stamp-P4, use -board m5stack_stamp_p4. The profile selects
ESP-IDF's P4 revision-1.x build mode, matching the tested chip revision v1.3;
an image built with IDF's default revision-3.x mode will not flash to this
board. The 32 MiB PSRAM can hold the managed heap with, for example,
-heap-region psram -heap 1m. The bare Stamp-P4 has no Wi-Fi or Bluetooth LE
radio; a separate companion module is required for wireless applications.
Its labeled GPIO pads are available in bmk boardinfo -board m5stack_stamp_p4.
The exposed LPG8 and LPG10 pads are GPIO8 and GPIO10 in the profile.
For the Espressif ESP32-S31-Korvo-1 V1.1, use
-board esp32_s31_korvo_1. Connect the UART USB-C port for automatic upload,
reset, and console output; the other USB-C connector supplies power. The board
profile describes its ES8389 audio codec, RGB LED, ADC button ladder, MicroSD
slot, camera and LCD signals, and their pin conflicts. The current shared SD
filesystem adapter uses the slot in one-bit SDMMC mode. Its 16 MiB PSRAM can
hold the managed heap with -heap-region psram -heap auto. ESP32-S31 support
is still marked preview in ESP-IDF 6.1: raw ADC reads work, but ESP-IDF does not
yet provide the chip with a calibrated millivolt-conversion scheme.
The dual-USB 44-pin N16R8 board uses its CH343P COM socket for automatic
upload, reset, and console output. Its separate USB socket is wired directly
to the ESP32-S3 native USB/OTG interface. To use its external PSRAM-backed
managed heap:
bmk makeapp -a -r -board esp32s3_44pin_n16r8 \
-heap-region psram -heap auto -x hello.bmxFor recognised profiles, bmk infers the ESP32 platform, compiler
architecture, and ESP-IDF target from -board. Explicit -l and -g options
remain available for scripts and are checked against the selected profile.
The important options are:
| Option | Meaning |
|---|---|
-l esp32 |
Explicitly select ESP32; inferred from a recognised -board profile |
-g xtensa |
Explicitly select the original ESP32/ESP32-S3 architecture |
-g riscv32 |
Explicitly select a RISC-V ESP32 architecture, such as ESP32-C3 or ESP32-C6 |
-board <name> |
Select a board profile |
-heap auto |
Use the default managed heap; currently 64 KiB in internal SRAM |
-heap <size> |
Set the managed heap in bytes or with k, KiB, m, or MiB |
-heap-region sram |
Place the managed heap in internal SRAM; this is the default |
-heap-region psram |
Place the managed heap in profile-declared external PSRAM |
-x |
Build, upload, reset, and start the application |
-o <name> |
Choose the output name |
-d |
Build with source-level GDB information |
-r |
Build optimised release firmware |
Without -x, the build produces an ELF image, flashable BIN image, and link
map. ESP-IDF's bootloader, partition table, flash arguments, and generated
configuration remain in the source file's .bmx build directory.
When exactly one compatible board is connected, upload selects it
automatically. Set ESPPORT or esp32.port in custom.bmk when more than one
device is available.
Tool locations and persistent defaults can be set in bin/custom.bmk within
the BlitzMax installation:
addoption esp32.idf "/path/to/esp-idf"
addoption esp32.tools "/path/to/.espressif"
addoption esp32.python "/path/to/.espressif/python_env/idf6.1_py3.10_env"
addoption esp32.target "baguette_s3"
addoption esp32.port "/dev/cu.usbmodem101"
addoption esp32.board.dirs "/path/to/custom/board/profiles"
addoption esp32.heap.region "psram"
The corresponding environment variables are:
custom.bmk key |
Environment variable | Purpose |
|---|---|---|
esp32.idf |
IDF_PATH |
ESP-IDF root containing tools/idf.py |
esp32.tools |
IDF_TOOLS_PATH |
ESP-IDF's downloaded-tools root |
esp32.python |
IDF_PYTHON_ENV_PATH |
Matching Python environment directory or executable |
esp32.port |
ESPPORT |
Serial or USB device used for inspection and upload |
esp32.board.dirs |
ESP32_BOARD_DIRS |
Additional board-profile roots |
esp32.target |
— | Default board profile when -board is omitted |
esp32.heap.region |
— | Default managed-heap region, sram or psram |
A command-line -board or -heap-region overrides the applicable persistent
default. For settings with both forms, the custom.bmk option takes precedence
over its environment variable. If no port is configured, bmk can select an
exactly matching single connected device automatically.
An installation created by Espressif's installer normally needs no entries in
custom.bmk. If esp32.idf and IDF_PATH are both absent, bmk searches
~/.espressif/v*/esp-idf and selects the newest recognized version.
A manual installation may keep the ESP-IDF checkout elsewhere while retaining
downloaded tools under ~/.espressif/tools and versioned Python environments
under ~/.espressif/python_env. bmk honours IDF_TOOLS_PATH and
IDF_PYTHON_ENV_PATH when they are set by ESP-IDF's export.sh. When they are
unset, it recognizes both the manual layout and Espressif's installer-managed
layout, and selects a Python environment whose idf_version.txt matches the
selected ESP-IDF checkout.
On Windows, separate multiple esp32.board.dirs or ESP32_BOARD_DIRS entries
with semicolons. On macOS and Linux, use colons.
Inspect a board profile without connecting hardware:
bmk boardinfo -l esp32 -board baguette_s3The report includes the target architecture, flash and PSRAM configuration, console transport, default buses, onboard resources, named pins, and important pin conflicts or restrictions.
Inspect a connected device without building or flashing:
bmk deviceinfo -l esp32Select a profile as well to compare the physical device with the intended build configuration:
bmk deviceinfo -l esp32 -board baguette_c3The retail board cannot be inferred reliably from its chip and flash header, so
deviceinfo reports detected hardware and selected configuration separately.
It warns when the configured image flash size differs from the detected flash.
Profiles live under boards. See the board-profile guide
to add a board or configure an additional profile directory.
Use Embedded.* when an application should compile unchanged for both Pico and
ESP32. Use the corresponding ESP32.* module when the application needs
ESP-IDF-specific capabilities. The target facade retains familiar operation
names where the underlying hardware concepts align.
For example, portable GPIO code imports:
Import Embedded.Hardware.GPIOESP32-specific GPIO code can instead import:
Import ESP32.Hardware.GPIOThe latter also provides ESP32 drive-capability controls. This pattern is used throughout GPIO, time, UART, I2C, SPI, ADC, PWM, watchdog, device, random, power, Wi-Fi, and BLE support.
| Area | Primary modules | Start with |
|---|---|---|
| GPIO | Embedded.Hardware.GPIO, ESP32.Hardware.GPIO |
gpio_mirrored.bmx, gpio_events.bmx |
| Time and calendar | Embedded.System.Time, ESP32.System.Time, ESP32.System.Calendar |
time_alarm.bmx, calendar.bmx |
| Power | Embedded.System.Power, ESP32.System.Power |
power.bmx |
| UART | Embedded.Hardware.UART, Embedded.IO.BufferedUART, ESP32.Hardware.UART |
uart_controller.bmx, buffered_uart.bmx |
| I2C and SPI | Embedded.Hardware.I2C, Embedded.Hardware.SPI, ESP32 facades |
i2c_controller.bmx, spi_controller.bmx |
| Timed pulses (RMT) | ESP32.Hardware.RMT |
rmt_pulses.bmx, rmt_loopback.bmx, rmt_ws2812_44pin.bmx |
| Random data | Embedded.Random, ESP32.Random |
random_esp32.bmx |
| ADC and PWM | Embedded.Hardware.ADC, Embedded.Hardware.PWM, ESP32 facades |
adc_pwm.bmx |
| Watchdog and identity | Embedded.Hardware.Watchdog, Embedded.System.Device, ESP32 facades |
watchdog_device.bmx |
| PSRAM | ESP32.Hardware.PSRAM |
psram_info.bmx |
| Filesystems | ESP32.Storage.LittleFS, ESP32.Storage.SDCard |
storage_smoke.bmx |
| NVS and partitions | ESP32.Storage.NVS, ESP32.System.Partition |
nvs_storage.bmx, partition_info.bmx |
| OTA | ESP32.System.OTA |
ota_from_storage.bmx |
| Wi-Fi and sockets | Embedded.Network.WiFi, ESP32.Network.WiFi, BRL.Socket |
wifi_scan.bmx, socket_loopback.bmx |
| BLE | Embedded.Network.BLE, ESP32.Network.BLE |
ble_scan.bmx, ble_peripheral.bmx, ble_client.bmx |
Hardware resources differ between ESP32 variants. Query capabilities where the
API provides them and consult boardinfo before choosing pins. For example,
the Baguette C3 profile reports that its documented I2C and SPI clocks share
GPIO6, and that GPIO2, GPIO8, and GPIO9 are strapping pins. The number of UARTs,
ADC channels, and other peripherals is likewise target-dependent.
ESP32.Hardware.RMT owns transmit and receive channels for precisely timed
GPIO pulse sequences. It supports packed symbols, byte-to-pulse encoding,
optional carrier modulation/demodulation, and nonblocking receive with a
native buffer. Transmit calls complete before returning; an RMT interrupt
never calls BlitzMax or retains a managed array. The WS2812 example shows how
to build an LED protocol on this general API, using the 44-pin S3 board's
GPIO48 LED. Change the pin and color order for other boards.
The loopback example tests reception with a jumper from GPIO4 to GPIO5.
-heap selects the fixed managed arena used for BlitzMax objects, strings, and
arrays. The size must be a multiple of 16 bytes. ESP-IDF and native subsystem
allocations remain outside this arena.
Boards with supported external RAM may place the managed arena in PSRAM:
bmk makeapp -a -r -l esp32 -g xtensa -board esp32s3_n8r8 \
-heap-region psram -heap 1m -x hello.bmxThe selected profile must declare a fixed PSRAM capacity and supply the required
ESP-IDF settings. Unsupported combinations fail before compilation. With a
PSRAM heap, -heap auto keeps one eighth of the declared capacity available to
ESP-IDF and native services, with a minimum reserve of 256 KiB. Use
ESP32.Hardware.PSRAM to inspect capacity, free space, and managed-arena
placement.
The managed runtime is single-task. Ordinary applications should use the provided event-based modules rather than call BlitzMax directly from interrupts or foreign FreeRTOS tasks. Authors of native integrations should read the runtime integration guide.
Importing ESP32.Storage.LittleFS mounts the profile's internal storage
partition and makes it the default filesystem. It works through the normal
BRL.FileSystem, stream, and text APIs:
Import BRL.TextStream
Import ESP32.Storage.LittleFS
SaveText "settings", "config.txt"
Print LoadText("littlefs::config.txt")On a board with a profile-defined SD slot, mount it explicitly and use the
sd:: prefix:
Import ESP32.Storage.SDCard
If MountSDCard() Then
SaveText "archive", "sd::archive.txt"
End Iflittlefs:: and sd:: always select a volume. Plain paths and file:: use the
selected default. SetDefaultStorageVolume("sd") changes that default, while
CopyFile can copy between volumes. Cross-volume renames are rejected.
The Baguette C3 has no SD slot; use LittleFS or NVS there. Examples that name an
sd:: path are intended for an SD-equipped profile unless adapted to use
LittleFS.
A completely erased LittleFS partition may be formatted automatically.
Unrecognised existing data is not erased implicitly. SD mounting is
non-destructive by default. FormatLittleFS, FormatSDCard, and mounting with
the SD formatting option are explicit destructive operations.
ESP32.Storage.NVS provides wear-levelled namespaced key/value storage for
integers, floating-point values, strings, managed byte arrays, and caller-owned
raw memory. Changes are persisted by an explicit commit.
Embedded.Network.WiFi provides the portable station interface. The
ESP32.Network.WiFi facade adds power saving, transmit power, protocols,
bandwidth, detailed station information, and concurrent SoftAP control.
BRL.Socket uses ESP-IDF/lwIP for UDP, TCP, address lookup, socket options, and
deferred readiness events.
wifi_scan.bmx needs no credentials. The live network
examples expect wifi.conf to contain the SSID on its first line and password
on its second. The supplied S3 examples read sd::wifi.conf; use a suitably
protected LittleFS path when adapting them for a board without SD storage.
Credentials are application data and are never compiled into these modules.
TLS and Net.HTTP integration are not currently part of this module. The HTTP
checks in the examples use plain local or test traffic.
Embedded.Network.BLE provides portable lifecycle, discovery, central, GATT
peripheral, security, bonding, and connection-management APIs.
ESP32.Network.BLE supplies the ESP-IDF NimBLE adapter and ESP32 result names.
Importing BLE enables the Bluetooth components for that application; programs
which do not import it retain a smaller non-Bluetooth build.
Useful examples include:
ble_scan.bmx: active discovery.ble_peripheral.bmx: read, write, and notify.ble_client.bmx: discovery and central operations.ble_gatt_long_client.bmx: MTU negotiation and 512-byte GATT values.ble_indication_peripheral.bmx: acknowledged indications.ble_secure_peripheral.bmx: encryption and persistent bonding.ble_passkey_peripheral.bmx: authenticated passkey pairing.ble_connection_management.bmx: connection, RSSI, parameters, PHY, and bond management.
BLE callbacks are converted into normal deferred BlitzMax events. Call
PollSystem, WaitSystem, or use BRL.EventQueue so those events are drained.
ESP32.System.OTA accepts firmware from a byte array, caller-owned raw memory,
or any TStream, including LittleFS, SD, and network streams. Finishing an
update validates the complete ESP-IDF application image; activating it is a
separate decision.
After a provisional boot, the new application should run its own startup checks and confirm the image:
Local state:UInt
If OTARunningImageState(state) = 0 And state = OTAImageStatePendingVerify Then
If OTAMarkRunningImageValid() <> 0 Then RuntimeError "Could not confirm OTA image"
End Ifota_network_receiver.bmx and
ota_upload_client.bmx demonstrate a simple
desktop-to-device development workflow. Their small TCP protocol has no peer
authentication or transport encryption and must not be exposed as a production
Internet-facing update service.
The Baguette S3 partition layout contains two 2 MiB OTA slots and 4024 KiB of LittleFS. The Baguette C3 layout contains two 1.5 MiB OTA slots and 952 KiB of LittleFS. When moving from another partition layout, flash the generated bootloader, partition table, application, and initial OTA metadata together. Back up persistent data first because changing partition offsets makes the old filesystem inaccessible.
The hardware runner builds, flashes, and checks self-verifying examples on a
connected board. It supports baguette_s3, esp32s3_44pin_n16r8,
xiao_esp32s3_plus, xiao_esp32c6, xiao_esp32c5,
waveshare_h2_dev_kit_n4, m5stack_stamp_p4, and esp32_s31_korvo_1; the
complete suite has been verified on the Baguette S3 and XIAO C6. The S31
random, time/alarm, mirrored GPIO, ADC/PWM, and PSRAM checks have been verified
on a V1.1 board, along with its onboard GPIO37 WS2812 through RMT. The basic,
loopback, and psram suites have each been verified on the XIAO C5. The
basic and loopback suites have each been verified on the Waveshare H2 Dev
Kit N4; its native USB port required a physical Reset between those suite runs.
The basic checks (with the P4-specific ADC input), LPG8–LPG10 loopback,
and psram suites have been verified on the Stamp-P4.
It checks the detected chip and flash against the board profile before
flashing and stops if they disagree. It does not format storage, join Wi-Fi, or
alter OTA partitions beyond the normal application upload.
Set the serial port for the connected board and run the basic suite:
ESP32_TEST_BOARD=baguette_s3 ESP32_TEST_PORT=/dev/cu.usbmodem101 \
./tests/run_hardware.sh basicFor the RMT receive/transmit loopback, connect GPIO4 to GPIO5 and run
./tests/run_hardware.sh loopback with the same variables. On the XIAO C5,
connect D3/GPIO7 to D4/GPIO23 instead; on the XIAO C6, connect D3/GPIO21 to
D4/GPIO22. The runner selects the board-specific loopback example. all runs
both the basic and loopback suites.
On the 44-pin S3, use its USB-to-UART COM socket. The runner needs
Python with pyserial; set ESP32_TEST_PYTHON to the ESP-IDF Tools Python if
your system Python does not have it. Test output is checked after an automatic
board reset, and the runner stops on the first missing pass result.
On the PSRAM-equipped XIAO S3 Plus or 44-pin S3, run
./tests/run_hardware.sh psram to verify that a managed arena can be placed
in PSRAM. The XIAO S3 Plus uses its native USB Serial/JTAG port.
- Architecture mismatch: omit
-gand let the board profile select it, or use-g xtensafor the original ESP32 and S3 and-g riscv32for C3/C6. - More than one serial device: set
ESPPORToresp32.port. - Uncertain board configuration: compare
boardinfowithdeviceinfo. - Application does not fit: inspect the reported image size and the selected profile's application partitions.
- SD mount fails: confirm that the profile defines an SD resource, a card is inserted, and it has a supported FAT filesystem. Formatting is never the default recovery action.
- No deferred network/BLE events: ensure the program regularly calls
PollSystem,WaitSystem, or usesBRL.EventQueue.