Computing the iOS device tilt
The tilt of a device is probably the most useful information you can get from the acceleration data of your iPhone, iPad or iPod touch. Besides, a lot of games are using it. The question is, how to get this tilt value ?
First thought
I’ve looked into the Core Motion documention and found out that Apple is computing some values for us, which is available within a CMAttitude
class instance.
An instance of the CMAttitude class represents a measurement of the device’s attitude at a point in time.
Well, Apple isn’t talking about a ballerina attitude but more of a flight-type attitude. Indeed, the iPhone orientation can be described just like an airplane by its roll
, pitch
and yaw
.
And you have guessed, the yaw
value is the rotation against the red axis. It seems pretty straight forward so let’s implement that.
- (void)viewDidLoad {
[super viewDidLoad];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 0.02; // 50 Hz
self.motionDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(motionRefresh:)];
[self.motionDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
if ([self.motionManager isDeviceMotionAvailable]) {
// to avoid using more CPU than necessary we use `CMAttitudeReferenceFrameXArbitraryZVertical`
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical];
}
}
- (void)motionRefresh:(id)sender {
double yaw = self.motionManager.deviceMotion.attitude.yaw;
// use the yaw value
// ...
}
And I thought “easy, problem solved”. In fact, it was terrible and unusable because the yaw
value was impacted by the iPhone roll
and pitch
. I mean, if we keep the iPhone vertical and twist it against the blue axis, it will modify the yaw
… Bad!
Check out the gimbal lock problem if you want to understand more about it.
So back to square one, I had to find a way to compute the yaw
by myself and I felt that math may rescue me!
The beauty of Quaternions
If you don’t know what a quaternion is yet, please don’t freak out by this strange word that seems coming right out of Star Trek.
Quaternions were first described by Hamilton in 1843 and applied to mechanics in three-dimensional space.
It eases the way we deal with the orientation of a body in a 3D space, and is better suited than the Euler angles that Apple is computing for us because of three reasons :
- it’s easier to compose rotations or to extract values from it.
- it avoids the gimbal lock problem.
- and Apple provides a quaternion in the
CMAttitude
class instance.
And because we only want to compute the yaw
we do not have to worry about the gimbal lock problem, since our goal is not to describe the complete iPhone orientation in the 3D space but only the tilt of it.
There is a very simple formula to compute yaw
from a quaternion :
So, the motionRefresh:
method described above become :
- (void)motionRefresh:(id)sender {
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
}
The icing on the cake
We can improve the code a bit to have a perfectly smooth yaw
signal, or to have some kind of internia in the tilt movement (just like I needed in my DPMeterView project).
In order to do that, we need a very simple one dimensional Kalman-filter. I’m not discussing the details of how it works because it’s not the purpose of the article. However, you can experiment by yourself the impact of changing some of those values.
- (void)motionRefresh:(id)sender {
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...
}
Fork it !
If you have some ideas of improvement, or just want to play with a working example, don’t hesitate to fork the DPMeterView project hosted on GitHub.
iPhone 5 model created by Pixeden.
comments powered by Disqus