One of the sensor devices present in every Windows phone is an accelerometer that provides real-time acceleration data in the X, Y, and Z directions. Applications can use that data to determine a phone’s 3D spatial orientation, and to detect changes in that orientation. Under the hood, Silverlight for Windows Phone uses the accelerometer to determine the page orientation. But the accelerometer can do much more than just differentiate between portrait and landscape: it can be used to build apps that simulate physics. Ever seen the popular iPhone game named Labyrinth, in which you guide a marble through a maze by tilting the phone? Labyrinth is a great example of an app that uses an accelerometer. The first time I saw it, I was blown away. So I’d like to introduce the accelerometer API in Silverlight for Windows Phone and demonstrate how you can build apps like Labyrinth, too.

To incorporate the accelerometer in a phone app, you begin by adding a reference to the assembly named Microsoft.Devices.Sensors. In that assembly’s Microsoft.Devices.Sensors namespace, you’ll find a class named Accelerometer that provides a software interface to – you guessed it – the phone’s accelerometer. Getting accelerometer data requires three simple steps:

  1. Instantiate an Accelerometer object
  2. Register a handler for Accelerometer.ReadingChanged events
  3. Call Accelerometer.Start

Once the Start method is called, your ReadingChanged event handler will begin receiving a stream of calls at roughly 50 Hz, or 50 times per second. Each call is accompanied by a reference to an AccelerometerReadingEventArgs object that contains four important properties:

  • X, which specifies the phone’s acceleration in the X direction
  • Y, which specifies the phone’s acceleration in the Y direction
  • Z, which specifies the phone’s acceleration in the Z direction
  • Timestamp, which specifies the time at which the reading was taken

X, Y, and Z are acceleration vectors. A value of 1.0 nominally equates to the acceleration of gravity (“nominally” because the strength of the earth’s gravitational field varies slightly from place to place), so when the phone is at rest, the magnitude of the vector denoted by the X, Y, and Z acceleration values, which can be computed using the formula M = Sqrt (X2 + Y2 + Z2), should be 1.0 (or very close to it). If all three acceleration values are 0, then the phone is currently weightless, which probably means it’s in outer space or is currently in free fall. (If the latter, it might be a good time save your data!)  If you shake your phone, you can produce acceleration values greater than 1.0. In my experience, the magnitude of the X, Y, and Z accelerations will never exceed 2.0, even if you shake the phone hard or bang it on the table.

From these acceleration values, you can determine the phone’s 3D spatial orientation. The following diagram, which was lifted from an article in the Windows team blog, explains how:

image

If the phone is laying face up on a perfectly level table, you’ll get accelerometer readings of X=0, Y=0, and Z=-1. (The –1 is the pull of gravity). If, on the other hand, the phone is laying on its right side, you’ll get accelerometer readings of X=1, Y=0, and Z=0. Given any set of X,Y, and Z acceleration values, you can use simple trigonometry to calculate angles relative to the X, Y, and Z axes.

The app pictured below is a simple one that starts the accelerometer running and displays X, Y, and Z accelerometer readings in real time. If you run it, you can see how the values change as you tilt the phone back and forth. Hold the phone straight up and down, and you’ll see Y values near -1, but X and Z values near 0. Now lay the phone on a level surface facing up and you’ll see Z values near -1 and X and Y values near 0. Even at rest, the values are constantly changing because 1) the accelerometer is an imperfect device, and 2) no physical body is ever completely at rest.

screen1

Here’s the code for the application. One aspect that may surprise you is that the ReadingChanged event handler doesn’t update the UI directly; instead, it uses Dispatcher.BeginInvoke to call a method named UpdateDisplay to do the updating. That’s because ReadingChanged events execute on background threads. In Silverlight for Windows Phone, as in Silverlight, a background thread can’t touch a XAML element; if it tries, it’ll generate an UnauthorizedAccessException containing the infamous “Invalid cross-thread access” error message. A background thread can, however, call Dispatcher.BeginInvoke to make an asynchronous call back to the application’s UI thread, which in turn can safely update the UI by modifying XAML elements. That’s exactly what’s happening here, and it’s important to realize that you can never update the UI directly from a ReadingChanged event handler.

 

// MainPage.xaml

