June 2012

Volume 27 Number 06

Touch and Go - Getting Oriented with the Windows Phone Compass

By Charles Petzold | June 2012

Charles PetzoldWe humans rarely use our senses entirely in isolation from each other: A combination of sight and hearing allows us to construct a mental image of our surroundings; a blend of taste, smell and touch affects our enjoyment when eating food; and we use a mix of touch, sight and hearing when playing a musical instrument.

It’s the same with a smartphone: A smartphone can “see” through its camera lens, “hear” through its microphone, “feel” through its touchscreen, and know its location in the world using GPS and the orientation sensor. But start combining input from these sensors and there’s no word to describe the result but synergy.

The Accelerometer Problem

The accelerometer in Windows Phone is a good example of a sensor that provides some essential information but becomes much more valuable when combined with another sensor, specifically the compass.

The accelerometer hardware actually measures force, but as we know from physics, force equals mass times acceleration, so the accelerometer responds to any kind of acceleration. When the phone is held still, the accelerometer measures gravity and provides a 3D vector pointing toward the center of the earth. This vector is relative to a 3D coordinate system, as shown in Figure 1. This coordinate system is the same whether you’re coding a Silverlight or XNA program, or running in portrait or landscape mode.

The Phone’s Sensor Coordinate System
Figure 1 The Phone’s Sensor Coordinate System

The Accelerometer class provides the acceleration vector in the form of a Vector3 value. This is an XNA type, so if you need to use it in a Silverlight program, you’ll need a reference to the Microsoft.Xna.Framework assembly.

Although the acceleration vector allows a program running on the phone to determine the orientation of the phone relative to the Earth, it’s missing some crucial information. Let me demonstrate what I’m talking about.

In the downloadable code for this column (available at archive.msdn.microsoft.com/mag201206TouchAndGo) is a Visual Studio solution named 3DPointers that contains four XNA 3D projects, all of which look somewhat similar. The Accelerometer 3D program draws a 3D “pin” that floats in space in the direction of the accelerometer vector, as shown in Figure 2.

The Accelerometer 3D Display
Figure 2 The Accelerometer 3D Display

Any program that uses a Windows Phone sensor needs a reference to the Microsoft.Devices.Sensors assembly. Normally XNA programs on the phone run in landscape mode, and that can be an issue because it doesn’t match the coordinate system used by the sensors. To make things easy for myself, I reoriented the XNA coordinate system for portrait mode in the constructor of the program’s Game derivative:

graphics.IsFullScreen = true;
graphics.PreferredBackBufferWidth = 480;
graphics.PreferredBackBufferHeight = 800;

In Windows Phone 7.1, the sensor API has been changed a bit to provide consistency among the various sensors. The Accelerometer 3D program constructor uses this new API to create an Accelerometer instance that’s saved as a field:

if (Accelerometer.IsSupported)
{
  accelerometer = new Accelerometer
  {
    TimeBetweenUpdates = this.TargetElapsedTime
  };
}

The default TimeBetweenUpdates property is 25 milliseconds; here it’s set to the program’s frame rate, which is 33 ms.

The program uses an override of the OnActivated method to start the Accelerometer:

if (accelerometer != null)
{
  try { accelerometer.Start(); }
  catch { }
}

Although it’s difficult to imagine a scenario where the Start method will fail at this point, it’s recommended that you put it in a try block. The compass is stopped in OnDeactivated:

if (accelerometer != null)
    accelerometer.Stop();

The program uses the LoadContent method to build 3D vertices for the “pin” and define a BasicEffect for storing the camera and lighting information. The pin is defined so that the base is at the origin and it extends one unit up the positive Y axis. The camera points directly at the origin from the positive Z axis.

The program’s Update method then uses the accelerometer vector to define a world transform. This world transform effectively moves the pin relative to the origin. Figure 3 shows the code.

Figure 3 The Update Method in Accelerometer 3D

