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  }