Going bare metal with Elixir and GRiSP

Writing a low-level hello world with the high-level functional language of the future.


What we will learn:

  1. Where to get your hardware, how to set up the development environment and how to generate your first application.
  2. How to configure the app to be deployed using mix_grisp.
  3. How to code a simple blinking light using Elixir.
  4. How to access our applications’ remote terminal via a serial connection.
  5. How to interact with our application using the remote terminal.

Introduction

With a background in mechanical and electronic engineering, my first love would probably be robotics. As such, I have been known to dabble in a bit of low-level C-code and got exposure to the likes of Arduino, ROS and Python, albeit as a hobbyist rather than a professional robotics engineer.

What pays the bills however is my day job. Here I leverage Elixir and Phoenix, and much of the beautiful concurrency that they offer to build pub/sub functionality in a reasonably carefree way. The comparative ease with which multiple processes could be wrangled was astounding, and got me thinking. What if we could do this in robotics applications? No more fake multi-threading in c-code. No more real-time processing with multiple interrupts that are impossible to debug… oh wait no, we want that. No, not the debugging headaches, the real-time capabilities that are required for control and sampling algorithms.

Aw man, that means no Linux right? Which in turn means no fun high level languages?

As it turns out, this may not be the case. Or at least not for long.

Meet, GRiSP.

How these guys decided to handle the situation was to keep the Erlang(BEAM) VM. But rather than run it on a full OS, they run it on top of a modified version on RTEMS, in order to bring the Erlang processes as close to direct hardware control as possible. Something that they hope allows for near hard real-time performance, without compromising the high level features that comes with using Erlang(they also plan on developing true hard real-time capabilities for the platform).

Now, back to the topic at hand. With the BEAM running on hardware, and Elixir being able to run on the BEAM, does this mean we can run Elixir on the bare metal too?

The short answers is… yes, and that is what this post is all about.


Setup

In order to get going on this project we first need to get the following in place:

1. Make sure you have your GRiSP board handy, as well as a micro SD card for your application. This is a hardware platform designed specifically for use with GRiSP. It can be found here.

2. Install all the required dependencies

sudo apt-get install build-essential git unzip zlib1g-dev bison flex texinfo curl automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev python2.7-dev python-minimal

3. Install Erlang(21) and Elixir(v1.7). I will be explaining what I did on Ubuntu 16.4, but you can find a really good guide for most common operating systems here.

On Ubuntu

a) Register the Erlang repo
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
b) Update the package database
sudo apt-get update
c) Install Elixir
sudo apt-get install esl-erlang

4. Set up your environment(you can also find these instructions on the GRiSP website)

4.1 Install rebar3.

mkdir ~/bin
curl -o ~/bin/rebar3 https://s3.amazonaws.com/rebar3/rebar3chmod +x ~/bin/rebar3

4.2 Configure rebar3.

We first create the config directory:

mkdir -p ~/.config/rebar3

In this, we place a file called rebar.config with the following contents:

{plugins, [
rebar3_hex,
rebar3_grisp
]}.

5. Finally we are ready to generate the raw project.

5.1 Create the directory(in my case inside my Documents folder).

mkdir GRiSP && cd GRiSP

5.2 Generate our new Elixir application, with a supervisor tree.

mix new grisp_blink --sup

This leaves us with both a development environment, and a basic app to work from. This is probably the point at which you would want to initiate a new repository if you are planning on using git.


Deployment Configuration

In the previous section we had set up our development environment, and our blank application. This does not, however, mean that we are ready to deploy the application to the SD card. But we are almost there. To get the application deployed, we will need the following:

  1. Include grisp, and mix_grisp Hex packages in our project.
  2. Add the grisp configuration to mix.exs.
  3. Set up the distillery configuration that is needed by mix_grisp to build a release.
  4. Add a .mustache file that serves as a template for the RTEMS boot configuration.

Each of these steps is described in some detail below.

Get grisp and mix_grisp packages

Add the dependencies to mix.exs.

defp deps do
[
{:grisp, "~> 1.1"},
{:mix_grisp, "~> 0.1.0", only: :dev}
]
end

Note that we do not need mix_grisp in the live application, so it is only specified for the dev environment.

Now let us fetch these dependencies.

mix deps.get

Add GRiSP configuration

Replace the line deps: deps() in mix.exs with the mentioned configuration, so that your project definition looks like this:

 def project do
[
app: :grisp_blink,
version: "0.1.0",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
     ------- New text below here -----
deps: deps(),
grisp: [
otp: [version: "21.0"],
deploy: [
destination: "{media_path}"
]
]
------ New text above here ------
    ]
end

Note that {media_path} will need to be replaced with the path to your SD card. I got this by simply inserting the card into my card reader and executing command below, and picking the path the card of my choice:

ls /media* -R

Distillery config

Scaffold the distillery config by running the following command:

mix release.init

Now replace the contents of the newly created file rel/config.exs with the following code:

~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))
use Mix.Releases.Config,
default_release: :default,
default_environment: :grisp
environment :grisp do
set include_erts: true
set include_src: false
set cookie: :"GRiSP"
end
release :grisp_blink do
set version: current_version(:grisp_blink)
end

Boot config

