{"id":13508262,"url":"https://github.com/fhunleth/elixir_ale","last_synced_at":"2025-04-12T14:56:55.039Z","repository":{"id":16185280,"uuid":"18931840","full_name":"fhunleth/elixir_ale","owner":"fhunleth","description":"Interact with hardware in Elixir - GPIOs, I2C and SPI","archived":false,"fork":false,"pushed_at":"2019-04-03T10:34:30.000Z","size":307,"stargazers_count":343,"open_issues_count":1,"forks_count":44,"subscribers_count":20,"default_branch":"master","last_synced_at":"2024-10-13T09:56:24.579Z","etag":null,"topics":["elixir","gpio","i2c","nerves","spi"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fhunleth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-19T02:48:39.000Z","updated_at":"2024-08-22T04:08:20.000Z","dependencies_parsed_at":"2022-09-26T21:10:43.729Z","dependency_job_id":null,"html_url":"https://github.com/fhunleth/elixir_ale","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fhunleth%2Felixir_ale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fhunleth%2Felixir_ale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fhunleth%2Felixir_ale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fhunleth%2Felixir_ale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fhunleth","download_url":"https://codeload.github.com/fhunleth/elixir_ale/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248586249,"owners_count":21128997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","gpio","i2c","nerves","spi"],"created_at":"2024-08-01T02:00:50.545Z","updated_at":"2025-04-12T14:56:55.002Z","avatar_url":"https://github.com/fhunleth.png","language":"C","funding_links":[],"categories":["Hardware","Libraries"],"sub_categories":[],"readme":"# elixir_ale - Elixir Actor Library for Embedded\n\n[![Build Status](https://travis-ci.org/fhunleth/elixir_ale.svg)](https://travis-ci.org/fhunleth/elixir_ale)\n[![Hex version](https://img.shields.io/hexpm/v/elixir_ale.svg \"Hex version\")](https://hex.pm/packages/elixir_ale)\n\n`elixir_ale` provides high level abstractions for interfacing to GPIOs, I2C\nbuses and SPI peripherals on Linux platforms. Internally, it uses the Linux\nsysclass interface so that it does not require platform-dependent code.\n\nNOTE: `elixir_ale` is actively maintained. However, future work is focused on\n`elixir_ale` 2.0 (aka Elixir Circuits). Elixir Circuits is more performant, has\nAPI naming and return value changes that we couldn't put here, and supports\nfeatures like automatic I2C retries and GPIO pullups/pulldowns on some\nplatforms. I2C, SPI and GPIO are split up in 3 repositories. See [Elixir\nCircuits](https://github.com/elixir-circuits/).\n\n`elixir_ale` works great with LEDs, buttons, many kinds of sensors, and simple\ncontrol of motors. In general, if a device requires high speed transactions or\nhas hard real-time constraints in its interactions, this is not the right\nlibrary. For those devices, it is recommended to look at other driver options, such\nas using a Linux kernel driver.\n\nIf this sounds similar to [Erlang/ALE](https://github.com/esl/erlang-ale), that's because it is. This\nlibrary is a Elixir-ized implementation of the original project with some updates\nto the C side. (Many of those changes have made it back to the original project\nnow.)\n\n# Getting started\n\nIf you're natively compiling elixir_ale, everything should work like any other\nElixir library. Normally, you would include elixir_ale as a dependency in your\n`mix.exs` like this:\n\n```elixir\ndef deps do\n  [{:elixir_ale, \"~\u003e 1.2\"}]\nend\n```\n\nIf you just want to try it out, you can do the following:\n\n```shell\ngit clone https://github.com/fhunleth/elixir_ale.git\ncd elixir_ale\nmix compile\niex -S mix\n```\n\nIf you're cross-compiling, you'll need to setup your environment so that the\nright C compiler is called. See the `Makefile` for the variables that will need\nto be overridden. At a minimum, you will need to set `CROSSCOMPILE`,\n`ERL_CFLAGS`, and `ERL_EI_LIBDIR`.\n\n`elixir_ale` doesn't load device drivers, so you'll need to make sure that any\nnecessary ones for accessing I2C or SPI are loaded beforehand. On the Raspberry\nPi, the [Adafruit Raspberry Pi I2C\ninstructions](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c)\nmay be helpful.\n\nIf you're trying to compile on a Raspberry Pi and you get errors indicated that Erlang headers are missing\n(`ie.h`), you may need to install erlang with `apt-get install\nerlang-dev` or build Erlang from source per instructions [here](http://elinux.org/Erlang).\n\n# Examples\n\n`elixir_ale` only supports simple uses of the GPIO, I2C, and SPI interfaces in\nLinux, but you can still do quite a bit. The following examples were tested on a\nRaspberry Pi that was connected to an [Erlang Embedded Demo\nBoard](http://solderpad.com/omerk/erlhwdemo/). There's nothing special about\neither the demo board or the Raspberry Pi, so these should work similarly on\nother embedded Linux platforms.\n\n## GPIO\n\nA [General Purpose Input/Output](https://en.wikipedia.org/wiki/General-purpose_input/output) (GPIO)\nis just a wire that you can use as an input or an output. It can only be\none of two values, 0 or 1. A 1 corresponds to a logic high voltage like 3.3 V\nand a 0 corresponds to 0 V. The actual voltage depends on the hardware.\n\nHere's an example of turning an LED on or off:\n\n![GPIO LED schematic](assets/images/schematic-gpio-led.png)\n\nTo turn on the LED that's connected to the net (or wire) labeled\n`GPIO18`, run the following:\n\n```elixir\niex\u003e alias ElixirALE.GPIO\niex\u003e {:ok, pid} = GPIO.start_link(18, :output)\n{:ok, #PID\u003c0.96.0\u003e}\n\niex\u003e GPIO.write(pid, 1)\n:ok\n```\n\nInput works similarly. Here's an example of a button with a pull down\nresistor connected.\n\n![GPIO Button schematic](assets/images/schematic-gpio-button.png)\n\nIf you're not familiar with pull up or pull down\nresistors, they're resistors whose purpose is to drive a wire\nhigh or low when the button isn't pressed. In this case, it drives the\nwire low. Many processors have ways of configuring internal resistors\nto accomplish the same effect without needing to add an external resistor.\nIt's platform-dependent and not shown here.\n\nThe code looks like this in `elixir_ale`:\n\n```elixir\niex\u003e {:ok, pid} = GPIO.start_link(17, :input)\n{:ok, #PID\u003c0.97.0\u003e}\n\niex\u003e GPIO.read(pid)\n0\n\n# Push the button down\n\niex\u003e GPIO.read(pid)\n1\n```\n\nIf you'd like to get a message when the button is pressed or released, call the\n`set_int` function. You can trigger on the `:rising` edge, `:falling` edge or\n`:both`.\n\n```elixir\niex\u003e GPIO.set_int(pid, :both)\n:ok\n\niex\u003e flush\n{:gpio_interrupt, 17, :rising}\n{:gpio_interrupt, 17, :falling}\n:ok\n```\n\nNote that after calling `set_int`, the calling process will receive an initial\nmessage with the state of the pin. This prevents the race condition between\ngetting the initial state of the pin and turning on interrupts. Without it,\nyou could get the state of the pin, it could change states, and then you could\nstart waiting on it for interrupts. If that happened, you would be out of sync.\n\n## SPI\n\nA [Serial Peripheral Interface](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus)\n(SPI) bus is a common multi-wire bus used to connect components on a circuit\nboard. A clock line drives the timing of sending bits between components. Bits\non the Master Out Slave In `MOSI` line go from the master (usually the\nprocessor running Linux) to the slave, and bits on the Master In Slave Out\n`MISO` line go the other direction. Bits transfer both directions\nsimultaneously. However, much of the time, the protocol used across the SPI\nbus has a request followed by a response and in these cases, bits going the\n\"wrong\" direction are ignored. This will become more clear in the example below.\n\nThe following shows an example Analog to Digital Converter (ADC) that\nreads from either a temperature sensor on CH0 (channel 0) or a potentiometer on\nCH1 (channel 1). It converts the analog measurements to digital, and sends the\ndigital measurements to SPI pins on the main processor running Linux (e.g.\nRaspberry Pi). Many processors, like the one on the Raspberry Pi, can't read\nanalog signals directly, so they need an ADC to convert the signal.\n\n![SPI schematic](assets/images/schematic-adc.png)\n\nThe protocol for talking to the ADC in the example below is described in the\n[MCP3002](http://www.microchip.com/wwwproducts/en/MCP3002) data sheet. The\nprotocol is very similar to an application program interface (API) for\nsoftware. It will tell you the position and function of the bits you will send\nto the ADC, along with how the data (in the form of bits)\nwill be returned.\n\nSee Figure 6-1 in the data sheet for the communication protocol. Sending a\n`0x68` first reads the temperature and sending a `0x78` reads the\npotentiometer. Since the data sheet shows bits, `0x68` corresponds to `01101000b`.\nThe leftmost bit is the \"Start\" bit. The second bit is SGL/DIFF, the third\nbit is ODD/SIGN, and the fourth bit is MSBF. From table 5-1, if SGL/DIFF==1,\nODD/SIGN==0, and MSBF==1 then that specifies channel 0 which is connected to\nthe thermometer.\n\n```elixir\n# Make sure that you've enabled or loaded the SPI driver or this will\n# fail.\niex\u003e alias ElixirALE.SPI\niex\u003e {:ok, pid} = SPI.start_link(\"spidev0.0\")\n{:ok, #PID\u003c0.124.0\u003e}\n\n# Read the potentiometer\n\n# Use binary pattern matching to pull out the ADC counts (low 10 bits)\niex\u003e \u003c\u003c_::size(6), counts::size(10)\u003e\u003e = SPI.transfer(pid, \u003c\u003c0x78, 0x00\u003e\u003e)\n\u003c\u003c1, 197\u003e\u003e\n\niex\u003e counts\n453\n\n# Convert counts to volts (1023 = 3.3 V)\niex\u003e volts = counts / 1023 * 3.3\n1.461290322580645\n```\n\nAs shown above, you'll find out that Elixir's binary pattern matching is\nextremely convenient when working with hardware. More information can be\nfound in the [Kernel.SpecialForms documentation](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1)\nand by running `h \u003c\u003c\u003e\u003e` at the IEx prompt.\n\n## I2C\n\nAn [Inter-Integrated Circuit](https://en.wikipedia.org/wiki/I%C2%B2C) (I2C)\nbus is similar to a SPI bus in function, but uses fewer wires. It\nsupports addressing hardware components and bidirectional use of the data line.\n\nThe following shows a bus IO expander connected via I2C to the processor.\n\n![I2C schematic](assets/images/schematic-i2c.png)\n\nThe protocol for talking to the IO expander is described in the [MCP23008\nDatasheet](http://www.microchip.com/wwwproducts/Devices.aspx?product=MCP23008).\nHere's a simple example of using it.\n\n```Elixir\n# On the Raspberry Pi, the IO expander is connected to I2C bus 1 (i2c-1).\n# Its 7-bit address is 0x20. (see datasheet)\niex\u003e alias ElixirALE.I2C\niex\u003e {:ok, pid} = I2C.start_link(\"i2c-1\", 0x20)\n{:ok, #PID\u003c0.102.0\u003e}\n\n# By default, all 8 GPIOs are set to inputs. Set the 4 high bits to outputs\n# so that we can toggle the LEDs. (Write 0x0f to register 0x00)\niex\u003e I2C.write(pid, \u003c\u003c0x00, 0x0f\u003e\u003e)\n:ok\n\n# Turn on the LED attached to bit 4 on the expander. (Write 0x10 to register\n# 0x09)\niex\u003e I2C.write(pid, \u003c\u003c0x09, 0x10\u003e\u003e)\n:ok\n\n# Read all 11 of the expander's registers to see that the bit 0 switch is\n# the only one on and that the bit 4 LED is on.\niex\u003e I2C.write(pid, \u003c\u003c0\u003e\u003e)  # Set the next register to be read to 0\n:ok\n\niex\u003e I2C.read(pid, 11)\n\u003c\u003c15, 0, 0, 0, 0, 0, 0, 0, 0, 17, 16\u003e\u003e\n\n# The operation of writing one or more bytes to select a register and\n# then reading is very common, so a shortcut is to just run the following:\niex\u003e I2C.write_read(pid, \u003c\u003c0\u003e\u003e, 11)\n\u003c\u003c15, 0, 0, 0, 0, 0, 0, 0, 0, 17, 16\u003e\u003e\n\n# The 17 in register 9 says that bits 0 and bit 4 are high\n# We could have just read register 9.\n\niex\u003e I2C.write_read(pid, \u003c\u003c9\u003e\u003e, 1)\n\u003c\u003c17\u003e\u003e\n```\n\n## FAQ\n\n### Where can I get help?\n\nMost issues people have are on how to communicate with hardware for the first\ntime. Since `elixir_ale` is a thin wrapper on the Linux sys class interface, you\nmay find help by searching for similar issues when using Python or C.\n\nFor help specifically with `elixir_ale`, you may also find help on the\nnerves channel on the [elixir-lang Slack](https://elixir-slackin.herokuapp.com/).\nMany [Nerves](http://nerves-project.org) users also use `elixir_ale`.\n\n### Why isn't elixir_ale a NIF?\n\nWhile `elixir_ale` should never crash, it's hard to guarantee that weird\nconditions on the I2C or SPI buses wouldn't hang the Erlang VM. `elixir_ale`\nerrors on the side of safety of the VM.\n\n### I tried turning on and off a GPIO as fast as I could. Why was it slow?\n\nPlease don't do that - there are so many better ways of accomplishing whatever\nyou're trying to do:\n\n  1. If you're trying to drive a servo or dim an LED, look into PWM. Many\n     platforms have PWM hardware and you won't tax your CPU at all. If your\n     platform is missing a PWM, several chips are available that take I2C\n     commands to drive a PWM output.\n  2. If you need to implement a wire level protocol to talk to a device, look\n     for a Linux kernel driver. It may just be a matter of loading the right\n     kernel module.\n  3. If you want a blinking LED to indicate status, `elixir_ale` really should\n     be fast enough to do that, but check out Linux's LED class interface. Linux\n     can flash LEDs, trigger off events and more. See [nerves_leds](https://github.com/nerves-project/nerves_leds).\n\nIf you're still intent on optimizing GPIO access, you may be interested in\n[gpio_twiddler](https://github.com/fhunleth/gpio_twiddler).\n\n### Where's PWM support?\n\nOn the hardware that I normally use, PWM has been implemented in a\nplatform-dependent way. For ease of maintenance, `elixir_ale` doesn't have any\nplatform-dependent code, so supporting it would be difficult. An Elixir PWM\nlibrary would be very interesting, though, should anyone want to implement it.\n\n### Where's 1-wire support?\n\nThere is a library available that supports the 1-wire protocol, see [onewire_therm](https://github.com/mokele/onewire_therm).\n\n### Can I develop code that uses elixir_ale on my laptop?\n\nYou'll need to fake out the hardware. Code to do this depends\non what your hardware actually does, but here's one example:\n\n  * http://www.cultivatehq.com/posts/compiling-and-testing-elixir-nerves-on-your-host-machine/\n\nPlease share other examples if you have them.\n\n### How do I debug?\n\nThe most common issue is communicating with an I2C or SPI device for the first time.\nFor I2C, first check that an I2C bus is available:\n\n```elixir\niex\u003e ElixirALE.I2C.device_names\n[\"i2c-1\"]\n```\n\nIf the list is empty, then I2C is either not available, not enabled, or not\nconfigured in the kernel. If you're using Raspbian, run `raspi-config` and check\nthat I2C is enabled in the advanced options. If you're on a BeagleBone, try\nrunning `config-pin` and see the [Universal I/O\nproject](https://github.com/cdsteinkuehler/beaglebone-universal-io) to enable\nthe I2C pins. On other ARM boards, double check that I2C is enabled in the\nkernel and that the device tree configures it.\n\nOnce an I2C bus is available, try detecting devices on it:\n\n```elixir\niex\u003e ElixirALE.I2C.detect_devices(\"i2c-1\")\n[4]\n```\n\nThe return value here is a list of device addresses that were detected. It is\nstill possible that the device will work even if it does not detect, but you\nprobably want to check wires at this point. If you have a logic analyzer, use it\nto verify that I2C transactions are being initiated on the bus.\n\nOptions for debugging a SPI issue are more limited. First check that the SPI bus\nis available:\n```elixir\niex\u003e ElixirALE.SPI.device_names\n[\"spidev0.0\", \"spidev0.1\"]\n```\n\nIf nothing is returned, verify that SPI is enabled and configured on your\nsystem. The steps are identical to the I2C ones above except looking for SPI.\n\nIf you're having trouble with GPIOs, the files controlling them are in\n`/sys/class/gpio`. ElixirALE is a thin wrapper on the files so if something can\nbe accomplished there, it usually can be accomplished in ElixirALE. One big\nomission from the directory is support for internal pull-ups and pull-downs.\nThese are very convenient for buttons so that external resisters aren't needed.\nUnfortunately, the way to handle pull-ups and pull-downs is device specific. If\nyou're on a Raspberry Pi, see [gpio_rpi](https://hex.pm/packages/gpio_rpi).\n\n### Will it run on Arduino?\n\nNo. 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](https://hex.pm/packages/nerves_uart) for communicating via the Arduino's serial connection or [firmata](https://github.com/mobileoverlord/firmata) for communication using the Arduino's Firmata protocol.\n\n### Can I help maintain elixir_ale?\n\nYes! If your life has been improved by `elixir_ale` and you want to give back,\nit would be great to have new energy put into this project. Please email me.\n\n# License\n\nThis library draws much of its design and code from the Erlang/ALE project which\nis licensed under the Apache License, Version 2.0. As such, it is licensed\nsimilarly.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffhunleth%2Felixir_ale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffhunleth%2Felixir_ale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffhunleth%2Felixir_ale/lists"}