tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/bma42x/bma42x.go (about) 1 // Package bma42x provides a driver for the BMA421 and BMA425 accelerometer 2 // chips. 3 // 4 // Here is a reasonably good datasheet: 5 // https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf 6 // 7 // This driver was originally written for the PineTime, using the datasheet as a 8 // guide. There is an open source C driver provided by Bosch, but unfortunately 9 // it needs some small modifications to work with other chips (most importantly, 10 // the "config file"). 11 // The InfiniTime and Wasp-OS drivers for this accelerometer have also been used 12 // to figure out some driver details (especially step counting). 13 package bma42x 14 15 import ( 16 _ "embed" 17 "errors" 18 "reflect" 19 "time" 20 "unsafe" 21 22 "tinygo.org/x/drivers" 23 ) 24 25 // Driver for BMA421 and BMA425: 26 // BMA421: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf 27 // BMA425: https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf 28 29 // This is the BMA421 firmware from the Wasp-OS project. 30 // It is identical to the so-called BMA423 firmware in InfiniTime, which I 31 // suspect to be actually a BMA421 firmware. I don't know where this firmware 32 // comes from or what the licensing status is. 33 // It has the FEATURES_IN command prepended, so that it can be written directly 34 // using I2C.Tx. 35 // Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma421.h 36 // 37 //go:embed bma421-config-waspos.bin 38 var bma421Firmware string 39 40 // Same as the BMA421 firmware, but for the BMA425. 41 // Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma425.h 42 // 43 //go:embed bma425-config-waspos.bin 44 var bma425Firmware string 45 46 var ( 47 errUnknownDevice = errors.New("bma42x: unknown device") 48 errUnsupportedDevice = errors.New("bma42x: device not part of config") 49 errConfigMismatch = errors.New("bma42x: config mismatch") 50 errTimeout = errors.New("bma42x: timeout") 51 errInitFailed = errors.New("bma42x: failed to initialize") 52 ) 53 54 const Address = 0x18 // BMA421/BMA425 address 55 56 type DeviceType uint8 57 58 const ( 59 DeviceBMA421 DeviceType = 1 << iota 60 DeviceBMA425 61 62 AnyDevice = DeviceBMA421 | DeviceBMA425 63 noDevice DeviceType = 0 64 ) 65 66 // Features to enable while configuring the accelerometer. 67 type Features uint8 68 69 const ( 70 FeatureStepCounting = 1 << iota 71 ) 72 73 type Config struct { 74 // Which devices to support (OR the device types together as needed). 75 Device DeviceType 76 77 // Which features to enable. With Features == 0, only the accelerometer will 78 // be enabled. 79 Features Features 80 } 81 82 type Device struct { 83 bus drivers.I2C 84 address uint8 85 accelData [6]byte 86 combinedTempSteps [5]uint8 // [0:3] steps, [4] temperature 87 dataBuf [2]byte 88 } 89 90 func NewI2C(i2c drivers.I2C, address uint8) *Device { 91 return &Device{ 92 bus: i2c, 93 address: address, 94 } 95 } 96 97 func (d *Device) Connected() bool { 98 val, err := d.read1(_CHIP_ID) 99 return err == nil && identifyChip(val) != noDevice 100 } 101 102 func (d *Device) Configure(config Config) error { 103 if config.Device == 0 { 104 config.Device = AnyDevice 105 } 106 107 // Check chip ID, to check the connection and to determine which BMA42x 108 // device we're dealing with. 109 chipID, err := d.read1(_CHIP_ID) 110 if err != nil { 111 return err 112 } 113 114 // Determine which firmware (config file?) we'll be using. 115 // There is an extra check for the device before using the given firmware. 116 // This check will typically be optimized away if the given device is not 117 // configured, so that the firmware (which is 6kB in size!) won't be linked 118 // into the binary. 119 var firmware string 120 switch identifyChip(chipID) { 121 case DeviceBMA421: 122 if config.Device&DeviceBMA421 == 0 { 123 return errUnsupportedDevice 124 } 125 firmware = bma421Firmware 126 case DeviceBMA425: 127 if config.Device&DeviceBMA425 == 0 { 128 return errUnsupportedDevice 129 } 130 firmware = bma425Firmware 131 default: 132 return errUnknownDevice 133 } 134 135 // Reset the chip, to be able to initialize it properly. 136 // The datasheet says a delay is needed after a SoftReset, but it doesn't 137 // say how long this delay should be. The bma423 driver however uses a 200ms 138 // delay, so that's what we'll be using. 139 err = d.write1(_CMD, cmdSoftReset) 140 if err != nil { 141 return err 142 } 143 time.Sleep(200 * time.Millisecond) 144 145 // Disable power saving. 146 err = d.write1(_PWR_CONF, 0x00) 147 if err != nil { 148 return err 149 } 150 time.Sleep(450 * time.Microsecond) 151 152 // Start initialization (because the datasheet says so). 153 err = d.write1(_INIT_CTRL, 0x00) 154 if err != nil { 155 return err 156 } 157 158 // Write "config file" (actually a firmware, I think) to the chip. 159 // To do this, unsafely cast the string to a byte slice to avoid putting it 160 // in RAM. This is safe in this case because Tx won't write to the 'w' 161 // slice. 162 err = d.bus.Tx(uint16(d.address), unsafeStringToSlice(firmware), nil) 163 if err != nil { 164 return err 165 } 166 167 // Read the config data back. 168 // We don't do that, as it slows down configuration and it probably isn't 169 // _really_ necessary with a reasonably stable I2C bus. 170 if false { 171 data := make([]byte, len(firmware)-1) 172 err = d.readn(_FEATURES_IN, data) 173 if err != nil { 174 return err 175 } 176 for i, c := range data { 177 if firmware[i+1] != c { 178 return errConfigMismatch 179 } 180 } 181 } 182 183 // Enable sensors. 184 err = d.write1(_INIT_CTRL, 0x01) 185 if err != nil { 186 return err 187 } 188 189 // Wait until the device is initialized. 190 start := time.Now() 191 status := uint8(0) // busy 192 for status == 0 { 193 status, err = d.read1(_INTERNAL_STATUS) 194 if err != nil { 195 return err // I2C bus error. 196 } 197 if status > 1 { 198 // Expected either 0 ("not_init") or 1 ("init_ok"). 199 return errInitFailed 200 } 201 if time.Since(start) >= 150*time.Millisecond { 202 // The datasheet says initialization should not take longer than 203 return errTimeout 204 } 205 // Don't bother the chip all the time while it's initializing. 206 time.Sleep(50 * time.Microsecond) 207 } 208 209 if config.Features&FeatureStepCounting != 0 { 210 // Enable step counter. 211 // TODO: support step counter parameters. 212 var buf [71]byte 213 buf[0] = _FEATURES_IN // prefix buf with the command 214 data := buf[1:] 215 err = d.readn(_FEATURES_IN, data) 216 if err != nil { 217 return err 218 } 219 data[0x3A+1] |= 0x10 // enable step counting by setting a magical bit 220 err = d.bus.Tx(uint16(d.address), buf[:], nil) 221 if err != nil { 222 return err 223 } 224 } 225 226 // Enable the accelerometer. 227 err = d.write1(_PWR_CTRL, 0x04) 228 if err != nil { 229 return err 230 } 231 232 // Configure accelerometer for low power usage: 233 // acc_perf_mode=0 (power saving enabled) 234 // acc_bwp=osr4_avg1 (no averaging) 235 // acc_odr=50Hz (50Hz sampling interval, enough for the step counter) 236 const accelConf = 0x00<<7 | 0x00<<4 | 0x07<<0 237 err = d.write1(_ACC_CONF, accelConf) 238 if err != nil { 239 return err 240 } 241 242 // Reduce current consumption. 243 // With power saving enabled (and the above ACC_CONF) the chip consumes only 244 // 14µA. 245 err = d.write1(_PWR_CONF, 0x03) 246 if err != nil { 247 return err 248 } 249 250 return nil 251 } 252 253 func (d *Device) Update(which drivers.Measurement) error { 254 // TODO: combine temperature and step counter into a single read. 255 if which&drivers.Temperature != 0 { 256 val, err := d.read1(_TEMPERATURE) 257 if err != nil { 258 return err 259 } 260 d.combinedTempSteps[4] = val 261 } 262 if which&drivers.Acceleration != 0 { 263 // The acceleration data is stored in DATA8 through DATA13 as 3 12-bit 264 // values. 265 err := d.readn(_DATA_8, d.accelData[:]) // ACC_X(LSB) 266 if err != nil { 267 return err 268 } 269 err = d.readn(_STEP_COUNTER_0, d.combinedTempSteps[:4]) 270 if err != nil { 271 return err 272 } 273 } 274 return nil 275 } 276 277 // Temperature returns the last read temperature in celsius milli degrees (1°C 278 // is 1000). 279 func (d *Device) Temperature() int32 { 280 // The temperature value is a two's complement number (meaning: signed) in 281 // units of 1 kelvin, with 0 being 23°C. 282 return (int32(int8(d.combinedTempSteps[4])) + 23) * 1000 283 } 284 285 // Acceleration returns the last read acceleration in µg (micro-gravity). 286 // When one of the axes is pointing straight to Earth and the sensor is not 287 // moving the returned value will be around 1000000 or -1000000. 288 func (d *Device) Acceleration() (x, y, z int32) { 289 // Combine raw data from d.accelData (stored as 12-bit signed values) into a 290 // number (0..4095): 291 x = int32(d.accelData[0])>>4 | int32(d.accelData[1])<<4 292 y = int32(d.accelData[2])>>4 | int32(d.accelData[3])<<4 293 z = int32(d.accelData[4])>>4 | int32(d.accelData[5])<<4 294 // Sign extend this number to -2048..2047: 295 x = (x << 20) >> 20 296 y = (y << 20) >> 20 297 z = (z << 20) >> 20 298 // Scale from -512..511 to -1000_000..998_046. 299 // Or, at the maximum range (4g), from -2048..2047 to -2000_000..3998_046. 300 // The formula derived as follows (where 512 is the expected value at 1g): 301 // x = x * 1000_000 / 512 302 // x = x * (1000_000/64) / (512/64) 303 // x = x * 15625 / 8 304 x = x * 15625 / 8 305 y = y * 15625 / 8 306 z = z * 15625 / 8 307 return 308 } 309 310 // Steps returns the number of steps counted since the BMA42x sensor was 311 // initialized. 312 func (d *Device) Steps() (steps uint32) { 313 steps |= uint32(d.combinedTempSteps[0]) << 0 314 steps |= uint32(d.combinedTempSteps[1]) << 8 315 steps |= uint32(d.combinedTempSteps[2]) << 16 316 steps |= uint32(d.combinedTempSteps[3]) << 24 317 return 318 } 319 320 func (d *Device) read1(register uint8) (uint8, error) { 321 d.dataBuf[0] = register 322 err := d.bus.Tx(uint16(d.address), d.dataBuf[:1], d.dataBuf[1:2]) 323 return d.dataBuf[1], err 324 } 325 326 func (d *Device) readn(register uint8, data []byte) error { 327 d.dataBuf[0] = register 328 return d.bus.Tx(uint16(d.address), d.dataBuf[:1], data) 329 } 330 331 func (d *Device) write1(register uint8, data uint8) error { 332 d.dataBuf[0] = register 333 d.dataBuf[1] = data 334 return d.bus.Tx(uint16(d.address), d.dataBuf[:2], nil) 335 } 336 337 func unsafeStringToSlice(s string) []byte { 338 // TODO: use unsafe.Slice(unsafe.StringData(...)) once we require Go 1.20. 339 sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 340 return unsafe.Slice((*byte)(unsafe.Pointer(sh.Data)), len(s)) 341 } 342 343 func identifyChip(chipID uint8) DeviceType { 344 switch chipID { 345 case 0x11: 346 return DeviceBMA421 347 case 0x13: 348 return DeviceBMA425 349 default: 350 return noDevice 351 } 352 }