Let’s run a simple Elixir “Blinky” application on the Seeed Studio XIAO ESP32-S3 using AtomVM — a lightweight virtual machine for running Elixir and Erlang on microcontrollers.
This setup assumes a Debian-based Linux host with the following tools installed:
$ uname
Linux
$ elixir --version
Elixir 1.17.3 (compiled with Erlang/OTP 27)
$ python --version
Python 3.12.5
$ esptool version
esptool v5.0.1
$ picocom -h | head -n1
picocom v3.1
Note: esptool.py
is now esptool
(without .py
) and uses hyphenated options (e.g. --chip
, not --chip_esp32
). The older syntax still works, but is deprecated.
We are targeting:
This board:
- Has native USB (no need for a USB-to-serial adapter)
- Exposes its internal LED on GPIO21 (active-low)
AtomVM firmware is built using Espressif’s ESP-IDF. Here’s how to install it:
For more details and official instructions:
# Install system dependencies
sudo apt install git wget flex bison gperf cmake ninja-build \
ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
# Clone the ESP-IDF source
mkdir -p $HOME/esp && cd $_
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
# Checkout a specific stable version (e.g. v5.5)
git checkout v5.5
git submodule update --init --recursive
# Install tools only for esp32s3
./install.sh esp32s3
# Add IDF environment to current shell
source ./export.sh
Now let’s fetch and build the AtomVM virtual machine and core libraries.
AtomVM source and release info:
# Clone AtomVM source
mkdir -p $HOME/Projects/atomvm && cd $_
git clone https://github.com/atomvm/AtomVM.git
cd AtomVM
# Checkout a stable release
git checkout v0.6.6
# Build the core VM
mkdir -p build && cd build
cmake ..
make -j$(nproc)
This compiles AtomVM's core (for desktop/host use) — not the ESP32 firmware yet.
Now let’s build the firmware for the actual ESP32-S3 target:
cd $HOME/Projects/atomvm/AtomVM/src/platforms/esp32
# Set up IDF in the shell
source $HOME/esp/esp-idf/export.sh
# Configure for ESP32-S3, then build
idf.py set-target esp32s3
idf.py reconfigure
idf.py build
This builds the full AtomVM firmware for the ESP32-S3: bootloader, partition table, VM binary, and Elixir stdlib.
You can now flash AtomVM to your device.
This ensures no conflicting firmware or partitions remain.
esptool --chip auto --port /dev/ttyACM0 --baud 115200 erase-flash
This wipes the entire flash memory of the ESP32-S3.
cd $HOME/Projects/atomvm/AtomVM/src/platforms/esp32
source $HOME/esp/esp-idf/export.sh
idf.py -p /dev/ttyACM0 flash
Let’s compile the Elixir application that will blink the onboard LED.
cd $HOME/Projects/atomvm
git clone https://github.com/atomvm/atomvm_examples.git
cd atomvm_examples/elixir/Blinky
# Install dependencies (ExAtomVM)
mix deps.get
To make the on-board yellow LED (GPIO21) blink on your XIAO ESP32-S3, update the pin number in the Blinky module:
- Open
lib/blinky.ex
- Change GPIO 2 to GPIO 21
The XIAO LED is active-low, so the LED turns ON when the pin is set to 0.
defmodule SampleApp do
@pin 21
def start() do
:gpio.set_pin_mode(@pin, :output)
loop(@pin, :low)
end
defp loop(pin, level) do
:io.format(~c"Setting pin ~p ~p~n", [pin, level])
:gpio.digital_write(pin, level)
Process.sleep(1000)
loop(pin, toggle(level))
end
defp toggle(:high), do: :low
defp toggle(:low), do: :high
end
Now compile the Elixir code and package it into a .avm
file:
mix atomvm.packbeam
ls -l *.avm
You should see something like Blinky.avm
in the current directory.
Use the AtomVM Mix task to flash your .avm
bundle to the ESP32-S3:
mix atomvm.esp32.flash --port /dev/ttyACM0 --baud 115200
You can repeat this step every time you modify your app — no need to reflash the firmware.
You can view AtomVM logs and Elixir output using either:
cd $HOME/Projects/atomvm/AtomVM/src/platforms/esp32
source $HOME/esp/esp-idf/export.sh
idf.py --port /dev/ttyACM0 monitor
Press Ctrl+]
to exit.
picocom /dev/ttyACM0 --baud 115200
To exit: Ctrl+A
, then Ctrl+X
For future updates:
# Make changes to Elixir code
mix atomvm.packbeam
mix atomvm.esp32.flash --port /dev/ttyACM0
No need to rebuild or reflash the firmware unless you update AtomVM itself.
Uh oh!
There was an error while loading. Please reload this page.