Source code for micropython_icp10111.icp10111

# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`icp10111`
================================================================================

MicroPython Driver for the TDK ICP-10111 Barometric Pressure and Temperature sensor


* Author(s): Jose D. Montoya


"""

try:
    from typing import Tuple, Union
except ImportError:
    pass

import struct
import time
from micropython import const


__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_ICP10111.git"


_DEVICE_ID = const(0xEFC8)
_SET_OTP = const(0xC59500669C)
_GET_VALUES = const(0xC7F7)

LOW_POWER = const(0x401A)
NORMAL = const(0x48A3)
LOW_NOISE = const(0x5059)
ULTRA_LOW_NOISE = const(0x58E0)
operation_mode_values = (LOW_POWER, NORMAL, LOW_NOISE, ULTRA_LOW_NOISE)


[docs] class ICP10111: """Driver for the ICP10111 Sensor connected over I2C. :param ~machine.I2C i2c: The I2C bus the ICP10111 is connected to. :param int address: The I2C device address. Defaults to :const:`0x63` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`ICP10111` class. First you will need to import the libraries to use the sensor .. code-block:: python from machine import Pin, I2C from micropython_icp10111 import icp10111 Once this is done you can define your `machine.I2C` object and define your sensor object .. code-block:: python i2c = I2C(1, sda=Pin(2), scl=Pin(3)) icp10111 = icp10111.ICP10111(i2c) Now you have access to the attributes .. code-block:: python press, temp = icp.measurements """ def __init__(self, i2c, address: int = 0x63) -> None: self._i2c = i2c self._address = address if self._get_device_id() != 0x48: raise RuntimeError("Failed to find the ICP10111 sensor") self._sensor_constants = [] self.get_conversion_values() # Conversion constants from the datasheet self._p_pa_calib = [45000.0, 80000.0, 105000.0] self._lut_lower = 3.5 * (2**20.0) self._lut_upper = 11.5 * (2**20.0) self._quadr_factor = 1 / 16777216.0 self._offset_factor = 2048.0 self._mode = NORMAL def _get_device_id(self): """ Get the device ID """ data = bytearray(3) self._i2c.writeto(self._address, _DEVICE_ID.to_bytes(2, "big"), False) self._i2c.readfrom_into(self._address, data, True) return data[1]
[docs] def reset(self): """ Reset the sensor """ self._i2c.writeto(self._address, bytes([0x80, 0x5D]), False) time.sleep(0.1)
@staticmethod def _generate_crc(data: Union[bytearray, memoryview], initialization=0xFF) -> int: """ 8-bit CRC algorithm for checking data """ crc = initialization for byte in data: crc ^= byte for _ in range(8): if crc & 0x80: crc = (crc << 1) ^ 0x31 else: crc <<= 1 crc &= 0xFF return crc & 0xFF
[docs] def get_conversion_values(self) -> None: """ Get conversion values from the sensor memory """ self._sensor_constants = [] data = bytearray(3) self._i2c.writeto(self._address, _SET_OTP.to_bytes(5, "big"), False) for _ in range(4): self._i2c.writeto(self._address, _GET_VALUES.to_bytes(2, "big"), True) self._i2c.readfrom_into(self._address, data, True) if data[2] != self._generate_crc(memoryview(data[:2])): raise RuntimeError("OTP calibration reading did not get correct values") self._sensor_constants.append(struct.unpack("H", memoryview(data[:2]))[0])
[docs] @staticmethod def calculate_conversion_constants(raw_pa, p_lut): """ calculate temperature dependent constants """ c = ( p_lut[0] * p_lut[1] * (raw_pa[0] - raw_pa[1]) + p_lut[1] * p_lut[2] * (raw_pa[1] - raw_pa[2]) + p_lut[2] * p_lut[0] * (raw_pa[2] - raw_pa[0]) ) / ( p_lut[2] * (raw_pa[0] - raw_pa[1]) + p_lut[0] * (raw_pa[1] - raw_pa[2]) + p_lut[1] * (raw_pa[2] - raw_pa[0]) ) a = ( raw_pa[0] * p_lut[0] - raw_pa[1] * p_lut[1] - (raw_pa[1] - raw_pa[0]) * c ) / (p_lut[0] - p_lut[1]) b = (raw_pa[0] - a) * (p_lut[0] + c) return a, b, c
[docs] def get_pressure(self, raw_pressure, raw_temperature): """ Convert an output from a calibrated sensor to a pressure in Pa. """ temperature_prov = raw_temperature - 32768.0 s1 = ( self._lut_lower + float(self._sensor_constants[0] * temperature_prov * temperature_prov) * self._quadr_factor ) s2 = ( self._offset_factor * self._sensor_constants[3] + float(self._sensor_constants[1] * temperature_prov * temperature_prov) * self._quadr_factor ) s3 = ( self._lut_upper + float(self._sensor_constants[2] * temperature_prov * temperature_prov) * self._quadr_factor ) a, b, c = self.calculate_conversion_constants(self._p_pa_calib, [s1, s2, s3]) return a + b / (c + raw_pressure)
@property def measurements(self) -> Tuple[float, float]: """ Return Pressure in Pascals and Temperature in Celsius """ data = bytearray(9) self._i2c.writeto(self._address, bytes([0xC7]), False) self._i2c.writeto(self._address, self._mode.to_bytes(2, "big"), False) time.sleep(0.03) self._i2c.readfrom_into(self._address, data, False) press_raw = data[0] << 16 | data[1] << 8 | data[3] temp_raw = data[6] << 8 | data[7] press = self.get_pressure(press_raw, temp_raw) temp = -45 + (175 / 2**16.0 * temp_raw) return press, temp @property def operation_mode(self) -> str: """ Sensor operation_mode +--------------------------------------+--------------------+ | Mode | Value | +======================================+====================+ | :py:const:`icp10111.LOW_POWER` | :py:const:`0x401A` | +--------------------------------------+--------------------+ | :py:const:`icp10111.NORMAL` | :py:const:`0x48A3` | +--------------------------------------+--------------------+ | :py:const:`icp10111.LOW_NOISE` | :py:const:`0x5059` | +--------------------------------------+--------------------+ | :py:const:`icp10111.ULTRA_LOW_NOISE` | :py:const:`0x58E0` | +--------------------------------------+--------------------+ """ values = { 0x401A: "LOW_POWER", 0x48A3: "NORMAL", 0x5059: "LOW_NOISE", 0x58E0: "ULTRA_LOW_NOISE", } return values[self._mode] @operation_mode.setter def operation_mode(self, value: int) -> None: if value not in operation_mode_values: raise ValueError("Value must be a valid operation_mode setting") self._mode = value