protected override void Update(GameTime gameTime)
{
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back 
    == ButtonState.Pressed)
      this.Exit();
  if (accelerometer != null && accelerometer.IsDataValid)
  {
    Vector3 acceleration = accelerometer.CurrentValue.Acceleration;
    text = String.Format("X = {0:F3}\nY = {1:F3}\nZ = {2:F3}",
                            acceleration.X,
                            acceleration.Y,
                            acceleration.Z);
    textPosition = new Vector2(0, this.GraphicsDevice.Viewport.Height -
                                    segoe24Font.MeasureString(text).Y);
    acceleration.Normalize();
    Vector3 axis = Vector3.Cross(acceleration, Vector3.UnitY);
    // Special case for magnetometer equal to (0, 1, 0) or (0, -1, 0)
    if (axis.LengthSquared() == 0)
        axis = Vector3.UnitX;
    else
        axis.Normalize();
    float angle = -(float)Math.Acos(Vector3.Dot(Vector3.UnitY, 
      acceleration));
    basicEffect.World = Matrix.CreateFromAxisAngle(axis, angle);
  }
  else
  {
    basicEffect.World = Matrix.Identity;
    text = "";
  }
  base.Update(gameTime);
}

The method obtains the Acceleration value directly from the Accelerometer object if the IsDataValid property is true. The pin must be rotated based on the angle between the Acceleration vector and the positive Y axis. The dot product of these two vectors provides that angle, while the axis of rotation is provided by the cross product.

But try this: Stand up and hold the phone in your hand at a particular angle. The pin points down. Now, while standing in place, turn around 360 degrees. While you’re turning, the pin remains in the same position (or nearly so). The Accelerometer can’t discern when the phone is moving around an axis parallel with the acceleration vector. If you’re writing an application that needs to know the complete 3D orientation of the phone, the accelerometer provides only part of what you need.

The Compass to the Rescue

When Windows Phone was first released, it wasn’t quite clear whether the phones even contained a compass. No one seemed to have a definitive answer. For application programmers, however, the situation was very simple: There was no programming interface for a compass, so even if it existed we couldn’t use it.

The release of Windows Phone 7.1 clarified the issue: Although a Windows Phone device isn’t required to have a compass, some Windows Phone devices always had one, including a smartphone running Windows Phone that I purchased in December 2010. Best of all, programmers can actually use this compass. Windows Phone 7.1 introduced a new Compass class that lets the application programmer know if the compass exists (through the static Compass.IsSupported property) and get access to its readings. Compass.IsSupported returns false on the Windows Phone emulator.

The basis of the phone’s compass is a piece of hardware known as a magnetometer. This magnetometer is affected by any magnetic forces near the phone, including those that might be in speakers on your computer desk. If you keep these magnets away from the phone, the magnetometer will measure the strength and direction of Earth’s magnetic field.

The Compass class provides data in the form of a CompassReading structure. The raw magnetometer data is available in a property named MagnetometerReading, which is another Vector3 relative to the coordinate system shown in Figure 1. In the absence of nearby magnets, this vector is aligned with Earth’s magnetic field. The vector points in a northerly direction, of course, but in most locations in the northern hemisphere, it also points into the earth. If you hold the phone with the screen facing up, this vector will have a significant –Z component.

The 3DPointers solution contains a project named Magnetometer 3D that is similar to the Accelerometer 3D project, except it uses the Compass class rather than the Accelerometer class. Figure 4shows this program’s display when the phone is held in my apartment in Manhattan with the screen facing up and the top of the phone pointing “uptown,” that is, with the left and right sides of the phones aligned (as close as I could manage) with New York City’s avenues. (A map reveals these avenues to run about 30 degrees east of north.)


Figure 4 The Magnetometer 3D Display

The documentation states that the MagnetometerReading vector is in units of microteslas. On two of the commercial Windows Phone devices I own, this vector generally has a magnitude in the 30s, which is approximately correct. (The vector values shown in Figure 4 have a composite magnitude of 43.) However, for a third phone I own, the MagnetometerReading vector is normalized and always has a magnitude of 1.

Now try this: Hold the phone so the Magnetometer 3D vector is nearly aligned with the positive Y axis of Figure 1. Now rotate the phone around an axis parallel to that vector. The vector stays the same (or nearly so), indicating that the phone’s magnetometer doesn’t have complete knowledge of the phone’s orientation either.

