AtomVM: how to run Elixir code on a 3 $ microcontroller

AtomVM is a tiny portable virtual machine that allows BEAM code to run on microcontrollers with less than 500KB of RAM such as the ESP32.

Disclaimer: AtomVM is not production ready yet, it might set your house on fire.

So what does it mean? In few words you can flash AtomVM on an ESP32 and get your Elixir/Erlang code running on it.

Why

I work full time on Astarte, which is an open source IoT platform written in Elixir, and I also use several embedded boards for work and fun, so while I was working with an ESP32 board I realized that it could be interesting to use some of the cool concepts behind Elixir on embedded hardware, so I started AtomVM.

So let’s focus on ESP32 specs just for a moment:

  • Connectivity: built-in Ethernet, BLE and WiFi (802.11 b/g/n) + several dev boards with an additional LoRa chip
  • CPU: dual core, up to 240 MHz
  • Memory: 520 KiB SRAM (built-in)
  • Flash: usually 4 MiB
  • Power: 5 µA deep sleep current

Erlang and Elixir are really good at handling network packets (ESP32 has network connectivity), running concurrent processes (ESP32 is dual core), dealing with faults (embedded software needs to be reliable), writing testable software (early testing while developing is way better than debugging on the real hardware), enabling rapid development (which is an Arduino selling point) but OTP is not designed to run on low end devices.

For instance there are already two great projects that allow Elixir code to run on embedded devices that are Nerves and GRiSP. However in order to run they need a Linux powered embedded board such as a Raspberry Pi (Nerves) or a custom board with 64 MiB of RAM (GRiSP).

AtomVM tries to overcome those limitations by design and it enables Elixir development for such low end devices like the ESP32.

How

AtomVM tries to use the smallest possible amount of memory by design thanks to a few tricks, such as executing all code in place on the flash memory (which is usually memory mapped).

Some features are left not implemented, while some others are planned to be optional, for instance floating point support might not be needed in some use case, like also arbitrary-precision integers might be not needed on those devices.

Last but not least also the standard library is stripped down to save precious flash memory.

How to blink a led with Elixir

Blinking a LED is the “hello world” program of physical computing, so we’ll start with it:

… but wait a second, before starting you should make sure to have all the required components:

Hardware:

  • An ESP32 dev board with a LED on it (it is possible to buy one for less than 10 €)
  • In case your ESP32 board doesn’t have an onboard LED: a LED, few cables and a breadboard

Software:

  • Erlang/OTP 20 (OTP 21 should be also fine)
  • Elixir compiler (1.6/1.7 should be fine)
  • esp-idf (v3.1)
  • CMake
  • zlib devel files
  • gperf

Compiling and flashing AtomVM to the ESP32 board

Just connect your board and make sure that $IDF_PATH points to the esp-idf directory and your esp-idf SDK is already configured. If you are using macOS Fred Dushin made an excellent blogpost.

~ $ git clone https://github.com/bettio/AtomVM.git
~ $ cd AtomVM
~/AtomVM $ cd src/platforms/esp32
~/AtomVM/src/platforms/esp32 $ make flash
~/AtomVM/src/platforms/esp32 $ cd ../../..

Compiling and flashing Elixir code

Blink.ex can be compiled using elixirc:

$ elixirc Blink.ex

Once compiled you should have Elixir.Blink.beam, which is the actual compiled module.

However Elixir.Blink.beam is not enough, it needs to be packed together with all the required modules into an .avm file.

~/AtomVM $ cd libs/estdlib
~/AtomVM/libs/estdlib $ erlc *.erl
~/AtomVM/libs/estdlib $ cd ../exavmlib/
~/AtomVM/libs/exavmlib $ elixirc GPIO.ex
~/AtomVM/libs/exavmlib $ cd ../..
~/AtomVM $ cmake .
~/AtomVM $ cd tools/packbeam
~/AtomVM/tools/packbeam $ make
~/AtomVM/tools/packbeam $ cd ../..
~/AtomVM $ tools/packbeam/PackBEAM blink.avm path/to/Elixir.Blink.beam libs/estdlib/*.beam libs/exavmlib/*.beam
~/AtomVM $ $IDF_PATH/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect  0x110000 blink.avm

For the sake of clarity we didn’t do an out-of-source build, but you should.

Portability and supported hardware

Right now only *nix systems and ESP32 boards are supported, however AtomVM is designed to be easily portable: all platform specific code is kept in a library, so porting AtomVM to a new operating system or new hardware should be fairly simple. AtomVM core itself is built as a library, so it can be embedded into a bigger project.

AtomVM has also really few dependencies, it requires ~40 libc functions and an optional zlib library.

Support for more platforms is forthcoming, including STM32.

Contributing

Contributions are welcome, just fork it on github and send a pull request. “Good first issue” issues are a good starting point, there are also “help wanted” issues for skilled developers (one of them is the Windows port).

New issues are welcome aswell, feel free to open them if you find any bug or miss any feature.

The future

More features are coming in the near future, such as funs, maps, better error handling, multi core support and a complete garbage collector rewrite.

Further developments are planned and they will be implemented in the future, like remote shell support, hot code swapping, clustering, etc…

… and of course I’ll also post more stories about AtomVM soon, so stay tuned :)