Skip to content

Commit f6761a9

Browse files
ysoldakdeadprogram
authored andcommitted
lsm9ds1: minimal implementation
1 parent 51c3aa2 commit f6761a9

File tree

5 files changed

+466
-0
lines changed

5 files changed

+466
-0
lines changed

examples/lsm9ds1/main.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// LSM9DS1, 9 axis Inertial Measurement Unit (IMU)
2+
package main
3+
4+
import (
5+
"fmt"
6+
"machine"
7+
"time"
8+
9+
"tinygo.org/x/drivers/lsm9ds1"
10+
)
11+
12+
const (
13+
PLOTTER = false
14+
SHOW_ACCELERATION = true
15+
SHOW_ROTATION = true
16+
SHOW_MAGNETIC_FIELD = true
17+
SHOW_TEMPERATURE = true
18+
)
19+
20+
func main() {
21+
22+
// I2C configure
23+
machine.I2C0.Configure(machine.I2CConfig{})
24+
25+
// LSM9DS1 setup
26+
device := lsm9ds1.New(machine.I2C0)
27+
err := device.Configure(lsm9ds1.Configuration{
28+
AccelRange: lsm9ds1.ACCEL_2G,
29+
AccelSampleRate: lsm9ds1.ACCEL_SR_119,
30+
GyroRange: lsm9ds1.GYRO_250DPS,
31+
GyroSampleRate: lsm9ds1.GYRO_SR_119,
32+
MagRange: lsm9ds1.MAG_4G,
33+
MagSampleRate: lsm9ds1.MAG_SR_40,
34+
})
35+
if err != nil {
36+
for {
37+
println("Failed to configure", err.Error())
38+
time.Sleep(time.Second)
39+
}
40+
}
41+
42+
for {
43+
44+
if con, err := device.Connected(); !con || err != nil {
45+
println("LSM9DS1 not connected")
46+
time.Sleep(time.Second)
47+
continue
48+
}
49+
50+
ax, ay, az, _ := device.ReadAcceleration()
51+
gx, gy, gz, _ := device.ReadRotation()
52+
mx, my, mz, _ := device.ReadMagneticField()
53+
t, _ := device.ReadTemperature()
54+
55+
if PLOTTER {
56+
printPlotter(ax, ay, az, gx, gy, gz, mx, my, mz, t)
57+
time.Sleep(time.Millisecond * 100)
58+
} else {
59+
printMonitor(ax, ay, az, gx, gy, gz, mx, my, mz, t)
60+
time.Sleep(time.Millisecond * 1000)
61+
}
62+
63+
}
64+
65+
}
66+
67+
// Arduino IDE's Serial Plotter
68+
func printPlotter(ax, ay, az, gx, gy, gz, mx, my, mz, t int32) {
69+
if SHOW_ACCELERATION {
70+
fmt.Printf("AX:%f, AY:%f, AZ:%f,", axis(ax), axis(ay), axis(az))
71+
}
72+
if SHOW_ROTATION {
73+
fmt.Printf("GX:%f, GY:%f, GZ:%f,", axis(gx), axis(gy), axis(gz))
74+
}
75+
if SHOW_MAGNETIC_FIELD {
76+
fmt.Printf("MX:%d, MY:%d, MZ:%d,", mx, my, mz)
77+
}
78+
if SHOW_TEMPERATURE {
79+
fmt.Printf("T:%f", float32(t)/1000)
80+
}
81+
println()
82+
}
83+
84+
// Any Serial Monitor
85+
func printMonitor(ax, ay, az, gx, gy, gz, mx, my, mz, t int32) {
86+
if SHOW_ACCELERATION {
87+
fmt.Printf("Acceleration (g): %f, %f, %f\r\n", axis(ax), axis(ay), axis(az))
88+
}
89+
if SHOW_ROTATION {
90+
fmt.Printf("Rotation (dps): %f, %f, %f\r\n", axis(gx), axis(gy), axis(gz))
91+
}
92+
if SHOW_MAGNETIC_FIELD {
93+
fmt.Printf("Magnetic field (nT): %d, %d, %d\r\n", mx, my, mz)
94+
}
95+
if SHOW_TEMPERATURE {
96+
fmt.Printf("Temperature C: %f\r\n", float32(t)/1000)
97+
}
98+
println()
99+
}
100+
101+
func axis(raw int32) float32 {
102+
return float32(raw) / 1000000
103+
}

