mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
231 lines
7.4 KiB
Go
231 lines
7.4 KiB
Go
package bmi160
|
|
|
|
import (
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
"tinygo.org/x/drivers/internal/legacy"
|
|
"tinygo.org/x/drivers/internal/pin"
|
|
)
|
|
|
|
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
|
|
// also an I2C interface, but it is not yet supported.
|
|
type DeviceSPI struct {
|
|
// Chip select pin
|
|
csb pin.OutputFunc
|
|
|
|
buf [7]byte
|
|
|
|
// SPI bus (requires chip select to be usable).
|
|
bus drivers.SPI
|
|
configurePins func()
|
|
}
|
|
|
|
// NewSPI returns a new device driver. The pin and SPI interface are not
|
|
// touched, provide a fully configured SPI object and call Configure to start
|
|
// using this device.
|
|
func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
|
|
return &DeviceSPI{
|
|
csb: csb.Set, // chip select
|
|
bus: spi,
|
|
configurePins: func() {
|
|
legacy.ConfigurePinOut(csb)
|
|
},
|
|
}
|
|
}
|
|
|
|
// Configure configures the BMI160 for use. It configures the CSB pin and
|
|
// configures the BMI160, but it does not configure the SPI interface (it is
|
|
// assumed to be up and running).
|
|
func (d *DeviceSPI) Configure() error {
|
|
if d.configurePins == nil {
|
|
return legacy.ErrConfigBeforeInstantiated
|
|
}
|
|
d.configurePins()
|
|
d.csb.High()
|
|
// The datasheet recommends doing a register read from address 0x7F to get
|
|
// SPI communication going:
|
|
// > If CSB sees a rising edge after power-up, the BMI160 interface switches
|
|
// > to SPI until a reset or the next power-up occurs. Therefore, a CSB
|
|
// > rising edge is needed before starting the SPI communication. Hence, it
|
|
// > is recommended to perform a SPI single read access to the ADDRESS 0x7F
|
|
// > before the actual communication in order to use the SPI interface.
|
|
d.readRegister(0x7F)
|
|
|
|
// Power up the accelerometer. 0b0001_00nn is the command format, with 0b01
|
|
// indicating normal mode.
|
|
d.runCommand(0b0001_0001)
|
|
|
|
// Power up the gyroscope. 0b0001_01nn is the command format, with 0b01
|
|
// indicating normal mode.
|
|
d.runCommand(0b0001_0101)
|
|
|
|
// Wait until the device is fully initialized. Even after the command has
|
|
// finished, the gyroscope may not be fully powered on. Therefore, wait
|
|
// until we get an expected value.
|
|
// This takes 30ms or so.
|
|
for {
|
|
// Wait for the acc_pmu_status and gyr_pmu_status to both be 0b01.
|
|
if d.readRegister(reg_PMU_STATUS) == 0b0001_0100 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Connected check whether the device appears to be properly connected. It reads
|
|
// the CHIPID, which must be 0xD1 for the BMI160.
|
|
func (d *DeviceSPI) Connected() bool {
|
|
return d.readRegister(reg_CHIPID) == 0xD1
|
|
}
|
|
|
|
// Reset restores the device to the state after power up. This can be useful to
|
|
// easily disable the accelerometer and gyroscope to reduce current consumption.
|
|
func (d *DeviceSPI) Reset() error {
|
|
d.runCommand(0xB6) // softreset
|
|
return nil
|
|
}
|
|
|
|
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
|
|
func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
|
|
data := d.buf[:3]
|
|
data[0] = 0x80 | reg_TEMPERATURE_0
|
|
data[1] = 0
|
|
data[2] = 0
|
|
d.csb.Low()
|
|
err = d.bus.Tx(data, data)
|
|
d.csb.High()
|
|
if err != nil {
|
|
return
|
|
}
|
|
rawTemperature := int16(uint16(data[1]) | uint16(data[2])<<8)
|
|
// 0x0000 is 23°C
|
|
// 0x7fff is ~87°C
|
|
// We use 0x8000 instead of 0x7fff to make the formula easier. The result
|
|
// should be near identical and shouldn't affect the result too much (the
|
|
// temperature sensor has an offset of around 2°C so isn't very reliable).
|
|
// So the formula is as follows:
|
|
// 1. Scale from 0x0000..0x8000 to 0..(87-23).
|
|
// rawTemperature * (87-23) / 0x8000
|
|
// 2. Convert to centidegrees.
|
|
// rawTemperature * 1000 * (87-23) / 0x8000
|
|
// 3. Add 23°C offset.
|
|
// rawTemperature * 1000 * (87-23) / 0x8000 + 23000
|
|
// 4. Simplify.
|
|
// rawTemperature * 1000 * 64 / 0x8000 + 23000
|
|
// rawTemperature * 64000 / 0x8000 + 23000
|
|
// rawTemperature * 125 / 64 + 23000
|
|
temperature = int32(rawTemperature)*125/64 + 23000
|
|
return
|
|
}
|
|
|
|
// ReadAcceleration reads the current acceleration from the device and returns
|
|
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
|
|
// and the sensor is not moving the returned value will be around 1000000 or
|
|
// -1000000.
|
|
func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
|
data := d.buf[:7]
|
|
data[0] = 0x80 | reg_ACC_XL
|
|
for i := 1; i < len(data); i++ {
|
|
data[i] = 0
|
|
}
|
|
d.csb.Low()
|
|
err = d.bus.Tx(data, data)
|
|
d.csb.High()
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Now do two things:
|
|
// 1. merge the two values to a 16-bit number (and cast to a 32-bit integer)
|
|
// 2. scale the value to bring it in the -1000000..1000000 range.
|
|
// This is done with a trick. What we do here is essentially multiply by
|
|
// 1000000 and divide by 16384 to get the original scale, but to avoid
|
|
// overflow we do it at 1/64 of the value:
|
|
// 1000000 / 64 = 15625
|
|
// 16384 / 64 = 256
|
|
x = int32(int16(uint16(data[1])|uint16(data[2])<<8)) * 15625 / 256
|
|
y = int32(int16(uint16(data[3])|uint16(data[4])<<8)) * 15625 / 256
|
|
z = int32(int16(uint16(data[5])|uint16(data[6])<<8)) * 15625 / 256
|
|
return
|
|
}
|
|
|
|
// ReadRotation reads the current rotation from the device and returns it in
|
|
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
|
|
// rotation along one axis and while doing so integrate all values over time,
|
|
// you would get a value close to 360000000.
|
|
func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
|
|
data := d.buf[:7]
|
|
data[0] = 0x80 | reg_GYR_XL
|
|
for i := 1; i < len(data); i++ {
|
|
data[i] = 0
|
|
}
|
|
d.csb.Low()
|
|
err = d.bus.Tx(data, data)
|
|
d.csb.High()
|
|
if err != nil {
|
|
return
|
|
}
|
|
// First the value is converted from a pair of bytes to a signed 16-bit
|
|
// value and then to a signed 32-bit value to avoid integer overflow.
|
|
// Then the value is scaled to µ°/s (micro-degrees per second).
|
|
// The default is 2000°/s full scale range for -32768..32767.
|
|
// The formula works as follows (taking X as an example):
|
|
// 1. Scale from 32768 to 2000. This means that it is in °/s units.
|
|
// rawX * 2000 / 32768
|
|
// 2. Scale to µ°/s by multiplying by 1e6.
|
|
// rawX * 1e6 * 2000 / 32768
|
|
// 3. Simplify.
|
|
// rawX * 2e9 / 32768
|
|
// rawX * 1953125 / 32
|
|
rawX := int32(int16(uint16(data[1]) | uint16(data[2])<<8))
|
|
rawY := int32(int16(uint16(data[3]) | uint16(data[4])<<8))
|
|
rawZ := int32(int16(uint16(data[5]) | uint16(data[6])<<8))
|
|
x = int32(int64(rawX) * 1953125 / 32)
|
|
y = int32(int64(rawY) * 1953125 / 32)
|
|
z = int32(int64(rawZ) * 1953125 / 32)
|
|
return
|
|
}
|
|
|
|
// runCommand runs a BMI160 command through the CMD register. It waits for the
|
|
// command to complete before returning.
|
|
func (d *DeviceSPI) runCommand(command uint8) {
|
|
d.writeRegister(reg_CMD, command)
|
|
for {
|
|
response := d.readRegister(reg_CMD)
|
|
if response == 0 {
|
|
return // command was completed
|
|
}
|
|
}
|
|
}
|
|
|
|
// readRegister reads from a single BMI160 register. It should only be used for
|
|
// single register reads, not for reading multiple registers at once.
|
|
func (d *DeviceSPI) readRegister(address uint8) uint8 {
|
|
// I don't know why but it appears necessary to sleep for a bit here.
|
|
time.Sleep(time.Millisecond)
|
|
|
|
data := d.buf[:2]
|
|
data[0] = 0x80 | address
|
|
data[1] = 0
|
|
d.csb.Low()
|
|
d.bus.Tx(data, data)
|
|
d.csb.High()
|
|
return data[1]
|
|
}
|
|
|
|
// writeRegister writes a single byte BMI160 register. It should only be used
|
|
// for writing to a single register.
|
|
func (d *DeviceSPI) writeRegister(address, data uint8) {
|
|
// I don't know why but it appears necessary to sleep for a bit here.
|
|
time.Sleep(time.Millisecond)
|
|
|
|
buf := d.buf[:2]
|
|
buf[0] = address
|
|
buf[1] = data
|
|
|
|
d.csb.Low()
|
|
d.bus.Tx(buf, buf)
|
|
d.csb.High()
|
|
}
|