<Grid VerticalAlignment="Center" HorizontalAlignment="Center">

    <Grid.RowDefinitions>

        <RowDefinition Height="64" />

        <RowDefinition Height="64" />

        <RowDefinition Height="64" />

    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="200" />

        <ColumnDefinition Width="200" />

    </Grid.ColumnDefinitions>

    <TextBlock Text="AccelX" FontSize="48" Grid.Row="0" Grid.Column="0"

        VerticalAlignment="Center" />

    <TextBlock Text="AccelY" FontSize="48" Grid.Row="1" Grid.Column="0"

        VerticalAlignment="Center" />

    <TextBlock Text="AccelZ" FontSize="48" Grid.Row="2" Grid.Column="0"

        VerticalAlignment="Center" />

    <TextBlock x:Name="AccelX" Text="0.000" FontSize="48" Grid.Row="0" Grid.Column="1"

        VerticalAlignment="Center" />

    <TextBlock x:Name="AccelY" Text="0.000" FontSize="48" Grid.Row="1" Grid.Column="1"

        VerticalAlignment="Center" />

    <TextBlock x:Name="AccelZ" Text="0.000" FontSize="48" Grid.Row="2" Grid.Column="1"

        VerticalAlignment="Center" />

</Grid>

 

// MainPage.xaml.cs

private const string _format = "0.000";

private Accelerometer _accel = new Accelerometer();

 

public MainPage()

{

    InitializeComponent();

 

    // Start the acceleroemeter

    _accel.ReadingChanged += (s, e) => Dispatcher.BeginInvoke(() =>

        UpdateDisplay(e.X, e.Y, e.Z));

    _accel.Start();

}

 

private void UpdateDisplay(double x, double y, double z)

{

    // Update the acceleration readouts

    AccelX.Text = x.ToString(_format);

    AccelY.Text = y.ToString(_format);

    AccelZ.Text = z.ToString(_format);

}

 

Of course, an app that does nothing more than textualize acceleration vectors is pretty boring. How about something more interesting…say, an app that simulates a carpenter’s level? I use one of these apps all the time because I’m anal about pictures that aren’t hung straight. (It’s true; just ask my wife.) By using my phone as a leveling device, I can avoid making a trip downstairs to my shop every time I walk past a picture that doesn’t seem to be hanging quite right.

Building a bubble level is a simple matter of translating acceleration vectors into positional values and moving a virtual indicator accordingly. Here’s one way to go about it:

 

// MainPage.xaml

<Rectangle x:Name="Track" Width="700" Height="80" Stroke="#FFC0C0C0"

    RadiusX="40" RadiusY="40" />

<Line Stroke="#FFC0C0C0" X1="0" Y1="0" X2="0" Y2="120"

    HorizontalAlignment="Center" VerticalAlignment="Center" />

<Ellipse x:Name="Ball" Width="80" Height="80">

    <Ellipse.Fill>

        <RadialGradientBrush GradientOrigin="0.234,0.255">

            <GradientStop Color="White"/>

            <GradientStop Color="Red" Offset="0.759"/>

        </RadialGradientBrush>

    </Ellipse.Fill>

    <Ellipse.RenderTransform>

        <TranslateTransform x:Name="BallTransform" />

    </Ellipse.RenderTransform>

</Ellipse>

 

// MainPage.xaml.cs

private Accelerometer _accel = new Accelerometer();

 

public MainPage()

{

    InitializeComponent();

 

    // Start the acceleroemeter

    _accel.ReadingChanged +=

        new EventHandler<AccelerometerReadingEventArgs>(OnReadingChanged);

    _accel.Start();

}

 

private void OnReadingChanged(object sender, AccelerometerReadingEventArgs e)

{

    // Get the acceleration in the Y direction

    // (Y rather than X since we’re in landscape mode)

    double x = e.Y;

           

    // Cap it at -0.5 and 0.5

    if (x > 0.5)

        x = 0.5;

    else if (x < -0.5)

        x = -0.5;

 

    // Pass the value to the UI thread

    Dispatcher.BeginInvoke(() => UpdateDisplay(x));

}

 

private void UpdateDisplay(double x)

{

    // Move the ball to the specified position

    BallTransform.X = -x * (Track.Width – Ball.Width);

}

 

And here’s the output. Tilt the phone to the right and the “bubble” moves left. Tilt the phone to the left and the bubble moves right…just like a carpenter’s level:

screen2

There are a couple of important take-aways from the code in this example. One, there’s a bit of computational work to do before passing the acceleration value (x) to the UI thread – namely, capping its minimum and maximum values to –0.5 and 0.5, respectively. It’s preferable to do this work on the background thread to avoid taking time away from the UI thread. Second, we’re reading the Y acceleration vector and using it to compute a horizontal (X) translational value. That’s because this application is configured to run in landscape mode, but acceleration vectors aren’t affected by the page orientation as TranslateTransforms are. If the application ran in portrait mode instead, you’d need to change e.Y to e.X to get the desired effect.

So far, so good. But there’s a problem. If you run this app on your phone, you’ll notice that it works – sort of – but that the red ball is jittery, even if the phone is laying motionless on a table. This is a direct result of the fact that the 50 Hz stream of acceleration vectors coming from the accelerometer is noisy. To make the motion of the bubble seem realistic, you must filter out the noise.

