tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/bmi160/bmi160.go (about) 1 package bmi160 2 3 import ( 4 "machine" 5 "time" 6 7 "tinygo.org/x/drivers" 8 ) 9 10 // DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is 11 // also an I2C interface, but it is not yet supported. 12 type DeviceSPI struct { 13 // Chip select pin 14 CSB machine.Pin 15 16 buf [7]byte 17 18 // SPI bus (requires chip select to be usable). 19 Bus drivers.SPI 20 } 21 22 // NewSPI returns a new device driver. The pin and SPI interface are not 23 // touched, provide a fully configured SPI object and call Configure to start 24 // using this device. 25 func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI { 26 return &DeviceSPI{ 27 CSB: csb, // chip select 28 Bus: spi, 29 } 30 } 31 32 // Configure configures the BMI160 for use. It configures the CSB pin and 33 // configures the BMI160, but it does not configure the SPI interface (it is 34 // assumed to be up and running). 35 func (d *DeviceSPI) Configure() error { 36 d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput}) 37 d.CSB.High() 38 39 // The datasheet recommends doing a register read from address 0x7F to get 40 // SPI communication going: 41 // > If CSB sees a rising edge after power-up, the BMI160 interface switches 42 // > to SPI until a reset or the next power-up occurs. Therefore, a CSB 43 // > rising edge is needed before starting the SPI communication. Hence, it 44 // > is recommended to perform a SPI single read access to the ADDRESS 0x7F 45 // > before the actual communication in order to use the SPI interface. 46 d.readRegister(0x7F) 47 48 // Power up the accelerometer. 0b0001_00nn is the command format, with 0b01 49 // indicating normal mode. 50 d.runCommand(0b0001_0001) 51 52 // Power up the gyroscope. 0b0001_01nn is the command format, with 0b01 53 // indicating normal mode. 54 d.runCommand(0b0001_0101) 55 56 // Wait until the device is fully initialized. Even after the command has 57 // finished, the gyroscope may not be fully powered on. Therefore, wait 58 // until we get an expected value. 59 // This takes 30ms or so. 60 for { 61 // Wait for the acc_pmu_status and gyr_pmu_status to both be 0b01. 62 if d.readRegister(reg_PMU_STATUS) == 0b0001_0100 { 63 break 64 } 65 } 66 67 return nil 68 } 69 70 // Connected check whether the device appears to be properly connected. It reads 71 // the CHIPID, which must be 0xD1 for the BMI160. 72 func (d *DeviceSPI) Connected() bool { 73 return d.readRegister(reg_CHIPID) == 0xD1 74 } 75 76 // Reset restores the device to the state after power up. This can be useful to 77 // easily disable the accelerometer and gyroscope to reduce current consumption. 78 func (d *DeviceSPI) Reset() error { 79 d.runCommand(0xB6) // softreset 80 return nil 81 } 82 83 // ReadTemperature returns the temperature in celsius milli degrees (°C/1000). 84 func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) { 85 data := d.buf[:3] 86 data[0] = 0x80 | reg_TEMPERATURE_0 87 data[1] = 0 88 data[2] = 0 89 d.CSB.Low() 90 err = d.Bus.Tx(data, data) 91 d.CSB.High() 92 if err != nil { 93 return 94 } 95 rawTemperature := int16(uint16(data[1]) | uint16(data[2])<<8) 96 // 0x0000 is 23°C 97 // 0x7fff is ~87°C 98 // We use 0x8000 instead of 0x7fff to make the formula easier. The result 99 // should be near identical and shouldn't affect the result too much (the 100 // temperature sensor has an offset of around 2°C so isn't very reliable). 101 // So the formula is as follows: 102 // 1. Scale from 0x0000..0x8000 to 0..(87-23). 103 // rawTemperature * (87-23) / 0x8000 104 // 2. Convert to centidegrees. 105 // rawTemperature * 1000 * (87-23) / 0x8000 106 // 3. Add 23°C offset. 107 // rawTemperature * 1000 * (87-23) / 0x8000 + 23000 108 // 4. Simplify. 109 // rawTemperature * 1000 * 64 / 0x8000 + 23000 110 // rawTemperature * 64000 / 0x8000 + 23000 111 // rawTemperature * 125 / 64 + 23000 112 temperature = int32(rawTemperature)*125/64 + 23000 113 return 114 } 115 116 // ReadAcceleration reads the current acceleration from the device and returns 117 // it in µg (micro-gravity). When one of the axes is pointing straight to Earth 118 // and the sensor is not moving the returned value will be around 1000000 or 119 // -1000000. 120 func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) { 121 data := d.buf[:7] 122 data[0] = 0x80 | reg_ACC_XL 123 for i := 1; i < len(data); i++ { 124 data[i] = 0 125 } 126 d.CSB.Low() 127 err = d.Bus.Tx(data, data) 128 d.CSB.High() 129 if err != nil { 130 return 131 } 132 // Now do two things: 133 // 1. merge the two values to a 16-bit number (and cast to a 32-bit integer) 134 // 2. scale the value to bring it in the -1000000..1000000 range. 135 // This is done with a trick. What we do here is essentially multiply by 136 // 1000000 and divide by 16384 to get the original scale, but to avoid 137 // overflow we do it at 1/64 of the value: 138 // 1000000 / 64 = 15625 139 // 16384 / 64 = 256 140 x = int32(int16(uint16(data[1])|uint16(data[2])<<8)) * 15625 / 256 141 y = int32(int16(uint16(data[3])|uint16(data[4])<<8)) * 15625 / 256 142 z = int32(int16(uint16(data[5])|uint16(data[6])<<8)) * 15625 / 256 143 return 144 } 145 146 // ReadRotation reads the current rotation from the device and returns it in 147 // µ°/s (micro-degrees/sec). This means that if you were to do a complete 148 // rotation along one axis and while doing so integrate all values over time, 149 // you would get a value close to 360000000. 150 func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) { 151 data := d.buf[:7] 152 data[0] = 0x80 | reg_GYR_XL 153 for i := 1; i < len(data); i++ { 154 data[i] = 0 155 } 156 d.CSB.Low() 157 err = d.Bus.Tx(data, data) 158 d.CSB.High() 159 if err != nil { 160 return 161 } 162 // First the value is converted from a pair of bytes to a signed 16-bit 163 // value and then to a signed 32-bit value to avoid integer overflow. 164 // Then the value is scaled to µ°/s (micro-degrees per second). 165 // The default is 2000°/s full scale range for -32768..32767. 166 // The formula works as follows (taking X as an example): 167 // 1. Scale from 32768 to 2000. This means that it is in °/s units. 168 // rawX * 2000 / 32768 169 // 2. Scale to µ°/s by multiplying by 1e6. 170 // rawX * 1e6 * 2000 / 32768 171 // 3. Simplify. 172 // rawX * 2e9 / 32768 173 // rawX * 1953125 / 32 174 rawX := int32(int16(uint16(data[1]) | uint16(data[2])<<8)) 175 rawY := int32(int16(uint16(data[3]) | uint16(data[4])<<8)) 176 rawZ := int32(int16(uint16(data[5]) | uint16(data[6])<<8)) 177 x = int32(int64(rawX) * 1953125 / 32) 178 y = int32(int64(rawY) * 1953125 / 32) 179 z = int32(int64(rawZ) * 1953125 / 32) 180 return 181 } 182 183 // runCommand runs a BMI160 command through the CMD register. It waits for the 184 // command to complete before returning. 185 func (d *DeviceSPI) runCommand(command uint8) { 186 d.writeRegister(reg_CMD, command) 187 for { 188 response := d.readRegister(reg_CMD) 189 if response == 0 { 190 return // command was completed 191 } 192 } 193 } 194 195 // readRegister reads from a single BMI160 register. It should only be used for 196 // single register reads, not for reading multiple registers at once. 197 func (d *DeviceSPI) readRegister(address uint8) uint8 { 198 // I don't know why but it appears necessary to sleep for a bit here. 199 time.Sleep(time.Millisecond) 200 201 data := d.buf[:2] 202 data[0] = 0x80 | address 203 data[1] = 0 204 d.CSB.Low() 205 d.Bus.Tx(data, data) 206 d.CSB.High() 207 return data[1] 208 } 209 210 // writeRegister writes a single byte BMI160 register. It should only be used 211 // for writing to a single register. 212 func (d *DeviceSPI) writeRegister(address, data uint8) { 213 // I don't know why but it appears necessary to sleep for a bit here. 214 time.Sleep(time.Millisecond) 215 216 buf := d.buf[:2] 217 buf[0] = address 218 buf[1] = data 219 220 d.CSB.Low() 221 d.Bus.Tx(buf, buf) 222 d.CSB.High() 223 }