Lucas Sifoni

Hello AtomVM — Elixir & Erlang on ESP32

elixir erlang atomvm


This post will show my log from no toolchain, to hello world running from Erlang on an ESP32 SOC, with a mac laptop as the host computer.

I started by installing the ESP toolchain as instructed by Espressif.

First, install some dependencies with brew :

$ brew install cmake ninja dfu-util

Then, get the toolchain repository :

$ mkdir -p ~/esp
$ cd ~/esp
$ git clone --recursive https://github.com/espressif/esp-idf.git

Be aware that this represented more than 1 gigabyte on my laptop.

After that, install the relevant toolchain for your specific chip. I installed all toolchains to cope with the facts that I have a bunch of different ESPs.

$ cd ~/esp/esp-idf
$ ./install.sh all

After that, you are invited to export the relevant environment vars by running :

$ . $HOME/esp/esp-idf/export.sh

You would think that this can be added to your profile or bashrc, but this actually takes some time to run and produces output, so this will be run whenever you need to work with the toolchain.

The instructions then invite you to create a sample project, introduce you to the project configuration tool menuconfig, and show how to get serial output.

I jumped to AtomVM’s Getting Started With ESP32 guide. Here are the requirements :

The getting started guide is actually educational, well-written and thought out, I recommend reading it with attention. It features memory layout diagrams :^).

You are invited to download the latest AtomVM release, at the time of writing, 0.6.0-alpha.1

Then, you have to flash the device with this image, placing it at the address 0x1000. You’ll have to adapt the port to your specific setup, on my computer and with my specific chip, it showed up as /dev/cu.usbserial-10.

$ esptool.py \
    --chip auto \
    --port /dev/cu.usbserial-10 --baud 115200 \
    --before default_reset --after hard_reset \
    write_flash -u \
    --flash_mode dio --flash_freq 40m --flash_size detect \
    0x1000 \
    ~/Downloads/AtomVM-esp32-v0.6.0-alpha.1.img

It is quite surprising, but with a no-name $3 chip, this procedure worked the very first time. I encourage you to actually read the output of this command as it contains a lot of useful information about your chip :

Serial port /dev/cu.usbserial-10
Connecting.....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-D0WD-V3 (revision v3.1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: b0:a7:32:28:b4:48
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB

After the VM is successfully flashed, follow the guide to the AtomVM Tooling chapter.

You are invited to clone the examples repository to start with a simple Hello World :

$ git clone https://github.com/atomvm/atomvm_examples
$ cd atomvm_examples/erlang/hello_world

Then, it’s a matter of running rebar3 with the packbeam target, which compiles your application to an avm file.

$ rebar3 atomvm packbeam
....
===> Compiling hello_world
===> AVM file written to /Users/lucas/atomvm_examples/erlang/hello_world/_build/default/lib/hello_world.avm

We then have to flash this file on the ESP :

$ rebar3 atomvm esp32_flash --port /dev/cu.usbserial-10 --baud 115200

This works too. Opening minicom, we are greeted by :

    ###########################################################

       ###    ########  #######  ##     ## ##     ## ##     ##
      ## ##      ##    ##     ## ###   ### ##     ## ###   ###
     ##   ##     ##    ##     ## #### #### ##     ## #### ####
    ##     ##    ##    ##     ## ## ### ## ##     ## ## ### ##
    #########    ##    ##     ## ##     ##  ##   ##  ##     ##
    ##     ##    ##    ##     ## ##     ##   ## ##   ##     ##
    ##     ##    ##     #######  ##     ##    ###    ##     ##

    ###########################################################

I (781) AtomVM: Starting AtomVM revision 0.6.0-alpha.1
I (791) sys: Loaded BEAM partition boot.avm at address 0x1d0000 (size=262144 bytes)
I (811) network_driver: Initialized network interface
I (811) network_driver: Created default event loop
I (831) AtomVM: Found startup beam esp32init.beam
I (831) AtomVM: Starting esp32init.beam...
---
AtomVM init.
I (841) sys: Loaded BEAM partition main.avm at address 0x210000 (size=1048576 bytes)
Starting application...
Hello World
AtomVM finished with return value: ok
I (881) AtomVM: AtomVM application terminated.  Going to sleep forever ...

That’s quite fantastic :^) . This is the Erlang program that was running :

$ cat src/hello_world.erl
%
% This file is part of AtomVM.
%
% Copyright 2018 Davide Bettio <davide@uninstall.it>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
%    http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(hello_world).
-export([start/0]).

start() ->
    io:format("Hello World~n").

So, we can already guess something here : io seems to sink to the serial output.

Let’s switch to an Elixir example :

$ cd atomvm_examples/elixir/HelloWorld

This time, it uses Mix as a build tool. Let’s fetch dependencies :

$ mix deps.get

We then run the mix tasks atomvm.packbeam and atomvm.esp32.flash, just like the Erlang example earlier :

$ mix atomvm.packbeam
$ mix atomvm.esp32.flash --port /dev/cu.usbserial-10 --baud 115200

And are greeted with :

    ###########################################################

       ###    ########  #######  ##     ## ##     ## ##     ##
      ## ##      ##    ##     ## ###   ### ##     ## ###   ###
     ##   ##     ##    ##     ## #### #### ##     ## #### ####
    ##     ##    ##    ##     ## ## ### ## ##     ## ## ### ##
    #########    ##    ##     ## ##     ##  ##   ##  ##     ##
    ##     ##    ##    ##     ## ##     ##   ## ##   ##     ##
    ##     ##    ##     #######  ##     ##    ###    ##     ##

    ###########################################################

I (781) AtomVM: Starting AtomVM revision 0.6.0-alpha.1
I (791) sys: Loaded BEAM partition boot.avm at address 0x1d0000 (size=262144 bytes)
I (811) network_driver: Initialized network interface
I (811) network_driver: Created default event loop
I (831) AtomVM: Found startup beam esp32init.beam
I (831) AtomVM: Starting esp32init.beam...
---
AtomVM init.
I (841) sys: Loaded BEAM partition main.avm at address 0x210000 (size=1048576 bytes)
Starting application...
Hello World from Elixir
AtomVM finished with return value: ok
I (881) AtomVM: AtomVM application terminated.  Going to sleep forever ...

(I edited the Elixir file just to illustrate the example.)

That is quite impressive. I invite you to explore the directory of examples, that are quite rich :

$ ls
README.md      blinky         esp_nvs        hello_world    ledc_example   spi_example    tcp_client     uart_example   udp_server
arepl_example  deep_sleep     gpio_interrupt i2c_example    read_priv      system_info    tcp_server     udp_client     wifi

Combined with Nerves, maybe a full-BEAM languages hardware project will become possible in a few years. Shout out to the AtomVM team for enabling that.


Previous post : Hello Bumblebee. Hello, Whisper !
Next post : An (Elixir+Nerves)-powered Cloud ☁️ (1/2)