Current section
Files
Jump to
Current section
Files
lib/SHT3x.ex
defmodule SHT3x do
@moduledoc """
I2C driver for the SHT3x-DIS Humidity and Temperature Sensor from Sensirion.
## Usage
{:ok, i2c_pid} = ElixirALE.I2C.start_link("i2c-1", 0x44)
[{:ok, temp}, {:ok, humidity}] = SHT3x.single_shot_result(i2c_pid, :high, true)
"""
alias ElixirALE.I2C
@crctable {
0x00,
0x31,
0x62,
0x53,
0xC4,
0xF5,
0xA6,
0x97,
0xB9,
0x88,
0xDB,
0xEA,
0x7D,
0x4C,
0x1F,
0x2E,
0x43,
0x72,
0x21,
0x10,
0x87,
0xB6,
0xE5,
0xD4,
0xFA,
0xCB,
0x98,
0xA9,
0x3E,
0x0F,
0x5C,
0x6D,
0x86,
0xB7,
0xE4,
0xD5,
0x42,
0x73,
0x20,
0x11,
0x3F,
0x0E,
0x5D,
0x6C,
0xFB,
0xCA,
0x99,
0xA8,
0xC5,
0xF4,
0xA7,
0x96,
0x01,
0x30,
0x63,
0x52,
0x7C,
0x4D,
0x1E,
0x2F,
0xB8,
0x89,
0xDA,
0xEB,
0x3D,
0x0C,
0x5F,
0x6E,
0xF9,
0xC8,
0x9B,
0xAA,
0x84,
0xB5,
0xE6,
0xD7,
0x40,
0x71,
0x22,
0x13,
0x7E,
0x4F,
0x1C,
0x2D,
0xBA,
0x8B,
0xD8,
0xE9,
0xC7,
0xF6,
0xA5,
0x94,
0x03,
0x32,
0x61,
0x50,
0xBB,
0x8A,
0xD9,
0xE8,
0x7F,
0x4E,
0x1D,
0x2C,
0x02,
0x33,
0x60,
0x51,
0xC6,
0xF7,
0xA4,
0x95,
0xF8,
0xC9,
0x9A,
0xAB,
0x3C,
0x0D,
0x5E,
0x6F,
0x41,
0x70,
0x23,
0x12,
0x85,
0xB4,
0xE7,
0xD6,
0x7A,
0x4B,
0x18,
0x29,
0xBE,
0x8F,
0xDC,
0xED,
0xC3,
0xF2,
0xA1,
0x90,
0x07,
0x36,
0x65,
0x54,
0x39,
0x08,
0x5B,
0x6A,
0xFD,
0xCC,
0x9F,
0xAE,
0x80,
0xB1,
0xE2,
0xD3,
0x44,
0x75,
0x26,
0x17,
0xFC,
0xCD,
0x9E,
0xAF,
0x38,
0x09,
0x5A,
0x6B,
0x45,
0x74,
0x27,
0x16,
0x81,
0xB0,
0xE3,
0xD2,
0xBF,
0x8E,
0xDD,
0xEC,
0x7B,
0x4A,
0x19,
0x28,
0x06,
0x37,
0x64,
0x55,
0xC2,
0xF3,
0xA0,
0x91,
0x47,
0x76,
0x25,
0x14,
0x83,
0xB2,
0xE1,
0xD0,
0xFE,
0xCF,
0x9C,
0xAD,
0x3A,
0x0B,
0x58,
0x69,
0x04,
0x35,
0x66,
0x57,
0xC0,
0xF1,
0xA2,
0x93,
0xBD,
0x8C,
0xDF,
0xEE,
0x79,
0x48,
0x1B,
0x2A,
0xC1,
0xF0,
0xA3,
0x92,
0x05,
0x34,
0x67,
0x56,
0x78,
0x49,
0x1A,
0x2B,
0xBC,
0x8D,
0xDE,
0xEF,
0x82,
0xB3,
0xE0,
0xD1,
0x46,
0x77,
0x24,
0x15,
0x3B,
0x0A,
0x59,
0x68,
0xFF,
0xCE,
0x9D,
0xAC
}
def crctable, do: @crctable
@calc_divisor :math.pow(2, 16) - 1
@single_mode_addresses %{
# clock_stretching
true => %{
# repeatability
high: <<0x2C, 0x06>>,
medium: <<0x2C, 0x0D>>,
low: <<0x2C, 0x10>>
},
false => %{
# repeatability
high: <<0x24, 0x00>>,
medium: <<0x24, 0x0B>>,
low: <<0x24, 0x16>>
}
}
@periodic_addresses %{
# mps
0.5 => %{
# repeatability
high: <<0x20, 0x32>>,
medium: <<0x20, 0x24>>,
low: <<0x20, 0x2F>>
},
# mps
1 => %{
# repeatability
high: <<0x21, 0x30>>,
medium: <<0x21, 0x26>>,
low: <<0x21, 0x2D>>
},
# mps
2 => %{
# repeatability
high: <<0x22, 0x36>>,
medium: <<0x22, 0x20>>,
low: <<0x22, 0x2B>>
},
# mps
4 => %{
# repeatability
high: <<0x23, 0x34>>,
medium: <<0x23, 0x22>>,
low: <<0x23, 0x29>>
},
# mps
10 => %{
# repeatability
high: <<0x27, 0x37>>,
medium: <<0x27, 0x21>>,
low: <<0x27, 0x2A>>
}
}
@fetch_data_address <<0xE0, 0x00>>
@art_address <<0x2B, 0x32>>
@break_address <<0x30, 0x93>>
@soft_reset_address <<0x30, 0xA2>>
@heater_enable_address <<0x30, 0x6D>>
@heater_disable_address <<0x30, 0x66>>
@read_status_address <<0xF3, 0x2D>>
@clear_status_address <<0x30, 0x41>>
@typedoc """
16bit temperature and humidity values along with respective 8 bit CRC values.
"""
@type reading :: <<_::48>>
@typedoc """
16bit status plus 8 bit CRC.
"""
@type status :: <<_::24>>
@typedoc """
Measurement repeatability: high, medium or low.
"""
@type repeatability :: :high | :medium | :low
@typedoc """
Ordered list of results: temperature, humidity.
"""
@type result :: {:ok, float} | {:error, :crc_failed}
@typedoc """
Ordered list of results: [temperature, humidity].
"""
@type result_list :: [result]
@typedoc """
Measurements per second. One in 0.5 | 1 | 2 | 4 | 10.
"""
@type mps :: float | 1 | 2 | 4 | 10
@spec single_shot_result(pid, repeatability, boolean) :: result_list
def single_shot_result(i2c_pid, repeatability, clock_stretching) do
<<temp::size(16), temp_crc::size(8), humidity::size(16), humidity_crc::size(8)>> =
single_shot(i2c_pid, repeatability, clock_stretching)
result_list(temp, temp_crc, humidity, humidity_crc)
end
@spec fetch_data_result(pid) :: result_list
def fetch_data_result(i2c_pid) do
<<temp::size(16), temp_crc::size(8), humidity::size(16), humidity_crc::size(8)>> =
fetch_data(i2c_pid)
result_list(temp, temp_crc, humidity, humidity_crc)
end
def status_result(i2c_pid) do
raw_status = status(i2c_pid)
<<status_bytes::size(16), status_crc::size(8)>> = raw_status
if calc_crc(status_bytes) == status_crc do
<<
alert::size(1),
_::size(1),
heater_status::size(1),
_::size(1),
humidity_alert::size(1),
temp_alert::size(1),
_::size(5),
reset_detected::size(1),
_::size(2),
command_status::size(1),
checksum_status::size(1),
_::size(8)
>> = raw_status
status_map = %{
alert: alert,
heater_status: heater_status,
humidity_alert: humidity_alert,
temp_alert: temp_alert,
reset_detected: reset_detected,
command_status: command_status,
checksum_status: checksum_status
}
{:ok, status_map}
else
{:error, :crc_failed}
end
end
@spec periodic(pid, repeatability, mps) :: :ok | :error
def periodic(i2c_pid, repeatability, mps) do
address = @periodic_addresses[mps][repeatability]
I2C.write(i2c_pid, address)
end
@spec art(pid) :: :ok | :error
def art(i2c_pid) do
I2C.write(i2c_pid, @art_address)
end
@spec break(pid) :: :ok | :error
def break(i2c_pid) do
I2C.write(i2c_pid, @break_address)
end
@spec soft_reset(pid) :: :ok | :error
def soft_reset(i2c_pid) do
I2C.write(i2c_pid, @soft_reset_address)
end
@spec heater_enable(pid) :: :ok | :error
def heater_enable(i2c_pid) do
I2C.write(i2c_pid, @heater_enable_address)
end
@spec heater_disable(pid) :: :ok | :error
def heater_disable(i2c_pid) do
I2C.write(i2c_pid, @heater_disable_address)
end
@spec status(pid) :: status
def status(i2c_pid) do
I2C.write_read(i2c_pid, @read_status_address, 3)
end
@spec clear_status(pid) :: :ok | :error
def clear_status(i2c_pid) do
I2C.write(i2c_pid, @clear_status_address)
end
@spec single_shot(pid, repeatability, boolean) :: reading
def single_shot(i2c_pid, repeatability, clock_stretching) do
address = @single_mode_addresses[clock_stretching][repeatability]
I2C.write_read(i2c_pid, address, 6)
end
@spec fetch_data(pid) :: reading
def fetch_data(i2c_pid) do
I2C.write_read(i2c_pid, @fetch_data_address, 6)
end
def calc_temp(raw_temp) do
-45 + 175 * (raw_temp / @calc_divisor)
end
def calc_humidity(raw_humidity) do
100 * raw_humidity / @calc_divisor
end
def calc_crc(val) do
# http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html
# CRC-8 polynomial 0x31 init 0xff
# using precalculated crc table
# we only ever need crc on 2 bytes
<<byte1, byte2>> = <<val::size(16)>>
crc = <<0xFF>>
<<data>> = :crypto.exor(<<byte1>>, crc)
crc = <<elem(SHT3x.crctable(), data)>>
<<data>> = :crypto.exor(<<byte2>>, crc)
elem(SHT3x.crctable(), data)
end
defp result_list(temp, temp_crc, humidity, humidity_crc) do
temp =
if calc_crc(temp) == temp_crc do
{:ok, calc_temp(temp)}
else
{:error, :crc_failed}
end
humidity =
if calc_crc(humidity) == humidity_crc do
{:ok, calc_humidity(humidity)}
else
{:error, :crc_failed}
end
[temp, humidity]
end
end