• Stars
    star
    339
  • Rank 124,632 (Top 3 %)
  • Language
    C
  • License
    Apache License 2.0
  • Created over 10 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Interact with hardware in Elixir - GPIOs, I2C and SPI

elixir_ale - Elixir Actor Library for Embedded

Build Status Hex version

elixir_ale provides high level abstractions for interfacing to GPIOs, I2C buses and SPI peripherals on Linux platforms. Internally, it uses the Linux sysclass interface so that it does not require platform-dependent code.

NOTE: elixir_ale is actively maintained. However, future work is focused on elixir_ale 2.0 (aka Elixir Circuits). Elixir Circuits is more performant, has API naming and return value changes that we couldn't put here, and supports features like automatic I2C retries and GPIO pullups/pulldowns on some platforms. I2C, SPI and GPIO are split up in 3 repositories. See Elixir Circuits.

elixir_ale works great with LEDs, buttons, many kinds of sensors, and simple control of motors. In general, if a device requires high speed transactions or has hard real-time constraints in its interactions, this is not the right library. For those devices, it is recommended to look at other driver options, such as using a Linux kernel driver.

If this sounds similar to Erlang/ALE, that's because it is. This library is a Elixir-ized implementation of the original project with some updates to the C side. (Many of those changes have made it back to the original project now.)

Getting started

If you're natively compiling elixir_ale, everything should work like any other Elixir library. Normally, you would include elixir_ale as a dependency in your mix.exs like this:

def deps do
  [{:elixir_ale, "~> 1.2"}]
end

If you just want to try it out, you can do the following:

git clone https://github.com/fhunleth/elixir_ale.git
cd elixir_ale
mix compile
iex -S mix

If you're cross-compiling, you'll need to setup your environment so that the right C compiler is called. See the Makefile for the variables that will need to be overridden. At a minimum, you will need to set CROSSCOMPILE, ERL_CFLAGS, and ERL_EI_LIBDIR.

elixir_ale doesn't load device drivers, so you'll need to make sure that any necessary ones for accessing I2C or SPI are loaded beforehand. On the Raspberry Pi, the Adafruit Raspberry Pi I2C instructions may be helpful.

If you're trying to compile on a Raspberry Pi and you get errors indicated that Erlang headers are missing (ie.h), you may need to install erlang with apt-get install erlang-dev or build Erlang from source per instructions here.

Examples

elixir_ale only supports simple uses of the GPIO, I2C, and SPI interfaces in Linux, but you can still do quite a bit. The following examples were tested on a Raspberry Pi that was connected to an Erlang Embedded Demo Board. There's nothing special about either the demo board or the Raspberry Pi, so these should work similarly on other embedded Linux platforms.

GPIO

A General Purpose Input/Output (GPIO) is just a wire that you can use as an input or an output. It can only be one of two values, 0 or 1. A 1 corresponds to a logic high voltage like 3.3 V and a 0 corresponds to 0 V. The actual voltage depends on the hardware.

Here's an example of turning an LED on or off:

GPIO LED schematic

To turn on the LED that's connected to the net (or wire) labeled GPIO18, run the following:

