├── CREDITS ├── TODO ├── README ├── VERSION ├── test ├── test_helper.exs └── nerves_grove_test.exs ├── AUTHORS ├── etc └── photos │ ├── led.png │ ├── button.png │ ├── buzzer.png │ ├── i2c-adc.png │ ├── i2c-hub.png │ ├── relay.png │ ├── oled-display.png │ ├── sensor-sound.png │ ├── led-chainable.png │ ├── sensor-adxl345.png │ ├── sensor-collision.png │ ├── potentiometer-slide.png │ ├── sensor-temperature.png │ └── potentiometer-rotary.png ├── lib ├── nerves_grove.ex └── nerves_grove │ ├── nfc.ex │ ├── button.ex │ ├── sensor │ ├── collision.ex │ ├── sound.ex │ └── temperature.ex │ ├── relay.ex │ ├── potentiometer.ex │ ├── led.ex │ ├── buzzer.ex │ ├── i2c_adc.ex │ └── oled_display.ex ├── .gitignore ├── .travis.yml ├── CHANGES.md ├── config └── config.exs ├── UNLICENSE ├── mix.exs ├── mix.lock └── README.md /CREDITS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.6.0-dev 2 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Arto Bendiken 2 | -------------------------------------------------------------------------------- /etc/photos/led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/led.png -------------------------------------------------------------------------------- /etc/photos/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/button.png -------------------------------------------------------------------------------- /etc/photos/buzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/buzzer.png -------------------------------------------------------------------------------- /etc/photos/i2c-adc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/i2c-adc.png -------------------------------------------------------------------------------- /etc/photos/i2c-hub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/i2c-hub.png -------------------------------------------------------------------------------- /etc/photos/relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/relay.png -------------------------------------------------------------------------------- /etc/photos/oled-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/oled-display.png -------------------------------------------------------------------------------- /etc/photos/sensor-sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/sensor-sound.png -------------------------------------------------------------------------------- /etc/photos/led-chainable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/led-chainable.png -------------------------------------------------------------------------------- /etc/photos/sensor-adxl345.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/sensor-adxl345.png -------------------------------------------------------------------------------- /etc/photos/sensor-collision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/sensor-collision.png -------------------------------------------------------------------------------- /etc/photos/potentiometer-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/potentiometer-slide.png -------------------------------------------------------------------------------- /etc/photos/sensor-temperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/sensor-temperature.png -------------------------------------------------------------------------------- /etc/photos/potentiometer-rotary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dryex/nerves_grove/HEAD/etc/photos/potentiometer-rotary.png -------------------------------------------------------------------------------- /lib/nerves_grove.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove do 4 | end 5 | -------------------------------------------------------------------------------- /lib/nerves_grove/nfc.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.NFC do 4 | # TODO 5 | end 6 | -------------------------------------------------------------------------------- /test/nerves_grove_test.exs: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Test do 4 | use ExUnit.Case, async: true 5 | 6 | doctest Nerves.Grove 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | 19 | # Mac OS X 20 | .DS_Store 21 | 22 | # Editor backup files 23 | *~ 24 | -------------------------------------------------------------------------------- /lib/nerves_grove/button.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Button do 4 | @moduledoc """ 5 | Seeed Studio [Grove Button](http://wiki.seeedstudio.com/wiki/Grove_-_Button) 6 | 7 | # Example 8 | 9 | alias Nerves.Grove.Button 10 | 11 | {:ok, pid} = Button.start_link(pin) 12 | 13 | state = Button.read(pid) # check if button is pressed 14 | """ 15 | 16 | @spec start_link(pos_integer) :: {:ok, pid} | {:error, any} 17 | def start_link(pin) do 18 | Gpio.start_link(pin, :input) 19 | end 20 | 21 | @spec read(pid) :: boolean 22 | def read(pid) do 23 | Gpio.read(pid) == 1 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/nerves_grove/sensor/collision.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Sensor.Collision do 4 | @moduledoc """ 5 | Seeed Studio [Grove Collision Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Collision_Sensor) 6 | 7 | # Example 8 | 9 | alias Nerves.Grove.Sensor 10 | 11 | {:ok, pid} = Sensor.Collision.start_link(pin) 12 | 13 | state = Sensor.Collision.read(pid) # check if sensor was triggered 14 | """ 15 | 16 | @spec start_link(pos_integer) :: {:ok, pid} | {:error, any} 17 | def start_link(pin) when is_integer(pin) do 18 | Gpio.start_link(pin, :input) 19 | end 20 | 21 | @spec read(pid) :: boolean 22 | def read(pid) when is_pid(pid) do 23 | Gpio.read(pid) == 0 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/nerves_grove/relay.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Relay do 4 | @moduledoc """ 5 | Seeed Studio [Grove Relay](http://wiki.seeedstudio.com/wiki/Grove_-_Relay) 6 | 7 | # Example 8 | 9 | alias Nerves.Grove.Relay 10 | 11 | {:ok, pid} = Relay.start_link(pin) 12 | 13 | Relay.on(pid) # start current flow 14 | Relay.off(pid) # stop current flow 15 | """ 16 | 17 | @spec start_link(pos_integer) :: {:ok, pid} | {:error, any} 18 | def start_link(pin) do 19 | Gpio.start_link(pin, :output) 20 | end 21 | 22 | @doc "Switches on the relay, enabling current to flow." 23 | @spec on(pid) :: any 24 | def on(pid) do 25 | Gpio.write(pid, 1) 26 | end 27 | 28 | @doc "Switches off the relay, preventing current from flowing." 29 | @spec off(pid) :: any 30 | def off(pid) do 31 | Gpio.write(pid, 0) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # See: https://travis-ci.org/bendiken/nerves_grove 2 | 3 | sudo: false 4 | 5 | language: elixir 6 | elixir: 7 | - 1.3.2 8 | otp_release: 9 | - 19.0 10 | script: 11 | - MIX_ENV=test mix do deps.get, compile, coveralls.travis 12 | 13 | notifications: 14 | slack: 15 | secure: BF/4x88nH9/kzs8zmeX60IxUy6pqX8oyBvc4FAZe+jOC5NPr+aIJwzT9ljfOFodiV5LisO/Il1QB/yspnYQxmoFZDouSmmk3XQTupKy5kcidOJyYvO7EQqPzQ0D9YaZN4egvQYh47cLs7SO1r9t7FoGG1QXJtn7fEwWyoBlS8mh4tdXTKPbepe58xjAoHFkf441k9QAXExzgM9AhxFNoeKdHV5Bs8fJfLTfRq9Wv1YWgO5l1LFZJ1eUS5gHoo0kcTt3QFf8A3qbcnJoEeSY5P7YSkpnCdMVRx4q2EzHut1OL81POJIrhCLCAf5I3zv5f0h8+NZkMbH8ycFYZ1NzEDg0EPickGyeMgwEBtaqdiiK/AH0y5YTZFQYpRW3iEvbUx5YH0Gl5JJYo1Bd4bM82zTdJDHXixqwz2DeHgspC0JDLfo56mWzNrejemPjFvL9r1/d72ZouueC2U56D+fW2U40Msx2jB0oQdDdNE77Pq+jKa+XkNLsu9ZcfqerTs6j6TGG21haZupdkGUqCdrLp1akGhtoRFdEy9AULyPgU3EbklJXL7K5YIHVwVS7KraGBVgNyNqn1afd/MP7iLPqKm94fj+U0SEkQuUIMEJUzrnMy2LcAu/+Qgl5rI9vekmkbly8n/s542R1o7wCk3NZStQrLzqvVG/cuqca0Ds9FSoU= 16 | -------------------------------------------------------------------------------- /lib/nerves_grove/sensor/sound.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Sensor.Sound do 4 | @moduledoc """ 5 | Seeed Studio [Grove Sound Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Sound_Sensor) 6 | 7 | ## Datasheet 8 | 9 | http://www.ti.com/lit/ds/symlink/lm358.pdf 10 | 11 | # Example 12 | 13 | alias Nerves.Grove.Sensor 14 | 15 | {:ok, pid} = Sensor.Sound.start_link(address) 16 | 17 | Sensor.Sound.read_value(pid) 18 | """ 19 | 20 | alias Nerves.Grove.I2C 21 | 22 | @default_address 0x50 # I2C ADC 23 | 24 | @spec start_link(byte) :: {:ok, pid} | {:error, any} 25 | def start_link(address \\ @default_address) when is_integer(address) do 26 | I2C.ADC.start_link(address) 27 | end 28 | 29 | @spec read_value(pid, integer) :: float 30 | def read_value(pid, samples \\ 5) when is_pid(pid) and is_integer(samples) do 31 | I2C.ADC.read_value(pid, samples) |> Float.round(3) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/nerves_grove/potentiometer.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Potentiometer do 4 | @moduledoc """ 5 | Seeed Studio [Grove Rotary Angle Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Rotary_Angle_Sensor) 6 | Seeed Studio [Grove Slide Potentiometer](http://wiki.seeedstudio.com/wiki/Grove_-_Slide_Potentiometer) 7 | 8 | # Example 9 | 10 | alias Nerves.Grove.Potentiometer 11 | 12 | {:ok, pid} = Potentiometer.start_link(address) 13 | 14 | Potentiometer.read_value(pid) 15 | """ 16 | 17 | alias Nerves.Grove.I2C 18 | 19 | @default_address 0x50 # I2C ADC 20 | @default_vcc 3.3 # V 21 | 22 | @spec start_link(byte) :: {:ok, pid} | {:error, any} 23 | def start_link(address \\ @default_address) when is_integer(address) do 24 | I2C.ADC.start_link(address) 25 | end 26 | 27 | @spec read_value(pid, integer) :: float 28 | def read_value(pid, samples \\ 5) when is_pid(pid) and is_integer(samples) do 29 | (min(I2C.ADC.read_voltage(pid, samples), @default_vcc) / @default_vcc) |> Float.round(3) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/nerves_grove/led.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.LED do 4 | @moduledoc """ 5 | Seeed Studio [Grove LED](http://wiki.seeedstudio.com/wiki/Grove_-_LED) 6 | 7 | # Example 8 | 9 | alias Nerves.Grove.LED 10 | 11 | {:ok, pid} = LED.start_link(pin) 12 | 13 | LED.blink(pid) 14 | """ 15 | 16 | @spec start_link(pos_integer) :: {:ok, pid} | {:error, any} 17 | def start_link(pin) when is_integer(pin) do 18 | Gpio.start_link(pin, :output) 19 | end 20 | 21 | @doc "Blinks the LED for a specified duration." 22 | @spec blink(pid, number) :: any 23 | def blink(pid, duration \\ 0.2) when is_pid(pid) and is_number(duration) do 24 | duration_in_ms = duration * 1000 |> round 25 | on(pid) 26 | :timer.sleep(duration_in_ms) 27 | off(pid) 28 | end 29 | 30 | @doc "Switches on the LED." 31 | @spec on(pid) :: any 32 | def on(pid) when is_pid(pid) do 33 | Gpio.write(pid, 1) 34 | end 35 | 36 | @doc "Switches off the LED." 37 | @spec off(pid) :: any 38 | def off(pid) when is_pid(pid) do 39 | Gpio.write(pid, 0) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/nerves_grove/buzzer.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Buzzer do 4 | @moduledoc """ 5 | Seeed Studio [Grove Buzzer](http://wiki.seeedstudio.com/wiki/Grove_-_Buzzer) 6 | 7 | # Example 8 | 9 | alias Nerves.Grove.Buzzer 10 | 11 | {:ok, pid} = Buzzer.start_link(pin) 12 | 13 | Buzzer.beep(pid, 0.1) # make some noise for 100 ms 14 | """ 15 | 16 | @spec start_link(pos_integer) :: {:ok, pid} | {:error, any} 17 | def start_link(pin) do 18 | Gpio.start_link(pin, :output) 19 | end 20 | 21 | @doc "Beeps the buzzer for a specified duration." 22 | @spec beep(pid, number) :: any 23 | def beep(pid, duration \\ 0.1) do 24 | duration_in_ms = duration * 1000 |> round 25 | on(pid) 26 | :timer.sleep(duration_in_ms) 27 | off(pid) 28 | end 29 | 30 | @doc "Switches on the buzzer, making a lot of noise." 31 | @spec on(pid) :: any 32 | def on(pid) do 33 | Gpio.write(pid, 1) 34 | end 35 | 36 | @doc "Switches off the buzzer, stopping the noise." 37 | @spec off(pid) :: any 38 | def off(pid) do 39 | Gpio.write(pid, 0) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [HEAD] - Unreleased 6 | 7 | ## [0.5.0] - 2016-09-08 8 | ### Added 9 | - Added `Grove.LED`. 10 | - Added `Grove.Potentiometer`. 11 | - Added `Grove.Sensor.Collision`. 12 | 13 | ## [0.4.0] - 2016-08-18 14 | ### Added 15 | - Added `Grove.I2C.ADC`. 16 | - Added `Grove.Sensor.Sound`. 17 | - Added `Grove.Sensor.Temperature`. 18 | 19 | ## [0.3.0] - 2016-08-17 20 | ### Added 21 | - Added `Grove.Buzzer.on/1` and `Grove.Buzzer.off/1`. 22 | - Added `Grove.OLED.Display`. 23 | - Added `Grove.Relay`. 24 | 25 | ## [0.2.0] - 2016-08-15 26 | ### Added 27 | - Added `Grove.Buzzer`. 28 | 29 | ## 0.1.0 - 2016-08-15 30 | ### Added 31 | - Initial release with support for `Grove.Button`. 32 | 33 | [HEAD]: https://github.com/bendiken/nerves_grove/compare/0.5.0...HEAD 34 | [0.5.0]: https://github.com/bendiken/nerves_grove/compare/0.4.0...0.5.0 35 | [0.4.0]: https://github.com/bendiken/nerves_grove/compare/0.3.0...0.4.0 36 | [0.3.0]: https://github.com/bendiken/nerves_grove/compare/0.2.0...0.3.0 37 | [0.2.0]: https://github.com/bendiken/nerves_grove/compare/0.1.0...0.2.0 38 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :nerves_grove, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:nerves_grove, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/nerves_grove/sensor/temperature.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.Sensor.Temperature do 4 | @moduledoc """ 5 | Seeed Studio [Grove Temperature Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Temperature_Sensor_V1.2) 6 | 7 | ## Datasheet 8 | 9 | http://wiki.seeedstudio.com/images/a/a1/NCP18WF104F03RC.pdf 10 | 11 | # Example 12 | 13 | alias Nerves.Grove.Sensor 14 | 15 | {:ok, pid} = Sensor.Temperature.start_link(address) 16 | 17 | Sensor.Temperature.read_centigrade(pid) 18 | """ 19 | 20 | alias Nerves.Grove.I2C 21 | 22 | @default_address 0x50 # I2C ADC 23 | @b_const 4_250 # v1.2 24 | @r_0 100_000.0 # ohm 25 | @t_0 298.15 # K (25 C) 26 | @t_celsius 273.15 # K (0 C) 27 | 28 | @spec start_link(byte) :: {:ok, pid} | {:error, any} 29 | def start_link(address \\ @default_address) when is_integer(address) do 30 | I2C.ADC.start_link(address) 31 | end 32 | 33 | @spec read_centigrade(pid, integer) :: float 34 | def read_centigrade(pid, samples \\ 20) when is_pid(pid) and is_integer(samples) do 35 | read_kelvin(pid, samples) - @t_celsius |> Float.round(2) 36 | end 37 | 38 | @spec read_kelvin(pid, integer) :: float 39 | def read_kelvin(pid, samples \\ 20) when is_pid(pid) and is_integer(samples) do 40 | # FIXME: This is based on Seeed's BBG example code, but it doesn't make 41 | # much sense in terms of the datasheet or Seeed's Arduino example code. 42 | voltage = I2C.ADC.read_voltage(pid, samples) 43 | sensor_value_tmp = voltage / 3.3 * 1_023 44 | resistance = (1_023 - sensor_value_tmp) * 10_000 / sensor_value_tmp 45 | temperature = 1 / (:math.log(resistance / 10_000) / @b_const + (1 / @t_0)) 46 | temperature |> Float.round(2) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Nerves.Grove.Mixfile do 2 | use Mix.Project 3 | 4 | @name "Nerves.Grove" 5 | @version File.read!("VERSION") |> String.strip 6 | @github "https://github.com/bendiken/nerves_grove" 7 | @bitbucket "https://bitbucket.org/bendiken/nerves_grove" 8 | @homepage @github 9 | 10 | def project do 11 | [app: :nerves_grove, 12 | version: @version, 13 | elixir: "~> 1.3", 14 | compilers: Mix.compilers, 15 | build_embedded: Mix.env == :prod, 16 | start_permanent: Mix.env == :prod, 17 | name: @name, 18 | source_url: @github, 19 | homepage_url: @homepage, 20 | description: description(), 21 | aliases: aliases(), 22 | deps: deps(), 23 | package: package(), 24 | docs: [source_ref: @version, main: "readme", extras: ["README.md"]], 25 | test_coverage: [tool: ExCoveralls], 26 | preferred_cli_env: [ 27 | "coveralls": :test, 28 | "coveralls.detail": :test, 29 | "coveralls.post": :test, 30 | "coveralls.html": :test, 31 | ]] 32 | end 33 | 34 | def application do 35 | [applications: [:logger]] 36 | end 37 | 38 | defp package do 39 | [files: ~w(lib mix.exs CHANGES.md README.md UNLICENSE VERSION), 40 | maintainers: ["Arto Bendiken"], 41 | licenses: ["Public Domain"], 42 | links: %{"GitHub" => @github, "Bitbucket" => @bitbucket}] 43 | end 44 | 45 | defp description do 46 | """ 47 | Grove module support for Nerves. 48 | """ 49 | end 50 | 51 | defp deps do 52 | [{:elixir_ale, "~> 0.5.5"}, 53 | {:credo, ">= 0.0.0", only: [:dev, :test]}, 54 | {:dialyxir, ">= 0.0.0", only: [:dev, :test]}, 55 | {:earmark, ">= 0.0.0", only: :dev}, 56 | {:ex_doc, ">= 0.0.0", only: :dev}, 57 | {:excoveralls, ">= 0.0.0", only: :test}] 58 | end 59 | 60 | defp aliases do 61 | [] 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/nerves_grove/i2c_adc.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.I2C.ADC do 4 | @moduledoc """ 5 | Seeed Studio [Grove I2C ADC](http://wiki.seeedstudio.com/wiki/Grove_-_I2C_ADC) 6 | 7 | ## Datasheet 8 | 9 | http://www.ti.com/lit/ds/snas415f/snas415f.pdf 10 | 11 | # Example 12 | 13 | alias Nerves.Grove.I2C 14 | 15 | {:ok, pid} = I2C.ADC.start_link(address) 16 | 17 | I2C.ADC.read_sample(pid) 18 | """ 19 | 20 | @default_address 0x50 21 | @result_register 0x00 22 | @config_register 0x02 23 | @v_ref 3.0 # V 24 | 25 | @spec start_link(byte) :: {:ok, pid} | {:error, any} 26 | def start_link(address \\ @default_address) when is_integer(address) do 27 | case I2c.start_link("i2c-2", address) do 28 | {:ok, pid} -> 29 | set_automatic_mode(pid, true) 30 | {:ok, pid} 31 | error -> error 32 | end 33 | end 34 | 35 | @spec set_automatic_mode(pid, false) :: :ok 36 | def set_automatic_mode(pid, false) when is_pid(pid) do 37 | I2c.write(pid, <<@config_register, 0b000_00000>>) 38 | end 39 | 40 | @spec set_automatic_mode(pid, true) :: :ok 41 | def set_automatic_mode(pid, true) when is_pid(pid) do 42 | # Configure for T_convert*32 and f_convert=27ksps: 43 | I2c.write(pid, <<@config_register, 0b001_00000>>) 44 | end 45 | 46 | @spec read_value(pid, integer) :: float 47 | def read_value(pid, samples \\ 5) when is_pid(pid) and is_integer(samples) do 48 | read_voltage(pid, samples) / @v_ref 49 | end 50 | 51 | @spec read_voltage(pid, integer) :: float 52 | def read_voltage(pid, samples \\ 5) when is_pid(pid) and is_integer(samples) do 53 | sum = read_samples(pid, samples) 54 | |> Enum.map(fn sample -> sample / 4095 end) 55 | |> Enum.sum 56 | avg = sum / samples 57 | avg * @v_ref * 2 # FIXME: why is the 2 necessary? 58 | end 59 | 60 | @spec read_samples(pid, integer) :: [0..4095] 61 | def read_samples(pid, count) when is_pid(pid) and is_integer(count) do 62 | Enum.map(1..count, fn _ -> read_sample(pid) end) 63 | end 64 | 65 | @spec read_sample(pid) :: 0..4095 66 | def read_sample(pid) when is_pid(pid) do 67 | << 68 | _ :: size(4), 69 | sample :: big-unsigned-integer-size(12) 70 | >> = I2c.write_read(pid, <<@result_register>>, 2) 71 | sample 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []}, 2 | "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []}, 3 | "credo": {:hex, :credo, "0.4.8", "1e71a4c21a4bead4911498d3e0f72a90f500075cf132c3ad8976b5a474b4c594", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]}, 4 | "dialyxir": {:hex, :dialyxir, "0.3.5", "eaba092549e044c76f83165978979f60110dc58dd5b92fd952bf2312f64e9b14", [:mix], []}, 5 | "earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []}, 6 | "elixir_ale": {:hex, :elixir_ale, "0.5.5", "995f1ef812658cb938ae8258b7ecf6b1e8f76e367e50d61a1f2380435e71668b", [:mix, :make], [{:elixir_make, "~> 0.3", [hex: :elixir_make, optional: false]}]}, 7 | "elixir_make": {:hex, :elixir_make, "0.3.0", "285147fa943806eee82f6680b7b446b5569bcf3ee8328fa0a7c200ffc44fbaba", [:mix], []}, 8 | "ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, 9 | "excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, 10 | "exjsx": {:hex, :exjsx, "3.2.0", "7136cc739ace295fc74c378f33699e5145bead4fdc1b4799822d0287489136fb", [:mix], [{:jsx, "~> 2.6.2", [hex: :jsx, optional: false]}]}, 11 | "hackney": {:hex, :hackney, "1.6.1", "ddd22d42db2b50e6a155439c8811b8f6df61a4395de10509714ad2751c6da817", [:rebar3], [{:certifi, "0.4.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}]}, 12 | "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, 13 | "jsx": {:hex, :jsx, "2.6.2", "213721e058da0587a4bce3cc8a00ff6684ced229c8f9223245c6ff2c88fbaa5a", [:mix, :rebar], []}, 14 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 15 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 16 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}} 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nerves.Grove 2 | ============ 3 | 4 | [![Project license](https://img.shields.io/hexpm/l/nerves_grove.svg)](https://unlicense.org/) 5 | [![Travis CI build status](https://img.shields.io/travis/bendiken/nerves_grove/master.svg)](https://travis-ci.org/bendiken/nerves_grove) 6 | [![Coveralls.io code coverage](https://img.shields.io/coveralls/bendiken/nerves_grove/master.svg)](https://coveralls.io/github/bendiken/nerves_grove) 7 | [![Hex.pm package](https://img.shields.io/hexpm/v/nerves_grove.svg)](https://hex.pm/packages/nerves_grove) 8 | [![Hex.pm downloads](https://img.shields.io/hexpm/dt/nerves_grove.svg)](https://hex.pm/packages/nerves_grove) 9 | [![Gratipay donations](https://img.shields.io/gratipay/user/bendiken.svg)](https://gratipay.com/~bendiken/) 10 | 11 | [Grove](http://wiki.seeedstudio.com/wiki/Grove_System) module support for 12 | the [Nerves](http://nerves-project.org/) embedded software framework. 13 | 14 | Supported Hardware 15 | ------------------ 16 | 17 | [![Grove Button][button.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Button.html) 18 | [![Grove Buzzer][buzzer.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Buzzer.html) 19 | [![Grove Collision Sensor][sensor-collision.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Collision.html) 20 | [![Grove I2C ADC][i2c-adc.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.I2C.ADC.html) 21 | [![Grove LED][led.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.LED.html) 22 | [![Grove OLED Display 96×96][oled-display.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.OLED.Display.html) 23 | [![Grove Relay][relay.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Relay.html) 24 | [![Grove Rotary Angle Sensor][potentiometer-rotary.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Potentiometer.html) 25 | [![Grove Slide Potentiometer][potentiometer-slide.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Potentiometer.html) 26 | [![Grove Sound Sensor][sensor-sound.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Sound.html) 27 | [![Grove Temperature Sensor][sensor-temperature.png]](https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Temperature.html) 28 | 29 | [button.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/button.png "Grove Button" 30 | [buzzer.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/buzzer.png "Grove Buzzer" 31 | [i2c-adc.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/i2c-adc.png "Grove I2C ADC" 32 | [led.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/led.png "Grove LED" 33 | [oled-display.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/oled-display.png "Grove OLED Display 96×96" 34 | [relay.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/relay.png "Grove Relay" 35 | [potentiometer-rotary.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/potentiometer-rotary.png "Grove Rotary Angle Sensor" 36 | [potentiometer-slide.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/potentiometer-slide.png "Grove Slide Potentiometer" 37 | [sensor-collision.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/sensor-collision.png "Grove Collision Sensor" 38 | [sensor-sound.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/sensor-sound.png "Grove Sound Sensor" 39 | [sensor-temperature.png]: https://raw.githubusercontent.com/bendiken/nerves_grove/master/etc/photos/sensor-temperature.png "Grove Temperature Sensor" 40 | 41 | Reference 42 | --------- 43 | 44 | https://hexdocs.pm/nerves_grove/ 45 | 46 | Examples 47 | -------- 48 | 49 | ### Seeed Studio [Grove Button](http://wiki.seeedstudio.com/wiki/Grove_-_Button) 50 | 51 | [`Grove.Button`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Button.html 52 | 53 | ```elixir 54 | alias Nerves.Grove.Button 55 | 56 | {:ok, pid} = Button.start_link(pin) 57 | 58 | state = Button.read(pid) # check if button is pressed 59 | ``` 60 | 61 | ### Seeed Studio [Grove Buzzer](http://wiki.seeedstudio.com/wiki/Grove_-_Buzzer) 62 | 63 | [`Grove.Buzzer`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Buzzer.html 64 | 65 | ```elixir 66 | alias Nerves.Grove.Buzzer 67 | 68 | {:ok, pid} = Buzzer.start_link(pin) 69 | 70 | Buzzer.beep(pid, 0.1) # make some noise for 100 ms 71 | ``` 72 | 73 | ### Seeed Studio [Grove Collision Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Collision_Sensor) 74 | 75 | [`Grove.Sensor.Collision`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Collision.html 76 | 77 | ```elixir 78 | alias Nerves.Grove.Sensor 79 | 80 | {:ok, pid} = Sensor.Collision.start_link(pin) 81 | 82 | state = Sensor.Collision.read(pid) # check if sensor was triggered 83 | ``` 84 | 85 | ### Seeed Studio [Grove I2C ADC](http://wiki.seeedstudio.com/wiki/Grove_-_I2C_ADC) 86 | 87 | [`Grove.I2C.ADC`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.I2C.ADC.html 88 | 89 | ```elixir 90 | alias Nerves.Grove.I2C 91 | 92 | {:ok, pid} = I2C.ADC.start_link(address) 93 | 94 | I2C.ADC.read_voltage(pid) 95 | ``` 96 | 97 | ### Seeed Studio [Grove LED](http://wiki.seeedstudio.com/wiki/Grove_-_LED) and [Grove LED Socket Kit](http://wiki.seeedstudio.com/wiki/Grove_-_LED_Socket_Kit) 98 | 99 | [`Grove.LED`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.LED.html 100 | 101 | ```elixir 102 | alias Nerves.Grove.LED 103 | 104 | {:ok, pid} = LED.start_link(pin) 105 | 106 | LED.blink(pid) 107 | ``` 108 | 109 | ### Seeed Studio [Grove OLED Display 96×96](http://wiki.seeedstudio.com/wiki/Grove_-_OLED_Display_1.12%22) 110 | 111 | [`Grove.OLED.Display`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.OLED.Display.html 112 | 113 | ```elixir 114 | alias Nerves.Grove.OLED 115 | 116 | {:ok, pid} = OLED.Display.start_link(address) 117 | 118 | OLED.Display.reset(pid) 119 | OLED.Display.clear(pid) 120 | OLED.Display.set_text_position(pid, 0, 0) 121 | OLED.Display.put_string(pid, "Hello, world") 122 | ``` 123 | 124 | ### Seeed Studio [Grove Relay](http://wiki.seeedstudio.com/wiki/Grove_-_Relay) 125 | 126 | [`Grove.Relay`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Relay.html 127 | 128 | ```elixir 129 | alias Nerves.Grove.Relay 130 | 131 | {:ok, pid} = Relay.start_link(pin) 132 | 133 | Relay.on(pid) # start current flow 134 | Relay.off(pid) # stop current flow 135 | ``` 136 | 137 | ### Seeed Studio [Grove Rotary Angle Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Rotary_Angle_Sensor) and [Grove Slide Potentiometer](http://wiki.seeedstudio.com/wiki/Grove_-_Slide_Potentiometer) 138 | 139 | [`Grove.Potentiometer`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Potentiometer.html 140 | 141 | ```elixir 142 | alias Nerves.Grove.Potentiometer 143 | 144 | {:ok, pid} = Potentiometer.start_link(address) 145 | 146 | Potentiometer.read_value(pid) 147 | ``` 148 | 149 | ### Seeed Studio [Grove Sound Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Sound_Sensor) 150 | 151 | [`Grove.Sensor.Sound`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Sound.html 152 | 153 | ```elixir 154 | alias Nerves.Grove.Sensor 155 | 156 | {:ok, pid} = Sensor.Sound.start_link(address) 157 | 158 | Sensor.Sound.read_value(pid) 159 | ``` 160 | 161 | ### Seeed Studio [Grove Temperature Sensor](http://wiki.seeedstudio.com/wiki/Grove_-_Temperature_Sensor_V1.2) 162 | 163 | [`Grove.Sensor.Temperature`]: https://hexdocs.pm/nerves_grove/Nerves.Grove.Sensor.Temperature.html 164 | 165 | ```elixir 166 | alias Nerves.Grove.Sensor 167 | 168 | {:ok, pid} = Sensor.Temperature.start_link(address) 169 | 170 | Sensor.Temperature.read_centigrade(pid) 171 | ``` 172 | 173 | Installation 174 | ------------ 175 | 176 | Add `nerves_grove` to your list of dependencies in your project's `mix.exs` file: 177 | 178 | ```elixir 179 | defp deps do 180 | [{:nerves_grove, "~> 0.5.0"}] 181 | end 182 | ``` 183 | 184 | Alternatively, to pull in the dependency directly from a Git tag: 185 | 186 | ```elixir 187 | defp deps do 188 | [{:nerves_grove, github: "bendiken/nerves_grove", tag: "0.5.0"}] 189 | end 190 | ``` 191 | 192 | Alternatively, to pull in the dependency directly from a Git branch: 193 | 194 | ```elixir 195 | defp deps do 196 | [{:nerves_grove, github: "bendiken/nerves_grove", branch: "master"}] 197 | end 198 | ``` 199 | -------------------------------------------------------------------------------- /lib/nerves_grove/oled_display.ex: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | 3 | defmodule Nerves.Grove.OLED.Display do 4 | @moduledoc """ 5 | Seeed Studio [Grove OLED Display 96×96](http://wiki.seeedstudio.com/wiki/Grove_-_OLED_Display_1.12%22) 6 | 7 | ## Datasheet 8 | 9 | http://garden.seeedstudio.com/images/8/82/SSD1327_datasheet.pdf 10 | 11 | # Example 12 | 13 | alias Nerves.Grove.OLED 14 | 15 | {:ok, pid} = OLED.Display.start_link(address) 16 | 17 | OLED.Display.reset(pid) 18 | OLED.Display.clear(pid) 19 | OLED.Display.set_text_position(pid, 0, 0) 20 | OLED.Display.put_string(pid, "Hello, world") 21 | """ 22 | 23 | @default_address 0x3C 24 | @command_mode 0x80 25 | @data_mode 0x40 26 | 27 | # 8x8 monochrome bitmap font for ASCII code points 32-128. 28 | @default_font \ 29 | {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, 30 | {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00}, 31 | {0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00}, 32 | {0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00}, 33 | {0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00}, 34 | {0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00}, 35 | {0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00}, 36 | {0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00}, 37 | {0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00}, 38 | {0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00}, 39 | {0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00}, 40 | {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00}, 41 | {0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00}, 42 | {0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00}, 43 | {0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00}, 44 | {0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00}, 45 | {0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00}, 46 | {0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00}, 47 | {0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00}, 48 | {0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00}, 49 | {0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00}, 50 | {0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00}, 51 | {0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00}, 52 | {0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00}, 53 | {0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00}, 54 | {0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00}, 55 | {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00}, 56 | {0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00}, 57 | {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, 58 | {0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00}, 59 | {0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00}, 60 | {0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00}, 61 | {0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00}, 62 | {0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00}, 63 | {0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00}, 64 | {0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00}, 65 | {0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00}, 66 | {0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00}, 67 | {0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00}, 68 | {0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00}, 69 | {0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00}, 70 | {0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00}, 71 | {0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00}, 72 | {0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00}, 73 | {0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00}, 74 | {0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00}, 75 | {0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00}, 76 | {0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00}, 77 | {0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00}, 78 | {0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00}, 79 | {0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00}, 80 | {0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00}, 81 | {0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00}, 82 | {0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00}, 83 | {0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00}, 84 | {0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00}, 85 | {0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00}, 86 | {0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00}, 87 | {0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00}, 88 | {0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00}, 89 | {0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00}, 90 | {0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00}, 91 | {0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00}, 92 | {0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00}, 93 | {0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00}, 94 | {0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00}, 95 | {0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00}, 96 | {0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00}, 97 | {0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00}, 98 | {0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00}, 99 | {0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00}, 100 | {0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00}, 101 | {0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00}, 102 | {0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00}, 103 | {0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00}, 104 | {0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00}, 105 | {0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00}, 106 | {0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00}, 107 | {0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00}, 108 | {0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00}, 109 | {0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00}, 110 | {0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00}, 111 | {0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00}, 112 | {0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00}, 113 | {0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00}, 114 | {0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00}, 115 | {0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00}, 116 | {0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00}, 117 | {0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00}, 118 | {0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00}, 119 | {0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00}, 120 | {0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00}, 121 | {0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00}, 122 | {0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00}, 123 | {0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00}, 124 | {0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00}} 125 | 126 | use Bitwise 127 | 128 | @spec start_link(byte) :: {:ok, pid} | {:error, any} 129 | def start_link(address \\ @default_address) do 130 | I2c.start_link("i2c-2", address) 131 | end 132 | 133 | @spec reset(pid) :: :ok 134 | def reset(pid) do 135 | send_commands(pid, <<0xFD, 0x12>>) 136 | off(pid) 137 | set_multiplex_ratio(pid, 95) # 96 138 | set_start_line(pid, 0) 139 | set_display_offset(pid, 96) 140 | set_vertical_mode(pid) 141 | send_commands(pid, <<0xAB, 0x01>>) 142 | set_contrast_level(pid, 0x53) # 100 nit 143 | send_commands(pid, <<0xB1, 0x51>>) 144 | send_commands(pid, <<0xB3, 0x01>>) 145 | send_commands(pid, <<0xB9>>) 146 | send_commands(pid, <<0xBC, 0x08>>) 147 | send_commands(pid, <<0xBE, 0x07>>) 148 | send_commands(pid, <<0xB6, 0x01>>) 149 | send_commands(pid, <<0xD5, 0x62>>) 150 | set_normal_mode(pid) 151 | set_activate_scroll(pid, false) 152 | on(pid) 153 | :timer.sleep(100) # ms 154 | set_row_address(pid, 0, 95) 155 | set_column_address(pid, 8, 8 + 47) 156 | end 157 | 158 | @spec on(pid) :: :ok 159 | def on(pid) do 160 | send_command(pid, 0xAF) 161 | end 162 | 163 | @spec off(pid) :: :ok 164 | def off(pid) do 165 | send_command(pid, 0xAE) 166 | end 167 | 168 | @spec clear(pid) :: :ok 169 | def clear(pid) do 170 | # TODO: optimize more once https://github.com/fhunleth/elixir_ale/issues/20 is fixed. 171 | block = :erlang.list_to_binary([@data_mode, String.duplicate("\x00", 16)]) 172 | Enum.each(1..48, fn _ -> 173 | Enum.each(1..div(96, 16), fn _ -> I2c.write(pid, block) end) 174 | end) 175 | end 176 | 177 | @spec set_text_position(pid, byte, byte) :: any 178 | def set_text_position(pid, row, column) do 179 | set_column_address(pid, 0x08 + column * 4, 0x08 + 47) 180 | set_row_address(pid, 0x00 + row * 8, 0x07 + row * 8) 181 | end 182 | 183 | @spec put_string(pid, <<>>) :: :ok 184 | def put_string(_pid, <<>>), do: nil 185 | 186 | @spec put_string(pid, binary) :: any 187 | def put_string(pid, <>) do 188 | put_char(pid, head) 189 | put_string(pid, rest) 190 | end 191 | 192 | @spec put_char(pid, byte) :: any 193 | def put_char(pid, char) when is_integer(char) and char in 32..127 do 194 | c = char - 32 195 | Enum.each([0, 2, 4, 6], fn i -> 196 | Enum.each(0..7, fn j -> 197 | glyph = elem(@default_font, c) 198 | bit1 = band(bsr(elem(glyph, i), j), 0x01) 199 | bit2 = band(bsr(elem(glyph, i + 1), j), 0x01) 200 | send_data(pid, 201 | bor(if bit1 != 0 do 0xF0 else 0 end, 202 | if bit2 != 0 do 0x0F else 0 end)) 203 | end) 204 | end) 205 | end 206 | 207 | @spec put_char(pid, byte) :: any 208 | def put_char(pid, char) when is_integer(char) and char in 0..31 do 209 | put_char(pid, ?\s) # replace with a space 210 | end 211 | 212 | @spec set_column_address(pid, byte, byte) :: :ok 213 | def set_column_address(pid, start, end_) do 214 | send_commands(pid, <<0x15, start, end_>>) 215 | end 216 | 217 | @spec set_row_address(pid, byte, byte) :: :ok 218 | def set_row_address(pid, start, end_) do 219 | send_commands(pid, <<0x75, start, end_>>) 220 | end 221 | 222 | @spec set_contrast_level(pid, byte) :: :ok 223 | def set_contrast_level(pid, level) do 224 | send_commands(pid, <<0x81, level>>) 225 | end 226 | 227 | @spec set_horizontal_mode(pid) :: :ok 228 | def set_horizontal_mode(pid) do 229 | send_commands(pid, <<0xA0, 0x42>>) 230 | set_row_address(pid, 0, 95) 231 | set_column_address(pid, 8, 8 + 47) 232 | end 233 | 234 | @spec set_vertical_mode(pid) :: :ok 235 | def set_vertical_mode(pid) do 236 | send_commands(pid, <<0xA0, 0x46>>) 237 | end 238 | 239 | @spec set_start_line(pid, 0..127) :: :ok 240 | def set_start_line(pid, row) do 241 | send_commands(pid, <<0xA1, row>>) 242 | end 243 | 244 | @spec set_display_offset(pid, 0..127) :: :ok 245 | def set_display_offset(pid, row) do 246 | send_commands(pid, <<0xA2, row>>) 247 | end 248 | 249 | @spec set_normal_mode(pid) :: :ok 250 | def set_normal_mode(pid) do 251 | send_command(pid, 0xA4) 252 | end 253 | 254 | @spec set_inverse_mode(pid) :: :ok 255 | def set_inverse_mode(pid) do 256 | send_command(pid, 0xA7) 257 | end 258 | 259 | @spec set_multiplex_ratio(pid, 16..128) :: :ok 260 | def set_multiplex_ratio(pid, ratio) do 261 | send_commands(pid, <<0xA8, ratio>>) 262 | end 263 | 264 | @spec set_activate_scroll(pid, false) :: :ok 265 | def set_activate_scroll(pid, false) do 266 | send_command(pid, 0x2E) 267 | end 268 | 269 | @spec set_activate_scroll(pid, true) :: :ok 270 | def set_activate_scroll(pid, true) do 271 | send_command(pid, 0x2F) 272 | end 273 | 274 | @spec send_commands(pid, <<>>) :: :ok 275 | defp send_commands(_pid, <<>>), do: nil 276 | 277 | @spec send_commands(pid, binary) :: :ok 278 | defp send_commands(pid, <>) do 279 | send_command(pid, head) 280 | send_commands(pid, rest) 281 | end 282 | 283 | @spec send_command(pid, byte) :: :ok 284 | defp send_command(pid, command) do 285 | I2c.write(pid, <<@command_mode, command>>) 286 | end 287 | 288 | @spec send_data(pid, byte) :: :ok 289 | defp send_data(pid, data) do 290 | I2c.write(pid, <<@data_mode, data>>) 291 | end 292 | end 293 | --------------------------------------------------------------------------------