Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

The Win7 Sensor and Location API Part 2: Accelerometer as a Joystick

Pete Brown - 01 November 2009

This is part 2 of a series. If you don’t yet have the Freescale board configured and tested with Windows 7, please refer to Part 1.

Now that we have the Freescale board working, I thought it would be fun to play with the accelerometer. In this post, I’ll show how to use the accelerometer on the board as a traditional 4 or 8 position joystick.

API Choices

There are three different ways you can use the accelerometer API. You can, of course, go directly against the COM API and generate you own wrappers and pinvokes. You can use the managed wrappers provided in the SDK, or you can use the Windows API Code Pack which includes support for the sensor API and a number of other Windows 7-specific enhancements.

In this case, I decided to use the code pack in concert with WPF 4.

Solution Structure

I intend to use the joystick functionality in another project, so I am wrapping that into a separate project. I also added just the sensor project from the Windows API Code Pack. You have a few choices there, including compiling the whole code pack as a separate assembly and simply referencing it. I decided to include only the bare minimum of assemblies required to support the example.

image

PeteBrown.Accelerometer is the reusable Accelerometer joystick class lib. The “Sensors” project is the Windows API Code Pack sensors project. The “Core” project is the required Windows API Code Pack core project

Sample Code

One great thing about the Windows API Code Pack is it includes a ton of example code. It just so happens that the accelerometer code is included in that.

Application Code

Typically I’m a binding fiend. I like to set up notification in my classes and just let binding handle all the dirty work. In this case, though, I wanted to go with a model that would work better in apps that used a type of GameLoop and instead of responding to events or binding, simply polled all input devices on an interval of their own design. For that reason, I’m using simple properties in this class, am not implementing INotifyPropertyChanged, and did not chose to raise any events.

If you’re a game programmer, or have that type of background, this decision probably won’t surprise you at all.

AccelerometerJoystick Class

The core class is the AccelerometerJoystick class.

I snagged some of the code right out of the Accelerometer sample from the code pack. One of the methods I borrowed and then messed with a bit was the Initialize method (called HookUpAccelerometer in the sample code). I changed it to only report X and Y, and to use an inline lambda event handler to cut down on code.

public void Initialize()
{
    SensorList<Accelerometer3D> sl = SensorManager.GetSensorsByTypeId<Accelerometer3D>();

    if (sl.Count > 0)
    {
        Accelerometer3D accel = sl[0];
        accel.AutoUpdateDataReport = true;
        accel.DataReportChanged += (s, e) =>
            {
                _rawXValue = accel.CurrentAcceleration[AccelerationAxis.X];
                _rawYValue = accel.CurrentAcceleration[AccelerationAxis.Y];
            };
    }
}

The code finds the first accelerometer and then wires up the DataReportChanged event to catch changes in the X/Y values in the accelerometer. I don’t call Initialize from the constructor, as it may throw an exception. Instead, I have the using code call it explicitly. I may change that

I first tested by watching the raw values on a regular interval. The raw values are two simple properties on the same class:

private float _rawXValue;
public float RawXValue
{
    get { return _rawXValue; }
}

private float _rawYValue;
public float RawYValue
{
    get { return _rawYValue; }
}

Once I had the values set, and they made sense to me, I implemented properties to report the direction the accelerometer is pointing.

There were a couple different ways I could have gone here. I could have created a N, S, E, W enum, and had a single method that reported which direction the mouse was pointed. However, to support NW, NE, SW, SE, I’d have to add four more branches to that code and four more enum members.

I think it is more flexible to simply report true/false for each of the four cardinal directions, and let the calling code decide how to handle it from there. So, that’s the way I went.

public bool IsJoystickPointedNorth
{
    get { return RoundValue(_rawYValue) > 0; }
}

public bool IsJoystickPointedSouth
{
    get { return RoundValue(_rawYValue) < 0; }
}

public bool IsJoystickPointedWest
{
    get { return RoundValue(_rawXValue) < 0; }
}

public bool IsJoystickPointedEast
{
    get { return RoundValue(_rawXValue) > 0; }
}


private int RoundValue(float value)
{
    return (int)Math.Round((double)value, 0);

    // example making the joystick more sensitive
    // return Math.Abs(value) > 0.25 ? 1 * Math.Sign(value) : 0;
}

A note on rounding: If you want to change the sensitivity, which you probably will, change the RoundValue method so that instead of using Math.Round, it does a check to see if the ABS of the value is, say, greater than 0.25 and return 1. That way you can have the joystick be a bit more sensitive to movement. Similarly, you can change it to return 1 only if the ABS of the value is greater than 0.75 for less sensitivity. Try it out to see how you prefer it.

MainWindow.xaml

image

The main window is pretty simple. I have a space in the middle where I report the raw X/Y values, and then four rectangles which light up (red in this case) when the joystick is pointed in that direction.

