|
1 |
| -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries |
2 | 1 | # SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
|
3 | 2 | #
|
4 | 3 | # SPDX-License-Identifier: MIT
|
|
16 | 15 |
|
17 | 16 | **Hardware:**
|
18 | 17 |
|
19 |
| -.. todo:: Add links to any specific hardware product page(s), or category page(s). |
20 |
| - Use unordered list & hyperlink rST inline format: "* `Link Text <url>`_" |
| 18 | +* `Adafruit LPS28 Pressure Sensor <https://www.adafruit.com/product/6067>`_" |
21 | 19 |
|
22 | 20 | **Software and Dependencies:**
|
23 | 21 |
|
24 | 22 | * Adafruit CircuitPython firmware for the supported boards:
|
25 | 23 | https://circuitpython.org/downloads
|
26 | 24 |
|
27 |
| -.. todo:: Uncomment or remove the Bus Device and/or the Register library dependencies |
28 |
| - based on the library's use of either. |
29 |
| -
|
30 |
| -# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice |
31 |
| -# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register |
| 25 | +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice |
| 26 | +* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register |
32 | 27 | """
|
33 | 28 |
|
34 |
| -# imports |
| 29 | +import time |
| 30 | + |
| 31 | +from adafruit_bus_device.i2c_device import I2CDevice |
| 32 | +from adafruit_register.i2c_bits import RWBits |
| 33 | +from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct |
| 34 | +from adafruit_register.i2c_struct_array import StructArray |
| 35 | +from micropython import const |
| 36 | + |
| 37 | +try: |
| 38 | + import typing |
| 39 | + |
| 40 | + from busio import I2C |
| 41 | +except ImportError: |
| 42 | + pass |
35 | 43 |
|
36 | 44 | __version__ = "0.0.0+auto.0"
|
37 | 45 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LPS28.git"
|
| 46 | + |
| 47 | +_DEFAULT_ADDR = const(0x5C) |
| 48 | +_WHOAMI = const(0x0F) |
| 49 | +_WHOAMI_VAL = const(0xB4) |
| 50 | +_THS_P = const(0x0C) |
| 51 | +_CTRL_REG1 = const(0x10) |
| 52 | +_CTRL_REG2 = const(0x11) |
| 53 | +_CTRL_REG3 = const(0x12) |
| 54 | +_CTRL_REG4 = const(0x13) |
| 55 | +_IF_CTRL = const(0x0E) |
| 56 | +_INTERRUPT_CFG = const(0x0B) |
| 57 | +_FIFO_CTRL = const(0x14) |
| 58 | +_FIFO_WTM = const(0x15) |
| 59 | +_REF_P = const(0x16) |
| 60 | +_RPDS = const(0x18) |
| 61 | +_INT_SOURCE = const(0x24) |
| 62 | +_FIFO_STATUS1 = const(0x25) |
| 63 | +_FIFO_STATUS2 = const(0x26) |
| 64 | +_STATUS = const(0x27) |
| 65 | +_PRESS_OUT = const(0x28) |
| 66 | +_TEMP_OUT = const(0x2B) |
| 67 | +_FIFO_DATA_OUT_PRESS_XL = const(0x78) |
| 68 | +_FIFO_STATUS_WTM_IA = const(0x80) |
| 69 | +_FIFO_STATUS_OVR_IA = const(0x40) |
| 70 | +_FIFO_STATUS_FULL_IA = const(0x20) |
| 71 | +_STATUS_TEMP_OVERRUN = const(0x20) |
| 72 | +_STATUS_PRESS_OVERRUN = const(0x10) |
| 73 | +_STATUS_TEMP_READY = const(0x02) |
| 74 | +_STATUS_PRESS_READY = const(0x01) |
| 75 | + |
| 76 | +_ODR_MAP = { |
| 77 | + 0: const(0b0000), |
| 78 | + 1: const(0b0001), |
| 79 | + 4: const(0b0010), |
| 80 | + 10: const(0b0011), |
| 81 | + 25: const(0b0100), |
| 82 | + 50: const(0b0101), |
| 83 | + 75: const(0b0110), |
| 84 | + 100: const(0b0111), |
| 85 | + 200: const(0b1000), |
| 86 | +} |
| 87 | + |
| 88 | +_AVG_MAP = { |
| 89 | + 4: const(0b000), |
| 90 | + 8: const(0b001), |
| 91 | + 16: const(0b010), |
| 92 | + 32: const(0b011), |
| 93 | + 64: const(0b100), |
| 94 | + 128: const(0b101), |
| 95 | + 512: const(0b111), |
| 96 | +} |
| 97 | + |
| 98 | +_FIFO_MODE_MAP = { |
| 99 | + "BYPASS": const(0b000), |
| 100 | + "FIFO": const(0b001), |
| 101 | + "CONTINUOUS": const(0b010), |
| 102 | + "CONTINUOUS_TO_FIFO": const(0b011), |
| 103 | + "BYPASS_TO_CONTINUOUS": const(0b100), |
| 104 | + "CONTINUOUS_TO_BYPASS": const(0b111), |
| 105 | +} |
| 106 | + |
| 107 | + |
| 108 | +class LPS28: |
| 109 | + """Driver for the LPS28 pressure sensor.""" |
| 110 | + |
| 111 | + _ctrl_reg1 = UnaryStruct(_CTRL_REG1, "B") |
| 112 | + _ctrl_reg1_ro = ROUnaryStruct(_CTRL_REG1, "B") |
| 113 | + _chip_id = ROUnaryStruct(_WHOAMI, "B") |
| 114 | + _raw_pressure_xlsb = ROUnaryStruct(_PRESS_OUT, "B") |
| 115 | + _raw_pressure_lsb = ROUnaryStruct(_PRESS_OUT + 1, "B") |
| 116 | + _raw_pressure_msb = ROUnaryStruct(_PRESS_OUT + 2, "B") |
| 117 | + _raw_temperature = ROUnaryStruct(_TEMP_OUT, "<h") |
| 118 | + _boot = RWBits(1, _CTRL_REG2, 7) |
| 119 | + _sw_reset = RWBits(1, _CTRL_REG2, 2) |
| 120 | + _raw_fifo_pressure = ROUnaryStruct(_FIFO_DATA_OUT_PRESS_XL, ">I") |
| 121 | + _data_rate = RWBits(4, _CTRL_REG1, 3) |
| 122 | + _averaging = RWBits(3, _CTRL_REG1, 0) |
| 123 | + _int_polarity = RWBits(1, _CTRL_REG3, 3) |
| 124 | + _int_open_drain = RWBits(1, _CTRL_REG3, 1) |
| 125 | + trigger_one_shot = RWBits(1, _CTRL_REG2, 0) |
| 126 | + """Start a one-shot pressure measurement""" |
| 127 | + threshold_pressure = UnaryStruct(_THS_P, ">H") |
| 128 | + """Pressure threshold for interrupt generation (16-bit value)""" |
| 129 | + full_scale_mode = RWBits(1, _CTRL_REG2, 6) |
| 130 | + """Enable full-scale mode (False = 1260 hPa, True = 4060 hPa)""" |
| 131 | + lpf_odr9 = RWBits(1, _CTRL_REG2, 5) |
| 132 | + """Enable low-pass filter with ODR/9 cutoff""" |
| 133 | + sda_pullup = RWBits(1, _IF_CTRL, 4) |
| 134 | + """Enable I2C SDA pull-up""" |
| 135 | + int_pulldown_disable = RWBits(1, _IF_CTRL, 2) |
| 136 | + """Disable interrupt pin internal pull-down""" |
| 137 | + auto_ref_pressure = RWBits(1, _INTERRUPT_CFG, 7) |
| 138 | + """Enable automatic reference pressure update""" |
| 139 | + reset_arp = RWBits(1, _INTERRUPT_CFG, 6) |
| 140 | + """Reset automatic reference pressure""" |
| 141 | + auto_zero = RWBits(1, _INTERRUPT_CFG, 5) |
| 142 | + """Enable auto-zeroing of pressure readings""" |
| 143 | + reset_auto_zero = RWBits(1, _INTERRUPT_CFG, 4) |
| 144 | + """Reset auto-zeroing function""" |
| 145 | + pressure_high = RWBits(1, _INTERRUPT_CFG, 1) |
| 146 | + """Enable high-pressure threshold interrupt""" |
| 147 | + pressure_low = RWBits(1, _INTERRUPT_CFG, 2) |
| 148 | + """Enable low-pressure threshold interrupt""" |
| 149 | + latch_interrupt = RWBits(1, _INTERRUPT_CFG, 3) |
| 150 | + """Enable latching of interrupt events""" |
| 151 | + int_source = ROUnaryStruct(_INT_SOURCE, "B") |
| 152 | + """Interrupt source flags""" |
| 153 | + fifo_unread_samples = ROUnaryStruct(_FIFO_STATUS1, "B") |
| 154 | + """Number of unread FIFO samples (0-127)""" |
| 155 | + status = ROUnaryStruct(_STATUS, "B") |
| 156 | + """Sensor status flags (pressure/temp ready, overruns)""" |
| 157 | + fifo_status = ROUnaryStruct(_FIFO_STATUS2, "B") |
| 158 | + """FIFO status flags (full, watermark, overrun)""" |
| 159 | + fifo_stop_on_watermark = RWBits(1, _FIFO_CTRL, 3) |
| 160 | + """Stop FIFO when watermark level is reached""" |
| 161 | + _fifo_mode = RWBits(3, _FIFO_CTRL, 0) |
| 162 | + fifo_watermark = UnaryStruct(_FIFO_WTM, "B") |
| 163 | + """FIFO watermark threshold (0-127 samples)""" |
| 164 | + reference_pressure = UnaryStruct(_REF_P, ">h") |
| 165 | + """Reference pressure value (16-bit)""" |
| 166 | + pressure_offset = UnaryStruct(_RPDS, ">h") |
| 167 | + """Pressure offset adjustment (16-bit)""" |
| 168 | + data_ready_pulse = RWBits(1, _CTRL_REG4, 6) |
| 169 | + """Data-ready interrupt as a pulse""" |
| 170 | + data_ready_int = RWBits(1, _CTRL_REG4, 5) |
| 171 | + """Enable data-ready interrupt""" |
| 172 | + pressure_threshold_int = RWBits(1, _CTRL_REG4, 4) |
| 173 | + """Enable pressure threshold interrupt""" |
| 174 | + fifo_full_int = RWBits(1, _CTRL_REG4, 2) |
| 175 | + """Enable FIFO full interrupt""" |
| 176 | + fifo_watermark_int = RWBits(1, _CTRL_REG4, 1) |
| 177 | + """Enable FIFO watermark interrupt""" |
| 178 | + fifo_overrun_int = RWBits(1, _CTRL_REG4, 0) |
| 179 | + """Enable FIFO overrun interrupt""" |
| 180 | + |
| 181 | + def __init__(self, i2c_bus: I2C, address: int = _DEFAULT_ADDR) -> None: |
| 182 | + """Initialize the LPS28 sensor. |
| 183 | +
|
| 184 | + :param i2c_bus: The I2C bus instance. |
| 185 | + :param address: The I2C address of the sensor (default: 0x5C). |
| 186 | + """ |
| 187 | + self.i2c_device = I2CDevice(i2c_bus, address) |
| 188 | + |
| 189 | + if self._chip_id != _WHOAMI_VAL: |
| 190 | + raise RuntimeError("Failed to find LPS28") |
| 191 | + self.reset() |
| 192 | + self.data_rate = 200 |
| 193 | + self.averaging = 4 |
| 194 | + self.full_scale_mode = True |
| 195 | + self.interrupt_pin(True, False) |
| 196 | + self.drdy_pulse = True |
| 197 | + |
| 198 | + @property |
| 199 | + def pressure(self) -> float: |
| 200 | + """Pressure in hPa.""" |
| 201 | + raw = self._raw_pressure_msb << 16 | self._raw_pressure_lsb << 8 | self._raw_pressure_xlsb |
| 202 | + divisor = 2048.0 if self.full_scale_mode else 4096.0 |
| 203 | + return raw / divisor |
| 204 | + |
| 205 | + @property |
| 206 | + def temperature(self) -> float: |
| 207 | + """Temperature in °C.""" |
| 208 | + return self._raw_temperature / 100 |
| 209 | + |
| 210 | + def reset(self) -> None: |
| 211 | + """Perform a software reset of the sensor.""" |
| 212 | + self._sw_reset = True |
| 213 | + |
| 214 | + def reboot_memory(self) -> None: |
| 215 | + """Reboot the memory content of the sensor.""" |
| 216 | + self._boot = True |
| 217 | + |
| 218 | + @property |
| 219 | + def data_rate(self) -> int: |
| 220 | + """Output data rate in Hz. |
| 221 | +
|
| 222 | + :param value: Desired data rate in Hz. |
| 223 | + :raises ValueError: If the provided value is not a valid data rate. |
| 224 | + """ |
| 225 | + raw_value = self._data_rate |
| 226 | + for rate, bits in _ODR_MAP.items(): |
| 227 | + if bits == raw_value: |
| 228 | + return rate |
| 229 | + return raw_value |
| 230 | + |
| 231 | + @data_rate.setter |
| 232 | + def data_rate(self, value: int) -> None: |
| 233 | + if value not in _ODR_MAP: |
| 234 | + raise ValueError(f"Invalid data rate. Must be one of: {sorted(_ODR_MAP.keys())}") |
| 235 | + self._data_rate = _ODR_MAP[value] |
| 236 | + |
| 237 | + @property |
| 238 | + def averaging(self) -> int: |
| 239 | + """Number of pressure and temperature samples to average |
| 240 | +
|
| 241 | + :param value: Desired averaging factor. |
| 242 | + :raises ValueError: If the provided value is not a valid averaging setting. |
| 243 | + """ |
| 244 | + raw_value = self._averaging |
| 245 | + for rate, bits in _AVG_MAP.items(): |
| 246 | + if bits == raw_value: |
| 247 | + return rate |
| 248 | + return raw_value |
| 249 | + |
| 250 | + @averaging.setter |
| 251 | + def averaging(self, value: int) -> None: |
| 252 | + if isinstance(value, int): |
| 253 | + if value not in _AVG_MAP: |
| 254 | + raise ValueError( |
| 255 | + f"Invalid output data rate. Must be one of: {sorted(_AVG_MAP.keys())}" |
| 256 | + ) |
| 257 | + self._averaging = _AVG_MAP[value] |
| 258 | + else: |
| 259 | + self._averaging = value |
| 260 | + |
| 261 | + def interrupt_pin(self, polarity: bool, open_drain: bool) -> None: |
| 262 | + """Configure the interrupt pin settings. |
| 263 | +
|
| 264 | + :param polarity: True for active-high, False for active-low. |
| 265 | + :param open_drain: True for open-drain output, False for push-pull. |
| 266 | + """ |
| 267 | + self._int_polarity = polarity |
| 268 | + self._int_open_drain = open_drain |
| 269 | + |
| 270 | + @property |
| 271 | + def data_ready(self) -> bool: |
| 272 | + """Check if data is ready to be read. |
| 273 | +
|
| 274 | + Returns: |
| 275 | + bool: True if data is ready |
| 276 | + """ |
| 277 | + return bool(self.status & _STATUS_PRESS_READY) |
| 278 | + |
| 279 | + @property |
| 280 | + def fifo_mode(self) -> str: |
| 281 | + """FIFO mode |
| 282 | +
|
| 283 | + Available modes: |
| 284 | + 'BYPASS', 'FIFO', 'CONTINUOUS', |
| 285 | + 'CONTINUOUS_TO_FIFO', 'BYPASS_TO_CONTINUOUS', |
| 286 | + 'CONTINUOUS_TO_BYPASS' |
| 287 | +
|
| 288 | + :return: The current FIFO mode as a string. |
| 289 | + :raises ValueError: If an invalid FIFO mode is given. |
| 290 | + """ |
| 291 | + raw_value = self._fifo_mode |
| 292 | + for mode, bits in _FIFO_MODE_MAP.items(): |
| 293 | + if bits == raw_value: |
| 294 | + return mode |
| 295 | + return raw_value |
| 296 | + |
| 297 | + @fifo_mode.setter |
| 298 | + def fifo_mode(self, value: str) -> None: |
| 299 | + if isinstance(value, str): |
| 300 | + if value not in _FIFO_MODE_MAP: |
| 301 | + raise ValueError( |
| 302 | + f"Invalid FIFO mode. Must be one of: {sorted(_FIFO_MODE_MAP.keys())}" |
| 303 | + ) |
| 304 | + value = _FIFO_MODE_MAP[value] |
| 305 | + self._fifo_mode = 0b000 |
| 306 | + time.sleep(0.01) |
| 307 | + self._fifo_mode = value |
| 308 | + |
| 309 | + @property |
| 310 | + def fifo_ready(self) -> bool: |
| 311 | + """Check if FIFO watermark level is reached. |
| 312 | +
|
| 313 | + :return: True if FIFO watermark level is reached, False otherwise. |
| 314 | + """ |
| 315 | + return bool(self.fifo_status & _FIFO_STATUS_WTM_IA) |
| 316 | + |
| 317 | + @property |
| 318 | + def fifo_pressure(self) -> float: |
| 319 | + """Reads and removes the next FIFO pressure sample in hPa. |
| 320 | +
|
| 321 | + :return: Pressure value in hPa. |
| 322 | + """ |
| 323 | + buffer = bytearray(3) |
| 324 | + with self.i2c_device as i2c: |
| 325 | + i2c.write_then_readinto(bytes([_FIFO_DATA_OUT_PRESS_XL]), buffer) |
| 326 | + raw = (buffer[2] << 16) | (buffer[1] << 8) | buffer[0] |
| 327 | + if raw & 0x800000: |
| 328 | + raw -= 0x1000000 |
| 329 | + divisor = 2048.0 if self.full_scale_mode else 4096.0 |
| 330 | + return raw / divisor |
0 commit comments