iex> alias ElixirALE.GPIO
iex> {:ok, pid} = GPIO.start_link(18, :output)
{:ok, #PID<0.96.0>}

iex> GPIO.write(pid, 1)
:ok

Input works similarly. Here's an example of a button with a pull down resistor connected.

GPIO Button schematic

If you're not familiar with pull up or pull down resistors, they're resistors whose purpose is to drive a wire high or low when the button isn't pressed. In this case, it drives the wire low. Many processors have ways of configuring internal resistors to accomplish the same effect without needing to add an external resistor. It's platform-dependent and not shown here.

The code looks like this in elixir_ale:

iex> {:ok, pid} = GPIO.start_link(17, :input)
{:ok, #PID<0.97.0>}

iex> GPIO.read(pid)
0

# Push the button down

iex> GPIO.read(pid)
1

If you'd like to get a message when the button is pressed or released, call the set_int function. You can trigger on the :rising edge, :falling edge or :both.

iex> GPIO.set_int(pid, :both)
:ok

iex> flush
{:gpio_interrupt, 17, :rising}
{:gpio_interrupt, 17, :falling}
:ok

Note that after calling set_int, the calling process will receive an initial message with the state of the pin. This prevents the race condition between getting the initial state of the pin and turning on interrupts. Without it, you could get the state of the pin, it could change states, and then you could start waiting on it for interrupts. If that happened, you would be out of sync.

SPI

A Serial Peripheral Interface (SPI) bus is a common multi-wire bus used to connect components on a circuit board. A clock line drives the timing of sending bits between components. Bits on the Master Out Slave In MOSI line go from the master (usually the processor running Linux) to the slave, and bits on the Master In Slave Out MISO line go the other direction. Bits transfer both directions simultaneously. However, much of the time, the protocol used across the SPI bus has a request followed by a response and in these cases, bits going the "wrong" direction are ignored. This will become more clear in the example below.

The following shows an example Analog to Digital Converter (ADC) that reads from either a temperature sensor on CH0 (channel 0) or a potentiometer on CH1 (channel 1). It converts the analog measurements to digital, and sends the digital measurements to SPI pins on the main processor running Linux (e.g. Raspberry Pi). Many processors, like the one on the Raspberry Pi, can't read analog signals directly, so they need an ADC to convert the signal.

SPI schematic

The protocol for talking to the ADC in the example below is described in the MCP3002 data sheet. The protocol is very similar to an application program interface (API) for software. It will tell you the position and function of the bits you will send to the ADC, along with how the data (in the form of bits) will be returned.

See Figure 6-1 in the data sheet for the communication protocol. Sending a 0x68 first reads the temperature and sending a 0x78 reads the potentiometer. Since the data sheet shows bits, 0x68 corresponds to 01101000b. The leftmost bit is the "Start" bit. The second bit is SGL/DIFF, the third bit is ODD/SIGN, and the fourth bit is MSBF. From table 5-1, if SGL/DIFF==1, ODD/SIGN==0, and MSBF==1 then that specifies channel 0 which is connected to the thermometer.

# Make sure that you've enabled or loaded the SPI driver or this will
# fail.
iex> alias ElixirALE.SPI
iex> {:ok, pid} = SPI.start_link("spidev0.0")
{:ok, #PID<0.124.0>}

# Read the potentiometer

# Use binary pattern matching to pull out the ADC counts (low 10 bits)
iex> <<_::size(6), counts::size(10)>> = SPI.transfer(pid, <<0x78, 0x00>>)
<<1, 197>>

iex> counts
453

# Convert counts to volts (1023 = 3.3 V)
iex> volts = counts / 1023 * 3.3
1.461290322580645

As shown above, you'll find out that Elixir's binary pattern matching is extremely convenient when working with hardware. More information can be found in the Kernel.SpecialForms documentation and by running h <<>> at the IEx prompt.

I2C

An Inter-Integrated Circuit (I2C) bus is similar to a SPI bus in function, but uses fewer wires. It supports addressing hardware components and bidirectional use of the data line.

The following shows a bus IO expander connected via I2C to the processor.

I2C schematic

The protocol for talking to the IO expander is described in the MCP23008 Datasheet. Here's a simple example of using it.

# On the Raspberry Pi, the IO expander is connected to I2C bus 1 (i2c-1).
# Its 7-bit address is 0x20. (see datasheet)
iex> alias ElixirALE.I2C
iex> {:ok, pid} = I2C.start_link("i2c-1", 0x20)
{:ok, #PID<0.102.0>}

# By default, all 8 GPIOs are set to inputs. Set the 4 high bits to outputs
# so that we can toggle the LEDs. (Write 0x0f to register 0x00)
iex> I2C.write(pid, <<0x00, 0x0f>>)
:ok

# Turn on the LED attached to bit 4 on the expander. (Write 0x10 to register
# 0x09)
iex> I2C.write(pid, <<0x09, 0x10>>)
:ok

# Read all 11 of the expander's registers to see that the bit 0 switch is
# the only one on and that the bit 4 LED is on.
iex> I2C.write(pid, <<0>>)  # Set the next register to be read to 0
:ok

iex> I2C.read(pid, 11)
<<15, 0, 0, 0, 0, 0, 0, 0, 0, 17, 16>>

# The operation of writing one or more bytes to select a register and
# then reading is very common, so a shortcut is to just run the following:
iex> I2C.write_read(pid, <<0>>, 11)
<<15, 0, 0, 0, 0, 0, 0, 0, 0, 17, 16>>

# The 17 in register 9 says that bits 0 and bit 4 are high
# We could have just read register 9.

iex> I2C.write_read(pid, <<9>>, 1)
<<17>>

FAQ

Where can I get help?

Most issues people have are on how to communicate with hardware for the first time. Since elixir_ale is a thin wrapper on the Linux sys class interface, you may find help by searching for similar issues when using Python or C.

For help specifically with elixir_ale, you may also find help on the nerves channel on the elixir-lang Slack. Many Nerves users also use elixir_ale.

Why isn't elixir_ale a NIF?

While elixir_ale should never crash, it's hard to guarantee that weird conditions on the I2C or SPI buses wouldn't hang the Erlang VM. elixir_ale errors on the side of safety of the VM.

I tried turning on and off a GPIO as fast as I could. Why was it slow?

Please don't do that - there are so many better ways of accomplishing whatever you're trying to do:

  1. If you're trying to drive a servo or dim an LED, look into PWM. Many platforms have PWM hardware and you won't tax your CPU at all. If your platform is missing a PWM, several chips are available that take I2C commands to drive a PWM output.
  2. If you need to implement a wire level protocol to talk to a device, look for a Linux kernel driver. It may just be a matter of loading the right kernel module.
  3. If you want a blinking LED to indicate status, elixir_ale really should be fast enough to do that, but check out Linux's LED class interface. Linux can flash LEDs, trigger off events and more. See nerves_leds.

If you're still intent on optimizing GPIO access, you may be interested in gpio_twiddler.

Where's PWM support?

On the hardware that I normally use, PWM has been implemented in a platform-dependent way. For ease of maintenance, elixir_ale doesn't have any platform-dependent code, so supporting it would be difficult. An Elixir PWM library would be very interesting, though, should anyone want to implement it.

Where's 1-wire support?

There is a library available that supports the 1-wire protocol, see onewire_therm.

Can I develop code that uses elixir_ale on my laptop?

You'll need to fake out the hardware. Code to do this depends on what your hardware actually does, but here's one example:

Please share other examples if you have them.

How do I debug?

The most common issue is communicating with an I2C or SPI device for the first time. For I2C, first check that an I2C bus is available:

iex> ElixirALE.I2C.device_names
["i2c-1"]

If the list is empty, then I2C is either not available, not enabled, or not configured in the kernel. If you're using Raspbian, run raspi-config and check that I2C is enabled in the advanced options. If you're on a BeagleBone, try running config-pin and see the Universal I/O project to enable the I2C pins. On other ARM boards, double check that I2C is enabled in the kernel and that the device tree configures it.

Once an I2C bus is available, try detecting devices on it:

iex> ElixirALE.I2C.detect_devices("i2c-1")
[4]

The return value here is a list of device addresses that were detected. It is still possible that the device will work even if it does not detect, but you probably want to check wires at this point. If you have a logic analyzer, use it to verify that I2C transactions are being initiated on the bus.

Options for debugging a SPI issue are more limited. First check that the SPI bus is available:

iex> ElixirALE.SPI.device_names
["spidev0.0", "spidev0.1"]

If nothing is returned, verify that SPI is enabled and configured on your system. The steps are identical to the I2C ones above except looking for SPI.

If you're having trouble with GPIOs, the files controlling them are in /sys/class/gpio. ElixirALE is a thin wrapper on the files so if something can be accomplished there, it usually can be accomplished in ElixirALE. One big omission from the directory is support for internal pull-ups and pull-downs. These are very convenient for buttons so that external resisters aren't needed. Unfortunately, the way to handle pull-ups and pull-downs is device specific. If you're on a Raspberry Pi, see gpio_rpi.

Will it run on Arduino?

No. Elixir ALE only runs on Linux-based boards. If you're interested in controlling an Arduino from a computer that can run Elixir, check out nerves_uart for communicating via the Arduino's serial connection or firmata for communication using the Arduino's Firmata protocol.

Can I help maintain elixir_ale?

Yes! If your life has been improved by elixir_ale and you want to give back, it would be great to have new energy put into this project. Please email me.

License

This library draws much of its design and code from the Erlang/ALE project which is licensed under the Apache License, Version 2.0. As such, it is licensed similarly.

More Repositories

1

muontrap

Keep your ports contained
Elixir
147
star
2

code128

Trivial Code 128 barcode encoder
C
51
star
3

elixirbot

Elixir robots created for Erlang Factory 2015
Eagle
35
star
4

bbb-buildroot-fwup

Example project showing how to use buildroot and fwup
Makefile
26
star
5

midi_synth

Play music in Elixir
Elixir
26
star
6

relsync

Synchronize Erlang/OTP releases to remote nodes
Erlang
26
star
7

snake

Snake in Elixir
Elixir
23
star
8

nerves_system_npi_imx6ull

Nerves system for the Seeed Studio i.MX6ULL-eMMC dev board
Elixir
14
star
9

mmccopy

Convenient alternative to dd for writing images to SDCards
C
14
star
10

embedded-elixir

News and Articles on Elixir in Embedded Systems
HTML
10
star
11

qt-rectangles

Test program for OpenGL ES performance on RPi
C++
10
star
12

qt-opencl

Fixes for http://qt.gitorious.org/qt-labs/opencl
C++
8
star
13

see-experiments

Erlang
8
star
14

one_dhcpd

The Elixir DHCP Server that only serves one very important client
Elixir
8
star
15

raspijpgs

Simple command-line driven MotionJPEG streamer for the Raspberry Pi
C
8
star
16

hollowcore-h264

The Android OpenCORE multimedia framework stripped down to just the H.264 decoder to make it easier to integrate.
C++
7
star
17

vultr_example

Experiment in using Nerves to deploy to the clouds
JavaScript
7
star
18

fhunleth-buildroot-experiments

A Buildroot repository for various experiments
Shell
7
star
19

u-boot-squashfs

Experimental support for SquashFS in U-Boot
C
6
star
20

buildroot-edison

Working on Intel Edison support in Buildroot...
Makefile
6
star
21

buildroot-bbb

Read README below
C
5
star
22

wpa_supplicant.ex

See Nerves.WpaSupplicant now
C
5
star
23

rpi_video

Try to playback video on the RPi on Nerves
C
5
star
24

nerves-see

Joe Armstrong's SEE code in the Nerves embedded environment
Erlang
5
star
25

gpio_twiddler

GPIO twiddling benchmark
Elixir
5
star
26

android_external_i2c_tools

i2c_tools for Android
Perl
4
star
27

beam_benchmarks

Collection of benchmarks to run on devices running the BEAM
Erlang
4
star
28

mdns_query

C
4
star
29

circuits_led

Experiments
Elixir
4
star
30

nbtty

Simplification of dtach to prevent things from blocking a tty
C
4
star
31

osd32mp157c-brk-buildroot

Buildroot fork to support the OSD32MP157C-BRK board
Makefile
4
star
32

nerves_ev3_example

Example application for getting started with the Lego EV3 and Nerves
Elixir
4
star
33

dumpdm365

Utility for dumping and modifying registers on TI Davinci DM36x processors
C
4
star
34

am3358_memtester

Bare metal port of memtester to AM335x-based boards
C
3
star
35

binary_clock

Build a Binary Clock with Elixir and Nerves
3
star
36

ffmpeg

Experimental ffmpeg modifications
C
3
star
37

export_server

A port of Garrett Smith's port_server example to Elixir
Elixir
3
star
38

kiosk_system_rpi4

Nerves QtWebEngine Kiosk system for Raspberry Pi 4
Elixir
3
star
39

nerves_presenter

C++
3
star
40

nerves_system_up_board

Elixir
3
star
41

nv12topnm

Simple utility for converting NV12 memory dumps to PNM images
C
3
star
42

realtime_tests

Real-time experiments in Elixir
Elixir
3
star
43

keytar

Rockband 3 Keytar USB driver and key generator
C
3
star
44

nerves_system_jslinux

Elixir
3
star
45

nerves_system_orange_pi_zero_3

Nerves system for the OrangePi Zero 3
Elixir
3
star
46

tar_merger

Elixir
3
star
47

eef-embedded-systems-wg

Temporary location for the EEF Embedded Systems WG repo
2
star
48

fwup-snap

Metadata for building snap packages for fwup
2
star
49

canbcm

Old Erlang project to interface with the Linux SocketCAN BCM driver (not maintained)
C
2
star
50

fbgrab

Random fbgrab updates
C
2
star
51

intel_hex

Decode Intel hex records in Elixir
Elixir
2
star
52

logging_demo

RingLogger demo code for the Baltimore Elixir meetup
Elixir
2
star
53

nerves_system_rpi3_kiosk

See https://github.com/LeToteTeam/kiosk_system_rpi3 now
HTML
2
star
54

elixir-ply

ply for Elixir (VERY EXPERIMENTAL)
Elixir
2
star
55

l2elog

Linux syslog/klog bridge to Erlang's Lager logger
Erlang
2
star
56

iodata_nif

IOData NIF experiments
Elixir
2
star
57

nerves_system_rpi0_2

64-bit Nerves System for RPi Zero 2 W and RPi 3A
Elixir
2
star
58

psk_maker

A to-be-named library that supports the passphrase->psk algorithm and maybe more
Elixir
1
star
59

dtach

Patches to http://dtach.sourceforge.net/ (These have been merged!)
C
1
star
60

nerves_key

1
star
61

nerves_system_alix

Base Nerves system configuration for the PC Engines ALIX - WARNING: Unmaintained
Elixir
1
star
62

dotfiles

Yay
Shell
1
star
63

net_managers.ex

Use Nerves.InterimWifi for wifi support
Elixir
1
star
64

elixirport

Elixir/Port process test app
C
1
star
65

erlangdc_demo

Nerves demo app for ErlangDC 2013
Erlang
1
star
66

nerves_system_galileo

Base Nerves system configuration for the Intel Galileo Gen 2 - WARNING: Unmaintained
Elixir
1
star
67

tls_experiment

Elixir
1
star
68

erlang_fileutils

Erlang versions of Unix file utilities to enable distributed use
Erlang
1
star
69

dumb_ldattach

ldattach, but dumber
C
1
star
70

jsnerves

Emscripten, TinyEMU, Nerves experiment
JavaScript
1
star
71

silhouette

Experiments in making shadows
C++
1
star
72

test_build_embedded

Elixir
1
star
73

troodon-cam

Work in progress AM335x PRU -> camera interface with Erlang
C
1
star
74

buildroot-pc-dhcpserver

DHCP/DNS server for managing a LAN island - ISO image
C
1
star
75

android_external_lsof

lsof for Android
C
1
star
76

dm36x-packager

Python
1
star
77

buildroot-olinuxino

Buildroot repo for Olimex OLinuxino support
C
1
star
78

presi-aoke

Presentation Karaoke Player
C++
1
star
79

laser-cutter-svgs

SVG files and utilities for various laser cutter designs
1
star
80

troodonsw-website

Troodon Software website source code
CSS
1
star
81

v4l2reg

Utility for getting and setting registers on V4L2 cameras
C
1
star
82

nerves_system_bbb_kiosk

Beaglebone Black configuration that drives a display
Shell
1
star
83

nerves_system_ag150

Base Nerves system configuration for the Logic Supply AG150 - WARNING: Unmaintained
Elixir
1
star
84

htc-kernel-incrediblec

2.6.32.15 kernel source for HTC incredible (use CyanogenMod version of kernel once it gets updated)
C
1
star
85

sdburner

UI for CoderDojoDC Raspberry Pi SDCard burner device
C++
1
star
86

net_basic.ex

This has been moved to Nerves.NetworkInterface
C
1
star