How to get data from the gyroscope sensor for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

 

This topic walks you through creating a gyroscope application that gives you a numeric display and a graphical representation of the gyroscope data. The example application also shows you how to sum up the sensor readings to determine the cumulative rotation of the device.

Note

In addition to the APIs described in this topic from the Microsoft.Devices.Sensors namespace, you can also program the phone’s sensors by using the similar classes in the Windows.Devices.Sensors namespace.

This topic contains the following sections.

 

Gyroscope overview

The gyroscope sensor measures the rotational velocity of the device along its three primary axes. When the device is still, the readings from the gyroscope are zero for all axes. If you rotate the device around its center point as it faces you, like an airplane propeller, the rotational velocity on the Z axis will elevate above zero, growing larger as you rotate the device faster. The rotational velocity is measured in units of radians per second, where 2 * Pi radians is a full rotation.

If you want to determine the device’s absolute orientation in space (yaw, pitch, and roll), we recommend that you use the combined motion API, accessed using the Motion class. For more information, see How to use the combined Motion API for Windows Phone 8.

Creating the gyroscope app

The following steps show you how to create a gyroscope application.

Important Note:

You cannot test this app on the emulator. The emulator doesn’t support the gyroscope. You can only test this app on a registered phone.

To create a gyroscope app

  1. In Visual Studio, create a new **Windows Phone App ** project. This template is in the Windows Phone category.

  2. This application requires references to the assemblies containing the sensor APIs and the XNA Framework because gyroscope data is passed in the form of an XNA Framework Vector3 object. From the Project menu, click Add Reference…, select Microsoft.Devices.Sensors and Microsoft.Xna.Framework, and then click OK.

  3. In the MainPage.xaml file, place the following XAML code in the Grid element named “ContentPanel”. This code creates TextBlock elements that are used to display the current status of the application and to numerically display current gyroscope readings. There are also three Line elements that are used to graphically represent the rotational velocity along each axis. Since this application will also keep a cumulative total of device rotation, another set of TextBlock and Line elements are provided to display the cumulative rotation.

    <StackPanel Orientation="Vertical">
      <StackPanel Orientation="Vertical">
        <TextBlock Height="30" Name="statusTextBlock" Text="status: " VerticalAlignment="Top"  />
        <TextBlock Height="30" Name="timeBetweenUpdatesTextBlock" Text="time between updates: " VerticalAlignment="Top"/>
      </StackPanel>
      <TextBlock Text="current rotational velocity (rads/sec)"></TextBlock>
      <Grid>
        <TextBlock Height="30" HorizontalAlignment="Left" Name="currentXTextBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontSize="28" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Center" Name="currentYTextBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontSize="28" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Right"  Name="currentZTextBlock" Text="Z: 1.0" VerticalAlignment="Top"  Foreground="Blue" FontSize="28" FontWeight="Bold"/>
      </Grid>
      <Grid Height="140">
        <Line x:Name="currentXLine" X1="240" Y1="40" X2="240" Y2="40" Stroke="Red" StrokeThickness="14"></Line>
        <Line x:Name="currentYLine" X1="240" Y1="70" X2="240" Y2="70" Stroke="Green" StrokeThickness="14"></Line>
        <Line x:Name="currentZLine" X1="240" Y1="100" X2="240" Y2="100" Stroke="Blue" StrokeThickness="14"></Line>
      </Grid>
      <TextBlock Text="cumulative rotation (degrees)"></TextBlock>
      <Grid>
        <TextBlock Height="30" HorizontalAlignment="Left" Name="cumulativeXTextBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontSize="28" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Center" Name="cumulativeYTextBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontSize="28" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Right" Name="cumulativeZTextBlock" Text="Z: 1.0" VerticalAlignment="Top"  Foreground="Blue" FontSize="28" FontWeight="Bold"/>
      </Grid>
      <Grid Height="200"  Name="cumulativeGrid">
        <Line x:Name="cumulativeXLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Red" StrokeThickness="14"></Line>
        <Line x:Name="cumulativeYLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Green" StrokeThickness="14"></Line>
        <Line x:Name="cumulativeZLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Blue" StrokeThickness="14"></Line>
      </Grid>
    </StackPanel>
    

    This is how the UI will appear.

  4. The last UI code to add to MainPage.xaml is the definition of an app bar with one button that will start and stop the acquisition of data from the compass. Paste the following code over the commented out app bar code that is included with the project template.

    <phone:PhoneApplicationPage.ApplicationBar>
      <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/onoff.png" Text="on/off" Click="ApplicationBarIconButton_Click"/>
      </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
    
  5. Now open the MainPage.xaml.cs code-behind page and add using directives for the sensors and the XNA Framework namespaces to other using directives at the top of the page. This application will use a DispatcherTimer to update the UI, so include the System.Windows.Threading namespace as well.

    using Microsoft.Devices.Sensors;
    using Microsoft.Xna.Framework;
    using System.Windows.Threading;
    
  6. Declare some member variables at the top of the MainPage class definition.

    public partial class MainPage : PhoneApplicationPage
    {
      Gyroscope gyroscope;
      DispatcherTimer timer;
    
      Vector3 currentRotationRate = Vector3.Zero;
      Vector3 cumulativeRotation = Vector3.Zero;
      DateTimeOffset lastUpdateTime = DateTimeOffset.MinValue;
      bool isDataValid;
    

    The first variable is an object of type Gyroscope, which will be used to obtain data from the compass sensor. Next, a DispatcherTimer is declared, which will be used to periodically update the UI. The currentRotationRate and cumulativeRotation variables will be used to store data obtained in the CurrentValueChanged event of the Gyroscope class and will be used to update the UI in the Tick event of the DispatcherTimer. lastUpdateTime will be used to calculate the cumulative rotation and isDataValid will track whether the gyroscope is currently active.

  7. In the page’s constructor, check to see whether the device on which the application is running supports the gyroscope sensor. Not all devices support all sensors, so you should always check before you use the sensor. If the gyroscope is not supported, a message is displayed to the user and the Application Bar is hidden. If the gyroscope is supported, the DispatcherTimer is initialized and an event handler is assigned, but the timer is not started at this point. Replace the existing page constructor with the following code.

    // Constructor
    public MainPage()
    {
      InitializeComponent();
      if (!Gyroscope.IsSupported)
      {
        // The device on which the application is running does not support
        // the gyroscope sensor. Alert the user and hide the
        // application bar.
        statusTextBlock.Text = "device does not support gyroscope";
        ApplicationBar.IsVisible = false;
      }
      else
      {
        // Initialize the timer and add Tick event handler, but don't start it yet.
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(60);
        timer.Tick += new EventHandler(timer_Tick);
      }
    }
    
  8. Add a handler for the Click event for the Application Bar Button. Depending on how you added the XAML code above, Visual Studio may have added this handler for you. If so, remove any code from inside the handler. If the handler was not added automatically, copy and paste the following empty function into the MainPage class definition.

    private void ApplicationBarIconButton_Click(object sender, EventArgs e)
    {
    
    }
    
  9. In the Application Bar Button click handler, first check to see if the Gyroscope object is not null and receiving data. If this is the case, then the user is clicking the button to stop the gyroscope, so call Stop()()() for both the Gyroscope and the DispatcherTimer. Paste the following code inside the empty button click handler.

      if (gyroscope != null && gyroscope.IsDataValid)
      {
        // Stop data acquisition from the gyroscope.
        gyroscope.Stop();
        timer.Stop();
        statusTextBlock.Text = "gyroscope stopped.";
      }
    
  10. Next, the code will handle the case where the user is starting the gyroscope. If the Gyroscope object is null, then create a new instance. Set the desired time between updates. Note that sensors on different devices support different update intervals; in this example, the property is queried after it has been set in order to display the sensor’s actual interval to the user. Next, an event handler is added for the CurrentValueChanged event, which is raised whenever the gyroscope has new data. Paste this code inside the button click handler, after the previous code section.

      else
      {
        if (gyroscope == null)
        {
          // Instantiate the Gyroscope.
          gyroscope = new Gyroscope();
    
          // Specify the desired time between updates. The sensor accepts
          // intervals in multiples of 20 ms.
          gyroscope.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
    
          // The sensor may not support the requested time between updates.
          // The TimeBetweenUpdates property reflects the actual rate.
          timeBetweenUpdatesTextBlock.Text = "time between updates: " + gyroscope.TimeBetweenUpdates.TotalMilliseconds + " ms";
    
          gyroscope.CurrentValueChanged +=
              new EventHandler<SensorReadingEventArgs<GyroscopeReading>>(gyroscope_CurrentValueChanged);
        }
    
  11. Now, start the gyroscope using the Start()()() method. It is possible for the call to Start to fail, so you should put this call in a try block. In the catch block, you can alert the user that the gyroscope could not be started. This code also starts the DispatcherTimer. Paste this code into the button click handler after the previous code section.

        try
        {
          statusTextBlock.Text = "starting gyroscope.";
          gyroscope.Start();
          timer.Start();
        }
        catch (InvalidOperationException ex)
        {
          statusTextBlock.Text = "unable to start gyroscope.";
        }
      }
    
  12. Now, implement the CurrentValueChanged event handler. This method will be called by the system with new gyroscope data at the frequency you specified with TimeBetweenUpdates. The handler receives a GyroscopeReading object containing the gyroscope data. This handler is called on a background thread that does not have access to the UI. So, if you want to modify the UI from this method, you must use the Dispatcher.Invoke method, which calls the specified code on the UI thread. This example uses a timer to update the UI, so it does not need to do so. This code sets the isDataValid variable to the IsDataValid member of the Gyroscope object. Next, the code checks to see if the lastUpdateTime member has been previously set. If not, it sets this variable to the Timestamp property of the gyroscope reading and exits the method. If lastUpdateTime has been previously set, the currentRotationRate variable is set to the RotationRate member of the GyroscopeReading class. Next, lastUpdateTime is subtracted from the TimeStamp value to determine what fraction of a second transpired between updates. The amount of rotation in since the last reading update is the current rotation rate multiplied by the number of seconds since the last update. The product of these two is added to the cumulativeRotation variable. Finally, the lastUpdateTime variable is updated to the current timestamp.

    void gyroscope_CurrentValueChanged(object sender, SensorReadingEventArgs<GyroscopeReading> e)
    {
      // Note that this event handler is called from a background thread
      // and therefore does not have access to the UI thread. To update 
      // the UI from this handler, use Dispatcher.BeginInvoke() as shown.
      // Dispatcher.BeginInvoke(() => { statusTextBlock.Text = "in CurrentValueChanged"; });
    
      isDataValid = gyroscope.IsDataValid;
    
      if (lastUpdateTime.Equals(DateTimeOffset.MinValue))
      {
        // If this is the first time CurrentValueChanged was raised,
        // only update the lastUpdateTime variable.
        lastUpdateTime = e.SensorReading.Timestamp;
      }
      else
      {
        // Get the current rotation rate. This value is in 
        // radians per second.
        currentRotationRate = e.SensorReading.RotationRate;
    
        // Subtract the previous timestamp from the current one
        // to determine the time between readings
        TimeSpan timeSinceLastUpdate = e.SensorReading.Timestamp - lastUpdateTime;
    
        // Obtain the amount the device rotated since the last update
        // by multiplying by the rotation rate by the time since the last update.
        // (radians/second) * secondsSinceLastReading = radiansSinceLastReading
        cumulativeRotation += currentRotationRate * (float)(timeSinceLastUpdate.TotalSeconds);
    
        lastUpdateTime = e.SensorReading.Timestamp;
      }
    }
    
  13. Implement the DispatcherTimerTick event handler that will show the gyroscope data to the user. This method first updates the status TextBlock to indicate that data is being received. Next, the TextBlock objects are updated to display the numeric values of the rotational acceleration and cumulative rotation around each of the sensor’s axes. Finally, the Line objects are updated to graphically illustrate the acceleration and rotation.

    void timer_Tick(object sender, EventArgs e)
    {
      if (isDataValid)
      {
        statusTextBlock.Text = "receiving data from gyroscope.";
      }
    
      currentXTextBlock.Text = currentRotationRate.X.ToString("0.000");
      currentYTextBlock.Text = currentRotationRate.Y.ToString("0.000");
      currentZTextBlock.Text = currentRotationRate.Z.ToString("0.000");
    
      cumulativeXTextBlock.Text =
        MathHelper.ToDegrees(cumulativeRotation.X).ToString("0.00");
      cumulativeYTextBlock.Text =
        MathHelper.ToDegrees(cumulativeRotation.Y).ToString("0.00");
      cumulativeZTextBlock.Text =
        MathHelper.ToDegrees(cumulativeRotation.Z).ToString("0.00");
    
    
      double centerX = cumulativeGrid.ActualWidth / 2.0;
      double centerY = cumulativeGrid.ActualHeight / 2.0;
    
      currentXLine.X2 = centerX + currentRotationRate.X * 100;
      currentYLine.X2 = centerX + currentRotationRate.Y * 100;
      currentZLine.X2 = centerX + currentRotationRate.Z * 100;
    
      cumulativeXLine.X2 = centerX - centerY * Math.Sin(cumulativeRotation.X);
      cumulativeXLine.Y2 = centerY - centerY * Math.Cos(cumulativeRotation.X);
      cumulativeYLine.X2 = centerX - centerY * Math.Sin(cumulativeRotation.Y);
      cumulativeYLine.Y2 = centerY - centerY * Math.Cos(cumulativeRotation.Y);
      cumulativeZLine.X2 = centerX - centerY * Math.Sin(cumulativeRotation.Z);
      cumulativeZLine.Y2 = centerY - centerY * Math.Cos(cumulativeRotation.Z);
    }