#include <Wire.h> #include <math.h> #define sampleFreq 33.30f // sample frequency in Hz #define twoKpDef (2.0f * 0.5f) // 2 * proportional gain #define twoKiDef (2.0f * 0.0f) // 2 * integral gain #define betaDef 0.1f //--------------------------------------------------------------------------------------------------- // Variable definitions volatile float twoKp = twoKpDef; // 2 * proportional gain (Kp) volatile float twoKi = twoKiDef; // 2 * integral gain (Ki) volatile float beta = betaDef; volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame volatile float integralFBx = 0.0f, integralFBy = 0.0f, integralFBz = 0.0f; // integral error terms scaled by Ki float phi; float theta; float psi; extern volatile float q0, q1, q2, q3; unsigned long time_ms = 0; void setup() { Serial.begin(9600); Wire.begin(); Wire.beginTransmission(0x68); Wire.write(0x6B); Wire.write(0); Wire.endTransmission(true); } void loop() { int x,y,z = 0; int t = 0; int a,b,c = 0; Wire.beginTransmission(0x68); Wire.write(0x3B); Wire.requestFrom(0x68, 14, true); Wire.endTransmission(true); x = Wire.read() << 8 | Wire.read(); y = Wire.read() << 8 | Wire.read(); z = Wire.read() << 8 | Wire.read(); t = Wire.read() << 8 | Wire.read(); a = Wire.read() << 8 | Wire.read(); b = Wire.read() << 8 | Wire.read(); c = Wire.read() << 8 | Wire.read(); double ax,ay,az; double gx,gy,gz; ax = x/16384.00f; ay = y/16384.00f; az = z/16384.00f; gx = a*3.1415926f/131.00f/180.00f; gy = b*3.1415926f/131.00f/180.00f; gz = c*3.1415926f/131.00f/180.00f; MadgwickAHRSupdateIMU(gx, gy, gz, ax, ay, az); time_ms = millis(); // Serial.print(time_ms); Serial.print(" "); Serial.print(q0); Serial.print(" "); Serial.print(q1); Serial.print(" "); Serial.print(q2); Serial.print(" "); Serial.print(q3); Serial.println(" "); // Serial.print(psi); // Serial.print(" "); // Serial.print(theta); // Serial.print(" "); // Serial.print(phi); // Serial.println(" "); // Serial.print(ax); // Serial.print(" "); // Serial.print(ay); // Serial.print(" "); // Serial.print(az); // Serial.println(" "); // Serial.print(gx); // Serial.print(" "); // Serial.print(gy); // Serial.print(" "); // Serial.print(gz); // Serial.println(" "); } void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) { float recipNorm; float halfvx, halfvy, halfvz; float halfex, halfey, halfez; float qa, qb, qc; // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) { // Normalise accelerometer measurement recipNorm = invSqrt(ax * ax + ay * ay + az * az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // Estimated direction of gravity and vector perpendicular to magnetic flux halfvx = q1 * q3 - q0 * q2; halfvy = q0 * q1 + q2 * q3; halfvz = q0 * q0 - 0.5f + q3 * q3; // Error is sum of cross product between estimated and measured direction of gravity halfex = (ay * halfvz - az * halfvy); halfey = (az * halfvx - ax * halfvz); halfez = (ax * halfvy - ay * halfvx); // Compute and apply integral feedback if enabled if(twoKi > 0.0f) { integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki integralFBy += twoKi * halfey * (1.0f / sampleFreq); integralFBz += twoKi * halfez * (1.0f / sampleFreq); gx += integralFBx; // apply integral feedback gy += integralFBy; gz += integralFBz; } else { integralFBx = 0.0f; // prevent integral windup integralFBy = 0.0f; integralFBz = 0.0f; } // Apply proportional feedback gx += twoKp * halfex; gy += twoKp * halfey; gz += twoKp * halfez; } // Integrate rate of change of quaternion gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors gy *= (0.5f * (1.0f / sampleFreq)); gz *= (0.5f * (1.0f / sampleFreq)); qa = q0; qb = q1; qc = q2; q0 += (-qb * gx - qc * gy - q3 * gz); q1 += (qa * gx + qc * gz - q3 * gy); q2 += (qa * gy - qb * gz + q3 * gx); q3 += (qa * gz + qb * gy - qc * gx); // Normalise quaternion recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= recipNorm; q1 *= recipNorm; q2 *= recipNorm; q3 *= recipNorm; phi = atan(2*(q1*q2+q0*q3)/(-2*q2*q2+1-2*q3*q3)); theta = asin(2*(q1*q3-q0*q2)); psi = atan(2*(q1*q2+q0*q3)/(-2*q1*q1+1-2*q2*q2)); } void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) { float recipNorm; float s0, s1, s2, s3; float qDot1, qDot2, qDot3, qDot4; float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3; // Rate of change of quaternion from gyroscope qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz); qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy); qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx); qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx); // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) { // Normalise accelerometer measurement recipNorm = invSqrt(ax * ax + ay * ay + az * az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // Auxiliary variables to avoid repeated arithmetic _2q0 = 2.0f * q0; _2q1 = 2.0f * q1; _2q2 = 2.0f * q2; _2q3 = 2.0f * q3; _4q0 = 4.0f * q0; _4q1 = 4.0f * q1; _4q2 = 4.0f * q2; _8q1 = 8.0f * q1; _8q2 = 8.0f * q2; q0q0 = q0 * q0; q1q1 = q1 * q1; q2q2 = q2 * q2; q3q3 = q3 * q3; // Gradient decent algorithm corrective step s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay; s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az; s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az; s3 = 4.0f * q1q1 * q3 - _2q1 * ax + 4.0f * q2q2 * q3 - _2q2 * ay; recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude s0 *= recipNorm; s1 *= recipNorm; s2 *= recipNorm; s3 *= recipNorm; // Apply feedback step qDot1 -= beta * s0; qDot2 -= beta * s1; qDot3 -= beta * s2; qDot4 -= beta * s3; } // Integrate rate of change of quaternion to yield quaternion q0 += qDot1 * (1.0f / sampleFreq); q1 += qDot2 * (1.0f / sampleFreq); q2 += qDot3 * (1.0f / sampleFreq); q3 += qDot4 * (1.0f / sampleFreq); // Normalise quaternion recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= recipNorm; q1 *= recipNorm; q2 *= recipNorm; q3 *= recipNorm; } float invSqrt(float x) { float halfx = 0.5f * x; float y = x; long i = *(long*)&y; i = 0x5f3759df - (i>>1); y = *(float*)&i; y = y * (1.5f - (halfx * y * y)); return y; }
上面的代码是Mahony及Madgwick的姿态解算方式。
#include <Kalman.h> #include <Wire.h> #include <Math.h> float fRad2Deg = 57.295779513f; //将弧度转为角度的乘数 const int MPU = 0x68; //MPU-6050的I2C地址 const int nValCnt = 7; //一次读取寄存器的数量 const int nCalibTimes = 1000; //校准时读数的次数 int calibData[nValCnt]; //校准数据 unsigned long nLastTime = 0; //上一次读数的时间 float fLastRoll = 0.0f; //上一次滤波得到的Roll角 float fLastPitch = 0.0f; //上一次滤波得到的Pitch角 Kalman kalmanRoll; //Roll角滤波器 Kalman kalmanPitch; //Pitch角滤波器 void setup() { Serial.begin(9600); //初始化串口,指定波特率 Wire.begin(); //初始化Wire库 WriteMPUReg(0x6B, 0); //启动MPU6050设备 Calibration(); //执行校准 nLastTime = micros(); //记录当前时间 } void loop() { int readouts[nValCnt]; ReadAccGyr(readouts); //读出测量值 float realVals[7]; Rectify(readouts, realVals); //根据校准的偏移量进行纠正 //计算加速度向量的模长,均以g为单位 float fNorm = sqrt(realVals[0] * realVals[0] + realVals[1] * realVals[1] + realVals[2] * realVals[2]); float fRoll = GetRoll(realVals, fNorm); //计算Roll角 if (realVals[1] > 0) { fRoll = -fRoll; } float fPitch = GetPitch(realVals, fNorm); //计算Pitch角 if (realVals[0] < 0) { fPitch = -fPitch; } //计算两次测量的时间间隔dt,以秒为单位 unsigned long nCurTime = micros(); float dt = (double)(nCurTime - nLastTime) / 1000000.0; //对Roll角和Pitch角进行卡尔曼滤波 float fNewRoll = kalmanRoll.getAngle(fRoll, realVals[4], dt); float fNewPitch = kalmanPitch.getAngle(fPitch, realVals[5], dt); //跟据滤波值计算角度速 float fRollRate = (fNewRoll - fLastRoll) / dt; float fPitchRate = (fNewPitch - fLastPitch) / dt; //更新Roll角和Pitch角 fLastRoll = fNewRoll; fLastPitch = fNewPitch; //更新本次测的时间 nLastTime = nCurTime; //向串口打印输出Roll角和Pitch角,运行时在Arduino的串口监视器中查看 Serial.print("Roll:"); Serial.print(fNewRoll); Serial.print('('); Serial.print(fRollRate); Serial.print("), Pitch:"); Serial.print(fNewPitch); Serial.print('('); Serial.print(fPitchRate); Serial.print(") "); delay(10); } //向MPU6050写入一个字节的数据 //指定寄存器地址与一个字节的值 void WriteMPUReg(int nReg, unsigned char nVal) { Wire.beginTransmission(MPU); Wire.write(nReg); Wire.write(nVal); Wire.endTransmission(true); } //从MPU6050读出一个字节的数据 //指定寄存器地址,返回读出的值 unsigned char ReadMPUReg(int nReg) { Wire.beginTransmission(MPU); Wire.write(nReg); Wire.requestFrom(MPU, 1, true); Wire.endTransmission(true); return Wire.read(); } //从MPU6050读出加速度计三个分量、温度和三个角速度计 //保存在指定的数组中 void ReadAccGyr(int *pVals) { Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.requestFrom(MPU, nValCnt * 2, true); Wire.endTransmission(true); for (long i = 0; i < nValCnt; ++i) { pVals[i] = Wire.read() << 8 | Wire.read(); } } //对大量读数进行统计,校准平均偏移量 void Calibration() { float valSums[7] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0}; //先求和 for (int i = 0; i < nCalibTimes; ++i) { int mpuVals[nValCnt]; ReadAccGyr(mpuVals); for (int j = 0; j < nValCnt; ++j) { valSums[j] += mpuVals[j]; } } //再求平均 for (int i = 0; i < nValCnt; ++i) { calibData[i] = int(valSums[i] / nCalibTimes); } calibData[2] += 16384; //设芯片Z轴竖直向下,设定静态工作点。 } //算得Roll角。算法见文档。 float GetRoll(float *pRealVals, float fNorm) { float fNormXZ = sqrt(pRealVals[0] * pRealVals[0] + pRealVals[2] * pRealVals[2]); float fCos = fNormXZ / fNorm; return acos(fCos) * fRad2Deg; } //算得Pitch角。算法见文档。 float GetPitch(float *pRealVals, float fNorm) { float fNormYZ = sqrt(pRealVals[1] * pRealVals[1] + pRealVals[2] * pRealVals[2]); float fCos = fNormYZ / fNorm; return acos(fCos) * fRad2Deg; } //对读数进行纠正,消除偏移,并转换为物理量。公式见文档。 void Rectify(int *pReadout, float *pRealVals) { for (int i = 0; i < 3; ++i) { pRealVals[i] = (float)(pReadout[i] - calibData[i]) / 16384.0f; } pRealVals[3] = pReadout[3] / 340.0f + 36.53; for (int i = 4; i < 7; ++i) { pRealVals[i] = (float)(pReadout[i] - calibData[i]) / 131.0f; } }
上面的代码是通过卡尔曼滤波获得传感器姿态。