Hello AtomVM — Elixir & Erlang on ESP32
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 :
- A computer running MacOS or Linux (Windows support is not currently supported);
okay - An ESP32 module with a USB/UART connector (typically part of an ESP32 development board);
okay - A USB cable capable of connecting the ESP32 module or board to your development machine (laptop or PC);
okay - The esptool program, for flashing the AtomVM image and AtomVM programs;
Hmm, I did not install that specifically ? Typing espt<TAB> shows that it’s there :-) - An Erlang/OTP;
okay ! - A serial console program, such as minicom or screen, so that you can view console output from your AtomVM application.
okay, and the ESP toolchain provides one too - (recommended) For Erlang programs, rebar3;
okay - (recommended) For Elixir programs, mix, which ships with the Elixir runtime;
okay
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.