Trying out Elixir Nerves

Nerves is a framework for developing embedded software with Elixir on a minimal Linux system. I was introduced to it at Abstractions conf this year, and wanted to give it a try.

Nerves is meant for devices that can run Linux, such as Raspberry Pi, Intel Galileo, and Beaglebone Black. I’ll be trying it out on my Raspberry Pi Zero.

Getting Started with Nerves

Once Nerves is installed (check out the getting started page), we can use mix to create a new project. Here I’m calling the project flight_controller (since I intend to use it for my quadcopter), and targeting the Raspberry Pi.

$ mix nerves.new flight_controller --target rpi
* creating flight_controller/config/config.exs
* creating flight_controller/lib/flight_controller.ex
* creating flight_controller/test/test_helper.exs
* creating flight_controller/test/flight_controller_test.exs
* creating flight_controller/rel/vm.args
* creating flight_controller/rel/.gitignore
* creating flight_controller/.gitignore
* creating flight_controller/mix.exs
* creating flight_controller/README.md

Let’s quickly set things up to blink an LED so that we can see that our firmware is running. I’ll use the Elixir Ale library to control IO, so let’s add it to dependencies and start it in mix.exs:15

mix.exs
def application do
  [
    mod: {FlightController, []},
    applications: [
      :logger,
      :elixir_ale  # <-- Added
    ]
  ]
end

def deps do
  [
    {:nerves, "~> 0.3.0"},
    {:elixir_ale, "~> 0.5.6"}  # <-- Added
  ]
end

Then, in flight_controller.ex, I’ll add some logic to blink the LED:

flight_controller.ex
defmodule FlightController do
  use Application

  @blink_duration 150 # ms
  @led_pin 18
  @gpio_on 1
  @gpio_off 0

  def start(_type, _args) do
    {:ok, pid} = Gpio.start_link(@led_pin, :output)

    spawn fn -> blink_forever(pid) end

    {:ok, self}
  end

  def blink_forever(pid) do
    Gpio.write(pid, @gpio_on)
    :timer.sleep @blink_duration
    Gpio.write(pid, @gpio_off)
    :timer.sleep @blink_duration

    blink_forever(pid)
  end
end

After that, I can run some commands to get the dependencies, compile, and build the firmware:

$ mix deps.get

$ mix compile

$ mix firmware

With the firmware created, I can then insert the SD card and burn it:

 $ mix firmware.burn

 Discovered devices:
 0) 14.98 GiB found at /dev/rdisk4
 1) 0.75 GiB found at /dev/rdisk3
 Which device do you want to burn to? 0

 100%

And we have a blinking LED!

Blinking LED

Accessing IEx over a serial cable

Instead of connecting a monitor and keyboard to my Pi, I just want to connect to the IEx console over a serial connection. Luckily I’ve got quite a few USB to Serial converters sitting around from working with Arduino, but if you don’t have one, you can get them on Ebay for ~$2.

As far as configuring the firmware to do this, the FAQ section of the docs covers this quite well.

We’ll need to override a config file in the filesystem. To do this, we’ll need to create some new directories:

$ mkdir -p config/rootfs-additions/etc

Then, we’ll copy the erlinit.config file from our system library into our application so we can override it:

$ cp deps/rpi/nerves_system_rpi/rootfs-additions/etc/erlinit.config config/rootfs-additions/etc

And then edit it:

erlinit.config
# -- snip --

# Specify the UART port that the shell should use.

# Access IEx via HDMI/keyboard (comment this out)
# -c tty1

# Access IEx via serial/UART (add this line)
-c ttyAMA0

# -- snip --

Finally, we tell Nerves to look for the new rootfs directory:

config/config.exs
# ...

config :nerves, :firmware, rootfs_additions: "config/rootfs-additions"

# ...

As a side note, anything you add to this rootfs-additions directory will be added to the filesystem! This will come in handy later.

Now we can insert the SD card and run the familiar compile/build/burn commands:

mix compile
mix firmware
mix firmware.burn

Connecting the Serial converter

Serial Converter

Once the serial converter is plugged into the Pi and computer, you can run:

screen /dev/tty.SLAB_USBtoUART 115200

And we’ve got IEx!

iex(1)> System.cmd("ls", ["/"]) |> elem(0) |> IO.puts
var
usr
tmp
...

Note that your device name may be different, you can figure it out by unplugging the converter, running ls /dev/tty.*, plugging it back in and then running the command again to see which device has been added

Working Wirelessly with Nerves

In a future post, I’ll show how to connect to Wifi, and access IEx and upload firmware wirelessly. I’ll link to it here when it’s complete!

Thanks for reading!

If you have any questions or comments tweet me @bolandrm!

Be sure to follow me if you’re interested if you’re interested in more Nerves related posts!