|
| 1 | +# QMI8658 driver for MicroPython |
| 2 | +# MIT license; Copyright (c) 2022-2024 WEMOS.CC |
| 3 | + |
| 4 | +import struct |
| 5 | +from machine import I2C |
| 6 | +import machine |
| 7 | +import time |
| 8 | +import array |
| 9 | +import math |
| 10 | + |
| 11 | +WHO_AM_I = const(0x00) # Device identifier |
| 12 | +REVISION_ID = const(0x01) |
| 13 | +CTRL1 = const(0x02) # Serial Interface and Sensor Enable |
| 14 | +CTRL2 = const(0x03) # Accelerometer Settings |
| 15 | +CTRL3 = const(0x04) # Gyroscope Settings |
| 16 | +CTRL4 = const(0x05) # Magnetometer Settings |
| 17 | +CTRL5 = const(0x06) # Sensor Data Processing Settings |
| 18 | +CTRL7 = const(0x08) # Enable Sensors and Configure Data Reads |
| 19 | +CTRL8 = const(0x09) # Reserved – Special Settings |
| 20 | + |
| 21 | +# <Sensor Data Output Registers> |
| 22 | + |
| 23 | +TEMP_L = const(0x33) |
| 24 | + |
| 25 | +AccX_L = const(0x35) |
| 26 | +AccX_H = const(0x36) |
| 27 | +AccY_L = const(0x37) |
| 28 | +AccY_H = const(0x38) |
| 29 | +AccZ_L = const(0x39) |
| 30 | +AccZ_H = const(0x3A) |
| 31 | + |
| 32 | +GyrX_L = const(0x3B) |
| 33 | +GyrX_H = const(0x3C) |
| 34 | +GyrY_L = const(0x3D) |
| 35 | +GyrY_H = const(0x3E) |
| 36 | +GyrZ_L = const(0x3F) |
| 37 | +GyrZ_H = const(0x40) |
| 38 | + |
| 39 | + |
| 40 | +class QMI8658(): |
| 41 | + def __init__(self, i2c, addr=0x6b, accel_scale=2, gyro_scale=2048,accel_odr=500,gyro_odr=500): |
| 42 | + self.i2c = i2c |
| 43 | + self.addr = addr |
| 44 | + |
| 45 | + self.accel_scale = accel_scale |
| 46 | + self.gyro_scale = gyro_scale |
| 47 | + |
| 48 | + self.accel_odr=accel_odr |
| 49 | + self.gyro_odr=gyro_odr |
| 50 | + |
| 51 | + self.accel_sensitivity=32768/self.accel_scale |
| 52 | + self.gyro_sensitivity=32768/gyro_scale |
| 53 | + |
| 54 | + self.scratch_int = array.array("h", [0, 0, 0]) |
| 55 | + |
| 56 | + if self.i2c.readfrom_mem(self.addr, WHO_AM_I, 1) != b'\x05': |
| 57 | + raise OSError( |
| 58 | + "No QMI8658 device was found at address 0x%x" % (self.addr)) |
| 59 | + |
| 60 | + # Accelerometer Full-scale |
| 61 | + SCALE_ACCEL = {2: 0, 4: 1, 8: 2, 16: 3} |
| 62 | + |
| 63 | + # Gyroscope Full-scale |
| 64 | + SCALE_GYRO = {16: 0, 32: 1, 64: 2, 128: 3, |
| 65 | + 256: 4, 512: 5, 1024: 6, 2048: 7} |
| 66 | + |
| 67 | + # Accelerometer Output Data Rate (ODR) |
| 68 | + ODR_ACCEL = { |
| 69 | + # Normal |
| 70 | + 8000: 0, |
| 71 | + 4000: 1, |
| 72 | + 2000: 2, |
| 73 | + 1000: 3, |
| 74 | + 500: 4, |
| 75 | + 250: 5, |
| 76 | + 125: 6, |
| 77 | + 62.5: 7, |
| 78 | + 31.25: 8, |
| 79 | + # Low Power |
| 80 | + 128: 12, |
| 81 | + 21: 13, # 58% Duty Cycle |
| 82 | + 11: 14, # 31% Duty Cycle |
| 83 | + 3: 15 # 8.5% Duty Cycle |
| 84 | + } |
| 85 | + |
| 86 | + # Gyroscope Output Data Rate (ODR) |
| 87 | + ODR_GYRO = { |
| 88 | + # Normal |
| 89 | + 8000: 0, |
| 90 | + 4000: 1, |
| 91 | + 2000: 2, |
| 92 | + 1000: 3, |
| 93 | + 500: 4, |
| 94 | + 250: 5, |
| 95 | + 125: 6, |
| 96 | + 62.5: 7, |
| 97 | + 31.25: 8, |
| 98 | + } |
| 99 | + |
| 100 | + # Sanity checks |
| 101 | + |
| 102 | + if not self.accel_scale in SCALE_ACCEL: |
| 103 | + raise ValueError("Invalid accelerometer scaling: %d" % |
| 104 | + self.accel_scale) |
| 105 | + |
| 106 | + if not self.gyro_scale in SCALE_GYRO: |
| 107 | + raise ValueError("invalid gyro scaling: %d" % self.gyro_scale) |
| 108 | + |
| 109 | + if not self.accel_odr in ODR_ACCEL: |
| 110 | + raise ValueError("Invalid sampling rate: %d" % self.accel_odr) |
| 111 | + |
| 112 | + if not self.gyro_odr in ODR_GYRO: |
| 113 | + raise ValueError("Invalid sampling rate: %d" % self.gyro_odr) |
| 114 | + |
| 115 | + # Serial Interface and Sensor Enable, address auto increment |
| 116 | + self.i2c.writeto_mem(self.addr, CTRL1, b'\x40') |
| 117 | + # Enable Sensors and Configure Data Reads<Enable Gyroscope Accelerometer> |
| 118 | + self.i2c.writeto_mem(self.addr, CTRL7, b'\x03') |
| 119 | + |
| 120 | + # Accelerometer Settings |
| 121 | + parm=(ODR_ACCEL[self.accel_odr])|(SCALE_ACCEL[self.accel_scale]<<4) |
| 122 | + self.i2c.writeto_mem(self.addr, CTRL2, bytes([parm])) |
| 123 | + |
| 124 | + # Gyroscope Settings< ±2048dps 500Hz> |
| 125 | + parm=(ODR_GYRO[self.gyro_odr])|(SCALE_GYRO[self.gyro_scale]<<4) |
| 126 | + self.i2c.writeto_mem(self.addr, CTRL3, bytes([parm])) |
| 127 | + |
| 128 | + # Sensor Data Processing Settings<Enable Gyroscope Accelerometer Low-Pass Filter> |
| 129 | + self.i2c.writeto_mem(self.addr, CTRL5, b'\x11') |
| 130 | + |
| 131 | + def test(self): |
| 132 | + return self.i2c.readfrom_mem(self.addr, AccX_L, 12) |
| 133 | + |
| 134 | + def read_gyro(self): |
| 135 | + mv = memoryview(self.scratch_int) |
| 136 | + self.i2c.readfrom_mem_into(self.addr,GyrX_L, mv) |
| 137 | + return (mv[0] / self.gyro_sensitivity, mv[1] / self.gyro_sensitivity, mv[2] / self.gyro_sensitivity) |
| 138 | + |
| 139 | + def read_accel(self): |
| 140 | + mv = memoryview(self.scratch_int) |
| 141 | + self.i2c.readfrom_mem_into(self.addr,AccX_L, mv) |
| 142 | + return (mv[0] / self.accel_sensitivity, mv[1] / self.accel_sensitivity, mv[2] / self.accel_sensitivity) |
| 143 | + |
| 144 | + def read_temp(self): |
| 145 | + return struct.unpack("<h",self.i2c.readfrom_mem(self.addr, TEMP_L, 2))[0]/256 |
| 146 | + |
| 147 | + def invSqrt(self,number): |
| 148 | + threehalfs = 1.5 |
| 149 | + x2 = number * 0.5 |
| 150 | + y = number |
| 151 | + |
| 152 | + packed_y = struct.pack('f', y) |
| 153 | + i = struct.unpack('i', packed_y)[0] # treat float's bytes as int |
| 154 | + i = 0x5f3759df - (i >> 1) # arithmetic with magic number |
| 155 | + packed_i = struct.pack('i', i) |
| 156 | + y = struct.unpack('f', packed_i)[0] # treat int's bytes as float |
| 157 | + |
| 158 | + y = y * (threehalfs - (x2 * y * y)) # Newton's method |
| 159 | + return y |
| 160 | + |
| 161 | + def get_euler_angles(self,gx,gy,gz,ax,ay,az): |
| 162 | + |
| 163 | + Ki=const(0.008) |
| 164 | + Kp=const(10.0) |
| 165 | + halfT=const(0.002127) |
| 166 | + |
| 167 | + q0 = 1.0 |
| 168 | + q1 = 0.0 |
| 169 | + q2 = 0.0 |
| 170 | + q3 = 0.0 #/** quaternion of sensor frame relative to auxiliary frame */ |
| 171 | + |
| 172 | + exInt=0.0 |
| 173 | + eyInt=0.0 |
| 174 | + ezInt=0.0 |
| 175 | + |
| 176 | + q0q0 = q0*q0 |
| 177 | + q0q1 = q0*q1 |
| 178 | + q0q2 = q0*q2 |
| 179 | + |
| 180 | + q1q1 = q1*q1 |
| 181 | + q1q3 = q1*q3 |
| 182 | + |
| 183 | + q2q2 = q2*q2 |
| 184 | + q2q3 = q2*q3 |
| 185 | + |
| 186 | + q3q3 = q3*q3 |
| 187 | + |
| 188 | + |
| 189 | + if( ax*ay*az==0): |
| 190 | + return 0 |
| 191 | + #/* 对加速度数据进行归一化处理 */ |
| 192 | + recipNorm = self.invSqrt( ax* ax +ay*ay + az*az) |
| 193 | + ax = ax *recipNorm |
| 194 | + ay = ay *recipNorm |
| 195 | + az = az *recipNorm |
| 196 | + #/* DCM矩阵旋转 */ |
| 197 | + vx = 2*(q1q3 - q0q2) |
| 198 | + vy = 2*(q0q1 + q2q3) |
| 199 | + vz = q0q0 - q1q1 - q2q2 + q3q3 |
| 200 | + #/* 在机体坐标系下做向量叉积得到补偿数据 */ |
| 201 | + ex = ay*vz - az*vy |
| 202 | + ey = az*vx - ax*vz |
| 203 | + ez = ax*vy - ay*vx |
| 204 | + #/* 对误差进行PI计算,补偿角速度 */ |
| 205 | + exInt = exInt + ex * Ki |
| 206 | + eyInt = eyInt + ey * Ki |
| 207 | + ezInt = ezInt + ez * Ki |
| 208 | + |
| 209 | + gx = gx + Kp*ex + exInt |
| 210 | + gy = gy + Kp*ey + eyInt |
| 211 | + gz = gz + Kp*ez + ezInt |
| 212 | + #/* 按照四元素微分公式进行四元素更新 */ |
| 213 | + q0 = q0 + (-q1*gx - q2*gy - q3*gz)*halfT |
| 214 | + q1 = q1 + (q0*gx + q2*gz - q3*gy)*halfT |
| 215 | + q2 = q2 + (q0*gy - q1*gz + q3*gx)*halfT |
| 216 | + q3 = q3 + (q0*gz + q1*gy - q2*gx)*halfT |
| 217 | + |
| 218 | + recipNorm = self.invSqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3) |
| 219 | + |
| 220 | + q0 = q0*recipNorm |
| 221 | + q1 = q1*recipNorm |
| 222 | + q2 = q2*recipNorm |
| 223 | + q3 = q3*recipNorm |
| 224 | + |
| 225 | + roll = math.atan2(2*q2*q3 + 2*q0*q1, -2*q1*q1 - 2*q2*q2 + 1)*57.3 |
| 226 | + pitch = math.asin(2*q1*q3 - 2*q0*q2)*57.3 |
| 227 | + yaw = -math.atan2(2*q1*q2 + 2*q0*q3, -2*q2*q2 -2*q3*q3 + 1)*57.3 |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | + # print("pitch:{:>8.3f} roll:{:>8.3f} yaw:{:>8.3f}\r\n".format(pitch,roll,yaw)) |
| 232 | + return pitch,roll,yaw |
0 commit comments