// Package qmi8658c provides a driver for the QMI8658C accelerometer and gyroscope // made by QST Solutions. // // Datasheet: // https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf package qmi8656c import ( "tinygo.org/x/drivers" "tinygo.org/x/drivers/internal/legacy" ) // Device wraps the I2C connection to the QMIC8658 sensor type Device struct { bus drivers.I2C Address uint16 AccLsbDiv uint16 GyroLsbDiv uint16 } type Config struct { // SPI Config SPIMode byte // One of SPI_X_WIRE SPIEndian byte // One of SPI_XXX_ENDIAN SPIAutoInc byte // One of SPI_NOT_AUTO_INC or SPI_AUTO_INC // Accelerometer AccEnable byte // One of ACC_ENABLE or ACC_DISABLE AccScale byte // One of ACC_XG AccRate byte // One of ACC_XX_YYHZ AccLowPass byte // One of ACC_LOW_PASS_X // Gyro GyroEnable byte // One of GYRO_X_ENABLE or GYRO_DISABLE GyroScale byte // One of GYRO_XDPS GyroRate byte // One of GYRO_X_YHZ GyroLowPass byte // One of GYRO_LOW_PASS_X } // Create a new device with the I2C passed, correct address and nil values for // AccLsbDiv and GyroLsbDiv, which will be corrected based on the config. func New(bus drivers.I2C) Device { return Device{ bus, Address, 1, 1, } } // Check if the device is connected by calling WHO_AM_I and checking the // default identifier. func (d *Device) Connected() bool { data := []byte{0} d.ReadRegister(WHO_AM_I, data) return data[0] == IDENTIFIER } // Create a basic default configuration that works with the "WaveShare RP2040 // Round LCD 1.28in". func DefaultConfig() (cfg Config) { return Config{ SPIMode: SPI_4_WIRE, SPIEndian: SPI_BIG_ENDIAN, SPIAutoInc: SPI_AUTO_INC, AccEnable: ACC_ENABLE, AccScale: ACC_8G, AccRate: ACC_NORMAL_1000HZ, AccLowPass: ACC_LOW_PASS_2_62, GyroEnable: GYRO_FULL_ENABLE, GyroScale: GYRO_512DPS, GyroRate: GYRO_1000HZ, GyroLowPass: GYRO_LOW_PASS_2_62, } } // Check if the user has defined a desired configuration, if not uses the // DefaultConfig, then defines the AccLsbDiv and GyroLsbDiv based on the // configurations and, finally, send the commands and configure the IMU. func (d *Device) Configure(cfg Config) { if cfg == (Config{}) { cfg = DefaultConfig() } var val uint16 // Setting accelerometer LSB switch cfg.AccScale { case ACC_2G: d.AccLsbDiv = 1 << 14 case ACC_4G: d.AccLsbDiv = 1 << 13 case ACC_8G: d.AccLsbDiv = 1 << 12 case ACC_16G: d.AccLsbDiv = 1 << 11 default: d.AccLsbDiv = 1 << 12 } // Setting gyro LSB switch cfg.GyroScale { case GYRO_16DPS: d.GyroLsbDiv = 2048 case GYRO_32DPS: d.GyroLsbDiv = 1024 case GYRO_64DPS: d.GyroLsbDiv = 512 case GYRO_128DPS: d.GyroLsbDiv = 256 case GYRO_256DPS: d.GyroLsbDiv = 128 case GYRO_512DPS: d.GyroLsbDiv = 64 case GYRO_1024DPS: d.GyroLsbDiv = 32 case GYRO_2048DPS: d.GyroLsbDiv = 16 default: d.GyroLsbDiv = 64 } // SPI Modes val = uint16((cfg.SPIMode | cfg.SPIEndian | cfg.SPIAutoInc)) d.WriteRegister(CTRL1, val) // Accelerometer config val = uint16(cfg.AccScale | cfg.AccRate) d.WriteRegister(CTRL2, val) // Gyro config val = uint16(cfg.GyroScale | cfg.GyroRate) d.WriteRegister(CTRL3, val) // Sensor DSP config val = uint16(cfg.GyroLowPass | cfg.AccLowPass) d.WriteRegister(CTRL5, val) // Sensors config val = uint16(cfg.GyroEnable | cfg.AccEnable) d.WriteRegister(CTRL7, val) } // Read the acceleration from the sensor, the values returned are in mg // (milli gravity), which means that 1000 = 1g. func (d *Device) ReadAcceleration() (x int32, y int32, z int32) { data := make([]byte, 6) raw := make([]int32, 3) d.ReadRegister(ACC_XOUT_L, data) for i := range raw { raw[i] = int32(uint16(data[(2*i+1)])<<8 | uint16(data[i])) if raw[i] >= 32767 { raw[i] = raw[i] - 65535 } } x = -raw[0] * 1000 / int32(d.AccLsbDiv) y = -raw[1] * 1000 / int32(d.AccLsbDiv) z = -raw[2] * 1000 / int32(d.AccLsbDiv) return x, y, z } // Read the rotation from the sensor, the values returned are in mdeg/sec // (milli degress/second), which means that a full rotation is 360000. func (d *Device) ReadRotation() (x int32, y int32, z int32) { data := make([]byte, 6) raw := make([]int32, 3) d.ReadRegister(GYRO_XOUT_L, data) for i := range raw { raw[i] = int32(uint16(data[(2*i+1)])<<8 | uint16(data[i])) if raw[i] >= 32767 { raw[i] = raw[i] - 65535 } } x = raw[0] * 1000 / int32(d.GyroLsbDiv) y = raw[1] * 1000 / int32(d.GyroLsbDiv) z = raw[2] * 1000 / int32(d.GyroLsbDiv) return x, y, z } // Read the temperature from the sensor, the values returned are in // millidegrees Celsius. func (d *Device) ReadTemperature() (int32, error) { data := make([]byte, 2) err := d.ReadRegister(TEMP_OUT_L, data) if err != nil { return 0, err } raw := uint16(data[1])<<8 | uint16(data[0]) t := int32(raw) * 1000 / 256 return t, err } // Convenience method to read the register and avoid repetition. func (d *Device) ReadRegister(reg uint8, buf []byte) error { return legacy.ReadRegister(d.bus, uint8(d.Address), reg, buf) } // Convenience method to write the register and avoid repetition. func (d *Device) WriteRegister(reg uint8, v uint16) error { data := []byte{byte(v)} err := legacy.WriteRegister(d.bus, uint8(d.Address), reg, data) return err }