Add the MPL3115A2 driver.

This commit is contained in:
James Harton 2019-10-07 15:10:06 +13:00
parent bea3cdbdc0
commit a923e320e7
9 changed files with 2000 additions and 17 deletions

View file

@ -1,6 +1,24 @@
# MPL3115A2
**TODO: Add description**
Elixir driver for the MPL3115A2 barometric pressure, altitude and temperature
sensor. I'm using [Adafruit's breakout](https://www.adafruit.com/product/1893).
## Usage
Add your device to your config like so:
config :mpl3115a2,
devices: [%{bus: "i2c-1", address: 0x60}]
And start your application. Your devices will be reset with defaults and you
will be able to take temperature and pressure or altitude readings. See
`MPL3115A2.Commands.initialize!/1` for more details on the default
initialization.
This device is capable of much more advanced usage than the `MPL3115A2.Device`
module makes use of. It was all that I needed at the time. For advanced usage
you can use the `MPL3115A2.Commands` and `MPL3115A2.Registers` modules directly.
Feel free to send PR's.
## Installation

4
config/config.exs Normal file
View file

@ -0,0 +1,4 @@
import Config
config :mpl3115a2,
devices: [%{bus: "i2c-1", address: 0x60}]

View file

@ -1,18 +1,30 @@
defmodule MPL3115A2 do
@moduledoc """
Documentation for MPL3115A2.
MPL3115A2 Driver for Elixir using ElixirALE.
## Usage:
Add your devices to your config like so:
config :mpl3115a2,
devices: [
%{bus: "i2c-1", address: 0x3d, reset_pin: 17}
]
Then use the functions in [MPL3115A2.Device] to send image data.
Pretty simple.
"""
@doc """
Hello world.
## Examples
iex> MPL3115A2.hello()
:world
Connect to an MPL3115A2 device.
"""
def hello do
:world
def connect(config),
do: Supervisor.start_child(MPL3115A2.Supervisor, {MPL3115A2.Device, config})
@doc """
Disconnect an MPL3115A2 device.
"""
def disconnect(device_name) do
Supervisor.terminate_child(MPL3115A2.Supervisor, {MPL3115A2.Device, device_name})
Supervisor.delete_child(MPL3115A2.Supervisor, {MPL3115A2.Device, device_name})
end
end

View file

@ -7,13 +7,17 @@ defmodule MPL3115A2.Application do
def start(_type, _args) do
children = [
# Starts a worker by calling: MPL3115A2.Worker.start_link(arg)
# {MPL3115A2.Worker, arg}
{Registry, keys: :unique, name: MPL3115A2.Registry}
]
devices =
:mpl3115a2
|> Application.get_env(:devices, [])
|> Enum.map(&{MPL3115A2.Device, &1})
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: MPL3115A2.Supervisor]
Supervisor.start_link(children, opts)
Supervisor.start_link(children ++ devices, opts)
end
end

1416
lib/mpl3115a2/commands.ex Normal file

File diff suppressed because it is too large Load diff

191
lib/mpl3115a2/device.ex Normal file
View file

