Blinking LED using Elixir embedded image on Raspberry Pi
In the April Seattle Erlang/Elixir Meetup the awesome guys at Rose Point Navigation gave a demo of burning Elixir/Erlang application as part of a custom built image and then running it on Raspberry Pi. This was very interesting to me as I was also curious how to do that kind of thing. A custom built image allows you to keep your firmware size at very low and keep only necessary things so that we get better boot time etc.
In this post we will make the 'Hello World' of the embedded world, which is blinking a light. The hardware I am going to use will be Raspberry Pi (model A+) and we will blink the LED that is already part of Raspberry Pi.
We will do the following
- Create Elixir project for blinking LED
- Create image for Raspberry PI using Nerves
- Burning image on SD card
- Ooze over the blinking LED
Create Elixir blinking LED project
Lets create an Elixir project using mix
and add the necessary dependencies.
$> mix new blinky
The application
and deps
in mix.exs
should be as follows
...
def application do
[applications: [:logger],
mod: {Blinky, []}]
end
defp deps, do: [
{ :exrm, "~> 0.15.0" }
]
...
Now lets write the code to blink the green LED on Raspberry Pi. Edit the lib/blinky.ex
to look like this
defmodule Blinky do
require Logger
# Trigger file for LED0
@led_trigger "/sys/class/leds/led0/trigger"
# Brightness file for LED0
@led_brightntess "/sys/class/leds/led0/brightness"
def start(_type, _args) do
# Setting the trigger to 'none' by default its 'mmc0'
File.write(@led_trigger, "none")
# Start blinking forever
blink_forever
end
def blink_forever do
# Turn on the green LED and sleep for 1000ms
Logger.debug "Turning ON green"
set_led(true)
:timer.sleep 1000
# Turn off the green LED and sleep for 1000ms
Logger.debug "Turning OFF green"
set_led(false)
:timer.sleep 1000
# Blink again
blink_forever
end
# Setting the brightness to 1 in case of true and 0 if false
def set_led(true), do: set_brightness("1")
def set_led(false), do: set_brightness("0")
def set_brightness(val) do
File.write(@led_brightntess, val)
|> inspect
|> Logger.debug
end
end
Lets see what we did there. Raspberry PI has some onboard LEDs like the power LED or the LEDs linked to ethernet.
The green LED on Raspberry Pi can be controller by the user by modifing few files. The files under /sys/class/leds/led0
control the green led on Raspberry Pi.
The files we care about are led0/trigger
and led0/brightness
. The trigger files defines that how the LED will be triggered, by default the green LED is trigged by any action on SD card hence its value is 'mmc0'. We set the trigger to none
so that it is not linked to SD card.
The second step is to turn the LED on and off. The brightness file controls that. Though the range of value you can put inside it is 0..255 but as the brightness is not controllable so anything above 0 turns the green LED on.
In blinky.ex
we first set the trigger to none and then turn on the green led, wait for 1s, turn off the green led, wait for 1s and then repeat the whole process again. Run mix do deps.get, compile
to make sure everything compiles.
Create Image for Raspberry PI
Nerves-Project uses the Erlang/Elixir release and creates a bootable firmware image from that using Buildroot. The advantage of it is that its a shippable image which you can publish and anyone can burn that image onto the board/SD card and be done with it. This creates a bare bone image, stripping away all the useless things for embedded system such as video, UI etc. which in turn makes it bootup pretty quickly.
You can use the instructions at Nerves-Project Github to download the source code and compile it but it can take upto hour or more depending upon your machine. I am a sucker for Docker hence I have published an image that you can just pull and use, making the process a lot quicker.
Lets first pull in the docker image, run it and run few commands under it. If you are interested in seeing the Dockerfile for that image then you can see it here.
$> docker pull zabirauf/nerves-sdk-elixir
$> docker run -i -t -v /path/to/blinky:/opt/blinky zabirauf/nerves-sdk-elixir /bin/bash
root@bb9f59897a2a: cd /Downloads/nerves-sdk && source ./nerves-env.sh
We have to create a Makefile
in our blinky project so that nerves can compile it and create an image.
/path/to/blinky: echo 'include $(NERVES_ROOT)/scripts/nerves-elixir.mk' > Makefile
So we just include the makefile that already comes with nerves.
Now in your docker terminal which is still running the docker container lets create the image
root@bb9f59897a2a: cd /opt/blinky
root@bb9f59897a2a: make
This will compile the project, create an erlang release from it and then use that release to create an image. After that is done your image would be under /path/to/blinky/_images
. Lets see how big are the images run ls -alh
under _images
.
drwxr-xr-x 6 zohaibrauf staff 204B Apr 25 03:31 .
drwxr-xr-x 18 zohaibrauf staff 612B Apr 25 02:31 ..
-rw-r--r--@ 1 zohaibrauf staff 6.0K Apr 25 03:01 .DS_Store
-rw-r--r-- 1 zohaibrauf staff 15M Apr 25 03:31 blinky.fw
-rw-r--r-- 1 zohaibrauf staff 328M Apr 25 03:31 blinky.img
drwxr-xr-x 20 zohaibrauf staff 680B Apr 23 23:05 rootfs
We can see that the blinky.fw
is actually 15MB. But we will burn blinky.img
to Raspberry Pi, you might wonder that why is that 328MB. If you use a hex viewer to open that, you will see that most of that is just 0
and actually everything i.e. Kernel, Erlang, Elixir and our application just adds upto 15MB.
Burning image on SD Card
I am using Mac so the instructions would be for Mac. If you are on Linux or Windows then you can get the instructions to burn .img
on SD card by just searching for it.
First run diskutil list
and see where is your SD card mounted (in my case its /dev/disk7). Then run the following
CAUTION: Wrong disk path can cause you to loose data for that disk. Make sure you are using the right SD card path.
$> diskutil unmountDisk /dev/disk7
$> sudo dd bs=1m if=/path/to/blinky/_images/blinky.img of=/dev/disk7
This will take some time and then once its done you can eject the SD card. If this does not work then use the alternative method mentioned at Raspberry Pi
Ooze over blinking LED
Insert the SD card into your Raspberry PI and power it up. Wait for few seconds and then you should see the blinking green LED.
You can get the complete project here.
What will be next
You can use the Elixir Ale to control the GPIO pins on Raspberry Pi. I will also explore that more in my next post and also look at some things open sourced by the people at Rose Point Navigation i.e. Cellulose, which will make discovery of these embedded devices easier and also provides easy controlling & updating firmware on the fly. You should check that out, though its in very early stages.