This is placed in grisp/grisp_base/files/grisp.ini.mustache. The file serves as a template for the boot config that will end up on the SD card. The compiled file will tell RTEMS where to locate ERTS(Erlang runtime system application), and to boot our application when Erlang starts up. It will also start an interactive Elixir shell which we can access via a serial connection.

[boot]
image_path = /media/mmcsd-0-0/{{release_name}}/erts-{{erts_vsn}}/bin/beam.bin
[erlang]
args = erl.rtems -- -mode embedded -home . -pa . -root {{release_name}} -boot {{release_name}}/releases/{{release_version}}/{{release_name}} -s elixir start_cli -noshell -user Elixir.IEx.CLI -extra --no-halt
[network]
ip_self = dhcp
wlan = enable

You are now ready to deploy your application. Do so by making sure the chosen card is empty, and then running the following command:

sudo mix grisp.deploy

The output looks as follows:

You would now be able to boot up the device, but there is likely no point, since we have not implemented any useful code. We will do this in the section that follows.


Blinking light code

The grisp package already contains an Erlang function that will allow you to blink a light. We did, however, want to explore the use of Elixir on the GRiSP board so we will be writing a simple Elixir version instead.

To do this we will:

  1. Implement an Elixir agent to keep track of the current state and add a function that toggles the light on or off, based on the current state.
  2. Deploy and test our newly created features.
  3. Automate the blinking using a timer function.

Each of these tasks are discussed below:

Elixir Agent

Elixir agents provide a way for us to hold state. We will be using this to keep track of the LED’s current state. Firstly we create a file lib/grisp_blink/blinker.ex, add use Agent to the top of the file, and provide a method by which our supervision tree can start up the agent.

The resulting code is shown below:

defmodule GrispBlink.Blinker do
use Agent
  def start_link(_) do
Agent.start_link(fn -> false end, name: __MODULE__)
end
end

The init function initiates the state as the Boolean false value, tells the supervisor to register the agent using the modules’ own name using __MODULE__. We will now be able to access the live agent using GrispBlink.Blinker, instead of a process id.

Next we add the functional code that allows us to toggle the LED on and off. For this tutorial, we will just be using the red led, but various other options are available if you would like to play around with them.

We provide a high level function toggle_led/0 through which we can toggle the light to a new state, without knowing the current state. See final module code below. The function gets the current state, and toggles its value, using Agent.get_and_update/2 with its own name as the process identifier. The returned value is used to call toggle_led/1, which switches the red LED on if the current state is false, and off if the current state is true. Note that the grisp_led Erlang module from GRiSP is used to achieve this.

defmodule GrispBlink.Blinker do
use Agent
  def start_link(_) do
Agent.start_link(fn -> false end, name: __MODULE__)
end
  def toggle_led() do
Agent.get_and_update(__MODULE__, fn(state) ->
{state, not state}
end)
|> toggle_led()
end
  def toggle_led(false) do
:grisp_led.color(1, :red)
end
def toggle_led(true) do
:grisp_led.off(1)
end
end

Next we need to start up the agent in our supervision tree, by adding it as a child thereof. To achieve this, we change the start function in application.exs to look as follows:

  def start(_type, _args) do
children = [
{GrispBlink.Blinker, []}
]
    opts = [strategy: :one_for_one, name: GrispBlink.Supervisor]
Supervisor.start_link(children, opts)
end

We now have an application that is worth deploying and booting up.

Deployment

As discussed before, mount the SD card, empty it out and run:

mix grisp.deploy

We wait for the copying of the files to finish, and unmount the SD card.

Remove the SD card, and insert it into the card slot on the GRiSP board.

Now for the moment of truth… plug a micro USB cable into your PC, and hook it up to the GRiSP board.

You can follow the boot procedure by opening a new terminal and running the following command inside it(if this does not work, please refer to the GRiSP website on how to identify the correct device):

screen /dev/ttyUSB1 115200

Unfortunately the boot process takes quite long. So please be patient while it is happening. If the boot is not successful you will receive an error message. Otherwise, after a while to you will see the familiar IEx console pop up.

You can now interact with the board in the typical IEx console fashion.

Please note that you currently cannot see what you type into the console. This makes working with it a bit more difficult.

The way I solve this is by typing my commands out in a text editor. I then copy these commands and paste it in the terminal using SHIFT+CTRL+C followed by RETURN.

To see the LED go on and off, try executing the following command in the console several times:

GrispBlink.Blinker.toggle_led()

Automating the blinking of the LED

We can also automate the blinking of the LED using a timer function.

For this, we use the Erlang module timer the and function apply_interval/4 which requires a time value in milliseconds, and the function details(module name, function name and arguments). The function returns {:ok, pid}. In order to manipulate this timer later, we had best keep that pid handy.

Try to blink the LED once every second using the following command:

{:ok, pid} = :timer.apply_interval(1000, GrispBlink.Blinker, :toggle_led, [])

In order to cancel the timer and stop the blinking, we can do the following:

:timer.cancel(pid)

Look at the Erlang timer module and try some more interesting timing combinations.


Conclusion

This is a very small, and seemingly inconsequential introduction to using Elixir in a bare metal application. It does however provide us with a basic start for other exciting projects. I hope that you will continue playing around with Erlang and GRiSP. When you do, please share the GRiSP community. And when you struggle? Please ask questions. There will always be someone who is willing to help. Happy coding!