diff --git a/.circleci/config.yml b/.circleci/config.yml index b070bcd..78ec7ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,7 @@ jobs: - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/ds3231/main.go - run: tinygo build -size short -o test.elf -target=microbit ./examples/easystepper/main.go - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/espat/esphub/main.go + - run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/lis3dh/main.go - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/mag3110/main.go - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/mma8653/main.go - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/mpu6050/main.go diff --git a/README.md b/README.md index 72111e8..9cc14de 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ func main() { | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | | ["Easystepper" stepper motor controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO | | [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART | +| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C | | [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C | | [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C | | [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C | diff --git a/examples/lis3dh/main.go b/examples/lis3dh/main.go new file mode 100644 index 0000000..250ebb6 --- /dev/null +++ b/examples/lis3dh/main.go @@ -0,0 +1,32 @@ +// Connects to a LIS3DH I2C accelerometer on the Adafruit Circuit Playground Express. +package main + +import ( + "machine" + "time" + + "github.com/tinygo-org/drivers/lis3dh" +) + +var i2c = machine.I2C1 + +func main() { + i2c.Configure(machine.I2CConfig{}) + + accel := lis3dh.New(i2c) + accel.Address = lis3dh.Address1 // address on the Circuit Playground Express + accel.Configure() + accel.SetRange(lis3dh.RANGE_2_G) + + println(accel.Connected()) + + for { + x, y, z, _ := accel.ReadAcceleration() + println("X:", x, "Y:", y, "Z:", z) + + rx, ry, rz := accel.ReadRawAcceleration() + println("X (raw):", rx, "Y (raw):", ry, "Z (raw):", rz) + + time.Sleep(time.Millisecond * 100) + } +} diff --git a/lis3dh/lis3dh.go b/lis3dh/lis3dh.go new file mode 100644 index 0000000..ddec329 --- /dev/null +++ b/lis3dh/lis3dh.go @@ -0,0 +1,126 @@ +// Package lis3dh provides a driver for the LIS3DH digital accelerometer. +// +// Datasheet: https://www.st.com/resource/en/datasheet/lis3dh.pdf +package lis3dh + +import ( + "machine" +) + +// Device wraps an I2C connection to a LIS3DH device. +type Device struct { + bus machine.I2C + Address uint16 + r Range +} + +// 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 machine.I2C) Device { + return Device{bus: bus, Address: Address0} +} + +// Configure sets up the device for communication +func (d *Device) Configure() { + // enable all axes, normal mode + d.bus.WriteRegister(uint8(d.Address), REG_CTRL1, []byte{0x07}) + + // 400Hz rate + d.SetDataRate(DATARATE_400_HZ) + + // High res & BDU enabled + d.bus.WriteRegister(uint8(d.Address), REG_CTRL4, []byte{0x88}) + + // get current range + d.r = d.ReadRange() +} + +// 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 := d.bus.ReadRegister(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) { + ctl1 := []byte{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_CTRL1, ctl1) + if err != nil { + println(err.Error()) + } + // mask off bits + ctl1[0] &^= 0xf0 + ctl1[0] |= (byte(rate) << 4) + d.bus.WriteRegister(uint8(d.Address), REG_CTRL1, ctl1) +} + +// SetRange sets the G range for LIS3DH. +func (d *Device) SetRange(r Range) { + ctl := []byte{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_CTRL4, ctl) + if err != nil { + println(err.Error()) + } + // mask off bits + ctl[0] &^= 0x30 + ctl[0] |= (byte(r) << 4) + d.bus.WriteRegister(uint8(d.Address), REG_CTRL4, ctl) + + // store the new range + d.r = r +} + +// ReadRange returns the current G range for LIS3DH. +func (d *Device) ReadRange() (r Range) { + ctl := []byte{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_CTRL4, ctl) + if err != nil { + println(err.Error()) + } + // mask off bits + r = Range(ctl[0] >> 4) + r &= 0x03 + + return r +} + +// 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) { + x, y, z := d.ReadRawAcceleration() + divider := float32(1) + switch d.r { + case RANGE_16_G: + divider = 1365 + case RANGE_8_G: + divider = 4096 + case RANGE_4_G: + divider = 8190 + case RANGE_2_G: + divider = 16380 + } + + return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil +} + +// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH +func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) { + d.bus.WriteRegister(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 +} diff --git a/lis3dh/registers.go b/lis3dh/registers.go new file mode 100644 index 0000000..82adbc4 --- /dev/null +++ b/lis3dh/registers.go @@ -0,0 +1,77 @@ +package lis3dh + +// Constants/addresses used for I2C. + +// The I2C addresses which this device listens to. +const ( + Address0 = 0x18 // SA0 is low + Address1 = 0x19 // SA0 is high +) + +// Registers. Names, addresses and comments copied from the datasheet. +const ( + WHO_AM_I = 0x0F + REG_STATUS1 = 0x07 + REG_OUTADC1_L = 0x08 + REG_OUTADC1_H = 0x09 + REG_OUTADC2_L = 0x0A + REG_OUTADC2_H = 0x0B + REG_OUTADC3_L = 0x0C + REG_OUTADC3_H = 0x0D + REG_INTCOUNT = 0x0E + REG_WHOAMI = 0x0F + REG_TEMPCFG = 0x1F + REG_CTRL1 = 0x20 + REG_CTRL2 = 0x21 + REG_CTRL3 = 0x22 + REG_CTRL4 = 0x23 + REG_CTRL5 = 0x24 + REG_CTRL6 = 0x25 + REG_REFERENCE = 0x26 + REG_STATUS2 = 0x27 + REG_OUT_X_L = 0x28 + REG_OUT_X_H = 0x29 + REG_OUT_Y_L = 0x2A + REG_OUT_Y_H = 0x2B + REG_OUT_Z_L = 0x2C + REG_OUT_Z_H = 0x2D + REG_FIFOCTRL = 0x2E + REG_FIFOSRC = 0x2F + REG_INT1CFG = 0x30 + REG_INT1SRC = 0x31 + REG_INT1THS = 0x32 + REG_INT1DUR = 0x33 + REG_CLICKCFG = 0x38 + REG_CLICKSRC = 0x39 + REG_CLICKTHS = 0x3A + REG_TIMELIMIT = 0x3B + REG_TIMELATEN = 0x3C + REG_TIMEWINDO = 0x3D + REG_ACTTHS = 0x3E + REG_ACTDUR = 0x3F +) + +type Range uint8 + +const ( + RANGE_16_G Range = 3 // +/- 16g + RANGE_8_G = 2 // +/- 8g + RANGE_4_G = 1 // +/- 4g + RANGE_2_G = 0 // +/- 2g (default value) +) + +type DataRate uint8 + +// Data rate constants. +const ( + DATARATE_400_HZ DataRate = 7 // 400Hz + DATARATE_200_HZ = 6 // 200Hz + DATARATE_100_HZ = 5 // 100Hz + DATARATE_50_HZ = 4 // 50Hz + DATARATE_25_HZ = 3 // 25Hz + DATARATE_10_HZ = 2 // 10 Hz + DATARATE_1_HZ = 1 // 1 Hz + DATARATE_POWERDOWN = 0 + DATARATE_LOWPOWER_1K6HZ = 8 + DATARATE_LOWPOWER_5KHZ = 9 +)