RTIMU::RTIMU(RTIMUSettings *settings)
{
    m_settings = settings;

    m_compassCalibrationMode = false;
    m_accelCalibrationMode = false;

    m_runtimeMagCalValid = false;

    for (int i = 0; i < 3; i++) {
        m_runtimeMagCalMax[i] = -1000;
        m_runtimeMagCalMin[i] = 1000;
    }

    switch (m_settings->m_fusionType) {
    case RTFUSION_TYPE_KALMANSTATE4:
        m_fusion = new RTFusionKalman4();
        break;

    case RTFUSION_TYPE_RTQF:
        m_fusion = new RTFusionRTQF();
        break;

    default:
        m_fusion = new RTFusion();
        break;
    }
    HAL_INFO1("Using fusion algorithm %s\n", RTFusion::fusionName(m_settings->m_fusionType));
}
RTFusionAHRS::RTFusionAHRS()
{
    float beta = sqrt(3.0f / 4.0f) * GyroMeasError;   // compute beta
    float zeta = sqrt(3.0f / 4.0f) * GyroMeasDrift;   // compute zeta, the other free parameter in the Madgwick scheme usually set to a small or zero value

    m_beta = beta;
    m_zeta = zeta;

    reset();

    m_enableAccel = true;
    m_enableCompass = true;
    m_enableGyro = true;
    
    if (m_debug) {
        HAL_INFO("\n------\n");
        HAL_INFO1("IMU beta %f\n", m_beta);
        HAL_INFO1("IMU zeta %f\n", m_zeta);
    }

}
Example #3
0
RTIMU::RTIMU(RTIMUSettings *settings)
{
    m_settings = settings;
    m_compassCalibrationMode = false;
    m_accelCalibrationMode = false;
    m_runtimeMagCalValid = false;
 
    m_runtimeMagCalMax = -1000.0f;
    m_runtimeMagCalMin =  1000.0f;
    //m_runtimeMagCalMax.setX(-1000);
    //m_runtimeMagCalMax.setY(-1000);
    //m_runtimeMagCalMax.setZ(-1000);
    //m_runtimeMagCalMin.setX( 1000);
    //m_runtimeMagCalMin.setY( 1000);
    //m_runtimeMagCalMin.setZ( 1000);

	m_gyroCalibrationMode = false;
    m_temperatureCalibrationMode = false;

	m_gyroRunTimeCalibrationEnable = true;
	m_gyroManualCalibrationEnable = false;
	m_accelRunTimeCalibrationEnable = false;
	m_compassRunTimeCalibrationEnable = false;
	
    switch (m_settings->m_fusionType) {
    case RTFUSION_TYPE_KALMANSTATE4:
        m_fusion = new RTFusionKalman4();
        break;

    case RTFUSION_TYPE_RTQF:
        m_fusion = new RTFusionRTQF();
        break;

    case RTFUSION_TYPE_AHRS:
        m_fusion = new RTFusionAHRS();
        break;
        
    default:
        m_fusion = new RTFusion();
        break;
    }
    HAL_INFO1("Using fusion algorithm %s\n", RTFusion::fusionName(m_settings->m_fusionType));
	
	m_compassAverageX = new RunningAverage(20); // 0.1f * m_sampleRate;
	m_compassAverageY = new RunningAverage(20);
	m_compassAverageZ = new RunningAverage(20);
}
Example #4
0
RTIMU::RTIMU(RTIMUSettings *settings)
{
    m_settings = settings;

    m_compassCalibrationMode = false;
    m_accelCalibrationMode = false;

    switch (m_settings->m_fusionType) {
    case RTFUSION_TYPE_KALMANSTATE4:
        m_fusion = new RTFusionKalman4();
        break;

    case RTFUSION_TYPE_RTQF:
        m_fusion = new RTFusionRTQF();
        break;

    default:
        m_fusion = new RTFusion();
        break;
    }
    HAL_INFO1("Using fusion algorithm %s\n", RTFusion::fusionName(m_settings->m_fusionType));
}
Example #5
0
void RTFusionKalman4::newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings)
{
    if (m_enableGyro)
        m_gyro = data.gyro;
    else
        m_gyro = RTVector3();
    
    m_accel = data.accel;
    m_compass = data.compass;
    m_compassValid = data.compassValid;

    if (m_firstTime) {
        m_lastFusionTime = data.timestamp;
        calculatePose(m_accel, m_compass, settings->m_compassAdjDeclination);
        m_Fk.fill(0);

        //  init covariance matrix to something

        m_Pkk.fill(0);
        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++)
                m_Pkk.setVal(i,j, 0.5);

        // initialize the observation model Hk
        // Note: since the model is the state vector, this is an identity matrix so it won't be used

        //  initialize the poses

        m_stateQ.fromEuler(m_measuredPose);
        m_fusionQPose = m_stateQ;
        m_fusionPose = m_measuredPose;
        m_firstTime = false;
    } else {
        m_timeDelta = (RTFLOAT)(data.timestamp - m_lastFusionTime) / (RTFLOAT)1000000;
        m_lastFusionTime = data.timestamp;
        if (m_timeDelta <= 0)
            return;

        if (m_debug) {
            HAL_INFO("\n------\n");
            HAL_INFO1("IMU update delta time: %f\n", m_timeDelta);
        }

        calculatePose(data.accel, data.compass, settings->m_compassAdjDeclination);

        predict();
        update();
        m_stateQ.toEuler(m_fusionPose);
        m_fusionQPose = m_stateQ;

        if (m_debug | settings->m_fusionDebug) {
            HAL_INFO(RTMath::displayRadians("Measured pose", m_measuredPose));
            HAL_INFO(RTMath::displayRadians("Kalman pose", m_fusionPose));
            HAL_INFO(RTMath::displayRadians("Measured quat", m_measuredPose));
            HAL_INFO(RTMath::display("Kalman quat", m_stateQ));
            HAL_INFO(RTMath::display("Error quat", m_stateQError));
         }
    }
    data.fusionPoseValid = true;
    data.fusionQPoseValid = true;
    data.fusionPose = m_fusionPose;
    data.fusionQPose = m_fusionQPose;
}
Example #6
0
bool RTIMUSettings::loadSettings()
{
    char buf[200];
    char key[200];
    char val[200];
    RTFLOAT ftemp;

    //  preset general defaults

    m_imuType = RTIMU_TYPE_MPU6050;
    m_I2CSlaveAddress = 0;
    m_I2CBus = 1;
    m_fusionType = RTFUSION_TYPE_RTQF;
    m_compassCalValid = false;

    //  MPU6050 defaults

    m_MPU6050GyroAccelSampleRate = 50;
    m_MPU6050CompassSampleRate = 25;
    m_MPU6050GyroAccelLpf = MPU6050_LPF_20;
    m_MPU6050GyroFsr = MPU6050_GYROFSR_1000;
    m_MPU6050AccelFsr = MPU6050_ACCELFSR_8;

    //  check to see if settings file exists

    if (!(m_fd = fopen(m_filename, "r"))) {
        HAL_INFO("Settings file not found. Using defaults and creating settings file\n");
        return saveSettings();
    }

    while (fgets(buf, 200, m_fd)) {
        if ((buf[0] == '#') || (buf[0] == ' ') || (buf[0] == '\n'))
            // just a comment
            continue;

        if (sscanf(buf, "%[^=]=%s", key, val) != 2) {
            HAL_ERROR1("Bad line in settings file: %s\n", buf);
            fclose(m_fd);
            return false;
        }

        //  now decode keys

        //  general config

        if (strcmp(key, RTIMULIB_IMU_TYPE) == 0) {
            m_imuType = atoi(val);
        } else if (strcmp(key, RTIMULIB_FUSION_TYPE) == 0) {
            m_fusionType = atoi(val);
        } else if (strcmp(key, RTIMULIB_I2C_BUS) == 0) {
            m_I2CBus = atoi(val);
        } else if (strcmp(key, RTIMULIB_I2C_SLAVEADDRESS) == 0) {
            m_I2CSlaveAddress = atoi(val);

        // compass calibration

        } else if (strcmp(key, RTIMULIB_COMPASSCAL_VALID) == 0) {
            m_compassCalValid = strcmp(val, "true") == 0;
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINX) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMin.setX(ftemp);
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINY) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMin.setY(ftemp);
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINZ) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMin.setZ(ftemp);
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXX) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMax.setX(ftemp);
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXY) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMax.setY(ftemp);
        } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXZ) == 0) {
            sscanf(val, "%f", &ftemp);
            m_compassCalMax.setZ(ftemp);

        //  MPU6050 settings

        } else if (strcmp(key, RTIMULIB_MPU6050_GYROACCEL_SAMPLERATE) == 0) {
            m_MPU6050GyroAccelSampleRate = atoi(val);
        } else if (strcmp(key, RTIMULIB_MPU6050_COMPASS_SAMPLERATE) == 0) {
            m_MPU6050CompassSampleRate = atoi(val);
        } else if (strcmp(key, RTIMULIB_MPU6050_GYROACCEL_LPF) == 0) {
            m_MPU6050GyroAccelLpf = atoi(val);
        } else if (strcmp(key, RTIMULIB_MPU6050_GYRO_FSR) == 0) {
            m_MPU6050GyroFsr = atoi(val);
        } else if (strcmp(key, RTIMULIB_MPU6050_ACCEL_FSR) == 0) {
            m_MPU6050AccelFsr = atoi(val);

        //  Handle unrecognized key

        } else {
            HAL_ERROR1("Unrecognized key in settings file: %s\n", buf);
        }
    }
    HAL_INFO1("Settings file %s loaded\n", m_filename);
    fclose(m_fd);
    return saveSettings();                                  // make sure settings file is correct and complete
}
bool RTIMUMPU9255::IMUInit()
{
    unsigned char result;

    m_firstTime = true;

#ifdef MPU9255_CACHE_MODE
    m_cacheIn = m_cacheOut = m_cacheCount = 0;
#endif

    // set validity flags

    m_imuData.fusionPoseValid = false;
    m_imuData.fusionQPoseValid = false;
    m_imuData.gyroValid = true;
    m_imuData.accelValid = true;
    m_imuData.compassValid = true;
    m_imuData.motion = true;
    m_imuData.IMUtemperatureValid = false;
    m_imuData.IMUtemperature = 0.0;
    m_imuData.humidityValid = false;
    m_imuData.humidity = -1.0;
    m_imuData.humidityTemperatureValid = false;
    m_imuData.humidityTemperature = 0.0;
    m_imuData.pressureValid = false;
    m_imuData.pressure = 0.0;
    m_imuData.pressureTemperatureValid = false;
    m_imuData.pressureTemperature = 0.0;
	
    //  configure IMU

    m_slaveAddr = m_settings->m_I2CSlaveAddress;

    setSampleRate(m_settings->m_MPU9255GyroAccelSampleRate);
    setCompassRate(m_settings->m_MPU9255CompassSampleRate);
    setGyroLpf(m_settings->m_MPU9255GyroLpf);
    setAccelLpf(m_settings->m_MPU9255AccelLpf);
    setGyroFsr(m_settings->m_MPU9255GyroFsr);
    setAccelFsr(m_settings->m_MPU9255AccelFsr);

    setCalibrationData();

    //  enable the bus

    if (!m_settings->HALOpen())
        return false;

    //  reset the MPU9255

    if (!m_settings->HALWrite(m_slaveAddr, MPU9255_PWR_MGMT_1, 0x80, "Failed to initiate MPU9255 reset"))
        return false;

    m_settings->delayMs(100);

    if (!m_settings->HALWrite(m_slaveAddr, MPU9255_PWR_MGMT_1, 0x00, "Failed to stop MPU9255 reset"))
        return false;

    if (!m_settings->HALRead(m_slaveAddr, MPU9255_WHO_AM_I, 1, &result, "Failed to read MPU9255 id"))
        return false;

    if (result != MPU9255_ID) {
        HAL_ERROR2("Incorrect %s id %d\n", IMUName(), result);
        return false;
    }

    //  now configure the various components

    if (!setGyroConfig())
        return false;

    if (!setAccelConfig())
        return false;

    if (!setSampleRate())
        return false;

    if(!compassSetup()) {
        return false;
    }

    if (!setCompassRate())
        return false;

    //  enable the sensors

    if (!m_settings->HALWrite(m_slaveAddr, MPU9255_PWR_MGMT_1, 1, "Failed to set pwr_mgmt_1"))
        return false;

    if (!m_settings->HALWrite(m_slaveAddr, MPU9255_PWR_MGMT_2, 0, "Failed to set pwr_mgmt_2"))
         return false;

    //  select the data to go into the FIFO and enable

    if (!resetFifo())
        return false;

    gyroBiasInit();

    HAL_INFO1("%s init complete\n", IMUName());
    return true;
}
void RTFusionAHRS::newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings)
{

    if (m_debug) {
        HAL_INFO("\n------\n");
        HAL_INFO1("IMU update delta time: %f\n", m_timeDelta);
    }

    m_gyro = data.gyro;
    m_accel = data.accel;
    m_compass = data.compass;
    m_compassValid = data.compassValid;

    if (m_firstTime) {
        
        // adjust for compass declination, compute the correction data only at beginning
        m_sin_theta_half = sin(-settings->m_compassAdjDeclination/2.0f);
        m_cos_theta_half = cos(-settings->m_compassAdjDeclination/2.0f);
        
        m_lastFusionTime = data.timestamp;
        calculatePose(m_accel, m_compass, settings->m_compassAdjDeclination);

        //  initialize the poses

        m_stateQ.fromEuler(m_measuredPose);
        m_fusionQPose = m_stateQ;
        m_fusionPose = m_measuredPose;
        m_firstTime = false;

    } else { // not first time
        m_timeDelta = (RTFLOAT)(data.timestamp - m_lastFusionTime) / (RTFLOAT)1000000;
        m_lastFusionTime = data.timestamp;
        if (m_timeDelta <= 0)
            return;

        calculatePose(data.accel, data.compass, settings->m_compassAdjDeclination);
    
        // =================================================
        //    AHRS
        //
        // Previous Q Pose; short name local variables for readability
        float q1 = m_stateQ.scalar();
        float q2 = m_stateQ.x();
        float q3 = m_stateQ.y();
        float q4 = m_stateQ.z();   

        float ax, ay, az, mx, my, mz, gx, gy, gz;           // accelerometer, magnetometer, gyroscope
        		
        float gerrx, gerry, gerrz; // gyro bias error

        float norm;
        float hx, hy, _2bx, _2bz;
        float s1, s2, s3, s4;
        float qDot1, qDot2, qDot3, qDot4;

        float _2q1mx,_2q1my, _2q1mz,_2q2mx;
        float _4bx, _4bz;
        float _2q1, _2q2, _2q3, _2q4;
        float q1q1, q1q2, q1q3, q1q4, q2q2, q2q3, q2q4, q3q4, q3q3, q4q4;  
        float _4q1, _4q2, _4q3, _8q2, _8q3;
        float _2q3q4, _2q1q3;

        if (m_enableCompass) {
          mx = m_compass.x();
          my = m_compass.y();
          mz = m_compass.z();
	  
        } else {
          mx = 0.0f;
          my = 0.0f;
          mz = 0.0f; 
		}   

        if (m_enableAccel) {
          ax = m_accel.x();
          ay = m_accel.y();
          az = m_accel.z();
        } else {
          ax = 0.0f;
          ay = 0.0f;
          az = 0.0f; }   

       if (m_enableGyro) {
          gx=m_gyro.x();
          gy=m_gyro.y();
          gz=m_gyro.z();
        } else { return; }   // We need to have valid gyroscope data

        ////////////////////////////////////////////////////////////////////////////
        // Regular Algorithm

        // Rate of change of quaternion from gyroscope
        qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz); // s
        qDot2 = 0.5f * ( q1 * gx + q3 * gz - q4 * gy); // x
        qDot3 = 0.5f * ( q1 * gy - q2 * gz + q4 * gx); // y
        qDot4 = 0.5f * ( q1 * gz + q2 * gy - q3 * gx); // z

        if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
            // Use this algorithm if accelerometer is valid 
            // If accelerometer is not valid, update pose based on previous qDot

            // Normalise accelerometer measurement
            norm = sqrt(ax * ax + ay * ay + az * az);
            if (norm == 0.0) return; // handle NaN
            ax /= norm;
            ay /= norm;
            az /= norm;  

            // Auxiliary variables to avoid repeated arithmetic
             _2q1 = 2.0f * q1;
             _2q2 = 2.0f * q2;
             _2q3 = 2.0f * q3;
             _2q4 = 2.0f * q4;
             q1q1 = q1 * q1;
             q2q2 = q2 * q2;
             q3q3 = q3 * q3;
             q4q4 = q4 * q4;  

            if(((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f))) {
                // If magnetometer is invalid

                // Auxiliary variables to avoid repeated arithmetic
                _4q1 = 4.0f * q1;
                _4q2 = 4.0f * q2;
                _4q3 = 4.0f * q3;
                _8q2 = 8.0f * q2;
                _8q3 = 8.0f * q3;

                // Gradient decent algorithm corrective step
                s1 = _4q1 * q3q3 + _2q3 * ax + _4q1 * q2q2 - _2q2 * ay;
                s2 = _4q2 * q4q4 - _2q4 * ax + 4.0f * q1q1 * q2 - _2q1 * ay - _4q2 + _8q2 * q2q2 + _8q2 * q3q3 + _4q2 * az;
                s3 = 4.0f * q1q1 * q3 + _2q1 * ax + _4q3 * q4q4 - _2q4 * ay - _4q3 + _8q3 * q2q2 + _8q3 * q3q3 + _4q3 * az;
                s4 = 4.0f * q2q2 * q4 - _2q2 * ax + 4.0f * q3q3 * q4 - _2q3 * ay;

             } else { 
                // Valid magnetometer available use this code

                // Normalise magnetometer measurement
                norm = sqrt(mx * mx + my * my + mz * mz);
                if (norm == 0.0) return; // handle NaN
                mx /= norm;
                my /= norm;
                mz /= norm;

                // Auxiliary variables to avoid repeated arithmetic
                q1q2 = q1 * q2;
                q1q3 = q1 * q3;
                q1q4 = q1 * q4;
                q2q3 = q2 * q3;
                q2q4 = q2 * q4;
                q3q4 = q3 * q4;
                _2q1q3 = 2.0f * q1q3;
                _2q3q4 = 2.0f * q3q4;
                _2q1mx = 2.0f * q1 * mx;
                _2q1my = 2.0f * q1 * my;
                _2q1mz = 2.0f * q1 * mz;
                _2q2mx = 2.0f * q2 * mx;

                // Reference direction of Earth's magnetic field
                 hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
                 hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
                _2bx = sqrt(hx * hx + hy * hy);
                _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
                _4bx = 2.0f * _2bx;
                _4bz = 2.0f * _2bz;  

                // Gradient decent algorithm corrective step
                s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
                s2 =  _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
                s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
                s4 =  _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);

             } // end valid magnetometer

            norm = sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4);    // normalise step magnitude
            if (norm == 0.0) return; // handle NaN
            s1 /= norm;
            s2 /= norm;
            s3 /= norm;
            s4 /= norm; 

            // Compute estimated gyroscope biases
            gerrx = _2q1 * s2 - _2q2 * s1 - _2q3 * s4 + _2q4 * s3;
            gerry = _2q1 * s3 + _2q2 * s4 - _2q3 * s1 - _2q4 * s2;
            gerrz = _2q1 * s4 - _2q2 * s3 + _2q3 * s2 - _2q4 * s1;

            // Compute and remove gyroscope biases
            m_gbiasx += gerrx * m_timeDelta * m_zeta;
            m_gbiasy += gerry * m_timeDelta * m_zeta;
            m_gbiasz += gerrz * m_timeDelta * m_zeta;

            gx -= m_gbiasx;
            gy -= m_gbiasy;
            gz -= m_gbiasz;

             // Apply feedback step
            qDot1 -= m_beta * s1;
            qDot2 -= m_beta * s2;
            qDot3 -= m_beta * s3;
            qDot4 -= m_beta * s4;

        } // end if valid accelerometer

        // Integrate to yield quaternion
        q1 += qDot1 * m_timeDelta;
        q2 += qDot2 * m_timeDelta;
        q3 += qDot3 * m_timeDelta;
        q4 += qDot4 * m_timeDelta;

        // normalise quaternion
        norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);    
        if (norm == 0.0) return; // handle NaN
        q1 /= norm;
        q2 /= norm;
        q3 /= norm;
        q4 /= norm;
	
        m_stateQ.setScalar(q1);
        m_stateQ.setX(q2);
        m_stateQ.setY(q3);
        m_stateQ.setZ(q4);

        // Rotate Quaternion by Magnetic Declination
        // m_stateQ = q_declination * m_statqQ;

        /*
        SAGE
                N.<c,d,q1,q2,q3,q4,cos_theta_half, sin_theta_half> = QQ[]
                H.<i,j,k> = QuaternionAlgebra(c,d)
                q = q1 + q2 * i + q3 * j + q4 * k
                // here rotation is around gravity vector by theta
                mag_declination = cos_theta_half + sin_theta_half * (0 * i + 0 * j+ 1*k)
                q * mag_declination

                s : -q4*sin_theta_half + q1*cos_theta_half  
                x :  q3*sin_theta_half + q2*cos_theta_half 
                y : -q2*sin_theta_half + q3*cos_theta_half
                z :  q4*cos_theta_half + q1*sin_theta_half
                */

        m_stateQdec.setScalar(q1*m_cos_theta_half - q4*m_sin_theta_half);
        m_stateQdec.setX(q3*m_sin_theta_half + q2*m_cos_theta_half);
        m_stateQdec.setY(q3*m_cos_theta_half - q2*m_sin_theta_half);
        m_stateQdec.setZ(q4*m_cos_theta_half + q1*m_sin_theta_half);

    } // end not first time

	
    // =================================================

    if (m_enableCompass || m_enableAccel) {
            m_stateQError = m_measuredQPose - m_stateQ;
    } else {
            m_stateQError = RTQuaternion();
    }

    m_stateQdec.toEuler(m_fusionPose);
    m_fusionQPose = m_stateQdec;

    if (m_debug | settings->m_fusionDebug) {
        HAL_INFO(RTMath::displayRadians("Measured pose", m_measuredPose));
        HAL_INFO(RTMath::displayRadians("AHRS pose", m_fusionPose));
        HAL_INFO(RTMath::displayRadians("Measured quat", m_measuredPose));
        HAL_INFO(RTMath::display("AHRS quat", m_stateQ));
        HAL_INFO(RTMath::display("Error quat", m_stateQError));
        HAL_INFO3("AHRS Gyro Bias: %+f, %+f, %+f\n", m_gbiasx, m_gbiasy, m_gbiasz);
     }

    data.fusionPoseValid = true;
    data.fusionQPoseValid = true;
    data.fusionPose = m_fusionPose;
    data.fusionQPose = m_fusionQPose;
} //