root / common / IMU.cpp @ 177eb1b610321312bf5141bd36952554e753559b
History | View | Annotate | Download (10.1 kB)
| 1 | #include "IMU.h" |
|---|---|
| 2 | #include "Hardware.h" |
| 3 | |
| 4 | #include <QObject> |
| 5 | #include <QDebug> |
| 6 | #include <QTime> |
| 7 | |
| 8 | #include <math.h> // sqrt, atan |
| 9 | |
| 10 | |
| 11 | #define Kp 2.0f // proportional gain governs rate of convergence to accelerometer/magnetometer |
| 12 | #define Ki 0.005f // integral gain governs rate of convergence of gyroscope biases |
| 13 | |
| 14 | |
| 15 | IMU::IMU(Hardware *hardware): |
| 16 | QObject(), hardware(hardware), enabled(false),
|
| 17 | yaw(0), pitch(0), roll(0), |
| 18 | q0(1), q1(0), q2(0), q3(0), |
| 19 | exInt(0), eyInt(0), ezInt(0), |
| 20 | lastUpdate(0), now(0), |
| 21 | halfT(0)
|
| 22 | {
|
| 23 | // FIXME: how to do this more nicely?
|
| 24 | accRaw[0] = accRaw[1] = accRaw[2] = 0; |
| 25 | gyroRaw[0] = gyroRaw[1] = gyroRaw[2] = 0; |
| 26 | magnRaw[0] = magnRaw[1] = magnRaw[2] = 0; |
| 27 | |
| 28 | accRaw8bit[0] = accRaw8bit[1] = accRaw8bit[2] = 0; |
| 29 | gyroRaw8bit[0] = gyroRaw8bit[1] = gyroRaw8bit[2] = 0; |
| 30 | magnRaw8bit[0] = magnRaw8bit[1] = magnRaw8bit[2] = 0; |
| 31 | |
| 32 | qDebug() << "In" << __FUNCTION__ << ", qX:" << q0 << q1 << q2 << q3; |
| 33 | |
| 34 | } |
| 35 | |
| 36 | |
| 37 | |
| 38 | IMU::~IMU() |
| 39 | {
|
| 40 | // Disable IMU, if still running
|
| 41 | if (enabled && !hardware->enableIMU(false)) { |
| 42 | qWarning("Failed to stop IMU");
|
| 43 | } |
| 44 | } |
| 45 | |
| 46 | |
| 47 | |
| 48 | bool IMU::enable(bool enable) |
| 49 | {
|
| 50 | |
| 51 | qDebug() << "In" << __FUNCTION__ << ", Enable:" << enable; |
| 52 | |
| 53 | if (!hardware) {
|
| 54 | qWarning("No hardware plugin set!");
|
| 55 | return false; |
| 56 | } |
| 57 | |
| 58 | // Enable IMU
|
| 59 | if (enable) {
|
| 60 | |
| 61 | // Do nothing, if already enabled
|
| 62 | if (enabled) {
|
| 63 | return true; |
| 64 | } |
| 65 | |
| 66 | if (!hardware->initIMU(this)) { |
| 67 | qWarning("Failed to initialize IMU");
|
| 68 | return false; |
| 69 | } |
| 70 | |
| 71 | hardware->enableIMU(true);
|
| 72 | enabled = true;
|
| 73 | return true; |
| 74 | } |
| 75 | |
| 76 | // Disable IMU
|
| 77 | |
| 78 | // Do nothing if already disabled
|
| 79 | if (!enabled) {
|
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | if (!hardware->enableIMU(false)) { |
| 84 | qWarning("Failed to stop IMU");
|
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | enabled = false;
|
| 89 | |
| 90 | return true; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | |
| 95 | double *IMU::getYawPitchRoll(void) |
| 96 | {
|
| 97 | qDebug() << "In" << __FUNCTION__;
|
| 98 | |
| 99 | double *data, *dp;
|
| 100 | |
| 101 | data = new double[3]; |
| 102 | dp = data; |
| 103 | |
| 104 | *dp++ = yaw; |
| 105 | *dp++ = pitch; |
| 106 | *dp++ = roll; |
| 107 | |
| 108 | return data;
|
| 109 | } |
| 110 | |
| 111 | |
| 112 | |
| 113 | |
| 114 | QByteArray *IMU::get9DoFRaw(void)
|
| 115 | {
|
| 116 | qDebug() << "In" << __FUNCTION__;
|
| 117 | |
| 118 | QByteArray *data; |
| 119 | |
| 120 | // One byte for each 9DoF value
|
| 121 | data = new QByteArray(9, 0); |
| 122 | |
| 123 | // Scale the raw values to 1 byte (gyro values from millidegrees/sec to degrees/sec)
|
| 124 | (*data)[0] = accRaw8bit[0]; |
| 125 | (*data)[1] = accRaw8bit[1]; |
| 126 | (*data)[2] = accRaw8bit[2]; |
| 127 | (*data)[3] = gyroRaw8bit[0]; |
| 128 | (*data)[4] = gyroRaw8bit[1]; |
| 129 | (*data)[5] = gyroRaw8bit[2]; |
| 130 | (*data)[6] = magnRaw8bit[0]; |
| 131 | (*data)[7] = magnRaw8bit[1]; |
| 132 | (*data)[8] = magnRaw8bit[2]; |
| 133 | |
| 134 | return data;
|
| 135 | } |
| 136 | |
| 137 | |
| 138 | |
| 139 | void IMU::pushSensorData(quint8 raw8bit[9], double data[9]) |
| 140 | {
|
| 141 | |
| 142 | qDebug() << "In" << __FUNCTION__ << ", data: " << data[0] << data[1] << data[2] << data[3] << data[4] << data[5] << data[6] << data[7] << data[8]; |
| 143 | |
| 144 | // Accelerometer data
|
| 145 | accRaw[0] = data[0]; |
| 146 | accRaw[1] = data[1]; |
| 147 | accRaw[2] = data[2]; |
| 148 | |
| 149 | // Gyroscope data
|
| 150 | gyroRaw[0] = data[3]; |
| 151 | gyroRaw[1] = data[4]; |
| 152 | gyroRaw[2] = data[5]; |
| 153 | |
| 154 | // Magnetometer data
|
| 155 | magnRaw[0] = data[6]; |
| 156 | magnRaw[1] = data[7]; |
| 157 | magnRaw[2] = data[8]; |
| 158 | |
| 159 | // 8bit Accelerometer data
|
| 160 | accRaw8bit[0] = raw8bit[0]; |
| 161 | accRaw8bit[1] = raw8bit[1]; |
| 162 | accRaw8bit[2] = raw8bit[2]; |
| 163 | |
| 164 | // 8bit Gyroscope data
|
| 165 | gyroRaw8bit[0] = raw8bit[3]; |
| 166 | gyroRaw8bit[1] = raw8bit[4]; |
| 167 | gyroRaw8bit[2] = raw8bit[5]; |
| 168 | |
| 169 | // 8bit Magnetometer data
|
| 170 | magnRaw8bit[0] = raw8bit[6]; |
| 171 | magnRaw8bit[1] = raw8bit[7]; |
| 172 | magnRaw8bit[2] = raw8bit[8]; |
| 173 | |
| 174 | doIMUCalc(); |
| 175 | } |
| 176 | |
| 177 | |
| 178 | |
| 179 | void IMU::pushYawPitchRoll(double yaw, double pitch, double roll) |
| 180 | {
|
| 181 | this->yaw = yaw;
|
| 182 | this->pitch = pitch;
|
| 183 | this->roll = roll;
|
| 184 | } |
| 185 | |
| 186 | |
| 187 | |
| 188 | /*
|
| 189 | * Update yaw/pitch/roll |
| 190 | */ |
| 191 | void IMU::doIMUCalc()
|
| 192 | {
|
| 193 | //qDebug() << "In" << __FUNCTION__;
|
| 194 | |
| 195 | // Update yaw/pitch/roll
|
| 196 | /*
|
| 197 | * Taken from FreeIMU: http://www.varesano.net/projects/hardware/FreeIMU |
| 198 | */ |
| 199 | double q[4]; // quaternion |
| 200 | double gx, gy, gz; // estimated gravity direction |
| 201 | getQ(q); |
| 202 | |
| 203 | //qDebug() << "In" << __FUNCTION__ << ", q: " << q[0] << q[1] << q[2] << q[3];
|
| 204 | |
| 205 | gx = 2 * (q[1]*q[3] - q[0]*q[2]); |
| 206 | gy = 2 * (q[0]*q[1] + q[2]*q[3]); |
| 207 | gz = q[0]*q[0] - q[1]*q[1] - q[2]*q[2] + q[3]*q[3]; |
| 208 | |
| 209 | //qDebug() << "In" << __FUNCTION__ << ", gravity: " << gx << gy << gz;
|
| 210 | |
| 211 | yaw = atan2(2 * q[1] * q[2] - 2 * q[0] * q[3], 2 * q[0]*q[0] + 2 * q[1] * q[1] - 1) * 180/M_PI; |
| 212 | if (isnan(yaw)) {
|
| 213 | yaw = 0;
|
| 214 | } |
| 215 | pitch = atan(gx / sqrt(gy*gy + gz*gz)) * 180/M_PI;
|
| 216 | if (isnan(pitch)) {
|
| 217 | pitch = 0;
|
| 218 | } |
| 219 | roll = atan(gy / sqrt(gx*gx + gz*gz)) * 180/M_PI;
|
| 220 | if (isnan(roll)) {
|
| 221 | roll = 0;
|
| 222 | } |
| 223 | |
| 224 | qDebug() << "In" << __FUNCTION__ << ", yaw/pitch/roll:" << yaw << pitch << roll; |
| 225 | } |
| 226 | |
| 227 | |
| 228 | /*
|
| 229 | * Taken from FreeIMU: http://www.varesano.net/projects/hardware/FreeIMU |
| 230 | */ |
| 231 | //=====================================================================================================
|
| 232 | // AHRS.c
|
| 233 | // S.O.H. Madgwick
|
| 234 | // 25th August 2010
|
| 235 | //=====================================================================================================
|
| 236 | // Description:
|
| 237 | //
|
| 238 | // Quaternion implementation of the 'DCM filter' [Mayhony et al]. Incorporates the magnetic distortion
|
| 239 | // compensation algorithms from my filter [Madgwick] which eliminates the need for a reference
|
| 240 | // direction of flux (bx bz) to be predefined and limits the effect of magnetic distortions to yaw
|
| 241 | // axis only.
|
| 242 | //
|
| 243 | // User must define 'halfT' as the (sample period / 2), and the filter gains 'Kp' and 'Ki'.
|
| 244 | //
|
| 245 | // Global variables 'q0', 'q1', 'q2', 'q3' are the quaternion elements representing the estimated
|
| 246 | // orientation. See my report for an overview of the use of quaternions in this application.
|
| 247 | //
|
| 248 | // User must call 'AHRSupdate()' every sample period and parse calibrated gyroscope ('gx', 'gy', 'gz'),
|
| 249 | // accelerometer ('ax', 'ay', 'ay') and magnetometer ('mx', 'my', 'mz') data. Gyroscope units are
|
| 250 | // radians/second, accelerometer and magnetometer units are irrelevant as the vector is normalised.
|
| 251 | //
|
| 252 | //=====================================================================================================
|
| 253 | void IMU::AHRSupdate(double gx, double gy, double gz, double ax, double ay, double az, double mx, double my, double mz) { |
| 254 | double norm;
|
| 255 | double hx, hy, hz, bx, bz;
|
| 256 | double vx, vy, vz, wx, wy, wz;
|
| 257 | double ex, ey, ez;
|
| 258 | |
| 259 | //qDebug() << "In" << __FUNCTION__ << ", data: " << gx << gy << gz << ax << ay << az << mx << my << mz;
|
| 260 | |
| 261 | //qDebug() << "In" << __FUNCTION__ << ", qX:" << q0 << q1 << q2 << q3;
|
| 262 | |
| 263 | // auxiliary variables to reduce number of repeated operations
|
| 264 | double q0q0 = q0*q0;
|
| 265 | double q0q1 = q0*q1;
|
| 266 | double q0q2 = q0*q2;
|
| 267 | double q0q3 = q0*q3;
|
| 268 | double q1q1 = q1*q1;
|
| 269 | double q1q2 = q1*q2;
|
| 270 | double q1q3 = q1*q3;
|
| 271 | double q2q2 = q2*q2;
|
| 272 | double q2q3 = q2*q3;
|
| 273 | double q3q3 = q3*q3;
|
| 274 | |
| 275 | //qDebug() << "In" << __FUNCTION__ << ", qXqY:" << q0q0 << q0q1 << q0q2 << q0q3 << q1q1 << q1q2 << q1q3 << q2q2 << q2q3 << q3q3;
|
| 276 | |
| 277 | // normalise the measurements
|
| 278 | |
| 279 | norm = sqrt(ax*ax + ay*ay + az*az); |
| 280 | if (norm == 0) { |
| 281 | norm = 1;
|
| 282 | } |
| 283 | ax = ax / norm; |
| 284 | ay = ay / norm; |
| 285 | az = az / norm; |
| 286 | |
| 287 | //qDebug() << "In" << __FUNCTION__ << ", acc:" << norm << ax << ay << az;
|
| 288 | |
| 289 | // Get current time in milliseconds
|
| 290 | QTime time = QTime::currentTime(); |
| 291 | now = time.hour() * 3600 * 1000 + time.minute() * 60 * 1000 + time.second() * 1000 + time.msec(); |
| 292 | |
| 293 | // Estimate the lastUpdate if running this for the first time
|
| 294 | if (lastUpdate == 0) { |
| 295 | lastUpdate = now - 10;
|
| 296 | } |
| 297 | |
| 298 | // If midnight just passed by, calculate the correct halfT
|
| 299 | if (now < lastUpdate) {
|
| 300 | halfT = (((24 * 3600 * 1000) - lastUpdate) + now) / 2000.0; |
| 301 | } else {
|
| 302 | halfT = (now - lastUpdate) / 2000.0; |
| 303 | } |
| 304 | lastUpdate = now; |
| 305 | |
| 306 | //qDebug() << "In" << __FUNCTION__ << ", halfT:" << halfT;
|
| 307 | |
| 308 | norm = sqrt(mx*mx + my*my + mz*mz); |
| 309 | if (norm == 0) { |
| 310 | norm = 1;
|
| 311 | } |
| 312 | mx = mx / norm; |
| 313 | my = my / norm; |
| 314 | mz = mz / norm; |
| 315 | |
| 316 | //qDebug() << "In" << __FUNCTION__ << ", magn:" << mx << my << mz;
|
| 317 | |
| 318 | // compute reference direction of flux
|
| 319 | hx = 2*mx*(0.5 - q2q2 - q3q3) + 2*my*(q1q2 - q0q3) + 2*mz*(q1q3 + q0q2); |
| 320 | hy = 2*mx*(q1q2 + q0q3) + 2*my*(0.5 - q1q1 - q3q3) + 2*mz*(q2q3 - q0q1); |
| 321 | hz = 2*mx*(q1q3 - q0q2) + 2*my*(q2q3 + q0q1) + 2*mz*(0.5 - q1q1 - q2q2); |
| 322 | bx = sqrt((hx*hx) + (hy*hy)); |
| 323 | bz = hz; |
| 324 | |
| 325 | //qDebug() << "In" << __FUNCTION__ << ", direction of flux:" << hx << hy << hz;
|
| 326 | |
| 327 | // estimated direction of gravity and flux (v and w)
|
| 328 | vx = 2*(q1q3 - q0q2);
|
| 329 | vy = 2*(q0q1 + q2q3);
|
| 330 | vz = q0q0 - q1q1 - q2q2 + q3q3; |
| 331 | wx = 2*bx*(0.5 - q2q2 - q3q3) + 2*bz*(q1q3 - q0q2); |
| 332 | wy = 2*bx*(q1q2 - q0q3) + 2*bz*(q0q1 + q2q3); |
| 333 | wz = 2*bx*(q0q2 + q1q3) + 2*bz*(0.5 - q1q1 - q2q2); |
| 334 | |
| 335 | //qDebug() << "In" << __FUNCTION__ << ", direction of grav:" << vx << vy << vz;
|
| 336 | //qDebug() << "In" << __FUNCTION__ << ", direction of flux:" << wx << wy << wz;
|
| 337 | |
| 338 | // error is sum of cross product between reference direction of fields and direction measured by sensors
|
| 339 | ex = (ay*vz - az*vy) + (my*wz - mz*wy); |
| 340 | ey = (az*vx - ax*vz) + (mz*wx - mx*wz); |
| 341 | ez = (ax*vy - ay*vx) + (mx*wy - my*wx); |
| 342 | |
| 343 | //qDebug() << "In" << __FUNCTION__ << ", sensor error:" << ex << ey << ez;
|
| 344 | |
| 345 | // integral error scaled integral gain
|
| 346 | exInt = exInt + ex*Ki; |
| 347 | eyInt = eyInt + ey*Ki; |
| 348 | ezInt = ezInt + ez*Ki; |
| 349 | |
| 350 | //qDebug() << "In" << __FUNCTION__ << ", integral error:" << exInt << eyInt << ezInt;
|
| 351 | |
| 352 | // adjusted gyroscope measurements
|
| 353 | gx = gx + Kp*ex + exInt; |
| 354 | gy = gy + Kp*ey + eyInt; |
| 355 | gz = gz + Kp*ez + ezInt; |
| 356 | |
| 357 | //qDebug() << "In" << __FUNCTION__ << ", gyro:" << gx << gy << gz;
|
| 358 | |
| 359 | // integrate quaternion rate and normalise
|
| 360 | q0 = q0 + (-q1*gx - q2*gy - q3*gz)*halfT; |
| 361 | q1 = q1 + (q0*gx + q2*gz - q3*gy)*halfT; |
| 362 | q2 = q2 + (q0*gy - q1*gz + q3*gx)*halfT; |
| 363 | q3 = q3 + (q0*gz + q1*gy - q2*gx)*halfT; |
| 364 | |
| 365 | //qDebug() << "In" << __FUNCTION__ << ", qX1:" << q0 << q1 << q2 << q3;
|
| 366 | |
| 367 | // normalise quaternion
|
| 368 | norm = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3); |
| 369 | if (norm == 0) { |
| 370 | norm = 1;
|
| 371 | } |
| 372 | q0 = q0 / norm; |
| 373 | q1 = q1 / norm; |
| 374 | q2 = q2 / norm; |
| 375 | q3 = q3 / norm; |
| 376 | |
| 377 | //qDebug() << "In" << __FUNCTION__ << ", qX2:" << q0 << q1 << q2 << q3;
|
| 378 | } |
| 379 | |
| 380 | |
| 381 | |
| 382 | /*
|
| 383 | * Taken from FreeIMU: http://www.varesano.net/projects/hardware/FreeIMU |
| 384 | */ |
| 385 | void IMU::getQ(double * q) { |
| 386 | |
| 387 | // gyro values are expressed in millidegrees/sec, the * M_PI/180 will convert it to radians/sec
|
| 388 | AHRSupdate((gyroRaw[0] / (double)1000) * M_PI/180, |
| 389 | (gyroRaw[1] / (double)1000) * M_PI/180, |
| 390 | (gyroRaw[2] / (double)1000) * M_PI/180, |
| 391 | accRaw[0], accRaw[1], accRaw[2], |
| 392 | magnRaw[0], magnRaw[1], magnRaw[2]); |
| 393 | q[0] = q0;
|
| 394 | q[1] = q1;
|
| 395 | q[2] = q2;
|
| 396 | q[3] = q3;
|
| 397 | } |