From c00cb3abdd021dae317d3beed840abcfbe67b627 Mon Sep 17 00:00:00 2001 From: Rafael Badiale <34357909+rcbadiale@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:14:37 -0300 Subject: [PATCH] qmi8658c: Add support for the QMI8658C sensor (#467) * Add support for the QMI8658C sensor * qmi8656c: update ReadTemperature signature * Update README devices count --- Makefile | 2 + README.md | 3 +- examples/qmi8658c/main.go | 66 +++++++++++++ qmi8658c/qmi8658c.go | 190 ++++++++++++++++++++++++++++++++++++++ qmi8658c/registers.go | 143 ++++++++++++++++++++++++++++ 5 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 examples/qmi8658c/main.go create mode 100644 qmi8658c/qmi8658c.go create mode 100644 qmi8658c/registers.go diff --git a/Makefile b/Makefile index 326714b..fac1065 100644 --- a/Makefile +++ b/Makefile @@ -209,6 +209,8 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/ @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=pico ./examples/qmi8658c/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go diff --git a/README.md b/README.md index 9e5f828..044ab43 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 83 devices are supported. +The following 90 devices are supported. | Device Name | Interface Type | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| @@ -112,6 +112,7 @@ The following 83 devices are supported. | [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI | | [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI | | [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C | +| [QMI8658C accelerometer/gyroscope](https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf) | I2C | | [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO | | [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART | | [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C | diff --git a/examples/qmi8658c/main.go b/examples/qmi8658c/main.go new file mode 100644 index 0000000..667fe43 --- /dev/null +++ b/examples/qmi8658c/main.go @@ -0,0 +1,66 @@ +// Connects to an QMI8658C I2C accelerometer/gyroscope and print the read data. +// This example was made with the "WaveShare RP2040 Round LCD 1.28in" in mind. +// For more infor about this development board: +// https://www.waveshare.com/wiki/RP2040-LCD-1.28 +package main + +import ( + "machine" + "time" + + imu "tinygo.org/x/drivers/qmi8658c" +) + +func main() { + i2c := machine.I2C1 + // This is the default pinout for the "WaveShare RP2040 Round LCD 1.28in" + err := i2c.Configure(machine.I2CConfig{ + SDA: machine.GP6, + SCL: machine.GP7, + Frequency: 100000, + }) + if err != nil { + println("unable to configure I2C:", err) + return + } + // Create a new device + d := imu.New(i2c) + + // Check if the device is connected + if !d.Connected() { + println("unable to connect to sensor") + return + } + + // This IMU has multiple configurations like output data rate, multiple + // measurements scales, low pass filters, low power modes, all the vailable + // values can be found in the datasheet and were defined at registers file. + // This is the default configuration which will be used if the `nil` value + // is passed do the `Configure` method. + config := imu.Config{ + SPIMode: imu.SPI_4_WIRE, + SPIEndian: imu.SPI_BIG_ENDIAN, + SPIAutoInc: imu.SPI_AUTO_INC, + AccEnable: imu.ACC_ENABLE, + AccScale: imu.ACC_8G, + AccRate: imu.ACC_NORMAL_1000HZ, + AccLowPass: imu.ACC_LOW_PASS_2_62, + GyroEnable: imu.GYRO_FULL_ENABLE, + GyroScale: imu.GYRO_512DPS, + GyroRate: imu.GYRO_1000HZ, + GyroLowPass: imu.GYRO_LOW_PASS_2_62, + } + d.Configure(config) + + // Read the accelation, rotation and temperature data and print them. + for { + acc_x, acc_y, acc_z := d.ReadAcceleration() + gyro_x, gyro_y, gyro_z := d.ReadRotation() + temp, _ := d.ReadTemperature() + println("-------------------------------") + println("acc:", acc_x, acc_y, acc_z) + println("gyro:", gyro_x, gyro_y, gyro_z) + println("temp:", temp) + time.Sleep(time.Millisecond * 100) + } +} diff --git a/qmi8658c/qmi8658c.go b/qmi8658c/qmi8658c.go new file mode 100644 index 0000000..2567ff3 --- /dev/null +++ b/qmi8658c/qmi8658c.go @@ -0,0 +1,190 @@ +// 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" + +// 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 d.bus.ReadRegister(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 := d.bus.WriteRegister(uint8(d.Address), reg, data) + return err +} diff --git a/qmi8658c/registers.go b/qmi8658c/registers.go new file mode 100644 index 0000000..6ed8bed --- /dev/null +++ b/qmi8658c/registers.go @@ -0,0 +1,143 @@ +package qmi8656c + +// The I2C address that the sensor listens to. +const Address = 0x6B + +const ( + // Who am I + WHO_AM_I = 0x00 + IDENTIFIER = 0x05 + + // Configuration registers + CTRL1 = 0x02 // SPI Modes + CTRL2 = 0x03 // Accelerometer config + CTRL3 = 0x04 // Gyro config + CTRL4 = 0x05 // Magnetometer config (ignored) + CTRL5 = 0x06 // Sensor DSP config + CTRL6 = 0x07 // Motion on Demand (ignored) + CTRL7 = 0x08 // Sensors config + + // Interface config (CTRL1) + SPI_4_WIRE = 0x00 + SPI_3_WIRE = 0x80 + SPI_NOT_AUTO_INC = 0x00 + SPI_AUTO_INC = 0x40 + SPI_LITTLE_ENDIAN = 0x00 + SPI_BIG_ENDIAN = 0x20 + + // Accelerometer scale config (CTRL2-H) + ACC_SELF_TEST = 0x80 + + // Accelerometer scale config (CTRL2-H) + ACC_2G = 0x00 + ACC_4G = 0x10 + ACC_8G = 0x20 + ACC_16G = 0x30 + + // Accelerometer output data rate (ODR) config (CTRL2-L) + ACC_NORMAL_8000HZ = 0x00 + ACC_NORMAL_4000HZ = 0x01 + ACC_NORMAL_2000HZ = 0x02 + ACC_NORMAL_1000HZ = 0x03 + ACC_NORMAL_500HZ = 0x04 + ACC_NORMAL_250HZ = 0x05 + ACC_NORMAL_125HZ = 0x06 + ACC_NORMAL_62HZ = 0x07 + ACC_NORMAL_31HZ = 0x08 + ACC_LOW_POWER_128HZ = 0x0C + ACC_LOW_POWER_21HZ = 0x0D + ACC_LOW_POWER_11HZ = 0x0E + ACC_LOW_POWER_3HZ = 0x0F + + // Gyro scale config (CTRL3-H) + GYRO_SELF_TEST = 0x80 + + // Gyro scale config (CTRL3-H) + GYRO_16DPS = 0x00 + GYRO_32DPS = 0x10 + GYRO_64DPS = 0x20 + GYRO_128DPS = 0x30 + GYRO_256DPS = 0x40 + GYRO_512DPS = 0x50 + GYRO_1024DPS = 0x60 + GYRO_2048DPS = 0x70 + + // Gyro output data rate (ODR) config (CTRL3-L) + GYRO_8000HZ = 0x00 + GYRO_4000HZ = 0x01 + GYRO_2000HZ = 0x02 + GYRO_1000HZ = 0x03 + GYRO_500HZ = 0x04 + GYRO_250HZ = 0x05 + GYRO_125HZ = 0x06 + GYRO_62HZ = 0x07 + GYRO_31HZ = 0x08 + + // Gyro DSP config (CTRL4-H) + GYRO_LOW_PASS_OFF = 0x00 // Disabled + GYRO_LOW_PASS_2_62 = 0x10 // 2.62% of output data rate (ODR) + GYRO_LOW_PASS_3_59 = 0x30 // 3.59% of output data rate (ODR) + GYRO_LOW_PASS_5_32 = 0x50 // 5.32% of output data rate (ODR) + GYRO_LOW_PASS_14 = 0x70 // 14% of output data rate (ODR) + + // Accelerometer DSP config (CTRL4-L) + ACC_LOW_PASS_OFF = 0x00 // Disabled + ACC_LOW_PASS_2_62 = 0x01 // 2.62% of output data rate (ODR) + ACC_LOW_PASS_3_59 = 0x03 // 3.59% of output data rate (ODR) + ACC_LOW_PASS_5_32 = 0x05 // 5.32% of output data rate (ODR) + ACC_LOW_PASS_14 = 0x07 // 14% of output data rate (ODR) + + // Motion on demand (MOD) (CTRL6) + MOD_DISABLE = 0x00 + MOD_ENABLE = 0x80 + + // Enable sensors (CTRL7) + GYRO_DISABLE = 0x00 + GYRO_FULL_ENABLE = 0x02 + GYRO_SNOOZE_ENABLE = 0x12 + ACC_DISABLE = 0x00 + ACC_ENABLE = 0x01 + + // Timestamp Outputs Register Adresses + TIMESTAMP_OUT_L = 0x30 + TIMESTAMP_OUT_M = 0x31 + TIMESTAMP_OUT_H = 0x32 + + // Temperature Outputs Register Adresses + TEMP_OUT_L = 0x33 + TEMP_OUT_H = 0x34 + + // Acceleration Outputs Register Adresses + ACC_XOUT_L = 0x35 + ACC_XOUT_H = 0x36 + ACC_YOUT_L = 0x37 + ACC_YOUT_H = 0x38 + ACC_ZOUT_L = 0x39 + ACC_ZOUT_H = 0x3A + + // Angular Rate Outputs Register Adresses + GYRO_XOUT_L = 0x3B + GYRO_XOUT_H = 0x3C + GYRO_YOUT_L = 0x3D + GYRO_YOUT_H = 0x3E + GYRO_ZOUT_L = 0x3F + GYRO_ZOUT_H = 0x40 + + // Quaternion Outputs Register Adresses + DELTA_QUAT_WOUT_L = 0x49 + DELTA_QUAT_WOUT_H = 0x4A + DELTA_QUAT_XOUT_L = 0x4B + DELTA_QUAT_XOUT_H = 0x4C + DELTA_QUAT_YOUT_L = 0x4D + DELTA_QUAT_YOUT_H = 0x4E + DELTA_QUAT_ZOUT_L = 0x4F + DELTA_QUAT_ZOUT_H = 0x50 + + // Delta Velocity Outputs Register Adresses + DELTA_VEL_XOUT_L = 0x51 + DELTA_VEL_XOUT_H = 0x52 + DELTA_VEL_YOUT_L = 0x53 + DELTA_VEL_YOUT_H = 0x54 + DELTA_VEL_ZOUT_L = 0x55 + DELTA_VEL_ZOUT_H = 0x56 +)