Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

qmi8658c: Add support for the QMI8658C sensor #467

Merged
merged 3 commits into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
Expand Down Expand Up @@ -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 |
Expand Down
66 changes: 66 additions & 0 deletions examples/qmi8658c/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
190 changes: 190 additions & 0 deletions qmi8658c/qmi8658c.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading