tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/lsm303agr/lsm303agr.go (about) 1 // Package lsm303agr implements a driver for the LSM303AGR, 2 // a 3 axis accelerometer/magnetic sensor which is included on BBC micro:bits v1.5. 3 // 4 // Datasheet: https://www.st.com/resource/en/datasheet/lsm303agr.pdf 5 package lsm303agr // import "tinygo.org/x/drivers/lsm303agr" 6 7 import ( 8 "errors" 9 "math" 10 11 "tinygo.org/x/drivers" 12 "tinygo.org/x/drivers/internal/legacy" 13 ) 14 15 // Device wraps an I2C connection to a LSM303AGR device. 16 type Device struct { 17 bus drivers.I2C 18 AccelAddress uint8 19 MagAddress uint8 20 AccelPowerMode uint8 21 AccelRange uint8 22 AccelDataRate uint8 23 MagPowerMode uint8 24 MagSystemMode uint8 25 MagDataRate uint8 26 buf [6]uint8 27 } 28 29 // Configuration for LSM303AGR device. 30 type Configuration struct { 31 AccelPowerMode uint8 32 AccelRange uint8 33 AccelDataRate uint8 34 MagPowerMode uint8 35 MagSystemMode uint8 36 MagDataRate uint8 37 } 38 39 var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor") 40 41 // New creates a new LSM303AGR connection. The I2C bus must already be configured. 42 // 43 // This function only creates the Device object, it does not touch the device. 44 func New(bus drivers.I2C) *Device { 45 return &Device{ 46 bus: bus, 47 AccelAddress: ACCEL_ADDRESS, 48 MagAddress: MAG_ADDRESS, 49 } 50 } 51 52 // Connected returns whether both sensor on LSM303AGR has been found. 53 // It does two "who am I" requests and checks the responses. 54 func (d *Device) Connected() bool { 55 data1, data2 := []byte{0}, []byte{0} 56 legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_WHO_AM_I, data1) 57 legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_WHO_AM_I, data2) 58 return data1[0] == 0x33 && data2[0] == 0x40 59 } 60 61 // Configure sets up the LSM303AGR device for communication. 62 func (d *Device) Configure(cfg Configuration) (err error) { 63 64 // Verify unit communication 65 if !d.Connected() { 66 return errNotConnected 67 } 68 69 if cfg.AccelDataRate != 0 { 70 d.AccelDataRate = cfg.AccelDataRate 71 } else { 72 d.AccelDataRate = ACCEL_DATARATE_100HZ 73 } 74 75 if cfg.AccelPowerMode != 0 { 76 d.AccelPowerMode = cfg.AccelPowerMode 77 } else { 78 d.AccelPowerMode = ACCEL_POWER_NORMAL 79 } 80 81 if cfg.AccelRange != 0 { 82 d.AccelRange = cfg.AccelRange 83 } else { 84 d.AccelRange = ACCEL_RANGE_2G 85 } 86 87 if cfg.MagPowerMode != 0 { 88 d.MagPowerMode = cfg.MagPowerMode 89 } else { 90 d.MagPowerMode = MAG_POWER_NORMAL 91 } 92 93 if cfg.MagDataRate != 0 { 94 d.MagDataRate = cfg.MagDataRate 95 } else { 96 d.MagDataRate = MAG_DATARATE_10HZ 97 } 98 99 if cfg.MagSystemMode != 0 { 100 d.MagSystemMode = cfg.MagSystemMode 101 } else { 102 d.MagSystemMode = MAG_SYSTEM_CONTINUOUS 103 } 104 105 data := d.buf[:1] 106 107 data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07) 108 err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data) 109 if err != nil { 110 return 111 } 112 113 data[0] = byte(0x80 | d.AccelRange<<4) 114 err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data) 115 if err != nil { 116 return 117 } 118 119 data[0] = byte(0xC0) 120 err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), TEMP_CFG_REG_A, data) 121 if err != nil { 122 return 123 } 124 125 // Temperature compensation is on for magnetic sensor 126 data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode) 127 err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, data) 128 if err != nil { 129 return 130 } 131 132 return nil 133 } 134 135 // ReadAcceleration reads the current acceleration from the device and returns 136 // it in µg (micro-gravity). When one of the axes is pointing straight to Earth 137 // and the sensor is not moving the returned value will be around 1000000 or 138 // -1000000. 139 func (d *Device) ReadAcceleration() (x, y, z int32, err error) { 140 data := d.buf[:6] 141 err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data) 142 if err != nil { 143 return 144 } 145 146 rangeFactor := int16(0) 147 switch d.AccelRange { 148 case ACCEL_RANGE_2G: 149 rangeFactor = 1 150 case ACCEL_RANGE_4G: 151 rangeFactor = 2 152 case ACCEL_RANGE_8G: 153 rangeFactor = 4 154 case ACCEL_RANGE_16G: 155 rangeFactor = 12 // the readings in 16G are a bit lower 156 } 157 158 x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024) 159 y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024) 160 z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024) 161 return 162 } 163 164 // ReadPitchRoll reads the current pitch and roll angles from the device and 165 // returns it in micro-degrees. When the z axis is pointing straight to Earth 166 // the returned values of pitch and roll would be zero. 167 func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) { 168 169 x, y, z, err := d.ReadAcceleration() 170 if err != nil { 171 return 172 } 173 xf, yf, zf := float64(x), float64(y), float64(z) 174 pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000) 175 roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000) 176 return 177 178 } 179 180 // ReadMagneticField reads the current magnetic field from the device and returns 181 // it in mG (milligauss). 1 mG = 0.1 µT (microtesla). 182 func (d *Device) ReadMagneticField() (x, y, z int32, err error) { 183 184 if d.MagSystemMode == MAG_SYSTEM_SINGLE { 185 cmd := d.buf[:1] 186 cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode) 187 err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd) 188 if err != nil { 189 return 190 } 191 } 192 193 data := d.buf[0:6] 194 legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data) 195 196 x = int32(int16((uint16(data[1])<<8 | uint16(data[0])))) 197 y = int32(int16((uint16(data[3])<<8 | uint16(data[2])))) 198 z = int32(int16((uint16(data[5])<<8 | uint16(data[4])))) 199 return 200 } 201 202 // ReadCompass reads the current compass heading from the device and returns 203 // it in micro-degrees. When the z axis is pointing straight to Earth and 204 // the y axis is pointing to North, the heading would be zero. 205 // 206 // However, the heading may be off due to electronic compasses would be effected 207 // by strong magnetic fields and require constant calibration. 208 func (d *Device) ReadCompass() (h int32, err error) { 209 210 x, y, _, err := d.ReadMagneticField() 211 if err != nil { 212 return 213 } 214 xf, yf := float64(x), float64(y) 215 h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000) 216 return 217 } 218 219 // ReadTemperature returns the temperature in Celsius milli degrees (°C/1000) 220 func (d *Device) ReadTemperature() (t int32, err error) { 221 222 data := d.buf[:2] 223 err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_AUTO_INC, data) 224 if err != nil { 225 return 226 } 227 228 r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C 229 t = 25000 + int32((float32(r)/8)*1000) 230 return 231 }