|
| 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 | +} |
0 commit comments