Skip to content

Commit 75d129b

Browse files
committed
micropython/drivers: Move "lsm6sox" imu driver from main repo.
Signed-off-by: Jim Mussared <[email protected]>
1 parent fd84cd9 commit 75d129b

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
"""
2+
LSM6DSOX STMicro driver for MicroPython based on LSM9DS1:
3+
Source repo: https://github.com/hoihu/projects/tree/master/raspi-hat
4+
5+
The MIT License (MIT)
6+
7+
Copyright (c) 2021 Damien P. George
8+
Copyright (c) 2021-2022 Ibrahim Abdelkader <[email protected]>
9+
10+
Permission is hereby granted, free of charge, to any person obtaining a copy
11+
of this software and associated documentation files (the "Software"), to deal
12+
in the Software without restriction, including without limitation the rights
13+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
copies of the Software, and to permit persons to whom the Software is
15+
furnished to do so, subject to the following conditions:
16+
17+
The above copyright notice and this permission notice shall be included in
18+
all copies or substantial portions of the Software.
19+
20+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26+
THE SOFTWARE.
27+
28+
Basic example usage:
29+
30+
import time
31+
from lsm6dsox import LSM6DSOX
32+
33+
from machine import Pin, SPI, I2C
34+
# Init in I2C mode.
35+
lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12)))
36+
37+
# Or init in SPI mode.
38+
#lsm = LSM6DSOX(SPI(5), cs_pin=Pin(10))
39+
40+
while (True):
41+
print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.read_accel()))
42+
print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.read_gyro()))
43+
print("")
44+
time.sleep_ms(100)
45+
"""
46+
47+
import array
48+
from micropython import const
49+
50+
51+
class LSM6DSOX:
52+
_CTRL3_C = const(0x12)
53+
_CTRL1_XL = const(0x10)
54+
_CTRL8_XL = const(0x17)
55+
_CTRL9_XL = const(0x18)
56+
57+
_CTRL2_G = const(0x11)
58+
_CTRL7_G = const(0x16)
59+
60+
_OUTX_L_G = const(0x22)
61+
_OUTX_L_XL = const(0x28)
62+
_MLC_STATUS = const(0x38)
63+
64+
_DEFAULT_ADDR = const(0x6A)
65+
_WHO_AM_I_REG = const(0x0F)
66+
67+
_FUNC_CFG_ACCESS = const(0x01)
68+
_FUNC_CFG_BANK_USER = const(0)
69+
_FUNC_CFG_BANK_HUB = const(1)
70+
_FUNC_CFG_BANK_EMBED = const(2)
71+
72+
_MLC0_SRC = const(0x70)
73+
_MLC_INT1 = const(0x0D)
74+
_TAP_CFG0 = const(0x56)
75+
76+
_EMB_FUNC_EN_A = const(0x04)
77+
_EMB_FUNC_EN_B = const(0x05)
78+
79+
def __init__(
80+
self,
81+
bus,
82+
cs_pin=None,
83+
address=_DEFAULT_ADDR,
84+
gyro_odr=104,
85+
accel_odr=104,
86+
gyro_scale=2000,
87+
accel_scale=4,
88+
ucf=None,
89+
):
90+
"""Initalizes Gyro and Accelerator.
91+
accel_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz)
92+
gyro_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz)
93+
gyro_scale: (245dps, 500dps, 1000dps, 2000dps)
94+
accel_scale: (+/-2g, +/-4g, +/-8g, +-16g)
95+
ucf: MLC program to load.
96+
"""
97+
self.bus = bus
98+
self.cs_pin = cs_pin
99+
self.address = address
100+
self._use_i2c = hasattr(self.bus, "readfrom_mem")
101+
102+
if not self._use_i2c and cs_pin is None:
103+
raise ValueError("A CS pin must be provided in SPI mode")
104+
105+
# check the id of the Accelerometer/Gyro
106+
if self.__read_reg(_WHO_AM_I_REG) != 108:
107+
raise OSError("No LSM6DS device was found at address 0x%x" % (self.address))
108+
109+
# allocate scratch buffer for efficient conversions and memread op's
110+
self.scratch_int = array.array("h", [0, 0, 0])
111+
112+
SCALE_GYRO = {250: 0, 500: 1, 1000: 2, 2000: 3}
113+
SCALE_ACCEL = {2: 0, 4: 2, 8: 3, 16: 1}
114+
# XL_HM_MODE = 0 by default. G_HM_MODE = 0 by default.
115+
ODR = {
116+
0: 0x00,
117+
1.6: 0x08,
118+
3.33: 0x09,
119+
6.66: 0x0A,
120+
12.5: 0x01,
121+
26: 0x02,
122+
52: 0x03,
123+
104: 0x04,
124+
208: 0x05,
125+
416: 0x06,
126+
888: 0x07,
127+
}
128+
129+
gyro_odr = round(gyro_odr, 2)
130+
accel_odr = round(accel_odr, 2)
131+
132+
# Sanity checks
133+
if not gyro_odr in ODR:
134+
raise ValueError("Invalid sampling rate: %d" % accel_odr)
135+
if not gyro_scale in SCALE_GYRO:
136+
raise ValueError("invalid gyro scaling: %d" % gyro_scale)
137+
if not accel_odr in ODR:
138+
raise ValueError("Invalid sampling rate: %d" % accel_odr)
139+
if not accel_scale in SCALE_ACCEL:
140+
raise ValueError("invalid accelerometer scaling: %d" % accel_scale)
141+
142+
# Soft-reset the device.
143+
self.reset()
144+
145+
# Load and configure MLC if UCF file is provided
146+
if ucf != None:
147+
self.load_mlc(ucf)
148+
149+
# Set Gyroscope datarate and scale.
150+
# Note output from LPF2 second filtering stage is selected. See Figure 18.
151+
self.__write_reg(_CTRL1_XL, (ODR[accel_odr] << 4) | (SCALE_ACCEL[accel_scale] << 2) | 2)
152+
153+
# Enable LPF2 and HPF fast-settling mode, ODR/4
154+
self.__write_reg(_CTRL8_XL, 0x09)
155+
156+
# Set Gyroscope datarate and scale.
157+
self.__write_reg(_CTRL2_G, (ODR[gyro_odr] << 4) | (SCALE_GYRO[gyro_scale] << 2) | 0)
158+
159+
self.gyro_scale = 32768 / gyro_scale
160+
self.accel_scale = 32768 / accel_scale
161+
162+
def __read_reg(self, reg, size=1):
163+
if self._use_i2c:
164+
buf = self.bus.readfrom_mem(self.address, reg, size)
165+
else:
166+
try:
167+
self.cs_pin(0)
168+
self.bus.write(bytes([reg | 0x80]))
169+
buf = self.bus.read(size)
170+
finally:
171+
self.cs_pin(1)
172+
if size == 1:
173+
return int(buf[0])
174+
return [int(x) for x in buf]
175+
176+
def __write_reg(self, reg, val):
177+
if self._use_i2c:
178+
self.bus.writeto_mem(self.address, reg, bytes([val]))
179+
else:
180+
try:
181+
self.cs_pin(0)
182+
self.bus.write(bytes([reg, val]))
183+
finally:
184+
self.cs_pin(1)
185+
186+
def __read_reg_into(self, reg, buf):
187+
if self._use_i2c:
188+
self.bus.readfrom_mem_into(self.address, reg, buf)
189+
else:
190+
try:
191+
self.cs_pin(0)
192+
self.bus.write(bytes([reg | 0x80]))
193+
self.bus.readinto(buf)
194+
finally:
195+
self.cs_pin(1)
196+
197+
def reset(self):
198+
self.__write_reg(_CTRL3_C, self.__read_reg(_CTRL3_C) | 0x1)
199+
for i in range(0, 10):
200+
if (self.__read_reg(_CTRL3_C) & 0x01) == 0:
201+
return
202+
time.sleep_ms(10)
203+
raise OSError("Failed to reset LSM6DS device.")
204+
205+
def set_mem_bank(self, bank):
206+
cfg = self.__read_reg(_FUNC_CFG_ACCESS) & 0x3F
207+
self.__write_reg(_FUNC_CFG_ACCESS, cfg | (bank << 6))
208+
209+
def set_embedded_functions(self, enable, emb_ab=None):
210+
self.set_mem_bank(_FUNC_CFG_BANK_EMBED)
211+
if enable:
212+
self.__write_reg(_EMB_FUNC_EN_A, emb_ab[0])
213+
self.__write_reg(_EMB_FUNC_EN_B, emb_ab[1])
214+
else:
215+
emb_a = self.__read_reg(_EMB_FUNC_EN_A)
216+
emb_b = self.__read_reg(_EMB_FUNC_EN_B)
217+
self.__write_reg(_EMB_FUNC_EN_A, (emb_a & 0xC7))
218+
self.__write_reg(_EMB_FUNC_EN_B, (emb_b & 0xE6))
219+
emb_ab = (emb_a, emb_b)
220+
221+
self.set_mem_bank(_FUNC_CFG_BANK_USER)
222+
return emb_ab
223+
224+
def load_mlc(self, ucf):
225+
# Load MLC config from file
226+
with open(ucf, "r") as ucf_file:
227+
for l in ucf_file:
228+
if l.startswith("Ac"):
229+
v = [int(v, 16) for v in l.strip().split(" ")[1:3]]
230+
self.__write_reg(v[0], v[1])
231+
232+
emb_ab = self.set_embedded_functions(False)
233+
234+
# Disable I3C interface
235+
self.__write_reg(_CTRL9_XL, self.__read_reg(_CTRL9_XL) | 0x01)
236+
237+
# Enable Block Data Update
238+
self.__write_reg(_CTRL3_C, self.__read_reg(_CTRL3_C) | 0x40)
239+
240+
# Route signals on interrupt pin 1
241+
self.set_mem_bank(_FUNC_CFG_BANK_EMBED)
242+
self.__write_reg(_MLC_INT1, self.__read_reg(_MLC_INT1) & 0x01)
243+
self.set_mem_bank(_FUNC_CFG_BANK_USER)
244+
245+
# Configure interrupt pin mode
246+
self.__write_reg(_TAP_CFG0, self.__read_reg(_TAP_CFG0) | 0x41)
247+
248+
self.set_embedded_functions(True, emb_ab)
249+
250+
def read_mlc_output(self):
251+
buf = None
252+
if self.__read_reg(_MLC_STATUS) & 0x1:
253+
self.__read_reg(0x1A, size=12)
254+
self.set_mem_bank(_FUNC_CFG_BANK_EMBED)
255+
buf = self.__read_reg(_MLC0_SRC, 8)
256+
self.set_mem_bank(_FUNC_CFG_BANK_USER)
257+
return buf
258+
259+
def read_gyro(self):
260+
"""Returns gyroscope vector in degrees/sec."""
261+
mv = memoryview(self.scratch_int)
262+
f = self.gyro_scale
263+
self.__read_reg_into(_OUTX_L_G, mv)
264+
return (mv[0] / f, mv[1] / f, mv[2] / f)
265+
266+
def read_accel(self):
267+
"""Returns acceleration vector in gravity units (9.81m/s^2)."""
268+
mv = memoryview(self.scratch_int)
269+
f = self.accel_scale
270+
self.__read_reg_into(_OUTX_L_XL, mv)
271+
return (mv[0] / f, mv[1] / f, mv[2] / f)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# LSM6DSOX Basic Example.
2+
import time
3+
from lsm6dsox import LSM6DSOX
4+
5+
from machine import Pin, I2C
6+
7+
lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12)))
8+
# Or init in SPI mode.
9+
# lsm = LSM6DSOX(SPI(5), cs_pin=Pin(10))
10+
11+
while True:
12+
print("Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}".format(*lsm.read_accel()))
13+
print("Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}".format(*lsm.read_gyro()))
14+
print("")
15+
time.sleep_ms(100)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# LSM6DSOX IMU MLC (Machine Learning Core) Example.
2+
# Download the raw UCF file, copy to storage and reset.
3+
4+
# NOTE: The pre-trained models (UCF files) for the examples can be found here:
5+
# https://github.com/STMicroelectronics/STMems_Machine_Learning_Core/tree/master/application_examples/lsm6dsox
6+
7+
import time
8+
from lsm6dsox import LSM6DSOX
9+
from machine import Pin, I2C
10+
11+
INT_MODE = True # Run in interrupt mode.
12+
INT_FLAG = False # Set True on interrupt.
13+
14+
15+
def imu_int_handler(pin):
16+
global INT_FLAG
17+
INT_FLAG = True
18+
19+
20+
if INT_MODE == True:
21+
int_pin = Pin(24)
22+
int_pin.irq(handler=imu_int_handler, trigger=Pin.IRQ_RISING)
23+
24+
i2c = I2C(0, scl=Pin(13), sda=Pin(12))
25+
26+
# Vibration detection example
27+
UCF_FILE = "lsm6dsox_vibration_monitoring.ucf"
28+
UCF_LABELS = {0: "no vibration", 1: "low vibration", 2: "high vibration"}
29+
# NOTE: Selected data rate and scale must match the MLC data rate and scale.
30+
lsm = LSM6DSOX(i2c, gyro_odr=26, accel_odr=26, gyro_scale=2000, accel_scale=4, ucf=UCF_FILE)
31+
32+
# Head gestures example
33+
# UCF_FILE = "lsm6dsox_head_gestures.ucf"
34+
# UCF_LABELS = {0:"Nod", 1:"Shake", 2:"Stationary", 3:"Swing", 4:"Walk"}
35+
# NOTE: Selected data rate and scale must match the MLC data rate and scale.
36+
# lsm = LSM6DSOX(i2c, gyro_odr=26, accel_odr=26, gyro_scale=250, accel_scale=2, ucf=UCF_FILE)
37+
38+
print("MLC configured...")
39+
40+
while True:
41+
if INT_MODE:
42+
if INT_FLAG:
43+
INT_FLAG = False
44+
print(UCF_LABELS[lsm.read_mlc_output()[0]])
45+
else:
46+
buf = lsm.read_mlc_output()
47+
if buf != None:
48+
print(UCF_LABELS[buf[0]])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module("lsm6dsox.py", opt=3)

0 commit comments

Comments
 (0)