lsm9ds1/lsm9ds1.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// LSM9DS1, 9 axis Inertial Measurement Unit (IMU)
2+
//
3+
// Datasheet: https://www.st.com/resource/en/datasheet/lsm6ds3.pdf
4+
//
5+
package lsm9ds1 // import "tinygo.org/x/drivers/lsm9ds1"
6+
7+
import (
8+
"errors"
9+
10+
"tinygo.org/x/drivers"
11+
)
12+
13+
type AccelRange uint8
14+
type AccelSampleRate uint8
15+
type AccelBandwidth uint8
16+
17+
type GyroRange uint8
18+
type GyroSampleRate uint8
19+
20+
type MagRange uint8
21+
type MagSampleRate uint8
22+
23+
// Device wraps connection to a LSM9DS1 device.
24+
type Device struct {
25+
bus drivers.I2C
26+
AccelAddress uint8
27+
MagAddress uint8
28+
accelMultiplier int32
29+
gyroMultiplier int32
30+
magMultiplier int32
31+
dataBufferSix []uint8
32+
dataBufferTwo []uint8
33+
}
34+
35+
// Configuration for LSM9DS1 device.
36+
type Configuration struct {
37+
AccelRange AccelRange
38+
AccelSampleRate AccelSampleRate
39+
AccelBandWidth AccelBandwidth
40+
GyroRange GyroRange
41+
GyroSampleRate GyroSampleRate
42+
MagRange MagRange
43+
MagSampleRate MagSampleRate
44+
}
45+
46+
var errNotConnected = errors.New("lsm9ds1: failed to communicate with either acel/gyro or magnet sensor")
47+
48+
// New creates a new LSM9DS1 connection. The I2C bus must already be configured.
49+
//
50+
// This function only creates the Device object, it does not touch the device.
51+
func New(bus drivers.I2C) *Device {
52+
return &Device{
53+
bus: bus,
54+
AccelAddress: ACCEL_ADDRESS,
55+
MagAddress: MAG_ADDRESS,
56+
dataBufferSix: make([]uint8, 6),
57+
dataBufferTwo: make([]uint8, 2),
58+
}
59+
}
60+
61+
// Connected returns whether both sensor on LSM9DS1 has been found.
62+
// It does two "who am I" requests and checks the responses.
63+
// In a rare case of an I2C bus issue, it can also return an error.
64+
// Case of boolean false and error nil means I2C is up,
65+
// but "who am I" responses have unexpected values.
66+
func (d *Device) Connected() (connected bool, err error) {
67+
data1, data2 := []byte{0}, []byte{0}
68+
err = d.bus.ReadRegister(d.AccelAddress, WHO_AM_I, data1)
69+
if err != nil {
70+
return false, err
71+
}
72+
err = d.bus.ReadRegister(d.MagAddress, WHO_AM_I_M, data2)
73+
if err != nil {
74+
return false, err
75+
}
76+
return data1[0] == 0x68 && data2[0] == 0x3D, nil
77+
}
78+
79+
// ReadAcceleration reads the current acceleration from the device and returns
80+
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
81+
// and the sensor is not moving the returned value will be around 1000000 or
82+
// -1000000.
83+
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
84+
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_XL, d.dataBufferSix)
85+
if err != nil {
86+
return
87+
}
88+
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.accelMultiplier
89+
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.accelMultiplier
90+
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.accelMultiplier
91+
return
92+
}
93+
94+
// ReadRotation reads the current rotation from the device and returns it in
95+
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
96+
// rotation along one axis and while doing so integrate all values over time,
97+
// you would get a value close to 360000000.
98+
func (d *Device) ReadRotation() (x, y, z int32, err error) {
99+
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_G, d.dataBufferSix)
100+
if err != nil {
101+
return
102+
}
103+
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.gyroMultiplier
104+
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.gyroMultiplier
105+
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.gyroMultiplier
106+
return
107+
}
108+
109+
// ReadMagneticField reads the current magnetic field from the device and returns
110+
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
111+
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
112+
err = d.bus.ReadRegister(uint8(d.MagAddress), OUT_X_L_M, d.dataBufferSix)
113+
if err != nil {
114+
return
115+
}
116+
x = int32(int16((int16(d.dataBufferSix[1])<<8)|int16(d.dataBufferSix[0]))) * d.magMultiplier
117+
y = int32(int16((int16(d.dataBufferSix[3])<<8)|int16(d.dataBufferSix[2]))) * d.magMultiplier
118+
z = int32(int16((int16(d.dataBufferSix[5])<<8)|int16(d.dataBufferSix[4]))) * d.magMultiplier
119+
return
120+
}
121+
122+
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
123+
func (d *Device) ReadTemperature() (t int32, err error) {
124+
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_L, d.dataBufferTwo)
125+
if err != nil {
126+
return
127+
}
128+
// From "Table 5. Temperature sensor characteristics"
129+
// temp = value/16 + 25
130+
t = 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/2
131+
return
132+
}
133+
134+
// --- end of public methods --------------------------------------------------
135+
136+
// doConfigure is called by public Configure methods after all
137+
// necessary board-specific initialisations are taken care of
138+
func (d *Device) doConfigure(cfg Configuration) (err error) {
139+
140+
// Verify unit communication
141+
if con, err := d.Connected(); !con || err != nil {
142+
return errNotConnected
143+
}
144+
145+
// Multipliers come from "Table 3. Sensor characteristics" of the datasheet * 1000
146+
switch cfg.AccelRange {
147+
case ACCEL_2G:
148+
d.accelMultiplier = 61
149+
case ACCEL_4G:
150+
d.accelMultiplier = 122
151+
case ACCEL_8G:
152+
d.accelMultiplier = 244
153+
case ACCEL_16G:
154+
d.accelMultiplier = 732
155+
}
156+
switch cfg.GyroRange {
157+
case GYRO_250DPS:
158+
d.gyroMultiplier = 8750
159+
case GYRO_500DPS:
160+
d.gyroMultiplier = 17500
161+
case GYRO_2000DPS:
162+
d.gyroMultiplier = 70000
163+
}
164+
switch cfg.MagRange {
165+
case MAG_4G:
166+
d.magMultiplier = 14
167+
case MAG_8G:
168+
d.magMultiplier = 29
169+
case MAG_12G:
170+
d.magMultiplier = 43
171+
case MAG_16G:
172+
d.magMultiplier = 58
173+
}
174+
175+
data := make([]byte, 1)
176+
177+
// Configure accelerometer
178+
// Sample rate & measurement range
179+
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
180+
err = d.bus.WriteRegister(d.AccelAddress, CTRL_REG6_XL, data)
181+
if err != nil {
182+
return
183+
}
184+
185+
// Configure gyroscope
186+
// Sample rate & measurement range
187+
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
188+
err = d.bus.WriteRegister(d.AccelAddress, CTRL_REG1_G, data)
189+
if err != nil {
190+
return
191+
}
192+
193+
// Configure magnetometer
194+
195+
// Temperature compensation enabled
196+
// High-performance mode XY axis
197+
// Sample rate
198+
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
199+
err = d.bus.WriteRegister(d.MagAddress, CTRL_REG1_M, data)
200+
if err != nil {
201+
return
202+
}
203+
204+
// Measurement range
205+
data[0] = uint8(cfg.MagRange) << 5
206+
err = d.bus.WriteRegister(d.MagAddress, CTRL_REG2_M, data)
207+
if err != nil {
208+
return
209+
}
210+
211+
// Continuous-conversion mode
212+
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
213+
data[0] = 0b00000000
214+
err = d.bus.WriteRegister(d.MagAddress, CTRL_REG3_M, data)
215+
if err != nil {
216+
return
217+
}
218+
219+
// High-performance mode Z axis
220+
data[0] = 0b00001000
221+
err = d.bus.WriteRegister(d.MagAddress, CTRL_REG4_M, data)
222+
if err != nil {
223+
return
224+
}
225+
226+
return nil
227+
}

