mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-03 06:27:47 +00:00
51b604ce97
This adjusts the API to the one proposed in https://github.com/tinygo-org/drivers/pull/345, which I think is much better than direct ReadAcceleration etc calls. I have also updated the code that converts raw acceleration values to normalized values. The new code should be faster (didn't measure) and avoids floating point math.
207 lines
5.6 KiB
Go
207 lines
5.6 KiB
Go
// Package lis3dh provides a driver for the LIS3DH digital accelerometer.
|
|
//
|
|
// Datasheet: https://www.st.com/resource/en/datasheet/lis3dh.pdf
|
|
package lis3dh // import "tinygo.org/x/drivers/lis3dh"
|
|
|
|
import (
|
|
"tinygo.org/x/drivers"
|
|
"tinygo.org/x/drivers/internal/legacy"
|
|
)
|
|
|
|
// Device wraps an I2C connection to a LIS3DH device.
|
|
type Device struct {
|
|
bus drivers.I2C
|
|
address uint16
|
|
r Range
|
|
accel [6]byte // stored acceleration data (from the Update call)
|
|
}
|
|
|
|
// Driver configuration, used for the Configure call. All fields are optional.
|
|
type Config struct {
|
|
Address uint16
|
|
}
|
|
|
|
// New creates a new LIS3DH connection. The I2C bus must already be configured.
|
|
//
|
|
// This function only creates the Device object, it does not touch the device.
|
|
func New(bus drivers.I2C) Device {
|
|
return Device{bus: bus, address: Address0}
|
|
}
|
|
|
|
// Configure sets up the device for communication
|
|
func (d *Device) Configure(config Config) error {
|
|
if config.Address != 0 {
|
|
d.address = config.Address
|
|
}
|
|
|
|
// enable all axes, normal mode
|
|
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 400Hz rate
|
|
err = d.SetDataRate(DATARATE_400_HZ)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// High res & BDU enabled
|
|
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// get current range
|
|
d.r, err = d.ReadRange()
|
|
return err
|
|
}
|
|
|
|
// Connected returns whether a LIS3DH has been found.
|
|
// It does a "who am I" request and checks the response.
|
|
func (d *Device) Connected() bool {
|
|
data := []byte{0}
|
|
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return data[0] == 0x33
|
|
}
|
|
|
|
// SetDataRate sets the speed of data collected by the LIS3DH.
|
|
func (d *Device) SetDataRate(rate DataRate) error {
|
|
ctl1 := []byte{0}
|
|
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// mask off bits
|
|
ctl1[0] &^= 0xf0
|
|
ctl1[0] |= (byte(rate) << 4)
|
|
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
|
}
|
|
|
|
// SetRange sets the G range for LIS3DH.
|
|
func (d *Device) SetRange(r Range) error {
|
|
ctl := []byte{0}
|
|
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// mask off bits
|
|
ctl[0] &^= 0x30
|
|
ctl[0] |= (byte(r) << 4)
|
|
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// store the new range
|
|
d.r = r
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadRange returns the current G range for LIS3DH.
|
|
func (d *Device) ReadRange() (r Range, err error) {
|
|
ctl := []byte{0}
|
|
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// mask off bits
|
|
r = Range(ctl[0] >> 4)
|
|
r &= 0x03
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// 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 *Device) ReadAcceleration() (int32, int32, int32, error) {
|
|
rawX, rawY, rawZ := d.ReadRawAcceleration()
|
|
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
|
|
return x, y, z, nil
|
|
}
|
|
|
|
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
|
|
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
|
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
|
|
|
data := []byte{0, 0, 0, 0, 0, 0}
|
|
d.bus.Tx(d.address, nil, data)
|
|
|
|
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
|
|
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
|
|
z = int16((uint16(data[5]) << 8) | uint16(data[4]))
|
|
|
|
return
|
|
}
|
|
|
|
// Update the sensor values of the 'which' parameter. Only acceleration is
|
|
// supported at the moment.
|
|
func (d *Device) Update(which drivers.Measurement) error {
|
|
if which&drivers.Acceleration != 0 {
|
|
// Read raw acceleration values and store them in the driver.
|
|
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = d.bus.Tx(d.address, nil, d.accel[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Acceleration returns the last read acceleration 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 *Device) Acceleration() (x, y, z int32) {
|
|
// Extract the raw 16-bit values.
|
|
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
|
|
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
|
|
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
|
|
|
|
// Normalize these values, to be in µg (micro-gravity).
|
|
return normalizeRange(rawX, rawY, rawZ, d.r)
|
|
}
|
|
|
|
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
|
|
// and divisions.
|
|
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
|
|
// We're going to convert the 16-bit raw values to values in the range
|
|
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
|
|
// adjust that range later.
|
|
// The formula is derived as follows, and carefully selected to avoid
|
|
// overflow and integer divisions (the division will be optimized to a
|
|
// bitshift):
|
|
// x = x * 1000_000 / 2048
|
|
// x = x * (1000_000/64) / (2048/64)
|
|
// x = x * 15625 / 32
|
|
x = int32(rawX) * 15625 / 32
|
|
y = int32(rawY) * 15625 / 32
|
|
z = int32(rawZ) * 15625 / 32
|
|
|
|
// Now we need to normalize the three values, since we assumed 16G before.
|
|
shift := uint32(0)
|
|
switch r {
|
|
case RANGE_16_G:
|
|
shift = 0
|
|
case RANGE_8_G:
|
|
shift = 1
|
|
case RANGE_4_G:
|
|
shift = 2
|
|
case RANGE_2_G:
|
|
shift = 3
|
|
}
|
|
x >>= shift
|
|
y >>= shift
|
|
z >>= shift
|
|
|
|
return
|
|
}
|