Arduino companions for Nerves devices (Part 1)
By: Gabriel Roldán
Tags: Embedded Systems, Arduino, Elixir, Avr, Nerves

The main goal of this articles is to show how to combine Nerves with an Arduino board (or a single AVR micro-controller) in a way that we can meet two objectives:

To have a mono-repo with the two projects as they are intimate related.

To keep the micro-controller firmware updated as easy as we do mix firmware && ./upload.sh or publish it through Nerves Hub.

This first part covers how to integrate the Arduino code into the Nerves project and how to tie them up to compile them both with mix firmware .

Once upon a time…

I started building things with Arduino (as many others) and moved slowly to other devices like the ESP8266. One of the wonderful features of these devices is the high amount of libraries you have to connect with many kinds of devices (Displays, Motor controllers, all kind of sensors, etc.).

A year ago (maybe a bit more) I got interested on Nerves. At that moment I had been working on an Elixir project for a while, and for that reason, using Elixir for embedded software sounded like an attractive concept to me.

So after some experimentation I decided to use it to re-implement an IR-Gateway device that I already had running on an ESP8266 to control my Air conditioner. The device wasn’t too complex, It was capable of send/receive raw IR signals and measure temperature and humidity. Sending/receiving the data through an MQTT server.

With an Arduino, the IR sending can done modulating a carrier signal generated by a PWM output, which is basically turning ON/OFF that carrier following the protocol timings.

So there I found the first issue: Using the PWM output on the Raspberry PI and depending on an Elixir process to turn the signal ON/OFF resulted in inexact timings. Probably, any process running in userspace will end up with the same issue because of the OS scheduler, and in this case, also the Erlang scheduler. Also the problem can be more serious if the system is on heavy load.

On that situation the posible solutions are:

To look for a library with a kernel-level driver that I could be used from Nerves. To move the IR management to an external micro-controller and maintain both projects.

At that moment I just gave up… but some months ago I decided to go for the second option and tried to design a solution that I could use in general when I want to combine both devices. And this is what this article is about.

Getting started

Let’s start creating the Nerves project:

mix nerves.new avr_companion_sample

Most people uses the Arduino IDE to edit/compile/upload Arduino sketches, however for this project we’ll use a Makefile build so we can use elixir_make to integrate it as part of the Nerves build.

Arduino-mk is a very robust Makefile that knows how to build Arduino sketches. Check the README to know how to install it.

We will put the Arduino code in the arduino folder on the root of the Nerves project.

We need to create a couple of Makefiles. The first one includes the Arduino-mk and configures BOARD_TAG var to uno as we going to use an Arduino Uno for this example.

# File: arduino/Makefile BOARD_TAG = uno

include /usr/share/arduino/Arduino.mk

NOTE: Check the documentation for your system in case the Arduino-mk file is installed in a different location.

The second Makefile has some definitions to make it work at Nerves compile time.

# File: Makefile NAME = arduino

PREFIX = $(MIX_APP_PATH)/priv

FIRMWARE = $(PREFIX)/$(NAME).hex # Unexport some vars defined by Nerves that

# will cause issues with the arduino Makefile

unexport CFLAGS

unexport CXX

unexport CXXFLAGS

unexport OVERRIDE_EXECUTABLES export TARGET = $(NAME)

export OBJDIR = $(MIX_APP_PATH)/obj $(FIRMWARE): build $(PREFIX)

cp $(OBJDIR)/$(NAME).hex $(FIRMWARE) build:

$(MAKE) -C $(NAME) $(PREFIX):

mkdir -p $@

Pay attention to the FIRMWARE var. It’s the place where we store the generated .hex file that we will need later in runtime. The file is stored in the application priv folder so it will be included in the release.

Now we need something to compile…

/* File: arduino/main.ino */ void setup() {



} void loop() {



}

Last but not least we have to include elixir_make in the project.

Add the lib to the dependencies:

{:elixir_make, "~> 0.4", runtime: false}

And to the project config:

def project do

[

...

compilers: [:elixir_make] ++ Mix.compilers(),

...

]

end

The project is now ready to do a little compilation test. For that you have to define the ARDMK_VENDOR environment variable depending on your system so you have to check the documentation.

In my case I’m using Arch Linux:

export ARDMK_VENDOR=archlinux-arduino

And that’s it! Now you can run mix compile and watch the output of the arduino compilation.

> mix compile [...] /usr/bin/avr-size --mcu=atmega328p -C --format=avr /tmp/avr_companion_sample/_build/dev/lib/avr_companion_sample/obj/arduino.elf

AVR Memory Usage

----------------

Device: atmega328p Program: 446 bytes (1.4% Full)

(.text + .data + .bootloader) Data: 9 bytes (0.4% Full)

(.data + .bss + .noinit) make[1]: Leaving directory '/tmp/avr_companion_sample/arduino'

mkdir -p /tmp/avr_companion_sample/_build/dev/lib/avr_companion_sample/priv

cp /tmp/avr_companion_sample/_build/dev/lib/avr_companion_sample/obj/arduino.hex /tmp/avr_companion_sample/_build/dev/lib/avr_companion_sample/priv/arduino.hex

Compiling 2 files (.ex)

Generated avr_companion_sample app

Conclusion

On this first part we learned how to add an Arduino Sketch into a Nerves project and how include the compilation output on the released firmware.

On the next part we will cover the code to update the companion device automatically and also we’ll include some code in both devices to interact with each other.

Continues on: Arduino companions for Nerves devices (Part 2)