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  }