forked from adafruit/Adafruit_CircuitPython_LIDARLite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathadafruit_lidarlite.py
285 lines (239 loc) · 9.47 KB
/
adafruit_lidarlite.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# SPDX-FileCopyrightText: 2018 ladyada for Adafruit Industries
# SPDX-FileCopyrightText: 2022 johnrbnsn
#
# SPDX-License-Identifier: MIT
"""
`adafruit_lidarlite`
====================================================
A CircuitPython & Python library for Garmin LIDAR Lite sensors over I2C
* Author(s): ladyada, johnrbnsn
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
# imports
import time
from adafruit_bus_device.i2c_device import I2CDevice
from digitalio import Direction
from micropython import const
try:
from typing import Optional
from microcontroller import Pin
from busio import I2C
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LIDARLite.git"
_ADDR_DEFAULT = const(0x62)
_REG_ACQ_COMMAND = const(0x00)
_REG_DIST_MEAS_V3 = const(0x8F)
_REG_DIST_MEAS_V3HP = const(0x0F)
_REG_SIG_COUNT_VAL = const(0x02)
_REG_ACQ_CONFIG_REG = const(0x04)
_REG_THRESHOLD_BYPASS = const(0x1C)
_REG_STATUS = const(0x01)
_REG_UNIT_ID_HIGH = const(0x16)
_REG_UNIT_ID_LOW = const(0x17)
_REG_SIGNAL_STRENGTH = const(0x0E)
_REG_HEALTH_STATUS_V3HP = const(0x48)
_REG_POWER_CONTROL = const(0x65)
_REG_I2C_CONFIG = const(0x1E)
_REG_TEST_COMMAND = const(0x40)
_REG_CORR_DATA = const(0x52)
_CMD_RESET = const(0x00)
_CMD_DISTANCENOBIAS = const(0x03)
_CMD_DISTANCEWITHBIAS = const(0x04)
_CMD_DISTANCE_V3HP = const(0x03)
_NUM_DIST_BYTES = 2 # How many bytes is the returned distance measurement?
TYPE_V3 = "V3"
TYPE_V3HP = "V3HP"
CONFIG_DEFAULT = 0
CONFIG_SHORTFAST = 1
CONFIG_DEFAULTFAST = 2
CONFIG_MAXRANGE = 3
CONFIG_HIGHSENSITIVE = 4
CONFIG_LOWSENSITIVE = 5
"""Status Registers"""
# v3
STATUS_BUSY = 0x01
STATUS_REF_OVERFLOW = 0x02
STATUS_SIGNAL_OVERFLOW = 0x04
STATUS_NO_PEAK = 0x08
STATUS_SECOND_RETURN = 0x10
STATUS_HEALTHY = 0x20
STATUS_SYS_ERROR = 0x40
# v3 HP
STATUS_BUSY_V3HP = 0x01
STATUS_SIGNAL_OVERFLOW_V3HP = 0x02
# The various configuration register values, from arduino library
_LIDAR_CONFIGS = (
(0x80, 0x08, 0x00), # default
(0x1D, 0x08, 0x00), # short range, high speed
(0x80, 0x00, 0x00), # default range, higher speed short range
(0xFF, 0x08, 0x00), # maximum range
(0x80, 0x08, 0x80), # high sensitivity & error
(0x80, 0x08, 0xB0),
) # low sensitivity & error
class LIDARLite:
"""
A driver for the Garmin LIDAR Lite laser distance sensor.
Initialize the hardware for the LIDAR over I2C. You can pass in an
optional reset_pin for when you call reset(). There are a few common
configurations Garmin suggests: CONFIG_DEFAULT, CONFIG_SHORTFAST,
CONFIG_DEFAULTFAST, CONFIG_MAXRANGE, CONFIG_HIGHSENSITIVE, and
CONFIG_LOWSENSITIVE. For the I2C address, the default is 0x62 but if you
pass a different number in, we'll try to change the address so multiple
LIDARs can be connected. (Note all but one need to be in reset for this
to work!)
:param i2c_bus: The `busio.I2C` object to use. This is the only
required parameter.
:param int address: (optional) The I2C address of the device to set
after initialization.
"""
def __init__(
self,
i2c_bus: I2C,
*,
reset_pin: Optional[Pin] = None,
configuration: Optional[int] = CONFIG_DEFAULT,
address: Optional[int] = _ADDR_DEFAULT,
sensor_type: Optional[str] = TYPE_V3,
) -> None:
self.i2c_device = I2CDevice(i2c_bus, address)
self._buf = bytearray(2)
self._bias_count = 0
self._reset = reset_pin
time.sleep(0.5)
self.configure(configuration)
self._status = self.status
self._sensor_type = sensor_type
def reset(self) -> None:
"""Hardware reset (if pin passed into init) or software reset. Will take
100 readings in order to 'flush' measurement unit, otherwise data is off."""
# Optional hardware reset pin
if self._reset is not None:
self._reset.direction = Direction.OUTPUT
self._reset.value = True
self._reset.value = False
time.sleep(0.01)
self._reset.value = True
else:
try:
self._write_reg(_REG_ACQ_COMMAND, _CMD_RESET)
except OSError:
print("OSError")
time.sleep(1)
# take 100 readings to 'flush' out sensor!
for _ in range(100):
try:
self.read_distance_v3(True)
except RuntimeError:
print("RuntimeError")
def configure(self, config: int) -> None:
"""Set the LIDAR desired style of measurement. There are a few common
configurations Garmin suggests: CONFIG_DEFAULT, CONFIG_SHORTFAST,
CONFIG_DEFAULTFAST, CONFIG_MAXRANGE, CONFIG_HIGHSENSITIVE, and
CONFIG_LOWSENSITIVE."""
settings = _LIDAR_CONFIGS[config]
self._write_reg(_REG_SIG_COUNT_VAL, settings[0])
self._write_reg(_REG_ACQ_CONFIG_REG, settings[1])
self._write_reg(_REG_THRESHOLD_BYPASS, settings[2])
def read_distance_v3(self, bias: Optional[bool] = False) -> int:
"""Perform a distance reading with or without 'bias'. It's recommended
to take a bias measurement every 100 non-bias readings (they're slower)"""
if bias:
self._write_reg(_REG_ACQ_COMMAND, _CMD_DISTANCEWITHBIAS)
else:
self._write_reg(_REG_ACQ_COMMAND, _CMD_DISTANCENOBIAS)
dist = self._read_reg(_REG_DIST_MEAS_V3, _NUM_DIST_BYTES)
if self._status & (STATUS_NO_PEAK | STATUS_SECOND_RETURN):
if self._status & STATUS_NO_PEAK:
raise RuntimeError("Measurement failure STATUS_NO_PEAK")
if self._status & STATUS_SECOND_RETURN:
raise RuntimeError("Measurement failure STATUS_NO_PEAK")
raise RuntimeError("Some other runtime error")
if (self._status & STATUS_SYS_ERROR) or (not self._status & STATUS_HEALTHY):
raise RuntimeError("System failure")
return dist[0] << 8 | dist[1]
def read_distance_v3hp(self) -> int:
"""Perform a distance measurement for the v3 HP sensor"""
# Any non-zero value written to _REG_ACQ_COMMAND will start a reading on v3HP, no bias vs.
# non-bias
self._write_reg(_REG_ACQ_COMMAND, _CMD_DISTANCEWITHBIAS)
dist = self._read_reg(_REG_DIST_MEAS_V3HP, _NUM_DIST_BYTES)
return dist[0] << 8 | dist[1]
@property
def correlation_data(self) -> int:
"""Reads correlation data"""
# TODO: How to translate correlation data property?
corr_data = self._read_reg(_REG_CORR_DATA, 2)
return corr_data[0] << 8 | corr_data[1]
@property
def test_command(self) -> int:
"""Reads the test command"""
return self._read_reg(_REG_TEST_COMMAND, 1)[0]
@property
def i2c_config(self) -> int:
"""Reads the I2C config"""
return self._read_reg(_REG_I2C_CONFIG, 1)[0]
@property
def power_control(self) -> int:
"""Reads the power control register"""
return self._read_reg(_REG_POWER_CONTROL, 1)[0]
@property
def health_status(self) -> int:
"""Reads health status for v3HP (not available on v3, will return -1)"""
if self._sensor_type == TYPE_V3HP:
return self._read_reg(_REG_HEALTH_STATUS_V3HP, 1)[0]
return -1
@property
def signal_strength(self) -> int:
"""Reads the signal strength of the last measurement"""
return self._read_reg(_REG_SIGNAL_STRENGTH, 1)[0]
@property
def unit_id(self) -> int:
"""Reads the serial number of the unit"""
high_byte = self._read_reg(_REG_UNIT_ID_HIGH, 1)
low_byte = self._read_reg(_REG_UNIT_ID_LOW, 1)
return high_byte[0] << 8 | low_byte[0]
@property
def distance(self) -> int: # pylint: disable=R1710
"""The measured distance in cm. Will take a bias reading every 100 calls"""
self._bias_count -= 1
if self._bias_count < 0:
self._bias_count = 100 # every 100 reads, check bias
if self._sensor_type == TYPE_V3:
return self.read_distance_v3(self._bias_count <= 0)
if self._sensor_type == TYPE_V3HP:
return self.read_distance_v3hp()
# If no sensor type has been identified, return a negative distance as an error
return -1.0
@property
def status(self) -> int:
"""The status byte, check datasheet for bitmask"""
buf = bytearray([_REG_STATUS])
with self.i2c_device as i2c:
i2c.write_then_readinto(buf, buf)
return buf[0]
def _write_reg(self, reg: int, value: int) -> None:
self._buf[0] = reg
self._buf[1] = value
with self.i2c_device as i2c:
# print("Writing: ", [hex(i) for i in self._buf])
i2c.write(self._buf)
time.sleep(0.001) # there's a delay in arduino library
def _read_reg(self, reg: int, num: int) -> bytearray:
while True:
self._status = self.status
if not self._status & STATUS_BUSY:
break
# no longer busy
self._buf[0] = reg
with self.i2c_device as i2c:
i2c.write_then_readinto(self._buf, self._buf, out_end=1, in_end=num)
# print("Read from ", hex(reg), [hex(i) for i in self._buf])
return self._buf