Not too long ago, my old friend and former MSDN Magazine editor Dave Edson wrote an excellent article on smoothing accelerometer data, and even provided a downloadable AccelerometerHelper class to do the smoothing for you. One way to smooth accelerometer data is to compute a running average of acceleration values. Another approach is to apply a low-pass filter. Here’s a pair of graphs that appeared in the article demonstrating how a raw data stream (left) compares to a data stream that has been smoothed (right) by averaging the last five acceleration values:

image

The more values you use for averaging, the smoother the data. The downside to averaging is that it introduces latency. That’s not a big deal for a bubble level – in fact, a small amount of latency improves the feel by making it seem as if there’s inertia – but it’s not so good for games, where an app’s response to the user’s action needs to be as fast as possible.

We can use averaging to eliminate the jitters from our bubble level. All we have to do is rewrite MainPage.xaml.cs as follows:

 

// MainPage.xaml.cs

private const int _num = 25;

private double[] _y = new double[_num];

private int _index = 0;

 

private Accelerometer _accel = new Accelerometer();

 

public MainPage()

{

    InitializeComponent();

 

    // Start the acceleroemeter

    _accel.ReadingChanged +=

        new EventHandler<AccelerometerReadingEventArgs>(OnReadingChanged);

    _accel.Start();

}

 

private void OnReadingChanged(object sender, AccelerometerReadingEventArgs e)

{

    // Smooth the acceleration in the Y direction

    // (Y rather than X since we’re in landscape mode)

    _y[_index++] = e.Y;

 

    if (_index >= _num)

        _index = 0;

 

    double ysum = 0.0;

 

    for (int i = 0; i < _num; i++)

        ysum += _y[i];

 

    double x = ysum / _num;           

           

    // Cap it at -0.5 and 0.5

    if (x > 0.5)

        x = 0.5;

    else if (x < -0.5)

        x = -0.5;

 

    // Pass the value to the UI thread

    Dispatcher.BeginInvoke(() => UpdateDisplay(x));

}

 

private void UpdateDisplay(double x)

{

    // Move the ball to the specified position

    BallTransform.X = -x * (Track.Width – Ball.Width);

}

 

Rather than use raw Y acceleration vectors, we now average the last 25 values and use the result as the latest vector. Run the app again and you’ll find that the bubble moves smoothly, and that it stays in place when the phone is at rest. Plus, the slight latency introduced by our smoothing algorithm gives the simulated level a more realistic feel.

I think you’ll agree that it takes remarkably little code to utilize accelerometer data to build a virtual bubble level. And every time I show an app like this to my non-technical friends, they’re amazed. But what about Labyrinth-type games? How difficult is it to write them?

Well, since you asked, I thought I’d throw one together – at least one that demonstrates the basics of how it’s done. Here’s what the finished app looks like:

screen3

When you tilt the phone, the steel ball in the center of the screen begins to roll. It obeys the laws of physics and even bounces if it impacts one of the sides of the screen. I purposely didn’t include friction in the calculations because there’s very little friction (and little contact area) between a steel ball and a hard wood surface. Try it. Pick a point and try to guide the ball to it by tilting the phone. It feels almost…real.

So what does it take to build an app like this? Not much, really, other than the few minutes it takes to blow the dust off your high-school physics textbook and review the equations of motion. Here’s the XAML and code:

 

// MainPage.xaml

<Image Source="Wood.jpg" />

<Ellipse x:Name="Ball" Width="80" Height="80" CacheMode="BitmapCache">

    <Ellipse.Fill>

        <RadialGradientBrush GradientOrigin="0.234,0.255">

            <GradientStop Color="White"/>

            <GradientStop Color="#808080" Offset="0.759"/>

        </RadialGradientBrush>

    </Ellipse.Fill>

    <Ellipse.RenderTransform>

        <TranslateTransform x:Name="BallTransform" />

    </Ellipse.RenderTransform>

</Ellipse>

 

// MainPage.xaml.cs

private const double _gravity = 32.2; // Acceleration of gravity (ft/sec2)

private const double _damping = 0.5;  // Damping factor for boundary collisions

private const double _mul = 128.0;    // Motion multiplier (sensitivity)

 

private Accelerometer _accel = new Accelerometer();

private double _sx = 0.0;

private double _sy = 0.0;

private double _vx = 0.0;

private double _vy = 0.0;

private double _ax = 0.0;

private double _ay = 0.0;

private double _time = DateTime.Now.Ticks;

 

private double _width, _height;

 

public MainPage()