lsm9ds1/lsm9ds1_generic.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build !nano_33_ble
2+
// +build !nano_33_ble
3+
4+
package lsm9ds1
5+
6+
// Configure sets up the device for communication.
7+
func (d *Device) Configure(cfg Configuration) error {
8+
return d.doConfigure(cfg)
9+
}

lsm9ds1/lsm9ds1_nano_33_ble.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build nano_33_ble
2+
// +build nano_33_ble
3+
4+
// Nano 33 BLE [Sense] has LSM9DS1 unit on-board.
5+
// This custom Configure function powers unit up
6+
// and enables I2C, so unit can can be accessed.
7+
package lsm9ds1
8+
9+
import (
10+
"machine"
11+
"time"
12+
)
13+
14+
// Configure sets up the device for communication.
15+
func (d *Device) Configure(cfg Configuration) error {
16+
// Following lines are Nano 33 BLE specific, they have nothing to do with sensor per se
17+
machine.LSM_PWR.Configure(machine.PinConfig{Mode: machine.PinOutput})
18+
machine.LSM_PWR.High()
19+
machine.I2C_PULLUP.Configure(machine.PinConfig{Mode: machine.PinOutput})
20+
machine.I2C_PULLUP.High()
21+
// Wait a moment
22+
time.Sleep(10 * time.Millisecond)
23+
// Common initialisation code
24+
return d.doConfigure(cfg)
25+
}

0 commit comments

Comments
 (0)