<Window x:Class="WpfAccelerometerApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    
    <Window.Resources>
        <SolidColorBrush x:Key="ActiveDirectionBrush"
                         Color="Red" />
        <SolidColorBrush x:Key="InactiveDirectionBrush"
                         Color="Gray" />
    </Window.Resources>
    
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Rectangle x:Name="North"
                   Grid.Row="0"
                   Grid.Column="1"
                   Margin="20"/>

        <Rectangle x:Name="South"
                   Grid.Row="2"
                   Grid.Column="1"
                   Margin="20" />

        <Rectangle x:Name="East"
                   Grid.Row="1"
                   Grid.Column="2"
                   Margin="20" />

        <Rectangle x:Name="West"
                   Grid.Row="1"
                   Grid.Column="0"
                   Margin="20" />
        
        <Grid Grid.Row="1" Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="2*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <TextBlock Text="X"
                       Grid.Row="0"
                       Grid.Column="0" />

            <TextBlock Text="Y"
                       Grid.Row="1"
                       Grid.Column="0" />

            <TextBlock x:Name="xValue" 
                       Text="0"
                       Grid.Row="0"
                       Grid.Column="1" />

            <TextBlock x:Name="yValue" 
                       Text="0"
                       Grid.Row="1"
                       Grid.Column="1" />
        
        </Grid>
    </Grid>
</Window>

There’s nothing WPF-specific about this app other than the specific timer I use below, and the use of xaml in the window. If you code in Windows forms or another client technology, you should be able to port this pretty easily.

MainWindow.xaml.cs

Here’s the code-behind for the main window. First, I keep two private member variables, one for the joystick and one for the DispatcherTimer I use to poll the joystick (again, because I’m going with a gameloop-type approach rather than event-driven. In my other app, the joystick poll is actually one step in a big loop)

private AccelerometerJoystick _joystick = new AccelerometerJoystick();
private DispatcherTimer _timer = new DispatcherTimer();

The constructor simply does the usual initialization, and then wires up the loaded event. I tend to do significant work in Loaded events to avoid race conditions with controls not being ready yet, and to avoid exceptions in constructors. It’s not a bad habit to be in.

public MainWindow()
{
    InitializeComponent();

    Loaded += new RoutedEventHandler(MainWindow_Loaded);
}

The MainWindow_Loaded event handler is where I’m doing the dirty work. In here, I first initialize the joystick, then start the timer. The timer’s tick event is handled as an inline lanbda event handler. That handler polls the joystick, updates the display values and then sets the brush used by the rectangles based on whether or not the joystick reports that it is pointed in that direction.

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _joystick.Initialize();


    _timer.Interval = TimeSpan.FromMilliseconds(PollingIntervalMilliseconds);
    _timer.Start();

    _timer.Tick += (s, ea) =>
        {
            // display position
            xValue.Text = _joystick.RawXValue.ToString();
            yValue.Text = _joystick.RawYValue.ToString();

            Brush ActiveBrush = (Brush)this.Resources["ActiveDirectionBrush"];
            Brush InactiveBrush = (Brush)this.Resources["InctiveDirectionBrush"];

            if (_joystick.IsJoystickPointedSouth)
                South.Fill = ActiveBrush;
            else
                South.Fill = InactiveBrush;

            if (_joystick.IsJoystickPointedWest)
                West.Fill = ActiveBrush;
            else
                West.Fill = InactiveBrush;

            if (_joystick.IsJoystickPointedNorth)
                North.Fill = ActiveBrush;
            else
                North.Fill = InactiveBrush;

            if (_joystick.IsJoystickPointedEast)
                East.Fill = ActiveBrush;
            else
                East.Fill = InactiveBrush;
        };
}

Runtime

At runtime, the directions light up as expected, and the raw X and Y values are displayed in the middle. Now to use this in a real application :)

image

The source code and a video walkthrough of this will be available on windowsclient.net soon. I’ll update this post with the link once that happens.

[Update: Source and video here]

       
posted by Pete Brown on Sunday, November 1, 2009
filed under:        

Comment on this Post

Remember me

5 trackbacks for “The Win7 Sensor and Location API Part 2: Accelerometer as a Joystick”

  1. ASPInsiderssays:
    If you attended the DC Windows 7 Launch yesterday in Tyson’s Corner, VA, you may have gotten a chance
  2. POKE 53280,0: Pete Brown's Blogsays:
    My primary development machine at home is still (gasp!) running the 32 bit version of Windows 7. I just
  3. Community Blogssays:
    My primary development machine at home is still (gasp!) running the 32 bit version of Windows 7. I just
  4. Windows Client Newssays:
    In this session, Pete Brown shows us how to use the Windows 7 Sensor API, via the Windows API Code Pack
  5. POKE 53280,0: Pete Brown's Blogsays:
    Windows 7 introduced the Sensor and Location API. I’ve previously blogged about the sensor API, but couldn’t