{

    InitializeComponent();

 

    // Initialize values used for boundary checking

    _width = (Application.Current.Host.Content.ActualWidth – Ball.Width) / 2.0;

    _height = (Application.Current.Host.Content.ActualHeight – Ball.Height) / 2.0;

           

    // Start the accelerometer

    _accel.ReadingChanged +=

        new EventHandler<AccelerometerReadingEventArgs>(OnReadingChanged);

    _accel.Start();

}

 

private void OnReadingChanged(object sender, AccelerometerReadingEventArgs e)

{

    // Compute number of seconds elapsed since last interval

    double time = (e.Timestamp.Ticks – _time) / 10000000.0;

 

    // Compute average accelerations during this time interval

    double ax = ((e.X * _gravity) + _ax) / 2.0;

    double ay = ((e.Y * _gravity) + _ay) / 2.0;

 

    // Compute velocities at end of this time interval

    double vx = _vx + (ax * time);

    double vy = _vy + (ay * time);

           

    // Compute new ball position

    double sx = _sx + ((((_vx + vx) / 2.0) * time) * _mul);

    double sy = _sy – ((((_vy + vy) / 2.0) * time) * _mul);

 

    // Check for boundary collisions and "bounce" if necessary

    if (sx < -_width)

    {

        sx = -_width;

        vx = -vx * _damping;

    }

    else if (sx > _width)

    {

        sx = _width;

        vx = -vx * _damping;

    }

 

    if (sy < -_height)

    {

        sy = -_height;

        vy = -vy * _damping;

    }

    else if (sy > _height)

    {

        sy = _height;

        vy = -vy * _damping;

    }

 

    // Save the latest motion parameters

    _time = e.Timestamp.Ticks;

    _ax = ax;

    _ay = ay;

    _vx = vx;

    _vy = vy;

    _sx = sx;

    _sy = sy;

 

    // Call back to the UI thread to update the ball position

    Dispatcher.BeginInvoke(() => UpdateDisplay(sx, sy));

}

 

private void UpdateDisplay(double x, double y)

{

    // Update the ball position

    BallTransform.X = x;

    BallTransform.Y = y;

}

 

The basic idea is that every time a ReadingChanged event fires, you compute a new position for the ball and call the UI thread to update the ball’s position. To compute a new position, you do the following:

  1. Subtract the AccelerometerReadingEventArgs.Timestamp in the last reading from the one in the current reading to compute how many seconds elapsed since the last ReadingChanged event
  2. Average the current acceleration and the acceleration in the last reading to compute the average acceleration during the time interval
  3. Use the equation v1 = v0 + at (where v1 is the new velocity, v0 is the previous velocity, a is the average acceleration, and t is elapsed time in seconds) to compute a new velocity
  4. Average the current velocity and the velocity computed in the last reading to compute the average velocity during the time interval
  5. Use the equation s1 = s0 + vt (where s1 is the new position, s0 is the previous position, v is the average velocity, and t is the elapsed time in seconds) to compute a new position
  6. Clamp the position to the boundaries of the screen by comparing Xs and Ys to the screen extents

There are a couple of other factors at work in the code, including a damping factor (a multiplier that determines how much momentum is lost when the ball bounces off an edge of the screen) and a multiplier named _mul that converts distances into pixels and affects the sensitivity of the simulation. To see what I mean, try changing _mul from 128.0 to, say, 256.0. The ball will suddenly feel much slicker because it’ll respond twice as sharply to every movement. If you lower _mul, on the other hand, the ball will move more slowly.

I didn’t incorporate any kind of filtering into the simulation because I wanted to absolutely minimize latency. The good news is that because of the way the computations are structured, noise in the data stream is partially damped. Sometimes the ball will jump, evidence of the fact that every now and then, the accelerometer on my Samsung Focus seems to push out a value that’s way out of range. An interesting exercise would be to use the AccelerometerHelper class in the aforementioned article to add low-pass filtering with the “optimal” option that reduces latency when large changes in accelerometer readings occur.

As a side note, given that accelerometer events fire roughly every 20 milliseconds, you may wonder if there’s a possibility that a second ReadingChanged event handler might begin to execute if the previous one takes more than 20 milliseconds to return. Empirical testing shows that this cannot happen. The run-time serializes calls to your event handler so that you don’t have to worry about overlapping execution. That’s important, because if it weren’t the case, you’d have to consider writing thread-safe event handlers. You obviously want to get in and out as quickly as possible, but at least you don’t have to worry about one event handler executing on two threads at the same time. And remember that it’s preferable to put non-trivial logic executed in response to accelerometer events in the event handler itself to avoid placing an undue burden on the UI thread.

You can download the samples presented in this article and try them on your phone. Feel free to use them as the basis for apps of your own. Happy accelerating!