diff --git a/README.md b/README.md index 6d8eec0..120b072 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..1045bac --- /dev/null +++ b/config/config.exs @@ -0,0 +1,4 @@ +import Config + +config :mpl3115a2, + devices: [%{bus: "i2c-1", address: 0x60}] diff --git a/lib/mpl3115a2.ex b/lib/mpl3115a2.ex index 20b1dc9..2b8189e 100644 --- a/lib/mpl3115a2.ex +++ b/lib/mpl3115a2.ex @@ -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 diff --git a/lib/mpl3115a2/application.ex b/lib/mpl3115a2/application.ex index 1ffd8fa..b4e245e 100644 --- a/lib/mpl3115a2/application.ex +++ b/lib/mpl3115a2/application.ex @@ -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 diff --git a/lib/mpl3115a2/commands.ex b/lib/mpl3115a2/commands.ex new file mode 100644 index 0000000..ca4b8d7 --- /dev/null +++ b/lib/mpl3115a2/commands.ex @@ -0,0 +1,1416 @@ +defmodule MPL3115A2.Commands do + alias MPL3115A2.Registers + use Bitwise + + @moduledoc """ + Commands for reading and modifying the device's registers. + """ + + @doc """ + Tries to configure the device according to sane defaults. + + `config` is a map containing any of the following keys: + + - `:standby` set to `true` to put the device in standby, otherwise defaults to `false`. + - `:oversample` set to the oversample rate you want. Valid values are two's complements from `1` to `128`. Defaults to `128`. + - `:mode`, set to either `:altimeter` or `:barometer`. Defaults to `:altimeter`. + - `:event_on_new_temperature` set to `false` to disable. Defaults to `true`. + - `:event_on_new_pressure` set to `false` to disable. Defaults to `true`. + - `:data_ready_event_mode` set to `false` to disable. Defaults to `true`. + """ + def initialize!(pid, config) do + standby = + case Map.get(config, :standby, false) do + true -> 0x00 + false -> 0x01 + end + + oversample = + case Map.get(config, :oversample, 128) do + 1 -> 0x00 + 2 -> 0x80 + 4 -> 0x10 + 8 -> 0x18 + 16 -> 0x20 + 32 -> 0x28 + 64 -> 0x30 + 128 -> 0x38 + end + + mode = + case Map.get(config, :mode, :altimeter) do + :altimeter -> 0x80 + :barometer -> 0x00 + end + + tdefe = + case Map.get(config, :event_on_new_temperature, true) do + true -> 0x01 + false -> 0x00 + end + + pdefe = + case Map.get(config, :event_on_new_pressure, true) do + true -> 0x02 + false -> 0x00 + end + + drem = + case Map.get(config, :data_ready_event_mode, true) do + true -> 0x04 + false -> 0x00 + end + + with :ok <- Registers.control_register1(pid, standby ||| oversample ||| mode), + :ok <- Registers.pt_data_configuration(pid, tdefe ||| pdefe ||| drem) do + :ok + end + end + + @doc """ + PTOW Pressure/Altitude OR Temperature data overwrite. + """ + def pressure_or_temperature_data_overwrite(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(7) + |> b + end + + @doc """ + POW Pressure/Altitude data overwrite. + """ + def pressure_data_overwrite(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(6) + |> b + end + + @doc """ + TOW Temperature data overwrite. + """ + def temperature_data_overwrite(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(5) + |> b + end + + @doc """ + PTDR Pressure/Altitude OR Temperature data ready. + """ + def pressure_or_temperature_data_ready(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(3) + |> b + end + + @doc """ + PDR Pressure/Altitude new data available. + """ + def pressure_data_available(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(2) + |> b + end + + @doc """ + TDR Temperature new Data Available. + """ + def temperature_data_available(pid) do + pid + |> Registers.data_ready_status() + |> get_bit(1) + |> b + end + + @doc """ + OUT_P Altitude in meters. + """ + def altitude(pid), do: Registers.pressure_data_out(pid) |> to_altitude + + @doc """ + OUT_P Pressure in Pascals. + """ + def pressure(pid), do: Registers.pressure_data_out(pid) |> to_pressure + + @doc """ + OUT_T Temperature in ℃. + """ + def temperature(pid), do: Registers.temperature_data_out(pid) |> to_temperature + + @doc """ + OUT_P_DELTA Altitude delta in meters. + """ + def altitude_delta(pid), do: Registers.pressure_data_out_delta(pid) |> to_altitude_delta + + @doc """ + OUT_P_DELTA Pressure delta in Pascals. + """ + def pressure_delta(pid), do: Registers.pressure_data_out_delta(pid) |> to_pressure_delta + + @doc """ + OUT_T_DELTA Temperature delta in ℃. + """ + def temperature_delta(pid), + do: Registers.temperature_data_out_delta(pid) |> to_temperature_delta + + @doc """ + WHO_AM_I Should always respond with 0x0c. + """ + def who_am_i(pid) do + <> = Registers.who_am_i(pid) + reg + end + + @doc """ + F_OVF FIFO overflow events detected? + """ + def fifo_overflow?(pid) do + pid + |> Registers.fifo_status() + |> get_bit(7) + |> b + end + + @doc """ + F_WMRK_FLAG FIFO watermark events detected? + """ + def fifo_watermark?(pid) do + pid + |> Registers.fifo_status() + |> get_bit(6) + |> b + end + + @doc """ + F_CNT FIFO sample count + """ + def fifo_sample_count(pid) do + <> = Registers.fifo_status(pid) + reg &&& 0x1F + end + + @doc """ + F_DATA Read FIFO data in Altitude mode. + """ + def fifo_read_altitude(pid) do + pid + |> fifo_read + |> Enum.map(&to_altitude(&1)) + end + + @doc """ + F_DATA Read FIFO data in Barometer mode. + """ + def fifo_read_pressure(pid) do + pid + |> fifo_read + |> Enum.map(&to_pressure(&1)) + end + + @doc """ + F_MODE FIFO mode, can be either `:fifo_disabled`, `:circular_buffer` or `:halt_on_overflow`. + """ + def fifo_overflow_mode(pid) do + <> = Registers.fifo_setup(pid) + + case reg >>> 6 do + 0 -> :fifo_disabled + 1 -> :circular_buffer + 2 -> :halt_on_overflow + end + end + + def fifo_overflow_mode(pid, :fifo_disabled) do + <> = Registers.fifo_setup(pid) + reg = reg &&& 0x7F + Registers.fifo_setup(pid, reg) + end + + def fifo_overflow_mode(pid, :circular_buffer) do + <> = Registers.fifo_setup(pid) + reg = reg &&& 0x7F + (1 <<< 6) + Registers.fifo_setup(pid, reg) + end + + def fifo_overflow_mode(pid, :halt_on_overflow) do + <> = Registers.fifo_setup(pid) + reg = reg &&& 0x7F + (1 <<< 7) + Registers.fifo_setup(pid, reg) + end + + @doc """ + F_WMRK FIFO Event Sample Count Watermark. + """ + def fifo_event_sample_count_watermark(pid) do + <> = Registers.fifo_setup(pid) + reg &&& 0x1F + end + + def fifo_event_sample_count_watermark(pid, count) do + <> = Registers.fifo_setup(pid) + reg = (reg >>> 5 <<< 5) + (count &&& 0x1F) + Registers.fifo_setup(pid, reg) + end + + @doc """ + TIME_DLY + + The time delay register contains the number of ticks of data sample time + since the last byte of the FIFO was written. This register starts to + increment on FIFO overflow or data wrap and clears when last byte of FIFO is + read. + """ + def time_delay(pid) do + <> = Registers.time_delay(pid) + reg + end + + @doc """ + SYSMOD System Mode, either `:standby` or `:active`. + """ + def system_mode(pid) do + mode = + pid + |> Registers.system_mode() + |> get_bit(0) + + case mode do + 0 -> :standby + 1 -> :active + end + end + + @doc """ + SRC_DRDY Data ready interrupt status. + + `true` indicates that Pressure/Altitude or Temperature data ready interrupt + is active indicating the presence of new data and/or a data overwrite, + otherwise it is `false`. + """ + def data_ready_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(7) + |> b + end + + @doc """ + SRC_FIFO FIFO interrupt status. + + `true` indicates that a FIFO interrupt event such as an overflow event has + occurred. `false` indicates that no FIFO interrupt event has occurred. + """ + def fifo_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(6) + |> b + end + + @doc """ + SRC_PW Altitude/Pressure alerter status near or equal to target Pressure/Altitude. + + Near is within target value ± window value. Window value needs to be non + zero for interrupt to trigger. + """ + def altitude_pressure_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(5) + |> b + end + + @doc """ + SRC_TW Temperature alerter status bit near or equal to target temperature. + + Near is within target value ± window value. Window value needs to be non zero + for interrupt to trigger. + """ + def temperature_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(4) + |> b + end + + @doc """ + SRC_PTH Altitude/Pressure threshold interrupt. + + With the window set to a non zero value, the trigger will occur on crossing + any of the thresholds: upper, center or lower. If the window is set to 0, it + will only trigger on crossing the center threshold. + """ + def altitude_pressure_threshold_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(3) + |> b + end + + @doc """ + SRC_TTH Temperature threshold interrupt. + + With the window set to a non zero value, the trigger will occur on crossing + any of the thresholds: upper, center or lower. If the window is set to 0, it + will only trigger on crossing the center threshold. + """ + def temperature_threshold_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(2) + |> b + end + + @doc """ + SRC_PCHG Delta P interrupt status. + """ + def altitude_pressure_delta_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(1) + |> b + end + + @doc """ + SRC_TCHG Delta T interrupt status. + """ + def temperature_delta_interrupt?(pid) do + pid + |> Registers.interrupt_source() + |> get_bit(0) + |> b + end + + @doc """ + DREM Data ready event mode. + + If the DREM bit is set `true` and one or more of the data ready event flags + (PDEFE, TDEFE) are enabled, then an event flag will be raised upon change in + state of the data. If the DREM bit is `false` and one or more of the data + ready event flags are enabled, then an event flag will be raised whenever + the system acquires a new set of data. + + Default value: `false`. + """ + def data_ready_event_mode(pid) do + pid + |> Registers.pt_data_configuration() + |> get_bit(2) + |> b + end + + def data_ready_event_mode(pid, true) do + reg = + pid + |> Registers.pt_data_configuration() + |> set_bit(2) + + Registers.pt_data_configuration(pid, reg) + end + + def data_ready_event_mode(pid, false) do + reg = + pid + |> Registers.pt_data_configuration() + |> clear_bit(2) + + Registers.pt_data_configuration(pid, reg) + end + + @doc """ + PDEFE Data event flag enable on new Pressure/Altitude data. + + Default value: `false`. + """ + def pressure_altitude_event_flag_enable(pid) do + pid + |> Registers.pt_data_configuration() + |> get_bit(1) + |> b + end + + def pressure_altitude_event_flag_enable(pid, true) do + reg = + pid + |> Registers.pt_data_configuration() + |> set_bit(1) + + Registers.pt_data_configuration(pid, reg) + end + + def pressure_altitude_event_flag_enable(pid, false) do + reg = + pid + |> Registers.pt_data_configuration() + |> clear_bit(1) + + Registers.pt_data_configuration(pid, reg) + end + + @doc """ + TDEFE Data event flag enable on new Temperature data. + + Default value: `false`. + """ + def temperature_event_flag_enable(pid) do + pid + |> Registers.pt_data_configuration() + |> get_bit(0) + |> b + end + + def temperature_event_flag_enable(pid, true) do + reg = + pid + |> Registers.pt_data_configuration() + |> set_bit(0) + + Registers.pt_data_configuration(pid, reg) + end + + def temperature_event_flag_enable(pid, false) do + reg = + pid + |> Registers.pt_data_configuration() + |> clear_bit(0) + + Registers.pt_data_configuration(pid, reg) + end + + @doc """ + BAR_IN Barometric input for altitude calculations. + + Input is equivalent sea level pressure for measurement location. + """ + def barometric_pressure_input(pid) do + <> = Registers.barometric_input(pid) + ((msb <<< 8) + lsb) * 2 + end + + def barometric_pressure_input(pid, pascals) do + pascals = pascals |> div(2) + Registers.barometric_input(pid, pascals) + end + + @doc """ + P_TGT Altitude/Pressure target value. + + This value works in conjunction with the window value (P_WND). + + In Altitude mode the result is in meters. + In Pressure mode the result is in Pascals. + """ + def pressure_altitude_target(pid) do + <> = Registers.pressure_target(pid) + (msb <<< 8) + lsb + end + + def pressure_altitude_target(pid, value) do + msb = value >>> 8 &&& 0xFF + lsb = value &&& 0xFF + Registers.pressure_target(pid, <>) + end + + @doc """ + T_TGT Temperature target value input in °C. + """ + def temperature_target(pid) do + <> = Registers.temperature_target(pid) + reg + end + + def temperature_target(pid, value) do + Registers.temperature_target(pid, value &&& 0xFF) + end + + @doc """ + P_WND Pressure/Altitude window value. + + In Altitude mode the result is in meters. + In Pressure mode the result is in Pascals. + """ + def pressure_altitude_window(pid) do + <> = Registers.pressure_altitude_window(pid) + (msb <<< 8) + lsb + end + + @doc """ + T_WND Temperature alarm window value in °C. + """ + def temperature_window(pid) do + <> = Registers.temperature_window(pid) + reg + end + + @doc """ + P_MIN Captured minimum Pressure/Altitude value. + """ + def minimum_pressure(pid) do + pid + |> Registers.minimum_pressure_data(pid) + |> to_pressure + end + + @doc """ + P_MAX Captured maximum Pressure/Altitude value. + """ + def maximum_pressure(pid) do + pid + |> Registers.maximum_pressure_data() + |> to_pressure + end + + @doc """ + T_MIN Captured minimum temperature value. + """ + def minimum_temperature(pid) do + pid + |> Registers.minimum_temperature_data() + |> to_temperature + end + + @doc """ + T_MAX Captured maximum temperature value. + """ + def maximum_temperature(pid) do + pid + |> Registers.maximum_temperature_data() + |> to_temperature + end + + @doc """ + SBYB System Standby + """ + def standby?(pid) do + pid + |> Registers.control_register1() + |> get_bit(0) + |> b + end + + def standby(pid, true) do + reg = + pid + |> Registers.control_register1() + |> set_bit(0) + + Registers.control_register1(pid, reg) + end + + def standby(pid, false) do + reg = + pid + |> Registers.control_register1() + |> clear_bit(0) + + Registers.control_register1(pid, reg) + end + + @doc """ + OST One-shot measurement + + OST bit will initiate a measurement immediately. If the SBYB bit is set to + active, setting the OST bit will initiate an immediate measurement, the part + will then return to acquiring data as per the setting of the ST bits in + CTRL_REG2. In this mode, the OST bit does not clear itself and must be + cleared and set again to initiate another immediate measurement. + + One Shot: When SBYB is 0, the OST bit is an auto-clear bit. When OST is set, + the device initiates a measurement by going into active mode. Once a + Pressure/Altitude and Temperature measurement is completed, it clears the + OST bit and comes back to STANDBY mode. User shall read the value of the OST + bit before writing to this bit again. + """ + def one_shot(pid) do + pid + |> Registers.control_register1() + |> get_bit(1) + |> b + end + + def one_shot(pid, true) do + reg = + pid + |> Registers.control_register1() + |> set_bit(1) + + Registers.control_register1(pid, reg) + end + + def one_shot(pid, false) do + reg = + pid + |> Registers.control_register1() + |> clear_bit(1) + + Registers.control_register1(pid, reg) + end + + @doc """ + RST Software Reset. + + This bit is used to activate the software reset. The Boot mechanism can be + enabled in STANDBY and ACTIVE mode. + + When the Boot bit is enabled the boot mechanism resets all functional block + registers and loads the respective internal registers with default values. + If the system was already in STANDBY mode, the reboot process will + immediately begin; else if the system was in ACTIVE mode, the boot mechanism + will automatically transition the system from ACTIVE mode to STANDBY mode, + only then can the reboot process begin. + + The I2C communication system is reset to avoid accidental corrupted data access. + + At the end of the boot process the RST bit is de-asserted to `false`. Reading + this bit will return a value of `false`. + + Default value: `false` + `false`: Device reset disabled + `true`: Device reset enabled + """ + def reset(pid) do + pid + |> Registers.control_register1() + |> get_bit(2) + |> b + end + + def reset!(pid) do + reg = + pid + |> Registers.control_register1() + |> set_bit(2) + + Registers.control_register1(pid, reg) + end + + @doc """ + OS Oversample Ratio. + + These bits select the oversampling ratio. + """ + def oversample_ratio(pid) do + <> = Registers.control_register1(pid) + :math.pow(2, reg >>> 3 &&& 0x3) |> trunc + end + + def oversample_ratio(pid, value) do + value = :math.sqrt(value) &&& 0x3 + <> = Registers.control_register1(pid) + head = reg >>> 5 + tail = reg &&& 0x7 + reg = (head <<< 5) + (value <<< 3) + tail + Registers.control_register1(pid, reg) + end + + @doc """ + Oversample delay in ms. + """ + def oversample_delay(pid) do + case oversample_ratio(pid) do + 1 -> 6 + 2 -> 10 + 4 -> 18 + 8 -> 34 + 16 -> 66 + 32 -> 130 + 64 -> 258 + 128 -> 512 + end + end + + @doc """ + RAW Raw output mode. + + RAW bit will output ADC data with no post processing, except for + oversampling. No scaling or offsets will be applied in the digital domain. + The FIFO must be disabled and all other functionality: Alarms, Deltas, and + other interrupts are disabled. + """ + def raw?(pid) do + pid + |> Registers.control_register1() + |> get_bit(6) + |> b + end + + def raw(pid, true) do + reg = + pid + |> Registers.control_register1() + |> set_bit(6) + + Registers.control_register1(pid, reg) + end + + def raw(pid, false) do + reg = + pid + |> Registers.control_register1() + |> clear_bit(6) + + Registers.control_register1(pid, reg) + end + + @doc """ + ALT Altimeter-Barometer mode. + + Selects whether the device is in Altimeter or Barometer mode. + Can be either `:barometer` or `:altimeter`. + """ + def altimeter_or_barometer(pid) do + mode = + pid + |> Registers.control_register1() + |> get_bit(7) + + case mode do + 0 -> :barometer + 1 -> :altimeter + end + end + + def altimeter_or_barometer(pid, :barometer) do + reg = + pid + |> Registers.control_register1() + |> clear_bit(7) + + Registers.control_register1(pid, reg) + end + + def altimeter_or_barometer(pid, :altimeter) do + reg = + pid + |> Registers.control_register1() + |> set_bit(7) + + Registers.control_register1(pid, reg) + end + + @doc """ + ST Auto acquisition time step. + """ + def data_acquisition_time_step(pid) do + <> = Registers.control_register2(pid) + :math.pow(2, reg &&& 0xF) + end + + def data_acquisition_time_step(pid, value) do + value = :math.sqrt(value) + <> = Registers.control_register2(pid) + reg = (reg >>> 3 <<< 3) + (value &&& 0xF) + Registers.control_register2(pid, reg) + end + + @doc """ + ALARM_SEL The bit selects the Target value for SRC_PW/SRC_TW and SRC_PTH/SRC_TTH + + Default value: 0 + 0: The values in P_TGT_MSB, P_TGT_LSB and T_TGT are used (Default) + 1: The values in OUT_P/OUT_T are used for calculating the interrupts SRC_PW/SRC_TW and SRC_PTH/SRC_TTH. + """ + def alarm_select(pid) do + pid + |> Registers.control_register2() + |> get_bit(4) + end + + def alarm_select(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register2() + |> set_bit(4, i) + + Registers.control_register2(pid, reg) + end + + @doc """ + LOAD_OUTPUT This is to load the target values for SRC_PW/SRC_TW and SRC_PTH/SRC_TTH. + + Default value: 0 + 0: Do not load OUT_P/OUT_T as target values + 1: The next values of OUT_P/OUT_T are used to set the target values for the interrupts. Note: + 1. This bit must be set at least once if ALARM_SEL=1 + 2. To reload the next OUT_P/OUT_T as the target values clear and set again. + """ + def load_output(pid) do + pid + |> Registers.control_register2() + |> get_bit(5) + end + + def load_output(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register2() + |> set_bit(4, i) + + Registers.control_register2(pid, reg) + end + + @doc """ + IPOL1 The IPOL bit selects the polarity of the interrupt signal. + + When IPOL is ‘0’ (default value) any interrupt event will signalled with a + logical ‘0'. Interrupt Polarity active high, or active low on interrupt pad + INT1. + Default value: 0 + 0: Active low + 1: Active high + """ + def interrupt1_polarity(pid) do + pid + |> Registers.control_register3() + |> get_bit(5) + end + + def interrupt1_polarity(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register3() + |> set_bit(5, i) + + Registers.control_register3(pid, reg) + end + + @doc """ + PP_OD1 This bit configures the interrupt pin to Push-Pull or in Open Drain mode. + + The default value is 0 which corresponds to Push-Pull mode. The open drain + configuration can be used for connecting multiple interrupt signals on the + same interrupt line. Push-Pull/Open Drain selection on interrupt pad INT1. + + Default value: 0 + 0: Internal Pullup + 1: Open drain + """ + def interrupt1_pp_or_od(pid) do + pid + |> Registers.control_register3() + |> get_bit(4) + end + + def interrupt1_pp_or_od(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register3() + |> set_bit(4, i) + + Registers.control_register3(pid, reg) + end + + @doc """ + IPOL2 Interrupt Polarity active high, or active low on interrupt pad INT2. + + Default value: 0 + 0: Active low + 1: Active high + """ + def interrupt2_polarity(pid) do + pid + |> Registers.control_register3() + |> get_bit(1) + end + + def interrupt2_polarity(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register3() + |> set_bit(1, i) + + Registers.control_register3(pid, reg) + end + + @doc """ + PP_OD2 Push-Pull/Open Drain selection on interrupt pad INT2. + + Default value: 0 + 0: Internal Pull-up + 1: Open drain + """ + def interrupt2_pp_or_od(pid) do + pid + |> Registers.control_register3() + |> get_bit(0) + end + + def interrupt2_pp_or_od(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register3() + |> set_bit(0, i) + + Registers.control_register3(pid, reg) + end + + @doc """ + INT_EN_DRDY Data Ready Interrupt Enable. + + Default value: 0 + 0: Data Ready interrupt disabled + 1: Data Ready interrupt enabled + """ + def interrupt_enable_data_ready(pid) do + pid + |> Registers.control_register4() + |> get_bit(7) + end + + def interrupt_enable_data_ready(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(7, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_FIFO FIFO Interrupt Enable. + + Default value: 0 + 0: FIFO interrupt disabled + 1: FIFO interrupt enabled + """ + def interrupt_enable_fifo(pid) do + pid + |> Registers.control_register4() + |> get_bit(6) + end + + def interrupt_enable_fifo(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(6, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_PW Pressure Window Interrupt Enable. + + Default value: 0 + 0: Pressure window interrupt disabled + 1: Pressure window interrupt enabled + """ + def interrupt_enable_pressure_window(pid) do + pid + |> Registers.control_register4() + |> get_bit(5) + end + + def interrupt_enable_pressure_window(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(5, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_TW Temperature Window Interrupt Enable. + + Interrupt Enable. + Default value: 0 + 0: Temperature window interrupt disabled + 1: Temperature window interrupt enabled + """ + def interrupt_enable_temperature_window(pid) do + pid + |> Registers.control_register4() + |> get_bit(4) + end + + def interrupt_enable_temperature_window(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(4, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_PTH Pressure Threshold Interrupt Enable. + + Default value: 0 + 0: Pressure Threshold interrupt disabled + 1: Pressure Threshold interrupt enabled + """ + def interrupt_enable_pressure_threshold(pid) do + pid + |> Registers.control_register4() + |> get_bit(3) + end + + def interrupt_enable_pressure_threshold(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(3, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_TTH Temperature Threshold Interrupt Enable. + + Default value: 0 + 0: Temperature Threshold interrupt disabled + 1: Temperature Threshold interrupt enabled + """ + def interrupt_enable_temperature_threshold(pid) do + pid + |> Registers.control_register4() + |> get_bit(2) + end + + def interrupt_enable_temperature_threshold(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(2, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_PCHG Pressure Change Interrupt Enable. + + Default value: 0 + 0: Pressure Change interrupt disabled + 1: Pressure Change interrupt enabled + """ + def interrupt_enable_pressure_change(pid) do + pid + |> Registers.control_register4() + |> get_bit(1) + end + + def interrupt_enable_pressure_change(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(1, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_EN_TCHG Temperature Change Interrupt Enable. + + Default value: 0 + 0: Temperature Change interrupt disabled + 1: Temperature Change interrupt enabled + """ + def interrupt_enable_temperature_change(pid) do + pid + |> Registers.control_register4() + |> get_bit(0) + end + + def interrupt_enable_temperature_change(pid, i) when i == 0 or i == 1 do + reg = + pid + |> Registers.control_register4() + |> set_bit(0, i) + + Registers.control_register4(pid, reg) + end + + @doc """ + INT_CFG_DRDY Data Ready Interrupt Pin Select. + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_data_ready(pid) do + pid + |> Registers.control_register5() + |> get_bit(7) + |> interrupt_pin_select + end + + def interrupt_pin_data_ready(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(7, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_FIFO FIFO Interrupt Pin Select. + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_fifo(pid) do + pid + |> Registers.control_register5() + |> get_bit(6) + |> interrupt_pin_select + end + + def interrupt_pin_fifo(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(6, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_PW Pressure Window Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_pressure_window(pid) do + pid + |> Registers.control_register5() + |> get_bit(5) + |> interrupt_pin_select + end + + def interrupt_pin_pressure_window(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(5, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_TW Temperature Window Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_temperature_window(pid) do + pid + |> Registers.control_register5() + |> get_bit(4) + |> interrupt_pin_select + end + + def interrupt_pin_temperature_window(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(4, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_PTH Pressure Threshold Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_pressure_threshold(pid) do + pid + |> Registers.control_register5() + |> get_bit(3) + |> interrupt_pin_select + end + + def interrupt_pin_pressure_threshold(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(3, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_TTH Temperature Threshold Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_temperature_threshold(pid) do + pid + |> Registers.control_register5() + |> get_bit(2) + |> interrupt_pin_select + end + + def interrupt_pin_temperature_threshold(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(2, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_PCHG - Pressure Change Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_pressure_change(pid) do + pid + |> Registers.control_register5() + |> get_bit(1) + |> interrupt_pin_select + end + + def interrupt_pin_pressure_change(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(1, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + INT_CFG_TCHG - Temperature Change Interrupt Pin Select + + `1` - Interrupt Pin 1 + `2` - Interrupt Pin 2 + """ + def interrupt_pin_temperature_change(pid) do + pid + |> Registers.control_register5() + |> get_bit(0) + |> interrupt_pin_select + end + + def interrupt_pin_temperature_change(pid, i) when i == 2 or i == 1 do + reg = + pid + |> Registers.control_register5() + |> set_bit(0, rem(2, i)) + + Registers.control_register5(pid, reg) + end + + @doc """ + OFF_P Pressure Offset + + Pressure user accessible offset trim value. + """ + def pressure_offset(pid) do + value = + pid + |> Registers.pressure_data_user_offset() + + value * 4 + end + + def pressure_offset(pid, value) do + pid + |> Registers.pressure_data_user_offset((value / 4) |> trunc) + end + + @doc """ + OFF_T Temperature Offset + + Temperature user accessible offset trim value. + """ + + # FIXME: These need to convert the data correctly. + def temperature_offset(pid) do + value = + pid + |> Registers.temperature_data_user_offset() + + value + end + + def temperature_offset(pid, value) do + pid + |> Registers.temperature_data_user_offset(value) + end + + @doc """ + OFF_H Altitude Offset + + Altitude user accessible offset trim value. + """ + + # FIXME: These need to convert the data correctly. + def altitude_offset(pid) do + value = + pid + |> Registers.altitude_data_user_offset() + + value + end + + def altitude_offset(pid, value) do + pid + |> Registers.altitude_data_user_offset(value) + end + + defp interrupt_pin_select(0), do: 2 + defp interrupt_pin_select(1), do: 1 + + defp fifo_read(pid) do + 1..fifo_sample_count(pid) + |> Enum.map(fn _ -> + msb = Registers.fifo_data_access(pid) + csb = Registers.fifo_data_access(pid) + lsb = Registers.fifo_data_access(pid) + msb <> csb <> lsb + end) + end + + defp get_bit(<>, bit), do: byte >>> bit &&& 1 + defp set_bit(byte, bit), do: set_bit(byte, bit, 1) + defp set_bit(<>, bit, 1), do: byte ||| 1 <<< bit + defp set_bit(byte, bit, 0), do: clear_bit(byte, bit) + defp clear_bit(<>, bit), do: byte ||| ~~~(1 <<< bit) + + # meters + defp to_altitude(<>) do + (msb <<< 24 ||| csb <<< 16 ||| lsb <<< 8) / 65536 + end + + defp to_altitude_delta(<>) do + (msb <<< 8) + csb + (lsb >>> 4) / 100 + end + + # pascals + defp to_pressure(<>) do + (msb <<< 16 ||| csb <<< 8 ||| lsb) / 64 + end + + defp to_pressure_delta(<>) do + (msb <<< 10) + (csb <<< 2) + (lsb &&& 3) + (lsb >>> 4 &&& 3) * 0.25 + end + + # ℃ + defp to_temperature(<>) do + (msb <<< 8 ||| lsb) / 256 + end + + defp to_temperature_delta(<>) do + (msb <<< 4) + (lsb >>> 4) + end + + # ℃ + defp to_raw_temperature(<>) do + (msb <<< 8) + lsb + end + + defp b(0), do: false + defp b(1), do: true +end diff --git a/lib/mpl3115a2/device.ex b/lib/mpl3115a2/device.ex new file mode 100644 index 0000000..28f3b60 --- /dev/null +++ b/lib/mpl3115a2/device.ex @@ -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 diff --git a/lib/mpl3115a2/registers.ex b/lib/mpl3115a2/registers.ex new file mode 100644 index 0000000..0808c64 --- /dev/null +++ b/lib/mpl3115a2/registers.ex @@ -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, <>, 1) + end + + defp write_register(pid, register, value) do + I2C.write(pid, <>) + end +end diff --git a/mix.exs b/mix.exs index 220d8db..405138f 100644 --- a/mix.exs +++ b/mix.exs @@ -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 "], + 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 diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..36b99ff --- /dev/null +++ b/mix.lock @@ -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"}, +}