Compass Headings

Normally when we’re using a compass, we don’t want a 3D vector aligned with Earth’s magnetic field. Much more useful would be a 2D vector that’s tangent to Earth’s surface.

As you probably know, Earth’s magnetic field does not coincide with the axis on which Earth spins. The direction of Earth’s axis is called geographic north or true north, and this is the north used for maps and virtually all other purposes. Angles on a two-dimensional surface are often used for representing the direction of north. This direction is often called a heading or bearing.

The difference between magnetic north and true north varies all over the globe. In New York City, about 13 degrees must be subtracted from the magnetic bearing to get true bearing, but in Seattle, 21 degrees must be added to the magnetic bearing. The Compass class performs these calculations for you based on the phone’s location. Besides the MagnetometerReading vector, CompassReading also supplies two properties of type double named MagneticHeading and TrueHeading. These are both angles in degrees ranging from 0 to 360 measured counterclockwise from the positive Y axis shown in Figure 1.

TrueHeading should always be interpreted as an approximate value, and even then it shouldn’t be trusted entirely. On two of the phones I own, TrueHeading is usually about right, but on another phone, it’s a good 70 degrees off.

I haven’t been able to make sense of the MagneticHeading value. For a particular location, the difference between the values of TrueHeading and MagneticHeading should be a constant. For example, where I live, the value of TrueHeading minus Magnetic­Heading should be about –13 degrees. On all three of my phones, the difference between TrueHeading and MagneticHeading jumps sporadically between two values depending on the orientation of the phone. The difference is sometimes –12 (which is about correct), but mostly the difference is 92. These are the only two values of the difference that I’ve seen. On none of my phones is MagneticHeading consistent with the angle derived from the X and Y values of the MagnetometerReading vector.

In an XNA program, as you’ve seen, you can simply obtain a current value from a sensor during the Update method. When using a sensor class in a Silverlight program, you’ll want to set a handler for the CurrentValueChanged event. You can then obtain a sensor reading object from the event arguments.

The downloadable code for this article contains two Silverlight programs—Arrow Compass and Dial Compass—that use the TrueHeading property to show the direction of north. All the graphics are defined in XAML. As with the XNA programs, these Silverlight programs create the Compass object in their constructors, but they also set a handler for the CurrentValueChanged property:

if (Compass.IsSupported)
{
  compass = new Compass();
  compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(33);
  compass.CurrentValueChanged += OnCompassCurrentValueChanged;
}

In Arrow Compass this handler sets the angle on a RotateTransform object attached to an arrow graphic:

this.Dispatcher.BeginInvoke(() =>
{
  arrowRotate.Angle = -args.SensorReading.TrueHeading;
  accuracyText.Text = String.Format("±{0}°",
                      args.SensorReading.HeadingAccuracy);
});

The CurrentValueChanged handler is called in a separate thread, so you’ll need to use a Dispatcher to update any UI objects. Because the TrueHeading angle indicates a counterclockwise offset and Silverlight rotations are clockwise, the code uses the negative of the heading angle for the rotation.

The result is shown in Figure 5, again with the phone pointing uptown in New York City.

The Arrow Compass Display
Figure 5 The Arrow Compass Display

In Dial Compass, the arrow remains fixed while a dial rotates to indicate the direction, as shown in Figure 6. You use this variation if you want to know the direction that the top of the phone is pointing, rather than the direction of north relative to the phone.

The Dial Compass Display
Figure 6 The Dial Compass Display

If you run either of these two programs and hold the phone so that the screen faces the earth, the compass no longer works correctly. The rotation is opposite what it should be. If you need to correct for this, you’ll want to use the positive value of the TrueHeading value when the Z value of the acceleration vector is positive.

Calibrating the Compass

In the lower-right corner, the Arrow Compass program displays the HeadingAccuracy property of the CompassReading value. In theory, this provides the accuracy of the heading values. In practice, I’ve seen HeadingAccuracy values ranging from 5 percent to 30 percent. (But I’ve only seen 5 percent on the phone that’s way off!)