@ -0,0 +1,191 @@
defmodule MPL3115A2.Device do
alias MPL3115A2.{Device, Commands}
alias ElixirALE.I2C
use GenServer
require Logger
@doc """
Returns true of there is pressure or temperature data ready for reading.
"""
def pressure_or_temperature_data_ready?(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:pressure_or_temperature_data_ready?
)
@doc """
Returns true of there is pressure data ready for reading.
"""
def pressure_data_available?(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:pressure_data_available?
)
@doc """
Returns true of there is temperature data ready for reading.
"""
def temperature_data_available?(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:temperature_data_available?
)
@doc """
Returns the current altutude (in meters).
"""
def altitude(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:altitude
)
@doc """
Returns the current barometric pressure (in Pascals).
"""
def pressure(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:pressure
)
@doc """
Returns the current temperature (in )
"""
def temperature(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:temperature
)
@doc """
Returns the change in altitude since the last sample (in meters).
"""
def altitude_delta(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:altitude_delta
)
@doc """
Returns the change in pressure since the last sample (in Pascals).
"""
def pressure_delta(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:pressure_delta
)
@doc """
Returns the change in temperature since the last sample (in )
"""
def temperature_delta(device_name),
do:
GenServer.call(
{:via, Registry, {MPL3115A2.Registry, device_name}},
:temperature_delta
)
@doc """
Execute an arbitrary function with the PID of the I2C connection.
"""
def execute(device_name, function) when is_function(function, 1),
do: GenServer.call({:via, Registry, {MPL3115A2.Registry, device_name}}, {:execute, function})
@doc false
def start_link(config), do: GenServer.start_link(Device, config)
@impl true
def init(%{bus: bus, address: address} = state) do
name = device_name(state)
{:ok, _} = Registry.register(MPL3115A2.Registry, name, self())
Process.flag(:trap_exit, true)
Logger.info("Connecting to MPL3115A2 device on #{inspect(name)}")
{:ok, pid} = I2C.start_link(bus, address)
with 0xC4 <- Commands.who_am_i(pid),
:ok <- Commands.initialize!(pid, state) do
state =
state
|> Map.merge(%{name: name, i2c: pid})
{:ok, state}
else
i when is_integer(i) ->
{:stop, "Device responded incorrectly to WHO_AM_I command with #{inspect(i)}"}
{:error, message} ->
{:stop, message}
end
end
@impl true
def terminate(_reason, %{i2c: pid, name: name}) do
Logger.info("Disconnecting from MPL3115A2 device on #{inspect(name)}")
I2C.release(pid)
end
@impl true
def handle_call(:pressure_or_temperature_data_ready?, _from, %{i2c: pid} = state) do
{:reply, Commands.pressure_or_temperature_data_ready(pid), state}
end
def handle_call(:pressure_data_available?, _from, %{i2c: pid} = state) do
{:reply, Commands.pressure_data_available(pid), state}
end
def handle_call(:temperature_data_available?, _from, %{i2c: pid} = state) do
{:reply, Commands.temperature_data_available(pid), state}
end
def handle_call(:altitude, _from, %{i2c: pid} = state) do
{:reply, Commands.altitude(pid), state}
end
def handle_call(:temperature, _from, %{i2c: pid} = state) do
{:reply, Commands.temperature(pid), state}
end
def handle_call(:pressure, _from, %{i2c: pid} = state) do
{:reply, Commands.pressure(pid), state}
end
def handle_call(:altitude_delta, _from, %{i2c: pid} = state) do
{:reply, Commands.altitude_delta(pid), state}
end
def handle_call(:temperature_delta, _from, %{i2c: pid} = state) do
{:reply, Commands.temperature_delta(pid), state}
end
def handle_call(:pressure_delta, _from, %{i2c: pid} = state) do
{:reply, Commands.pressure_delta(pid), state}
end
def handle_call({:execute, function}, _from, %{i2c: pid} = state) do
{:reply, function.(pid), state}
end
@doc false
def child_spec(config) do
%{
id: {MPL3115A2.Device, device_name(config)},
start: {MPL3115A2.Device, :start_link, [config]},
restart: :transient
}
end
defp device_name(%{bus: bus, address: address} = config),
do: Map.get(config, :name, {bus, address})
end

317
lib/mpl3115a2/registers.ex Normal file
View file

