periph 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- periph/__init__.py +1 -0
- periph/chips/accelerometer/adxl345.py +524 -0
- periph/chips/accelerometer/adxl362.py +704 -0
- periph/chips/adc_dac/ad7705.py +476 -0
- periph/chips/adc_dac/ad7706.py +495 -0
- periph/chips/adc_dac/hx710a.py +187 -0
- periph/chips/adc_dac/hx710b.py +189 -0
- periph/chips/adc_dac/hx711.py +170 -0
- periph/chips/adc_dac/mcp4725.py +167 -0
- periph/chips/adc_dac/mcp4728.py +317 -0
- periph/chips/adc_dac/pcf8591.py +199 -0
- periph/chips/comms/mcp2515.py +651 -0
- periph/chips/comms/rda5807m.py +420 -0
- periph/chips/comms/rfm9x.py +594 -0
- periph/chips/display/pcf8576.py +223 -0
- periph/chips/environmental/aht21.py +150 -0
- periph/chips/environmental/bme280.py +384 -0
- periph/chips/environmental/bme680.py +501 -0
- periph/chips/gas/ens160.py +273 -0
- periph/chips/gnss/neo6.py +439 -0
- periph/chips/gyroscope/l3g4200d.py +448 -0
- periph/chips/gyroscope/l3gd20h.py +337 -0
- periph/chips/humidity/dht11.py +123 -0
- periph/chips/imu/mpu6050.py +251 -0
- periph/chips/imu/mpu9250.py +378 -0
- periph/chips/imu/mpu9255.py +434 -0
- periph/chips/io_expander/mcp23017.py +494 -0
- periph/chips/io_expander/pcf8574.py +401 -0
- periph/chips/io_expander/pcf8575.py +420 -0
- periph/chips/io_expander/tpic6b595.py +271 -0
- periph/chips/led/_color.py +23 -0
- periph/chips/led/_neopixel_rgb_base.py +175 -0
- periph/chips/led/_neopixel_rgbw_base.py +193 -0
- periph/chips/led/apa102.py +200 -0
- periph/chips/led/sk6812rgbw.py +52 -0
- periph/chips/led/ws2812b.py +46 -0
- periph/chips/led/ws2814.py +52 -0
- periph/chips/light/apds9960.py +496 -0
- periph/chips/light/apds_9930.py +364 -0
- periph/chips/magnetometer/as5600.py +290 -0
- periph/chips/magnetometer/hmc5883l.py +210 -0
- periph/chips/memory/_24aa02uid.py +175 -0
- periph/chips/motor/drv8830.py +236 -0
- periph/chips/other/mpr121.py +465 -0
- periph/chips/power/ade7953.py +1196 -0
- periph/chips/power/ina219.py +195 -0
- periph/chips/power/ina226.py +217 -0
- periph/chips/power/ina3221.py +359 -0
- periph/chips/pressure/bmp085.py +234 -0
- periph/chips/pressure/bmp180.py +234 -0
- periph/chips/pressure/bmp280.py +292 -0
- periph/chips/pressure/bmp384.py +471 -0
- periph/chips/pressure/bmp581.py +561 -0
- periph/chips/pressure/lps22df.py +394 -0
- periph/chips/pressure/lps28dfw.py +337 -0
- periph/chips/pressure/lps33hw.py +385 -0
- periph/chips/rfid/mfrc522.py +818 -0
- periph/chips/rtc/ds3231.py +522 -0
- periph/chips/rtc/pcf8523.py +549 -0
- periph/chips/temperature/mcp9808.py +453 -0
- periph/chips/temperature/tmp117.py +461 -0
- periph/chips/tof/_vl53_base.py +174 -0
- periph/chips/tof/vl53l0x.py +816 -0
- periph/chips/tof/vl53l1x.py +815 -0
- periph/connection/__init__.py +31 -0
- periph/connection/base.py +103 -0
- periph/connection/dhtxx_auto.py +52 -0
- periph/connection/dhtxx_circuitpython.py +138 -0
- periph/connection/dhtxx_linux.py +230 -0
- periph/connection/dhtxx_micropython.py +133 -0
- periph/connection/dhtxx_mock.py +34 -0
- periph/connection/hx711_auto.py +66 -0
- periph/connection/hx711_circuitpython.py +114 -0
- periph/connection/hx711_linux.py +121 -0
- periph/connection/hx711_micropython.py +113 -0
- periph/connection/hx711_mock.py +43 -0
- periph/connection/i2c_auto.py +54 -0
- periph/connection/i2c_circuitpython.py +84 -0
- periph/connection/i2c_linux.py +81 -0
- periph/connection/i2c_micropython.py +65 -0
- periph/connection/i2c_mock.py +57 -0
- periph/connection/input_pin.py +259 -0
- periph/connection/neo6_mock.py +36 -0
- periph/connection/neopixel_auto.py +75 -0
- periph/connection/neopixel_circuitpython.py +44 -0
- periph/connection/neopixel_linux.py +47 -0
- periph/connection/neopixel_micropython.py +38 -0
- periph/connection/output_pin.py +81 -0
- periph/connection/sipo_circuitpython.py +119 -0
- periph/connection/sipo_linux.py +149 -0
- periph/connection/sipo_micropython.py +104 -0
- periph/connection/smbus_auto.py +68 -0
- periph/connection/smbus_circuitpython.py +118 -0
- periph/connection/smbus_linux.py +119 -0
- periph/connection/smbus_micropython.py +100 -0
- periph/connection/spi_auto.py +79 -0
- periph/connection/spi_circuitpython.py +97 -0
- periph/connection/spi_linux.py +66 -0
- periph/connection/spi_micropython.py +64 -0
- periph/connection/spi_mock.py +96 -0
- periph/connection/uart_auto.py +76 -0
- periph/connection/uart_circuitpython.py +81 -0
- periph/connection/uart_linux.py +120 -0
- periph/connection/uart_micropython.py +81 -0
- periph-1.2.1.dist-info/METADATA +113 -0
- periph-1.2.1.dist-info/RECORD +108 -0
- periph-1.2.1.dist-info/WHEEL +5 -0
- periph-1.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""TMP117 — ±0.1°C high-accuracy, low-power digital temperature sensor (Texas Instruments).
|
|
2
|
+
|
|
3
|
+
NIST-traceable 16-bit temperature sensor (0.0078125°C per LSB) read over an
|
|
4
|
+
I2C/SMBus-compatible two-wire bus. Offers continuous, one-shot, and shutdown
|
|
5
|
+
conversion modes with selectable averaging and cycle time, a window-alert /
|
|
6
|
+
latching-Therm / Data-Ready ALERT output, and EEPROM persistence of its
|
|
7
|
+
configuration, limit, and offset registers plus general-purpose scratch
|
|
8
|
+
storage. Four selectable addresses (0x48-0x4B) via the 4-level ADD0 strap. The
|
|
9
|
+
same driver file is used on MicroPython, CircuitPython, and Linux hosts.
|
|
10
|
+
|
|
11
|
+
Registers are 16-bit, big-endian, addressed through a non-incrementing
|
|
12
|
+
Register Pointer.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
connection: Configured I2C connection pointing at the device
|
|
16
|
+
(0x48-0x4B, per the board's ADD0 strapping).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import threading as _threading
|
|
21
|
+
_LINUX = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
_LINUX = False
|
|
24
|
+
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
I2C_ADDRESS = 0x48
|
|
29
|
+
|
|
30
|
+
DEVICE_ID = 0x117
|
|
31
|
+
|
|
32
|
+
# Interrupt sources — CONFIGURATION's HIGH_Alert / LOW_Alert flags.
|
|
33
|
+
SOURCE_HIGH = 0x01
|
|
34
|
+
SOURCE_LOW = 0x02
|
|
35
|
+
|
|
36
|
+
# Conversion cycle times in seconds, indexed by CONV[2:0] (no-averaging column).
|
|
37
|
+
_CYCLES = (0.0155, 0.125, 0.25, 0.5, 1.0, 4.0, 8.0, 16.0)
|
|
38
|
+
|
|
39
|
+
# Averaging counts, indexed by AVG[1:0].
|
|
40
|
+
_AVERAGINGS = (0, 8, 32, 64)
|
|
41
|
+
|
|
42
|
+
# Conversion modes, indexed by MOD[1:0] (0b10 reads back as continuous).
|
|
43
|
+
_MODES = ('continuous', 'shutdown', 'continuous', 'one_shot')
|
|
44
|
+
|
|
45
|
+
# EEPROM scratch slot -> register address.
|
|
46
|
+
_SCRATCH_REGS = {1: 0x05, 2: 0x06, 3: 0x08}
|
|
47
|
+
|
|
48
|
+
_LSB_C = 0.0078125
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _decode_temperature(raw16):
|
|
52
|
+
raw = raw16 - 0x10000 if raw16 & 0x8000 else raw16
|
|
53
|
+
return raw * _LSB_C
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _encode_temperature(celsius):
|
|
57
|
+
steps = celsius / _LSB_C
|
|
58
|
+
steps = int(steps + 0.5) if steps >= 0 else -int(-steps + 0.5)
|
|
59
|
+
if steps < -32768:
|
|
60
|
+
steps = -32768
|
|
61
|
+
elif steps > 32767:
|
|
62
|
+
steps = 32767
|
|
63
|
+
return steps & 0xFFFF
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TMP117Minimal:
|
|
67
|
+
"""TMP117 ±0.1°C high-accuracy digital temperature sensor — minimal interface.
|
|
68
|
+
|
|
69
|
+
Reads temperature. No register writes at construction: the POR/EEPROM
|
|
70
|
+
default (continuous conversion, 8-conversion averaging, 1 s cycle, Alert
|
|
71
|
+
mode) already serves the primary use case.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
connection: Configured I2C connection pointing at the device.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If the DEVICE_ID register does not identify a TMP117.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_REG_TEMP_RESULT = 0x00
|
|
81
|
+
_REG_CONFIG = 0x01
|
|
82
|
+
_REG_THIGH = 0x02
|
|
83
|
+
_REG_TLOW = 0x03
|
|
84
|
+
_REG_EEPROM_UL = 0x04
|
|
85
|
+
_REG_TEMP_OFFSET = 0x07
|
|
86
|
+
_REG_DEVICE_ID = 0x0F
|
|
87
|
+
|
|
88
|
+
def __init__(self, connection):
|
|
89
|
+
self._connection = connection
|
|
90
|
+
did = self._read_reg(self._REG_DEVICE_ID) & 0x0FFF
|
|
91
|
+
if did != DEVICE_ID:
|
|
92
|
+
raise ValueError('TMP117 not found: expected DEVICE_ID 0x{:03X}, got 0x{:03X}'.format(
|
|
93
|
+
DEVICE_ID, did))
|
|
94
|
+
|
|
95
|
+
def _read_reg(self, reg):
|
|
96
|
+
data = self._connection.write_read(bytes([reg]), 2)
|
|
97
|
+
return (data[0] << 8) | data[1]
|
|
98
|
+
|
|
99
|
+
def _write_reg(self, reg, value):
|
|
100
|
+
self._connection.write(bytes([reg, (value >> 8) & 0xFF, value & 0xFF]))
|
|
101
|
+
|
|
102
|
+
def read_temperature(self):
|
|
103
|
+
"""Read the temperature.
|
|
104
|
+
|
|
105
|
+
Decodes TEMP_RESULT's 16-bit two's-complement value (0.0078125°C per
|
|
106
|
+
LSB). Returns -256.0 until the first conversion after power-up
|
|
107
|
+
completes.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
float: Temperature in °C.
|
|
111
|
+
"""
|
|
112
|
+
return _decode_temperature(self._read_reg(self._REG_TEMP_RESULT))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TMP117Full(TMP117Minimal):
|
|
116
|
+
"""TMP117 full interface — extends Minimal with conversion mode, averaging,
|
|
117
|
+
and cycle-time control, one-shot triggering, both temperature limits, the
|
|
118
|
+
calibration offset, soft reset, EEPROM persistence and scratch storage,
|
|
119
|
+
and the Level-2 Alert/interrupt API.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
connection: Configured I2C connection pointing at the device.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
ValueError: If the DEVICE_ID register does not identify a TMP117.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# CONFIGURATION (0x01) bits.
|
|
129
|
+
_CFG_HIGH_ALERT = 0x8000
|
|
130
|
+
_CFG_LOW_ALERT = 0x4000
|
|
131
|
+
_CFG_DATA_READY = 0x2000
|
|
132
|
+
_CFG_EEPROM_BUSY = 0x1000
|
|
133
|
+
_CFG_MOD_SHIFT = 10
|
|
134
|
+
_CFG_MOD_MASK = 0x0C00
|
|
135
|
+
_CFG_CONV_SHIFT = 7
|
|
136
|
+
_CFG_CONV_MASK = 0x0380
|
|
137
|
+
_CFG_AVG_SHIFT = 5
|
|
138
|
+
_CFG_AVG_MASK = 0x0060
|
|
139
|
+
_CFG_TNA = 0x0010
|
|
140
|
+
_CFG_POL = 0x0008
|
|
141
|
+
_CFG_DR_ALERT = 0x0004
|
|
142
|
+
_CFG_SOFT_RESET = 0x0002
|
|
143
|
+
# Writable bits: MOD/CONV/AVG/T-nA/POL/DR-Alert. Soft_Reset is set only on purpose.
|
|
144
|
+
_CFG_WRITE_MASK = 0x0FFC
|
|
145
|
+
|
|
146
|
+
_MOD_ONE_SHOT = 0x03
|
|
147
|
+
|
|
148
|
+
# EEPROM_UL (0x04) bits.
|
|
149
|
+
_EUN = 0x8000
|
|
150
|
+
_EEPROM_BUSY = 0x4000
|
|
151
|
+
|
|
152
|
+
def __init__(self, connection):
|
|
153
|
+
super().__init__(connection)
|
|
154
|
+
self._callback = None
|
|
155
|
+
self._int_pin_used = None
|
|
156
|
+
self._poll_stop = False
|
|
157
|
+
self._poll_thread = None
|
|
158
|
+
self._last_status = 0
|
|
159
|
+
|
|
160
|
+
def _read_config(self):
|
|
161
|
+
return self._read_reg(self._REG_CONFIG) & self._CFG_WRITE_MASK
|
|
162
|
+
|
|
163
|
+
def _write_config(self, value):
|
|
164
|
+
self._write_reg(self._REG_CONFIG, value & self._CFG_WRITE_MASK)
|
|
165
|
+
|
|
166
|
+
# --- Conversion -------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def configure(self, mode='continuous', averaging=8, cycle_seconds=1.0):
|
|
169
|
+
"""Set conversion mode, averaging, and cycle time.
|
|
170
|
+
|
|
171
|
+
The cycle time is matched to the nearest CONV[2:0] step from the
|
|
172
|
+
no-averaging column (15.5 ms, 125 ms, 250 ms, 500 ms, 1 s, 4 s, 8 s,
|
|
173
|
+
16 s); at higher averaging the hardware lengthens short cycles
|
|
174
|
+
automatically. The Alert configuration bits are preserved.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
mode: 'continuous', 'shutdown', or 'one_shot'.
|
|
178
|
+
averaging: Conversions averaged per result: 0, 8, 32, or 64.
|
|
179
|
+
cycle_seconds: Desired conversion cycle time in s.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError: If mode or averaging is not one of the listed values.
|
|
183
|
+
"""
|
|
184
|
+
if mode == 'continuous':
|
|
185
|
+
mod = 0x00
|
|
186
|
+
elif mode == 'shutdown':
|
|
187
|
+
mod = 0x01
|
|
188
|
+
elif mode == 'one_shot':
|
|
189
|
+
mod = self._MOD_ONE_SHOT
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError("mode must be 'continuous', 'shutdown', or 'one_shot'")
|
|
192
|
+
if averaging not in _AVERAGINGS:
|
|
193
|
+
raise ValueError('averaging must be one of 0, 8, 32, 64')
|
|
194
|
+
avg = _AVERAGINGS.index(averaging)
|
|
195
|
+
conv = 0
|
|
196
|
+
for code, cycle in enumerate(_CYCLES):
|
|
197
|
+
if abs(cycle - cycle_seconds) < abs(_CYCLES[conv] - cycle_seconds):
|
|
198
|
+
conv = code
|
|
199
|
+
config = self._read_config() & ~(self._CFG_MOD_MASK | self._CFG_CONV_MASK | self._CFG_AVG_MASK)
|
|
200
|
+
config |= (mod << self._CFG_MOD_SHIFT) | (conv << self._CFG_CONV_SHIFT) | (avg << self._CFG_AVG_SHIFT)
|
|
201
|
+
self._write_config(config)
|
|
202
|
+
|
|
203
|
+
def get_config(self):
|
|
204
|
+
"""Read the conversion mode, averaging, and cycle time.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
tuple: (mode: str, averaging: int, cycle_seconds: float), where
|
|
208
|
+
cycle_seconds is the no-averaging CONV[2:0] step.
|
|
209
|
+
"""
|
|
210
|
+
config = self._read_reg(self._REG_CONFIG)
|
|
211
|
+
mode = _MODES[(config & self._CFG_MOD_MASK) >> self._CFG_MOD_SHIFT]
|
|
212
|
+
averaging = _AVERAGINGS[(config & self._CFG_AVG_MASK) >> self._CFG_AVG_SHIFT]
|
|
213
|
+
cycle = _CYCLES[(config & self._CFG_CONV_MASK) >> self._CFG_CONV_SHIFT]
|
|
214
|
+
return (mode, averaging, cycle)
|
|
215
|
+
|
|
216
|
+
def is_shutdown(self):
|
|
217
|
+
"""Report whether the sensor is in Shutdown mode.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
bool: True if MOD[1:0] is Shutdown.
|
|
221
|
+
"""
|
|
222
|
+
return (self._read_reg(self._REG_CONFIG) & self._CFG_MOD_MASK) >> self._CFG_MOD_SHIFT == 0x01
|
|
223
|
+
|
|
224
|
+
def trigger_one_shot(self):
|
|
225
|
+
"""Start a single conversion (MOD[1:0]=One-Shot).
|
|
226
|
+
|
|
227
|
+
The sensor returns to Shutdown once the conversion (including
|
|
228
|
+
averaging) completes.
|
|
229
|
+
"""
|
|
230
|
+
config = self._read_config() & ~self._CFG_MOD_MASK
|
|
231
|
+
self._write_config(config | (self._MOD_ONE_SHOT << self._CFG_MOD_SHIFT))
|
|
232
|
+
|
|
233
|
+
def is_data_ready(self):
|
|
234
|
+
"""Report whether a fresh conversion result is available.
|
|
235
|
+
|
|
236
|
+
Reading this flag clears it (as does reading TEMP_RESULT).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
bool: True if Data_Ready is set.
|
|
240
|
+
"""
|
|
241
|
+
return bool(self._read_reg(self._REG_CONFIG) & self._CFG_DATA_READY)
|
|
242
|
+
|
|
243
|
+
# --- Limits and offset ------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def get_high_limit(self):
|
|
246
|
+
"""Read THIGH_LIMIT.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
float: High limit in °C.
|
|
250
|
+
"""
|
|
251
|
+
return _decode_temperature(self._read_reg(self._REG_THIGH))
|
|
252
|
+
|
|
253
|
+
def set_high_limit(self, celsius):
|
|
254
|
+
"""Write THIGH_LIMIT, rounded to the nearest 0.0078125°C.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
celsius: High limit in °C (-256.0 to 255.9921875, clamped).
|
|
258
|
+
"""
|
|
259
|
+
self._write_reg(self._REG_THIGH, _encode_temperature(celsius))
|
|
260
|
+
|
|
261
|
+
def get_low_limit(self):
|
|
262
|
+
"""Read TLOW_LIMIT.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
float: Low limit in °C.
|
|
266
|
+
"""
|
|
267
|
+
return _decode_temperature(self._read_reg(self._REG_TLOW))
|
|
268
|
+
|
|
269
|
+
def set_low_limit(self, celsius):
|
|
270
|
+
"""Write TLOW_LIMIT, rounded to the nearest 0.0078125°C.
|
|
271
|
+
|
|
272
|
+
In Therm mode this is HIGH_Alert's reset threshold (hysteresis).
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
celsius: Low limit in °C (-256.0 to 255.9921875, clamped).
|
|
276
|
+
"""
|
|
277
|
+
self._write_reg(self._REG_TLOW, _encode_temperature(celsius))
|
|
278
|
+
|
|
279
|
+
def get_temperature_offset(self):
|
|
280
|
+
"""Read TEMP_OFFSET.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
float: Calibration offset in °C.
|
|
284
|
+
"""
|
|
285
|
+
return _decode_temperature(self._read_reg(self._REG_TEMP_OFFSET))
|
|
286
|
+
|
|
287
|
+
def set_temperature_offset(self, celsius):
|
|
288
|
+
"""Write TEMP_OFFSET, added to every result after linearization.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
celsius: Calibration offset in °C (-256.0 to 255.9921875, clamped).
|
|
292
|
+
"""
|
|
293
|
+
self._write_reg(self._REG_TEMP_OFFSET, _encode_temperature(celsius))
|
|
294
|
+
|
|
295
|
+
# --- Reset ------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
def reset(self):
|
|
298
|
+
"""Software reset (Soft_Reset=1), then wait the 2 ms reset time.
|
|
299
|
+
|
|
300
|
+
Reloads CONFIGURATION, THIGH_LIMIT, TLOW_LIMIT, and TEMP_OFFSET from
|
|
301
|
+
EEPROM.
|
|
302
|
+
"""
|
|
303
|
+
self._write_reg(self._REG_CONFIG, self._CFG_SOFT_RESET)
|
|
304
|
+
time.sleep(0.002)
|
|
305
|
+
|
|
306
|
+
# --- EEPROM -----------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
def unlock_eeprom(self):
|
|
309
|
+
"""Unlock the EEPROM (EUN=1).
|
|
310
|
+
|
|
311
|
+
While unlocked, writes to CONFIGURATION, THIGH_LIMIT, TLOW_LIMIT,
|
|
312
|
+
TEMP_OFFSET, and EEPROM2 also program the EEPROM as the new power-on
|
|
313
|
+
default. Poll is_eeprom_busy() after each such write.
|
|
314
|
+
"""
|
|
315
|
+
self._write_reg(self._REG_EEPROM_UL, self._EUN)
|
|
316
|
+
|
|
317
|
+
def lock_eeprom(self):
|
|
318
|
+
"""Lock the EEPROM (EUN=0); register writes become volatile only."""
|
|
319
|
+
self._write_reg(self._REG_EEPROM_UL, 0x0000)
|
|
320
|
+
|
|
321
|
+
def is_eeprom_busy(self):
|
|
322
|
+
"""Report whether an EEPROM programming operation is in progress.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
bool: True if EEPROM_Busy is set.
|
|
326
|
+
"""
|
|
327
|
+
return bool(self._read_reg(self._REG_EEPROM_UL) & self._EEPROM_BUSY)
|
|
328
|
+
|
|
329
|
+
def read_eeprom_scratch(self, slot):
|
|
330
|
+
"""Read a general-purpose EEPROM scratch register.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
slot: 1 (EEPROM1), 2 (EEPROM2), or 3 (EEPROM3). Slots 1 and 3
|
|
334
|
+
hold factory NIST-traceability data.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
int: 16-bit register value.
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
ValueError: If slot is not 1, 2, or 3.
|
|
341
|
+
"""
|
|
342
|
+
if slot not in _SCRATCH_REGS:
|
|
343
|
+
raise ValueError('slot must be 1, 2, or 3')
|
|
344
|
+
return self._read_reg(_SCRATCH_REGS[slot])
|
|
345
|
+
|
|
346
|
+
def write_eeprom_scratch(self, slot, value):
|
|
347
|
+
"""Write the general-purpose EEPROM2 scratch register.
|
|
348
|
+
|
|
349
|
+
Only slot 2 is writable — EEPROM1/EEPROM3 hold factory
|
|
350
|
+
NIST-traceability data. Persists across power cycles only while the
|
|
351
|
+
EEPROM is unlocked.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
slot: Must be 2.
|
|
355
|
+
value: 16-bit value.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
ValueError: If slot is not 2.
|
|
359
|
+
"""
|
|
360
|
+
if slot != 2:
|
|
361
|
+
raise ValueError('only EEPROM scratch slot 2 is writable')
|
|
362
|
+
self._write_reg(_SCRATCH_REGS[2], value & 0xFFFF)
|
|
363
|
+
|
|
364
|
+
# --- Alert output -----------------------------------------------------
|
|
365
|
+
|
|
366
|
+
def configure_alert(self, mode='alert', polarity='active_low', pin_function='alert'):
|
|
367
|
+
"""Configure the ALERT output's mode, polarity, and pin function.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
mode: 'alert' (window alert) or 'therm' (latching thermostat;
|
|
371
|
+
TLOW_LIMIT becomes the reset threshold).
|
|
372
|
+
polarity: 'active_low' (needs pull-up) or 'active_high'.
|
|
373
|
+
pin_function: 'alert' (alert/Therm status) or 'data_ready'.
|
|
374
|
+
|
|
375
|
+
Raises:
|
|
376
|
+
ValueError: If an argument is not one of the listed values.
|
|
377
|
+
"""
|
|
378
|
+
if mode not in ('alert', 'therm'):
|
|
379
|
+
raise ValueError("mode must be 'alert' or 'therm'")
|
|
380
|
+
if polarity not in ('active_low', 'active_high'):
|
|
381
|
+
raise ValueError("polarity must be 'active_low' or 'active_high'")
|
|
382
|
+
if pin_function not in ('alert', 'data_ready'):
|
|
383
|
+
raise ValueError("pin_function must be 'alert' or 'data_ready'")
|
|
384
|
+
config = self._read_config() & ~(self._CFG_TNA | self._CFG_POL | self._CFG_DR_ALERT)
|
|
385
|
+
if mode == 'therm':
|
|
386
|
+
config |= self._CFG_TNA
|
|
387
|
+
if polarity == 'active_high':
|
|
388
|
+
config |= self._CFG_POL
|
|
389
|
+
if pin_function == 'data_ready':
|
|
390
|
+
config |= self._CFG_DR_ALERT
|
|
391
|
+
self._write_config(config)
|
|
392
|
+
|
|
393
|
+
# --- Interrupt API ----------------------------------------------------
|
|
394
|
+
|
|
395
|
+
def poll_interrupt(self):
|
|
396
|
+
"""Read CONFIGURATION's HIGH_Alert / LOW_Alert flags.
|
|
397
|
+
|
|
398
|
+
In Alert mode this read also clears both flags (a hardware side
|
|
399
|
+
effect). In Therm mode HIGH_Alert clears only once the temperature
|
|
400
|
+
drops below TLOW_LIMIT.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
int: Mask of SOURCE_HIGH / SOURCE_LOW.
|
|
404
|
+
"""
|
|
405
|
+
config = self._read_reg(self._REG_CONFIG)
|
|
406
|
+
status = 0
|
|
407
|
+
if config & self._CFG_HIGH_ALERT:
|
|
408
|
+
status |= SOURCE_HIGH
|
|
409
|
+
if config & self._CFG_LOW_ALERT:
|
|
410
|
+
status |= SOURCE_LOW
|
|
411
|
+
return status
|
|
412
|
+
|
|
413
|
+
def on_interrupt(self, callback, int_pin=None):
|
|
414
|
+
"""Subscribe to ALERT events.
|
|
415
|
+
|
|
416
|
+
Delivery: if int_pin is given, it is wired directly. Otherwise falls
|
|
417
|
+
back to connection.int_pin, or a 5 ms polling thread on Linux if
|
|
418
|
+
neither is available. With a pin, the callback runs on every ALERT
|
|
419
|
+
edge (the edge direction follows the configured polarity). The
|
|
420
|
+
polling fallback calls it whenever the status mask changes.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
callback: Callable(status: int) — the poll_interrupt() mask;
|
|
424
|
+
test it against the SOURCE_* constants.
|
|
425
|
+
int_pin: Optional InputPin for this call, overriding connection.int_pin.
|
|
426
|
+
"""
|
|
427
|
+
self._callback = callback
|
|
428
|
+
pin = int_pin if int_pin is not None else getattr(self._connection, 'int_pin', None)
|
|
429
|
+
self._int_pin_used = pin
|
|
430
|
+
if pin is not None:
|
|
431
|
+
from periph.connection.input_pin import InputPin
|
|
432
|
+
active_high = self._read_reg(self._REG_CONFIG) & self._CFG_POL
|
|
433
|
+
pin.on_edge(self._int_handler, InputPin.RISING if active_high else InputPin.FALLING)
|
|
434
|
+
elif _LINUX:
|
|
435
|
+
self._last_status = self.poll_interrupt()
|
|
436
|
+
self._poll_stop = False
|
|
437
|
+
self._poll_thread = _threading.Thread(target=self._poll_loop, daemon=True)
|
|
438
|
+
self._poll_thread.start()
|
|
439
|
+
|
|
440
|
+
def off_interrupt(self):
|
|
441
|
+
"""Unsubscribe and stop delivery."""
|
|
442
|
+
if self._int_pin_used is not None:
|
|
443
|
+
self._int_pin_used.off_edge(self._int_handler)
|
|
444
|
+
self._int_pin_used = None
|
|
445
|
+
elif _LINUX:
|
|
446
|
+
self._poll_stop = True
|
|
447
|
+
self._callback = None
|
|
448
|
+
|
|
449
|
+
def _int_handler(self):
|
|
450
|
+
status = self.poll_interrupt()
|
|
451
|
+
if self._callback:
|
|
452
|
+
self._callback(status)
|
|
453
|
+
|
|
454
|
+
def _poll_loop(self):
|
|
455
|
+
while not self._poll_stop:
|
|
456
|
+
status = self.poll_interrupt()
|
|
457
|
+
if status != self._last_status:
|
|
458
|
+
self._last_status = status
|
|
459
|
+
if self._callback:
|
|
460
|
+
self._callback(status)
|
|
461
|
+
time.sleep(0.005)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Shared base for ST's VL53 FlightSense Time-of-Flight ranging family.
|
|
2
|
+
|
|
3
|
+
Internal module — not a public driver. VL53L0X and VL53L1X have different
|
|
4
|
+
register maps (8-bit vs 16-bit register index), so this base holds no
|
|
5
|
+
register addresses and no ranging logic, only the plumbing both chips share:
|
|
6
|
+
big-endian register access with a 1- or 2-byte index, the bounded poll
|
|
7
|
+
helper, the XSHUT boot wait, the lock that serializes multi-register
|
|
8
|
+
sequences against the interrupt poller, interrupt delivery (int_pin edge or
|
|
9
|
+
Linux polling thread), the volatile re-addressing helper, and the family's
|
|
10
|
+
shared constants. See specs/tof/_vl53_base.md.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import threading as _threading
|
|
15
|
+
_LINUX = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
_LINUX = False
|
|
18
|
+
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
I2C_ADDRESS = 0x29
|
|
23
|
+
|
|
24
|
+
TIMEOUT_MS = 500
|
|
25
|
+
BOOT_US = 1200
|
|
26
|
+
|
|
27
|
+
# Logical interrupt sources shared by the family (mutually exclusive).
|
|
28
|
+
SOURCE_LEVEL_LOW = 1
|
|
29
|
+
SOURCE_LEVEL_HIGH = 2
|
|
30
|
+
SOURCE_OUT_OF_WINDOW = 3
|
|
31
|
+
SOURCE_NEW_SAMPLE_READY = 4
|
|
32
|
+
SOURCE_IN_WINDOW = 5 # VL53L1X only
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _NoLock:
|
|
36
|
+
def __enter__(self):
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def __exit__(self, *exc):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _ticks_ms():
|
|
44
|
+
if hasattr(time, 'ticks_ms'):
|
|
45
|
+
return time.ticks_ms()
|
|
46
|
+
if hasattr(time, 'monotonic'):
|
|
47
|
+
return int(time.monotonic() * 1000)
|
|
48
|
+
return int(time.time() * 1000)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _elapsed_ms(start):
|
|
52
|
+
if hasattr(time, 'ticks_diff'):
|
|
53
|
+
return time.ticks_diff(time.ticks_ms(), start)
|
|
54
|
+
return _ticks_ms() - start
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _VL53Base:
|
|
58
|
+
"""Protected helpers shared by the VL53 chip drivers.
|
|
59
|
+
|
|
60
|
+
Never instantiated directly. Subclasses implement
|
|
61
|
+
_poll_interrupt_status() for the interrupt delivery helpers.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
connection: Configured I2C connection. Its optional en_pin drives
|
|
65
|
+
XSHUT (high = enabled), its optional int_pin receives GPIO1.
|
|
66
|
+
index_bytes: Register index width on the wire, 1 or 2.
|
|
67
|
+
chip_name: Chip name used in error messages.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, connection, index_bytes, chip_name):
|
|
71
|
+
self._connection = connection
|
|
72
|
+
self._index_bytes = index_bytes
|
|
73
|
+
self._chip_name = chip_name
|
|
74
|
+
# Serializes multi-register sequences against the interrupt polling
|
|
75
|
+
# thread (a status read/clear must not interleave with them).
|
|
76
|
+
self._lock = _threading.RLock() if _LINUX else _NoLock()
|
|
77
|
+
self._callback = None
|
|
78
|
+
self._int_pin_used = None
|
|
79
|
+
self._poll_stop = False
|
|
80
|
+
self._poll_thread = None
|
|
81
|
+
|
|
82
|
+
# --- Register access --------------------------------------------------
|
|
83
|
+
|
|
84
|
+
def _index(self, reg):
|
|
85
|
+
if self._index_bytes == 2:
|
|
86
|
+
return bytes([(reg >> 8) & 0xFF, reg & 0xFF])
|
|
87
|
+
return bytes([reg & 0xFF])
|
|
88
|
+
|
|
89
|
+
def _wr_block(self, reg, data):
|
|
90
|
+
self._connection.write(self._index(reg) + bytes(data))
|
|
91
|
+
|
|
92
|
+
def _rd_block(self, reg, n):
|
|
93
|
+
return self._connection.write_read(self._index(reg), n)
|
|
94
|
+
|
|
95
|
+
def _wr8(self, reg, value):
|
|
96
|
+
self._wr_block(reg, (value & 0xFF,))
|
|
97
|
+
|
|
98
|
+
def _rd8(self, reg):
|
|
99
|
+
return self._rd_block(reg, 1)[0]
|
|
100
|
+
|
|
101
|
+
def _wr16(self, reg, value):
|
|
102
|
+
self._wr_block(reg, ((value >> 8) & 0xFF, value & 0xFF))
|
|
103
|
+
|
|
104
|
+
def _rd16(self, reg):
|
|
105
|
+
data = self._rd_block(reg, 2)
|
|
106
|
+
return (data[0] << 8) | data[1]
|
|
107
|
+
|
|
108
|
+
def _wr32(self, reg, value):
|
|
109
|
+
self._wr_block(reg, ((value >> 24) & 0xFF, (value >> 16) & 0xFF,
|
|
110
|
+
(value >> 8) & 0xFF, value & 0xFF))
|
|
111
|
+
|
|
112
|
+
def _rd32(self, reg):
|
|
113
|
+
data = self._rd_block(reg, 4)
|
|
114
|
+
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]
|
|
115
|
+
|
|
116
|
+
# --- Polling and boot -------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def _wait_until(self, predicate, what):
|
|
119
|
+
start = _ticks_ms()
|
|
120
|
+
while True:
|
|
121
|
+
if predicate():
|
|
122
|
+
return
|
|
123
|
+
if _elapsed_ms(start) > TIMEOUT_MS:
|
|
124
|
+
raise OSError('{} timeout waiting for {}'.format(self._chip_name, what))
|
|
125
|
+
|
|
126
|
+
def _boot_wait(self):
|
|
127
|
+
if getattr(self._connection, 'en_pin', None):
|
|
128
|
+
self._connection.enable()
|
|
129
|
+
time.sleep(BOOT_US / 1000000)
|
|
130
|
+
|
|
131
|
+
# --- Re-addressing ----------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def _set_address_reg(self, reg, address):
|
|
134
|
+
if address < 0x08 or address > 0x77:
|
|
135
|
+
raise ValueError('address must be 0x08 to 0x77')
|
|
136
|
+
self._wr8(reg, address & 0x7F)
|
|
137
|
+
|
|
138
|
+
# --- Interrupt delivery -----------------------------------------------
|
|
139
|
+
|
|
140
|
+
def _poll_interrupt_status(self):
|
|
141
|
+
"""Read and clear a pending interrupt; return its SOURCE_* value or 0."""
|
|
142
|
+
raise NotImplementedError
|
|
143
|
+
|
|
144
|
+
def _subscribe(self, callback, int_pin=None):
|
|
145
|
+
self._callback = callback
|
|
146
|
+
pin = int_pin if int_pin is not None else getattr(self._connection, 'int_pin', None)
|
|
147
|
+
self._int_pin_used = pin
|
|
148
|
+
if pin is not None:
|
|
149
|
+
from periph.connection.input_pin import InputPin
|
|
150
|
+
pin.on_edge(self._int_handler, InputPin.FALLING)
|
|
151
|
+
elif _LINUX:
|
|
152
|
+
self._poll_stop = False
|
|
153
|
+
self._poll_thread = _threading.Thread(target=self._poll_loop, daemon=True)
|
|
154
|
+
self._poll_thread.start()
|
|
155
|
+
|
|
156
|
+
def _unsubscribe(self):
|
|
157
|
+
if self._int_pin_used is not None:
|
|
158
|
+
self._int_pin_used.off_edge(self._int_handler)
|
|
159
|
+
self._int_pin_used = None
|
|
160
|
+
elif _LINUX:
|
|
161
|
+
self._poll_stop = True
|
|
162
|
+
self._callback = None
|
|
163
|
+
|
|
164
|
+
def _int_handler(self):
|
|
165
|
+
status = self._poll_interrupt_status()
|
|
166
|
+
if status and self._callback:
|
|
167
|
+
self._callback(status)
|
|
168
|
+
|
|
169
|
+
def _poll_loop(self):
|
|
170
|
+
while not self._poll_stop:
|
|
171
|
+
status = self._poll_interrupt_status()
|
|
172
|
+
if status and self._callback:
|
|
173
|
+
self._callback(status)
|
|
174
|
+
time.sleep(0.005)
|