The Compass class also defines an event named Calibrate that’s fired when the HeadingAccuracy value exceeds 20 percent.

You can perform a calibration maneuver to reduce this HeadingAccuracy: Hold the phone out from your body with the screen pointing left or right, and then sweep your arm in an infinity pattern several times. Some sample code provided with the MSDN tutorials (at bit.ly/yYrHrL) even has a graphic you can display to notify your users when the compass needs calibration.

Combining Compass and Accelerometer

The phone’s compass is a perfect example of a sensor that obviously has some utility by itself—particularly if you’re lost in the woods—but which becomes much more valuable when combined with other sensors, especially the accelerometer. Together, these two sensors can provide a complete orientation of the phone in 3D space.

Indeed, Windows Phone 7.1 defines a new class that does precisely this. Besides providing the phone orientation in three different ways, the Motion class also incorporates information from the new Gyroscope class, if one is present on the phone.

Moreover, the Motion class does additional work by smoothing out the data from the Accelerometer and Compass. If you’ve been running the programs presented so far, you might have noticed significant jitter in the data from these classes. That jitter is all gone from the Motion class.

However, because I’m the type of person who enjoys a good challenge, I thought I’d take a shot at combining the Accelerometer and Compass data “manually,” and I must admit that the experience left me with a much deeper appreciation of the Motion class!

The Compass 3D program displays four differently colored pins arranged in a circle to point north (silver), east (red), south (green) and west (blue). The program attempts to display this plane of pins parallel to Earth’s surface and oriented correctly toward the four points of the compass.

The strategy I took was to derive Euler angles. These are three angles representing rotation around the X, Y and Z axes, and together they describe an orientation in 3D space. In flight dynamics, the three angles are labeled pitch, roll and yaw. From the perspective of an aircraft, pitch indicates whether the nose is up or down and by how much, while roll indicates any banking of the plane to the right or left. These rotation angles can be visualized as relative to two axes: pitch is rotation around an axis that extends through the wings, while roll is based on an axis from the front of the plane to the back. Yaw is rotation around an axis perpendicular to Earth’s surface and indicates the compass heading for the plane.

To visualize these angles with respect to the phone’s coordinate system in Figure 1, imagine yourself riding the phone like a magic carpet, sitting on the screen with the top of the phone to your front and the three buttons to your rear. Pitch is rotation around the X axis, roll is rotation around the Y axis and yaw is rotation around the Z axis.

Calculating roll and pitch from the acceleration vector turned out to be fairly easy and involves standard formulas:

float roll = (float)Math.Asin(-acceleration.X);
float pitch = (float)Math.Atan2(acceleration.Y, -acceleration.Z);

With the phone sitting flat on a table with its screen facing up, both roll and pitch are zero. Roll ranges from –π/2 to π/2, and the values go back to zero as the phone is turned facedown. Pitch ranges from –π to π with the maximum values when the phone is facedown. With the phone’s screen facing up, yaw should be more or less the same value as the TrueHeading property from the Compass, but converted to radians for XNA purposes:

float yaw = MathHelper.ToRadians((float)compass.CurrentValue.TrueHeading);

However, as you saw with the two Silverlight compass programs, TrueHeading stops working correctly when the phone is turned with its screen toward Earth, so yaw needs to be corrected for that. After some theoretical and empirical excursions into the subject, I left it as is, and constructed a world transform from the three angles:

basicEffect.World = Matrix.CreateRotationZ(yaw) *
                    Matrix.CreateRotationY(roll) *
                    Matrix.CreateRotationX(pitch);

The results are shown in Figure 7.

The Compass 3D Program
Figure 7 The Compass 3D Program

I’ve also included an Orientation 3D program that obtains these three angles from the Motion class. You can see for yourself how much smoother the results are (in addition to working better when the phone is upside down).

The Motion class is too important an addition to the sensor API for just this single program. As you’ll see in the next installment of this column, the class can actually serve as a portal into a 3D world.


Charles Petzold is a longtime contributor to MSDN Magazine. His Web site is charlespetzold.com.

Thanks to the following technical expert for reviewing this article: Drew Batchelor