@ -0,0 +1,317 @@
defmodule MPL3115A2.Registers do
use Bitwise
alias ElixirALE.I2C
@moduledoc """
This module provides a wrapper around the MPL3115A2 registers
described in Freescale's data sheet.
Don't access these directly unless you know what you're doing.
It's better to use the `Commands` module instead.
"""
@doc """
STATUS register; 0x00; 1 byte, RO
"""
def status(pid), do: read_register(pid, 0)
@doc """
OUT_P_MSB register; 0x01, 1 byte, RO
OUT_P_CSB register; 0x02, 1 byte, RO
OUT_P_LSB register; 0x03, 1 byte, RO
"""
def pressure_data_out(pid) do
with msb <- read_register(pid, 1),
csb <- read_register(pid, 2),
lsb <- read_register(pid, 3),
do: msb <> csb <> lsb
end
@doc """
OUT_T_MSB register; 0x04, 1 byte, RO
OUT_T_LSB register; 0x05, 1 byte, RO
"""
def temperature_data_out(pid) do
with msb <- read_register(pid, 4),
lsb <- read_register(pid, 5),
do: msb <> lsb
end
@doc """
DR_STATUS register; 0x06, 1 byte, RO
"""
def data_ready_status(pid), do: read_register(pid, 6)
@doc """
OUT_P_DELTA_MSB register; 0x07, 1 byte, RO
OUT_P_DELTA_CSB register; 0x08, 1 byte, RO
OUT_P_DELTA_LSB register; 0x09, 1 byte, RO
"""
def pressure_data_out_delta(pid) do
with msb <- read_register(pid, 7),
csb <- read_register(pid, 8),
lsb <- read_register(pid, 9),
do: msb <> csb <> lsb
end
@doc """
OUT_T_DELTA_MSB register; 0x0a, 1 byte, RO
OUT_T_DELTA_LSB register; 0x0b, 1 byte, RO
"""
def temperature_data_out_delta(pid) do
with msb <- read_register(pid, 0xA),
lsb <- read_register(pid, 0xB),
do: msb <> lsb
end
@doc """
WHO_AM_I register; 0x0c, 1 byte, RO
"""
def who_am_i(pid), do: read_register(pid, 0xC)
@doc """
F_STATUS register; 0x0d, 1 byte, RO
"""
def fifo_status(pid), do: read_register(pid, 0xD)
@doc """
F_DATA register; 0x0e, 1 byte, RO
"""
def fifo_data_access(pid), do: read_register(pid, 0xE)
@doc """
F_SETUP register; 0x0f, 1 byte, RW
"""
def fifo_setup(pid), do: read_register(pid, 0xF)
def fifo_setup(pid, value), do: write_register(pid, 0xF, value)
@doc """
TIME_DLY register; 0x10, 1 byte, RO
"""
def time_delay(pid), do: read_register(pid, 0x10)
@doc """
SYSMOD register; 0x11, 1 byte, RO
"""
def system_mode(pid), do: read_register(pid, 0x11)
@doc """
INT_SOURCE register; 0x12, 1 byte, RO
"""
def interrupt_source(pid), do: read_register(pid, 0x12)
@doc """
PT_DATA_CFG register; 0x13, 1 byte, RW
"""
def pt_data_configuration(pid), do: read_register(pid, 0x13)
def pt_data_configuration(pid, value), do: write_register(pid, 0x13, value)
@doc """
BAR_IN_MSB register; 0x14, 1 byte, RW
BAR_IN_LSB register; 0x15, 1 byte, RW
"""
def barometric_input(pid) do
with msb <- read_register(pid, 0x14),
lsb <- read_register(pid, 0x15),
do: msb <> lsb
end
def barometric_input(pid, value) do
msb = value >>> 8 &&& 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x14, msb),
:ok <- write_register(pid, 0x15, lsb),
do: :ok
end
@doc """
P_TGT_MSB register; 0x16, 1 byte, RW
P_TGT_LSB register; 0x17, 1 byte, RW
"""
def pressure_target(pid) do
with msb <- read_register(pid, 0x16),
lsb <- read_register(pid, 0x17),
do: msb <> lsb
end
def pressure_target(pid, value) do
msb = value >>> 8 &&& 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x16, msb),
:ok <- write_register(pid, 0x17, lsb),
do: :ok
end
@doc """
T_TGT register; 0x18, 1 byte, RO
"""
def temperature_target(pid), do: read_register(pid, 0x18)
def temperature_target(pid, value), do: write_register(pid, 0x18, value)
@doc """
P_WND_MSB register; 0x19, 1 byte, RW
P_WND_LSB register; 0x1a, 1 byte, RW
"""
def pressure_altitude_window(pid) do
with msb <- read_register(pid, 0x19),
lsb <- read_register(pid, 0x1A),
do: msb <> lsb
end
def pressure_altitude_window(pid, value) do
msb = value >>> 8 &&& 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x19, msb),
:ok <- write_register(pid, 0x1A, lsb),
do: :ok
end
@doc """
T_WND register; 0x1b, 1 byte, RW
"""
def temperature_window(pid), do: read_register(pid, 0x1B)
def temperature_window(pid, value), do: write_register(pid, 0x1B, value)
@doc """
P_MIN_MSB register; 0x1c, 1 byte, RW
P_MIN_CSB register; 0x1d, 1 byte, RW
P_MIN_LSB register; 0x1e, 1 byte, RW
"""
def minimum_pressure_data(pid) do
with msb <- read_register(pid, 0x1C),
csb <- read_register(pid, 0x1D),
lsb <- read_register(pid, 0x1E),
do: msb <> csb <> lsb
end
def minimum_pressure_data(pid, value) do
msb = value >>> 16 &&& 0xFF
csb = value >>> 8 &&& 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x1C, msb),
:ok <- write_register(pid, 0x1D, csb),
:ok <- write_register(pid, 0x1E, lsb),
do: :ok
end
@doc """
T_MIN_MSB register; 0x1f, 1 byte, RW
T_MIN_LSB register; 0x20, 1 byte, RW
"""
def minimum_temperature_data(pid) do
with msb <- read_register(pid, 0x1F),
lsb <- read_register(pid, 0x20),
do: msb <> lsb
end
def minimum_temperature_data(pid, value) do
msb = value >>> 8 && 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x1F, msb),
:ok <- write_register(pid, 0x20, lsb),
do: :ok
end
@doc """
P_MAX_MSB register, 0x21, 1 byte, RW
P_MAX_CSB register, 0x22, 1 byte, RW
P_MAX_LSB register, 0x23, 1 byte, RW
"""
def maximum_pressure_data(pid) do
with msb <- read_register(pid, 0x21),
csb <- read_register(pid, 0x22),
lsb <- read_register(pid, 0x23),
do: msb <> csb <> lsb
end
def maximum_pressure_data(pid, value) do
msb = value >>> 16 &&& 0xFF
csb = value >>> 8 &&& 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x21, msb),
:ok <- write_register(pid, 0x22, csb),
:ok <- write_register(pid, 0x23, lsb),
do: :ok
end
@doc """
T_MAX_MSB register; 0x24, 1 byte, RW
T_MAX_LSB register; 0x25, 1 byte, RW
"""
def maximum_temperature_data(pid) do
with msb <- read_register(pid, 0x24),
lsb <- read_register(pid, 0x25),
do: msb <> lsb
end
def maximum_temperature_data(pid, value) do
msb = value >>> 8 && 0xFF
lsb = value &&& 0xFF
with :ok <- write_register(pid, 0x24, msb),
:ok <- write_register(pid, 0x25, lsb),
do: :ok
end
@doc """
CTRL_REG1 register; 1 byte, 0x26, RW
"""
def control_register1(pid), do: read_register(pid, 0x26)
def control_register1(pid, value), do: write_register(pid, 0x26, value)
@doc """
CTRL_REG2 register; 1 byte, 0x27, RW
"""
def control_register2(pid), do: read_register(pid, 0x27)
def control_register2(pid, value), do: write_register(pid, 0x27, value)
@doc """
CTRL_REG3 register; 1 byte, 0x28, RW
"""
def control_register3(pid), do: read_register(pid, 0x28)
def control_register3(pid, value), do: write_register(pid, 0x28, value)
@doc """
CTRL_REG4 register; 1 byte, 0x29, RW
"""
def control_register4(pid), do: read_register(pid, 0x29)
def control_register4(pid, value), do: write_register(pid, 0x29, value)
@doc """
CTRL_REG5 register; 1 byte, 0x2a, RW
"""
def control_register5(pid), do: read_register(pid, 0x2A)
def control_register5(pid, value), do: write_register(pid, 0x2A, value)
@doc """
OFF_P register; 1 byte, 0x2b, RW
"""
def pressure_data_user_offset(pid), do: read_register(pid, 0x2B)
def pressure_data_user_offset(pid, value), do: write_register(pid, 0x2B, value)
@doc """
OFF_T register; 1 byte, 0x2c, RW
"""
def temperature_data_user_offset(pid), do: read_register(pid, 0x2C)
def temperature_data_user_offset(pid, value), do: write_register(pid, 0x2C, value)
@doc """
OFF_H register; 1 byte, 0x2d, RW
"""
def altitude_data_user_offset(pid), do: read_register(pid, 0x2D)
def altitude_data_user_offset(pid, value), do: write_register(pid, 0x2D, value)
defp read_register(pid, register) do
I2C.write_read(pid, <<register>>, 1)
end
defp write_register(pid, register, value) do
I2C.write(pid, <<register, value>>)
end
end

18
mix.exs
View file

@ -7,7 +7,9 @@ defmodule MPL3115A2.MixProject do
version: "0.1.0",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps()
description: "Driver for the MPL3115A2 altimeter connected via I2C.",
deps: deps(),
package: package()
]
end
@ -19,11 +21,21 @@ defmodule MPL3115A2.MixProject do
]
end
def package do
[
maintainers: ["James Harton <james@automat.nz>"],
licenses: ["MIT"],
links: %{
"Source" => "https://gitlab.com/jimsy/mpl3115a2"
}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:elixir_ale, "~> 1.2"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false}
]
end
end

9
mix.lock Normal file
View file

@ -0,0 +1,9 @@
%{
"earmark": {:hex, :earmark, "1.4.1", "07bb382826ee8d08d575a1981f971ed41bd5d7e86b917fd012a93c51b5d28727", [:mix], [], "hexpm"},
"elixir_ale": {:hex, :elixir_ale, "1.2.1", "07ac2f17a0191b8bd3b0df6b526c7f699a3a4d690c9def573fcb5824eef24d98", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
}