tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/lsm303agr/lsm303agr.go (about)

     1  // Package lsm303agr implements a driver for the LSM303AGR,
     2  // a 3 axis accelerometer/magnetic sensor which is included on BBC micro:bits v1.5.
     3  //
     4  // Datasheet: https://www.st.com/resource/en/datasheet/lsm303agr.pdf
     5  package lsm303agr // import "tinygo.org/x/drivers/lsm303agr"
     6  
     7  import (
     8  	"errors"
     9  	"math"
    10  
    11  	"tinygo.org/x/drivers"
    12  	"tinygo.org/x/drivers/internal/legacy"
    13  )
    14  
    15  // Device wraps an I2C connection to a LSM303AGR device.
    16  type Device struct {
    17  	bus            drivers.I2C
    18  	AccelAddress   uint8
    19  	MagAddress     uint8
    20  	AccelPowerMode uint8
    21  	AccelRange     uint8
    22  	AccelDataRate  uint8
    23  	MagPowerMode   uint8
    24  	MagSystemMode  uint8
    25  	MagDataRate    uint8
    26  	buf            [6]uint8
    27  }
    28  
    29  // Configuration for LSM303AGR device.
    30  type Configuration struct {
    31  	AccelPowerMode uint8
    32  	AccelRange     uint8
    33  	AccelDataRate  uint8
    34  	MagPowerMode   uint8
    35  	MagSystemMode  uint8
    36  	MagDataRate    uint8
    37  }
    38  
    39  var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
    40  
    41  // New creates a new LSM303AGR connection. The I2C bus must already be configured.
    42  //
    43  // This function only creates the Device object, it does not touch the device.
    44  func New(bus drivers.I2C) *Device {
    45  	return &Device{
    46  		bus:          bus,
    47  		AccelAddress: ACCEL_ADDRESS,
    48  		MagAddress:   MAG_ADDRESS,
    49  	}
    50  }
    51  
    52  // Connected returns whether both sensor on LSM303AGR has been found.
    53  // It does two "who am I" requests and checks the responses.
    54  func (d *Device) Connected() bool {
    55  	data1, data2 := []byte{0}, []byte{0}
    56  	legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_WHO_AM_I, data1)
    57  	legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_WHO_AM_I, data2)
    58  	return data1[0] == 0x33 && data2[0] == 0x40
    59  }
    60  
    61  // Configure sets up the LSM303AGR device for communication.
    62  func (d *Device) Configure(cfg Configuration) (err error) {
    63  
    64  	// Verify unit communication
    65  	if !d.Connected() {
    66  		return errNotConnected
    67  	}
    68  
    69  	if cfg.AccelDataRate != 0 {
    70  		d.AccelDataRate = cfg.AccelDataRate
    71  	} else {
    72  		d.AccelDataRate = ACCEL_DATARATE_100HZ
    73  	}
    74  
    75  	if cfg.AccelPowerMode != 0 {
    76  		d.AccelPowerMode = cfg.AccelPowerMode
    77  	} else {
    78  		d.AccelPowerMode = ACCEL_POWER_NORMAL
    79  	}
    80  
    81  	if cfg.AccelRange != 0 {
    82  		d.AccelRange = cfg.AccelRange
    83  	} else {
    84  		d.AccelRange = ACCEL_RANGE_2G
    85  	}
    86  
    87  	if cfg.MagPowerMode != 0 {
    88  		d.MagPowerMode = cfg.MagPowerMode
    89  	} else {
    90  		d.MagPowerMode = MAG_POWER_NORMAL
    91  	}
    92  
    93  	if cfg.MagDataRate != 0 {
    94  		d.MagDataRate = cfg.MagDataRate
    95  	} else {
    96  		d.MagDataRate = MAG_DATARATE_10HZ
    97  	}
    98  
    99  	if cfg.MagSystemMode != 0 {
   100  		d.MagSystemMode = cfg.MagSystemMode
   101  	} else {
   102  		d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
   103  	}
   104  
   105  	data := d.buf[:1]
   106  
   107  	data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
   108  	err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
   109  	if err != nil {
   110  		return
   111  	}
   112  
   113  	data[0] = byte(0x80 | d.AccelRange<<4)
   114  	err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
   115  	if err != nil {
   116  		return
   117  	}
   118  
   119  	data[0] = byte(0xC0)
   120  	err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), TEMP_CFG_REG_A, data)
   121  	if err != nil {
   122  		return
   123  	}
   124  
   125  	// Temperature compensation is on for magnetic sensor
   126  	data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
   127  	err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, data)
   128  	if err != nil {
   129  		return
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // ReadAcceleration reads the current acceleration from the device and returns
   136  // it in µg (micro-gravity). When one of the axes is pointing straight to Earth
   137  // and the sensor is not moving the returned value will be around 1000000 or
   138  // -1000000.
   139  func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
   140  	data := d.buf[:6]
   141  	err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data)
   142  	if err != nil {
   143  		return
   144  	}
   145  
   146  	rangeFactor := int16(0)
   147  	switch d.AccelRange {
   148  	case ACCEL_RANGE_2G:
   149  		rangeFactor = 1
   150  	case ACCEL_RANGE_4G:
   151  		rangeFactor = 2
   152  	case ACCEL_RANGE_8G:
   153  		rangeFactor = 4
   154  	case ACCEL_RANGE_16G:
   155  		rangeFactor = 12 // the readings in 16G are a bit lower
   156  	}
   157  
   158  	x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024)
   159  	y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024)
   160  	z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024)
   161  	return
   162  }
   163  
   164  // ReadPitchRoll reads the current pitch and roll angles from the device and
   165  // returns it in micro-degrees. When the z axis is pointing straight to Earth
   166  // the returned values of pitch and roll would be zero.
   167  func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) {
   168  
   169  	x, y, z, err := d.ReadAcceleration()
   170  	if err != nil {
   171  		return
   172  	}
   173  	xf, yf, zf := float64(x), float64(y), float64(z)
   174  	pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
   175  	roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
   176  	return
   177  
   178  }
   179  
   180  // ReadMagneticField reads the current magnetic field from the device and returns
   181  // it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
   182  func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
   183  
   184  	if d.MagSystemMode == MAG_SYSTEM_SINGLE {
   185  		cmd := d.buf[:1]
   186  		cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
   187  		err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd)
   188  		if err != nil {
   189  			return
   190  		}
   191  	}
   192  
   193  	data := d.buf[0:6]
   194  	legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data)
   195  
   196  	x = int32(int16((uint16(data[1])<<8 | uint16(data[0]))))
   197  	y = int32(int16((uint16(data[3])<<8 | uint16(data[2]))))
   198  	z = int32(int16((uint16(data[5])<<8 | uint16(data[4]))))
   199  	return
   200  }
   201  
   202  // ReadCompass reads the current compass heading from the device and returns
   203  // it in micro-degrees. When the z axis is pointing straight to Earth and
   204  // the y axis is pointing to North, the heading would be zero.
   205  //
   206  // However, the heading may be off due to electronic compasses would be effected
   207  // by strong magnetic fields and require constant calibration.
   208  func (d *Device) ReadCompass() (h int32, err error) {
   209  
   210  	x, y, _, err := d.ReadMagneticField()
   211  	if err != nil {
   212  		return
   213  	}
   214  	xf, yf := float64(x), float64(y)
   215  	h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000)
   216  	return
   217  }
   218  
   219  // ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
   220  func (d *Device) ReadTemperature() (t int32, err error) {
   221  
   222  	data := d.buf[:2]
   223  	err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_AUTO_INC, data)
   224  	if err != nil {
   225  		return
   226  	}
   227  
   228  	r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
   229  	t = 25000 + int32((float32(r)/8)*1000)
   230  	return
   231  }