Unusual CoreMotion Sensorfusion Data
<p>I'm working on an app which collects CoreMotion data and I'm using an existing app store application as a reference to ensure I'm collecting data correctly.</p>

<p>For the most part, everything is equivalent but I'm seeing some strange data in certain tests. The problem is showing up in the roll, pitch and yaw values, however, the raw data from the accelerometer, gyroscope, and magnetometer is equivalent...</p>

<p>The charts below plots the data collected from 5 devices on a rig @100Hz:</p>

<ol>
<li>iPhone 5 (iOS 10.0.2)</li>
<li>iPhone 5 (iOS 10.0.2)</li>
<li>iPhone 5 (iOS 10.0.2)</li>
<li>iPhone 5s (iOS 10.0.2)</li>
<li>iPhone 6s (iOS 10.0.2)</li>
</ol>

<p>First, the data collected from <a href="https://itunes.apple.com/au/app/sensorlog/id388014573?mt=8" rel="nofollow">SensorLog</a>:
<a href="https://i.stack.imgur.com/ZE6RO.png" rel="nofollow"><img src="https://i.stack.imgur.com/ZE6RO.png" alt="SensorLog Data"></a>
Second, data collected from my app:
<a href="https://i.stack.imgur.com/TXvre.png" rel="nofollow"><img src="https://i.stack.imgur.com/TXvre.png" alt="My App Data"></a>
Third, data collected from my app but this time @10Hz:
<a href="https://i.stack.imgur.com/KMlE2.png" rel="nofollow"><img src="https://i.stack.imgur.com/KMlE2.png" alt="enter image description here"></a></p>

<p>Observations:</p>

<ul>
<li>Problem seems to be limited to iPhone 5 &amp; 5s <strong>Update:</strong> Problem has been observed in 6s too </li>
<li>SensorLog is able to record values correctly (although I have seen one instance of it generating 'bad?' data) which would seem to eliminate a hardware limitation (previous iPhone 4s tests ok too)</li>
<li>iPhone 6s performs fine in all tests <strong>Update:</strong> No longer the case</li>
<li>My app may be affected by the frequency as the results show that @10Hz, 1 iPhone 5 is ok but @100Hz none are</li>
<li>Raw data is equivalent in all devices so it's not clear why sensor fusion data is off for some devices</li>
<li>Using Quaternions does not fix the problem as they are derived from the same raw data</li>
<li>The 'bad' data in some cases appears to be following the good data but the amplitude is way too large as demonstrated below</li>
</ul>

<p>Zooming in on the plots:</p>

<p><a href="https://i.stack.imgur.com/rEH82.png" rel="nofollow"><img src="https://i.stack.imgur.com/rEH82.png" alt="Zoomed Chart"></a></p>

<p>And overlaying the manually stretched out roll data on the plot above:</p>

<p><a href="https://i.stack.imgur.com/3Wm4i.png" rel="nofollow"><img src="https://i.stack.imgur.com/3Wm4i.png" alt="Overlay roll"></a></p>

<p>Code:</p>

<pre><code>func start(_ interval: TimeInterval = 0.1) {
    self.interval = interval

    logTimer = Timer.new(every: interval, {
        self.motionData.currentRecord = self.motionDataRecord
        self.motionData.createCoreDataRecord()
        NotificationCenter.default.post(name: .motionHelperDidUpdateData, object: nil)
    })
    logTimer.start()

    startCoreLocation()
    startAccelerometer()
    startDeviceMotion()
    startGyroscope()
    startMagnetometer()
}

func startCoreLocation() {
    switch CLLocationManager.authorizationStatus() {
    case .authorizedAlways:
        locationManager.startUpdatingLocation()
        locationManager.startUpdatingHeading()
    case .notDetermined:
        locationManager.requestAlwaysAuthorization()
    case .authorizedWhenInUse, .restricted, .denied:
        delegate?.reRequestAlwaysAuthorization()
    }
}

func startAccelerometer() {
    if motionManager.isAccelerometerAvailable {
        motionManager.accelerometerUpdateInterval = interval
        motionManager.startAccelerometerUpdates(to: queue) {
            [weak self] (data, error) in

            guard let weakSelf = self else { return }

            if error != nil {
                print("Accelerometer Error: %@", error)
            }
            guard let data = data else { return }

            weakSelf.motionDataRecord.accelerometer = data
        }
    } else {
        print("The accelerometer is not available")
    }

}

func startGyroscope() {
    if motionManager.isGyroAvailable {
        motionManager.gyroUpdateInterval = interval
        motionManager.startGyroUpdates(to: queue) {
            [weak self] (data, error) in

            guard let weakSelf = self else { return }

            if error != nil {
                print("Gyroscope Error: %@", error)
            }
            guard let data = data else { return }

            weakSelf.motionDataRecord.gyro = data
        }
    } else {
        print("The gyroscope is not available")
    }
}

func startMagnetometer() {
    if motionManager.isMagnetometerAvailable {
        motionManager.magnetometerUpdateInterval = interval
        motionManager.startMagnetometerUpdates(to: queue) {
            [weak self] (data, error) in

            guard let weakSelf = self else { return }

            if error != nil {
                print("Magnetometer Error: %@", error)
            }
            guard let data = data else { return }

            weakSelf.motionDataRecord.magnetometer = data
        }
    } else {
        print("The magnetometer is not available")
    }
}

func startDeviceMotion() {
    if motionManager.isDeviceMotionAvailable {
        motionManager.deviceMotionUpdateInterval = interval
        motionManager.startDeviceMotionUpdates(using: attitudeReferenceFrame, to: queue) {
            [weak self] (data, error) in

            guard let weakSelf = self else { return }

            if error != nil {
                print("Device Motion Error: %@", error)
            }
            guard let data = data else { return }

            weakSelf.motionDataRecord.deviceMotion = data
        }
    } else {
        print("Device motion is not available")
    }
}
</code></pre>

<p>Is there a problem with the way I am collecting the data from CoreMotion? Is there a more efficient way to do it?</p>

<p>What could be happening here?</p>

<p><strong>UPDATE:</strong></p>

<p>I've written a bare bones app as follows and I'm getting similar results:</p>

<pre><code>class ViewController: UIViewController {

    @IBOutlet weak var startStop: UIButton!

    var isRunning = false

    let manager: CMMotionManager = {
        let manager = CMMotionManager()
        manager.deviceMotionUpdateInterval = 1/100
        return manager
    }()

    @IBAction func handleStartStop(_ sender: AnyObject) {
        if isRunning {
            stopMotionUpdates()
            startStop.setTitle("Start", for: .normal)
        } else {
            startMotionUpdates()
            startStop.setTitle("Stop", for: .normal)
        }
        isRunning = !isRunning
    }

    func startMotionUpdates() {
        manager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: .main) { (data, error) in
            print("Roll: \(data!.attitude.roll), Pitch: \(data!.attitude.pitch), Yaw: \(data!.attitude.yaw)")
        }
    }

    func stopMotionUpdates() {
        manager.stopDeviceMotionUpdates()
    }

}
</code></pre>