Contents
I was as fascinated with the Nintendo Wii gaming console as the rest of the world was when it came out and thought its unique input system was jaw-dropping awesome. I subsequently happened to stumble upon some incredibly creative work by Johnny Chung Lee on the internet one day where he demonstrates how the Wii remote - also known as the Wiimote - can be used to build innovative new ways of interacting with a computer. He got me so excited with the idea that I almost immediately went out and got a Wiimote myself though I don't even own the Wii console!
When Windows 7 came out and I learnt about the new Sensor and Location Platform, I figured writing up a little bridge driver that exposed the 3-axis accelerometer that each Wiimote comes with to the sensor platform would be kind of cool. This article attempts to explain how the driver works and what it takes, in general, to write a driver for the Sensor platform.
The implementation of the Wiimote sensor driver, particularly the communication protocol implementation, has been heavily "inspired" by Brian Peek's WiimoteLib library which has turned into a sort of de-facto standard API for gaining access to the Wiimote from managed code via the Windows HID API. Also, the sample driver implementations from the Windows Driver Kit provided the starting point to get the project off the ground. The I/O management code was taken with some slight modifications from the Sensor Development Kit.
If you're looking to just use the driver and don't really care how it works, then here's what you've got to do. The Wiimote is a surprisingly self-contained piece of hardware that really is a stand-alone product by itself. It communicates with the Wii console via standard Bluetooth radio signals and identifies itself as a HID input device. To make it work with your computer, here's what you'll need:
- A Wiimote (duh!)
- A computer with Windows 7
- A Bluetooth card
There have been some reports out there where people had trouble getting their Bluetooth driver stack to recognize the Wiimote. If you're one of them you'll probably need to get updated drivers for your Bluetooth card. Assuming your computer is able to pair with the Wiimote, here's how you do it in Windows 7.
- Click the "Start" button, type "bluetooth" into the search box and select "Add a Bluetooth device".
- Press the 1 and the 2 buttons on the Wiimote at the same time. You should see the 4 blue LEDs blinking. Keep both the buttons pressed until the pairing is complete.
- You should now see the Wiimote show up in the "Add a device" window. Select the Wiimote and hit "Next".
- In the "Select a pairing option" screen click "Pair without using a code".
- You'll see the following screen next. This does not however mean that the pairing was successful. If you look in your system tray you'll see the system trying to locate a driver and install it. Windows will try to find a driver for the device on Windows Update first and that usually takes a while to complete - you can expedite the process by cancelling the look up on Windows Update.
- Once the driver installation is complete, you should see this in the system tray area if everything went well.
And in your Bluetooth devices window you should see the Wiimote listed as a game controller device.
- Now, you're probably seeing the 4 LEDs on your Wiimote continue to blink non-stop. I suggest you run Brian Peek's "WiimoteTest.exe" tool (which is included in the "tools" download provided along with the downloads for this article) about now and have only 1 of the LEDs light up so you know it's on. Also, running "WiimoteTest.exe" appears to initialize the Wiimote somehow and is required to get the sensor driver talking with the Wiimote. I hope to figure out what exactly "WiimoteTest.exe" does to initialize the Wiimote and do that from the sensor driver also one of these days.
To install the Wiimote sensor driver so it shows up as a sensor in your system, here's what you need to do:
- Download the relevant binaries package depending on the number of bits your CPU can grok in a single cycle. In other words if you have a 32-bit CPU then download the 32-bit version of the binary and if you have a 64-bit CPU then download the 64-bit version. Unzip it somewhere - I'll assume you unzipped it to "C:\Wiisensor".
- Download the tools ZIP file and unzip it to the same location that you used in step 1.
- Open a command prompt with administrator privileges and navigate to "C:\Wiisensor".
Now, depending on whether you are running a 32-bit or a 64-bit version of Windows 7 navigate further to either the "i386" folder or the "amd64" folder in the command window. If you're on a 64-bit edition of Windows 7 then run the following command (all on one line):
C:\Wiisensor\amd64>..\tools\devcon\amd64\devcon.exe install WiimoteSensor.inf
"HID\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306"
If you're on a 32-bit edition of Windows 7 then run the following command (again, all on one line):
C:\Wiisensor\i386>..\tools\devcon\i386\devcon.exe install WiimoteSensor.inf
"HID\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306"
If you are presented with an unsettling dialog such as this one, just hit "Install this driver software anyway":
You're seeing this because the driver needs to be signed with a trusted digital certificate - which it would be had this been a commercial driver.
- Once the installation is complete you should be able to see the device listed under the "Sensor" class in device manager (you can launch the device manager by clicking "Start" and typing "device manager" into the search box).
As you can see, the Wiimote is listed not once, but twice! I haven't quite figured out why it does this, but it is safe to simply uninstall the entry that shows with a yellow exclamation mark next to it. Simply right click on the entry and select "Uninstall".
The final step is to enable the sensor in the sensors control panel. Click "Start" and type "sensors" into the search box and select "Location and Other Sensors".
Check the "Enabled" box against the "Nintendo Wiimote" entry in the sensors control panel and hit "Apply".
And that's it! Now you're ready for business! I know you're thinking that all this is way too complicated and I agree. But in a real app all this should ideally be taken care of by an installer app and end users wouldn't need to have to mess with this stuff.
If you are with me so far, then you should be able to launch the "Wiidiag" tool from "C:\Wiisensor\bin\Wiidiag\Wiidiag.exe" and watch a dull little graph respond to you whenever you wave your Wiimote around!
Now, what's cool is that you can pretty much get any app off the shelf and have it work with your Wiimote's accelerometer if it is designed to work with the Sensor platform. You could for instance, download the plane demo app from here and it should just work! In fact I have included the binary for that demo with the download for this article and you should find it in "C:\Wiisensor\tools\PlaneDemo\DemoApp.exe". When you launch it you should see a little plane model rendered on the screen. Just check the 3 check boxes representing the 3 axes and it should immediately start rotating the plane around as you move the Wiimote.
The Windows Sensor and Location Platform is a new framework on Windows 7 designed to make it easy for hardware vendors and application developers work seamlessly with sensor devices. Here's some standard palaver from Microsoft:
The Windows Sensor and Location platform, which is new for Windows 7, enables your computer and applications to adapt to their current environment. With location sensors -- including GPS devices, WWAN radios, and even triangulation technology -- your applications and gadgets can know exactly where they are, enabling them to provide more locally relevant content and functionality. Ambient light sensors, for example, can allow your computer to automatically adjust your screen's brightness based on the current lighting conditions. They can also enable applications to optimize their content for readability, making your computer more useful in a range of operating environments than ever before.
The platform provides a standard way to integrate sensor and location devices into Windows, as well as a standard programming interface for applications to take advantage of these devices. On Windows 7, the user has control over how data from these devices is exposed to applications.
Sensor client apps can be written using the new Sensor API which is basically a COM based unmanaged API. But Microsoft has however, provided helpful little managed wrapper classes as part of their Windows 7 API code pack, which you can download from here. The "Wiidiag" sample app that comes with the download for this article for instance is an example of how straightforward the API is to use.
You first setup a reference to the "Microsoft.WindowsAPICodePack.dll" and the "Microsoft.WindowsAPICodePack.Sensors.dll" files and use the SensorManager
class to gain access to the sensors installed on the system. Here's the code from "Wiidiag" that initializes a drop down with all the "Motion" sensors installed on the system.
_sensors = SensorManager.GetSensorsByCategoryId(SensorCategories.Motion);
foreach (Sensor s in _sensors)
{
cmbMotionSensors.Items.Add(s.FriendlyName);
s.StateChanged += new StateChangedEventHandler(Sensor_StateChanged);
s.DataReportChanged += new DataReportChangedEventHandler(Sensor_DataReportChanged);
}
The StateChanged
event fires whenever something of note happens to the sensor such as when it becomes available or when it goes away. The DataReportChanged
event fires whenever the sensor has some data to provide to the app. The Wiimote accelerometer data for instance, is made available via this event.
Here's the code from "Wiidiag" for handling the DataReportChanged
event. It essentially simply adds the new data sample to the graph user control.
void Sensor_DataReportChanged(Sensor sender, EventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { Sensor_DataReportChanged(sender, e); }));
return;
}
if (_current.SensorId == sender.SensorId)
{
Accelerometer3D accel = _current as Accelerometer3D;
if (accel != null)
{
Acceleration3D data = accel.CurrentAcceleration;
historyGraphX.AddValue("x", data[AccelerationAxis.X]);
historyGraphY.AddValue("y", data[AccelerationAxis.Y]);
historyGraphZ.AddValue("z", data[AccelerationAxis.Z]);
}
}
}
That's it! It couldn't get any simpler, could it?!
Now, writing a sensor driver is a tad more complicated, as you might expect and is in fact the focus of the rest of this article. Having said that, you really should not let that scare you as writing a sensor driver is nothing like writing drivers in the traditional sense, i.e. if you're thinking endless machine restarts and the blue screen of death (BSOD) - rest assured that the experience isn't at all like that primarily because sensor drivers are written using the User Mode Driver Framework (UMDF). Before I can tell you how a sensor driver works, I'll need to give you a brief primer on how UMDF drivers work. If you already know how a UMDF driver is built, then feel free to skip the next section.
Describing how to write a UMDF driver is sophisticated enough to warrant a separate article in its own right and I am not therefore, going to try and attempt to explain every nuance of it. In fact I myself am only a beginner and won't pretend that I know all that there is to know about it. I will however try and depict how I visualize the framework in my head. Folks over at OSR have written a nice little overview of what it takes to implement a UMDF driver here. An architectural overview is available here from the same site.
The basic idea behind the UMDF is that for a certain class of drivers, it simply is not worth the pain of implementing a kernel mode driver when the same can be achieved with code running in user mode. Here're some obvious benefits from having your driver run in user land (taken from the UMDF FAQ).
- Increased stability. User-mode drivers can crash or hang only their own process, not the entire system.
- Ease of development. User-mode drivers can be written in C++ and debugged by using user-mode debuggers, without requiring a separate debugging system.
- Increased security. User-mode drivers do not have access to the kernel-mode address space and therefore cannot expose kernel-mode data, such as system information or data belonging to other processes.
The most important benefit is of course that a driver crash does not mean a BSOD and a system restart. Instead, the process hosting the UMDF driver can simply be terminated and then restarted. As a developer, the fact that you can use the debuggers that you love (Visual Studio for instance) for your regular user mode application development is an added bonus.
The next section describes just enough of UMDF so that you can appreciate how the sensor driver works. It does not, for instance, try to explain all the different ways that a UMDF driver can handle I/O.
A UMDF driver is a regular windows DLL. The framework uses a COM based development environment without actually using the COM runtime library. The COM-connection exists almost solely for managing object lifetimes and for type introspection which translates to the requirement that all UMDF objects must implement IUnknown
.
Life in a UMDF driver begins with an object that implements the interface IDriverEntry
. This interface has only 3 methods in it with the key one being OnDeviceAdd
which is invoked by the framework when a device of interest arrives in the system. The driver is expected to respond to the OnDeviceAdd
call by creating an IWDFDevice
object and hooking it up with a object that the driver provides which in turn implements all of the callback interfaces containing event notifications that the driver is interested in being notified with.
In the Wiimote sensor driver for example, the class CWiimoteDriver
implements IDriverEntry
. In its OnDeviceAdd
method, it calls the static method CWiimoteDevice::CreateInstance
which in turn instantiates an IWDFDevice
object and associates it with an instance of CWiimoteDevice
via IWDFDriver::CreateDevice
. CWiimoteDevice
implements the IPnpCallback
, IPnpCallbackHardware
and the IFileCallbackCleanup
interfaces which contain callback methods that are invoked by the framework at various points. IPnpCallback
for instance, contains methods that are called whenever a plug-and-play device enters and exits the system.
Here's a sequence diagram that might help illustrate the flow of control in a UMDF driver as it loads.
The CWiimoteDevice
class implements IPnpCallback
, IPnpCallbackHardware
and IFileCallbackCleanup
. The key interface here is IPnpCallbackHardware
which contains 2 methods - OnPrepareHardware
and OnReleaseHardware
. OnPrepareHardware
is called by the framework to indicate that the driver should do everything that is necessary to prepare the device for access and OnReleaseHardware
is called to have the driver perform clean-up after the device stops being accessible. CWiimoteDevice::OnPrepareHardware
bootstraps the sensor framework by initializing all the sensor objects, the sensor class extension object (explained later in the article) and the object that implements ISensorDriver
(again, explained elsewhere).
CWiimoteDevice
's implementation of the IPnpCallback
interface does something useful only in the CWiimoteDevice::OnD0Entry
and the CWiimoteDevice::OnD0Exit
methods where it instructs all the sensor devices in the driver to start and stop I/O operations respectively.
Like all drivers, UMDF drivers are also installed using an INF file as the source. Among other things, the INF file describes:
- what driver class the driver belongs to
- what version of the OS the driver requires for it to work
- what device the driver is built to work with
- what files must be included in the driver package and where they must be copied during the install
- the driver version and time-stamp
The Windows device manager uses this information to correctly install and configure the driver. For UMDF drivers, one other critical piece of information is the COM CLSID of the object implementing IDriverEntry
as without this the framework cannot really boot the driver up. For a detailed explanation of how INF files work I suggest you take a look at the MSDN documentation.
Beyond this point, control shifts to the sensor framework. Before we delve into that however, it is necessary that we take a short detour looking into how the build system for the Wiimote sensor driver works.
If you, like me, have been spoiled by Visual Studio in developing, debugging and testing applications you might feel as at sea as I felt initially when working with UMDF where you are pretty much forced to use the build environment that the Windows WDK installs for you. But some of the saintly folks at OSR have taken the effort to build a batch file that allows you to integrate the WDK with Visual Studio and build and code from right within the IDE. In the next few sections I explain the things you need to setup on your machine before you can hit Ctrl+Shift+B (or just F7 if you've got the VC++ keyboard bindings setup in VS as I have).
First, download and install all of the following:
You might be wondering what's the deal with downloading UnxUtils. I'll explain the exact role that this plays later on in this section but I basically use some of the UNIX tools such as "sed" to automatically update build revision numbers.
Make sure that the system PATH
environment variable has been updated to point to the "\usr\local\wbin" folder of your UnxUtils installation. This is required so that the build system can invoke the UNIX tools simply by name.
The Visual Studio project for the driver is a VC++ Makefile project. Makefile projects are basically projects where the actual building of the binaries are delegated to an external script/batch file. Visual Studio can be configured to invoke an external command (such as makefile) whenever you build your project. VS will automatically parse the output from the command and list the errors and warnings directly in the IDE if they are printed in standard forms such as those produced by the VC++ compiler so that double-clicking on an error will directly take you to the offending line. The next few sections describe how this process works.
As I had explained earlier, building drivers (user mode or otherwise) using the WDK requires you to launch the relevant build environment depending on the CPU architecture and operating system you are targeting and issuing the build command. The build utility uses a file called "sources" to determine what the source files are and how the binary must be built. The "sources" file used for the Wiimote sensor driver project is a fairly standard one except for a couple of things:
- I added the
USE_STL = 1
directive as I have grown to love and adore the STL container classes. - I customized the flags passed to the C++ compiler using the
USER_C_FLAGS = /EHsc /wd4238
directive as I needed exception handling support (which /EHsc
enables). The /wd4238
flag prevents the compiler from issuing warning 4238. This was needed because a certain line of code in the project causes the compiler to issue this warning and WDK builds are by default configured to treat all warnings as errors. I happen to believe that this particular warning can be safely ignored in this case.
Now that we have the "sources" file in place we should be able to launch a WDK build environment and run build -ceZ
to do a clean rebuild. To be able to do this from Visual Studio however I used the DDKBUILD batch file from OSR as the external build command for the VS Makefile project. The article at that link describes how the batch file works. I created separate VC++ build configurations for building x86 32-bit and 64-bit versions of the driver. So, to build a 32-bit version of the Wiimote driver, simply select the current configuration as "Debug32" or "Release32" depending on whether you want a debug or a release build (also called as a "checked" or "free" build in the driver community) and hit F7 (or Ctrl+Shift+B). You'd select "Debug64" or "Release64" to get the corresponding 64-bit versions. Here're the build and rebuild settings for all the 4 configurations:
- Debug64 - Build
- ddkbuild -W7X64 checked . -WDF
- Debug64 - Rebuild
- ddkbuild -W7X64 checked . -cZ -WDF
- Release64 - Build
- ddkbuild -W7X64 free . -WDF
- Release64 - Rebuild
- ddkbuild -W7X64 free . -cZ -WDF
- Debug32 - Build
- ddkbuild -W7 checked . -WDF
- Debug32 - Rebuild
- ddkbuild -W7 checked . -cZ -WDF
- Release32 - Build
- Release32 - Rebuild
- ddkbuild -W7 free . -cZ -WDF
Being a driver, you can't just hit F5 to start a debugging session because the driver is loaded in the UMDF host process on-demand when a device that needs the driver shows up on the system. Debugging UMDF drivers has been adequately explained in MSDN and that is your best bet for figuring out how to do it. I personally used the technique as described here to attach to the UMDF host process when the driver loads. The host process is called "WUDFHost.exe" and you can attach to this process immediately after you have installed the driver using devcon.exe
. I set the HostProcessDbgBreakOnStart
value in the registry to 60 seconds so that I have adequate time to attach to the host process.
The only tricky part here is figuring out which instance of "WUDFHost.exe" to attach to as chances are there are multiple instances running at a given point in time. I usually just attach to all the running instances. Sometimes I'd attach to all the running instances and find that the driver DLL does not load - this usually means that a new host process was launched after I'd attached to all the existing hosts.
It turns out that the Windows device manager caches driver installation files (in "C:\windows\system32\DriverStore\FileRepository" on Windows 7) the first time that you install it and will always pick the files from the cache during a subsequent reinstall/update unless the new files have an updated version number. This is a bit of a pain during a standard build-deploy-debug cycle because you have to remember to manually update the RC file and the "WiimoteSensor.inf" file with a new version number every time you build. I decided to automate this little bit so that every time I created a new build from VS, it'd automatically increment the version numbers. Here's how this works for the Wiimote sensor project:
- DDKBUILD supports the execution of pre/post build tasks by creating files called "ddkprebld.cmd" and "ddkpostbld.cmd" in the folder where you have all of your source files. In "ddkprebld.cmd" I call another batch file called "incv.bat" inside the "mbld" folder to have it update the version numbers.
- "incv.bat" uses 2 other batch files called "incm.bat" and "incr.bat" to increment the version numbers in the "makefile.inc" and the "WiimoteSensor.rc" files.
- All these script files primarily use the UNIX "sed" tool to look for and update the specific string in the source file that is to be updated.
While the sample drivers that the WDK comes with for sensor drivers give you a general idea of how to go about writing sensor drivers, I personally found the way the code was structured quite confusing. I decided to re-factor things a little when writing the Wiimote sensor driver. This section seeks to explain how I went about implementing the core driver.
The primary task of a sensor driver is to, well, sense things and report data! From a sensor driver's perspective the outside world is represented via a pointer to an object implementing the interface ISensorClassExtension
. This object is provided by the sensor platform and acts as the broker between the driver and the client apps that are interested in the data that the sensor makes available. From the ISensorClassExtension
object's perspective, the sensor driver is an object that implements ISensorDriver
. The ISensorDriver
object is provided by the driver and contains various methods that are invoked at different points by the sensor platform as needed. The key responsibilities of the sensor driver are:
- Manage I/O with the sensor hardware device.
- Manage sensor properties and data.
- Track client apps that connect to the sensor and honor client app preferences.
- Raise sensor data events.
Each sensor supported by the sensor driver is essentially a collection of properties and data. Properties are different from data in the sense that "properties" are usually attributes - such as the friendly name of the sensor, the name of the manufacturer, the model number etc. - attributes that tend to stay static. And "data" is, well, data! In the case of the Wiimote sensor driver, the acceleration values reported by the Wiimote qualifies as data.
The sensor platform requires that you report and maintain the properties and data supported by your sensor through objects implementing IPortableDeviceKeyCollection
and IPortableDeviceValues
. IPortableDeviceValues
is basically a dictionary object where the keys are GUID values representing individual properties and the value is a PROPVARIANT
which can hold pretty much anything (its the same thing as the VARIANT
type in COM). IPortableDeviceKeyCollection
is a collection of GUID values representing a collection of property keys.
To make the management of properties and data somewhat painless, I came up with a class called CPortableDeviceProps
that almost completely shields its users from having to deal directly with the IPortableDeviceKeyCollection
and IPortableDeviceValues
objects. It uses the CDeviceProperty
object to support seamless specification of property values in a type-independent (without sacrificing type-safety) manner. Creating the collection of data properties supported by the accelerometer for instance looks like this:
if(SUCCEEDED(hr))
{
hr = m_Data.AddProps(DEVPROPS(
MAKEPROP(SENSOR_DATA_TYPE_ACCELERATION_X_G, DEFAULT_ACCELERATION_G),
MAKEPROP(SENSOR_DATA_TYPE_ACCELERATION_Y_G, DEFAULT_ACCELERATION_G),
MAKEPROP(SENSOR_DATA_TYPE_ACCELERATION_Z_G, DEFAULT_ACCELERATION_G)
));
}
Here, m_Data
is an instance of the CPortableDeviceProps
class.
When you think about implementing ISensorDriver
it turns out to be useful to think of the methods in this interface as being grouped into clusters of related functionality. I suspect that all the methods were clumped into a single interface in an attempt at keeping the requirements on the driver somewhat simple. Here's a listing of the methods supported by this interface classified into functional groups.
Client Management
- OnClientConnect
- OnClientDisonnect
Event Management
- OnClientSubscribeToEvents
- OnClientUnsubscribeFromEvents
- OnGetSupportedEvents
Data Management
- OnGetSupportedDataFields
- OnGetSupportedProperties
- OnGetDataFields
- OnGetProperties
Other Methods
- OnGetSupportedSensorObjects
- OnProcessWpdMessage
- OnSetProperties
The overall design of the sensor driver reflects this classification of functionality and the implementation of ISensorDriver
mostly just delegates to other participating objects that are responsible for the relevant functional area.
The sensor driver can be visualized as being a collection of sensor objects that each manage an individual sensor. The ISensorDriver
interface is implemented by the class CSensorDDI
. As might be expected, all sensors share functionality that can be neatly factored into a common base class. In the Wiimote sensor driver, this class is the abstract CSensor
class. What follows is a description of the key objects in the driver implementation and how they interact with each other.
CSensor
This class is the base class for all the sensor classes. In the Wiimote driver, 2 other classes inherit from CSensor
- CSensorDevice
and CAccelerometerSensor
.
CSensor
has the following responsibilities:
- Manage all data and properties on the sensor.
- Create and initialize properties common to all sensors.
- Manage state changes and state change event notifications.
- Manage I/O.
The CSensor
class takes upon itself the responsibility of providing the core implementation for the "data management" methods on the ISensorDriver
interface. Each sensor is identified by a unique string identifier that this class maintains.
CSensorRegistry
This class maintains a collection of references to objects inheriting from CSensor
. This is the registry of sensor objects supported by the driver. It maintains a mapping between the sensor identifiers and the sensor objects. It supports enumeration of all supported sensors and the retrieval of the associated sensor object given a string identifier. Each concrete sensor class is expected to use the IMPLEMENT_SENSOR
macro in one of its implementation source files to register itself with the sensor registry.
CSensorDevice
All sensor drivers are expected to support a pseudo sensor called "DEVICE" that supports some common properties. In the Wiimote driver this support is provided via this class. CSensorDevice
inherits from CSensor
and simply provides values for a set of properties.
CAccelerometerSensor
This is the core class implementing the accelerometer functionality. It handles the I/O with the actual Wiimote using the CReadWriteRequest
class. CAccelerometerSensor
inherits from CSensor
and manages the properties and data unique to the accelerometer. When its StartIO
method is invoked, it starts off a continuous chain of I/O read requests to the next driver stacked below the Wiimote driver. It also issues a "read memory" request on the Wiimote to read accelerometer calibration data when I/O is initiated.
The accelerometer data is parsed in the ParseAccelerationData
method. This method is also responsible for raising the data event as and when new data is available. Additional information on how exactly the I/O works is provided later on in this article.
CClientsManager
This class is responsible for tracking connection and disconnection of client apps with the sensor. It implements the client management and the event management responsibilities of the ISensorDriver
interface.
When I set out to build this driver, I figured that using Brian Peek's WiimoteLib library as a sort of reference for setting up I/O would be useful. Briefly, here's how I/O with the Wiimote works in the WiimoteLib.
- Use HID API to enumerate all HID input devices on the system.
- Inspect the product and vendor identifiers on each device and match it against the well-known product and vendor identifiers for the Wiimote (
0x057E
and 0x0306
respectively). - If a match is found, well, then we know it is a Wiimote. Open a Win32 file handle to the device using the device path fetched with
SetupDiGetDeviceInterfaceDetail
. The file handle itself is created using the standard CreateFile
API. - Now that we have a file handle, I/O can be performed using simple
ReadFile
and WriteFile
calls (synchronously or otherwise). - Unfortunately, sometimes, the Wiimote does not seem to respond to
WriteFile
calls. Brian must have been getting uncertain results with this which would explain why he put in a secondary I/O mechanism in case WriteFile
is not working. The secondary mechanism to simply use the HID HidD_SetOutputReport
API to send data to the Wiimote.
I wanted to stick to the same I/O mechanism in general. Being a driver however, things work a little differently (the fact that this is running in user mode does not seem to matter - it still is a driver and has some of the same functional constraints as kernel mode drivers). Consider the following line from the file "WiimoteSensor.inx" (this file is used as the template for generating the "WiimoteSensor.inf" file during the build process - "WiimoteSensor.inf" contains the installation instructions used by Windows when the driver is installed):
[Nerdworks.NT$ARCH$]
;
; This is the hardware ID the Wiimote gets registered with.
See http://msdn.microsoft.com/en-us/library/aa938560.aspx
;
%WiimoteSensor.DeviceDesc% = WiimoteSensor_Install,HID\{
00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306
This line basically associates this driver with a specific hardware device. The string HID\{000...
indicates that this driver talks to a HID device and the stuff after the \
is used to identify the particular HID device that this driver is built for. This sub-string can be considered as being delimited with _
characters with the first token being a GUID - {00001124-0000-1000-8000-00805f9b34fb}
- that specifies that this is a Bluetooth HID device. The second token - VID&0002057e
- specifies the vendor ID of the device and the third token - PID&0306
- specifies the product ID. This information is used by the device installer component of the OS to stack the correct lower level driver below the sensor driver. In our case, this would have to be the Bluetooth HID driver.
When an application issues an I/O request to a device, the request is usually made under the context of file handle that the application had previously opened. The device driver, UMDF or otherwise, has access to the file handle if it needs to. Now, in case a UMDF driver needs to issue a new I/O request to the next driver stacked below it - separate from anything that a client application might have done - then it is expected to use the IWDFDevice::CreateWdfFile
method to first obtain a pointer to an object implementing IWDFDriverCreatedFile
and then use that pointer like a file handle to refer to the device to which the I/O must be directed. The handle is to be used in conjunction with an IWDFIoTarget
object which represents the lower level driver stacked below the UMDF driver. Issuing an asynchronous read request for instance is performed like so:
- Call
IWDFDevice::CreateRequest
to create an IWDFIoRequest
object. - Call
IWDFIoRequest::SetCompletionCallback
to supply a callback object that implements IRequestCallbackRequestCompletion
so it can be notified of it when the I/O operation completes. - Call
IWDFDevice::GetDefaultIoTarget
to obtain a reference to the IWDFIoTarget
object. - Use
IWDFDevice::CreatePreallocatedWdfMemory
and IWDFIoTarget::FormatRequestForRead
to setup the memory buffers that will be used during the I/O. - Call
IWDFIoRequest::Send
to actually issue the request.
In the Wiimote driver, this whole process is initiated from CAccelerometerSensor::StartIO
. The core logic for performing the I/O is implemented in the CReadWriteRequest
class.
The CAccelerometerSensor::StartIO
method also tries to read the calibration data for the accelerometer from a pre-defined memory location on the Wiimote. Just like the WiimoteLib, the driver first attempts this by issuing a "read memory" request using CReadWriteRequest::CreateAndSendWriteRequest
. If that times out, i.e. a response is not received in a reasonable time-frame then it uses an alternate method by sending the IOCTL_HID_SET_OUTPUT_REPORT
IOCTL to the HID driver. It accomplishes this by calling CReadWriteRequest::CreateAndSendIOCTLRequest
.
The code in the Wiimote sensor driver can be used as a template for implementing your own sensor drivers. The key class to pay attention to is the CSensor
class. Implementing a new sensor should essentially involve defining a new class that inherits from CSensor
and overrides the relevant methods. This allows the driver writer to focus on the implementation of the core functionality instead of the plumbing required to interact with the sensor platform and UMDF.
The following are some of the things that I'd like to work upon in this implementation:
- Fix the issue where the sensor shows up twice upon installation.
- Figure out what initialization to perform on the Wiimote during load so that it is no longer necessary to run "WiimoteTest.exe" before getting the driver to work.
- Make pairing with the Wiimote via bluetooth seamless if at all possible.
- February 03, 2010: Article first published.
- February 16, 2010: Expanded text on how driver I/O works, split downloads into multiple files and added a missing file to the binaries download.