为 Windows 7 上的 Wiimote 编写传感器驱动程序






4.93/5 (55投票s)
如何为 Nintendo Wiimote 上的 3 轴加速度计编写传感器驱动程序,以在 Windows 7 上提供访问。
- 下载源代码 - 252 KB
- 下载 64 位 CPU 二进制文件 - 2.83 MB
- 下载 32 位 CPU 二进制文件 - 2.55 MB
- 下载 Wiidiag 工具二进制文件 - 59.2 KB
- 下载其他工具 - 549 KB
目录
- 引言
- 致谢
- 只告诉我如何使用这个东西
- Windows 7 上的传感器平台是什么?
- 什么是 UMDF?
- 设置构建环境
- Wiimote 传感器驱动程序设计
- 如何使用框架实现您自己的传感器驱动程序
- 未来增强功能
- 修订历史
引言
当 Nintendo Wii 游戏机问世时,我像世界其他地方一样着迷,并认为其独特的输入系统令人惊叹。后来有一天,我在互联网上偶然发现了 Johnny Chung Lee 令人难以置信的创意作品,他 演示 了如何使用 Wii 遥控器——也称为 Wiimote——来构建与计算机交互的创新方式。他的想法让我非常兴奋,以至于我几乎立即出门买了一个 Wiimote,尽管我甚至没有 Wii 游戏机!
当 Windows 7 发布时,我了解到了新的 传感器和位置平台,我想到编写一个小型桥接驱动程序,将每个 Wiimote 附带的 3 轴 加速度计 暴露给传感器平台会很酷。本文试图解释该驱动程序的工作原理,以及通常编写传感器平台驱动程序所需的工作。
致谢
Wiimote 传感器驱动程序的实现,特别是通信协议的实现,受到了 Brian Peek 的 WiimoteLib 库的“启发”,该库已成为通过 Windows HID API 从托管代码访问 Wiimote 的事实标准 API。此外,来自 Windows 驱动程序工具包 的示例驱动程序实现为该项目的启动提供了起点。I/O 管理代码取自 传感器开发工具包,并进行了一些细微修改。
只告诉我如何使用这个东西
如果您只是想使用驱动程序而不太关心它的工作原理,那么您需要做以下事情。Wiimote 是一款令人惊讶的自给自足的硬件,本身就是一个独立产品。它通过标准蓝牙无线电信号与 Wii 游戏机通信,并标识自己为 HID 输入设备。要使其与您的计算机配合使用,您需要
- 一个 Wiimote (废话!)
- 一台安装了 Windows 7 的电脑
- 一块蓝牙卡
有一些报告称,人们在让他们的蓝牙驱动程序堆栈识别 Wiimote 时遇到了麻烦。如果您是其中之一,您可能需要为您的蓝牙卡获取更新的驱动程序。假设您的计算机能够与 Wiimote 配对,以下是在 Windows 7 中操作的方法。
将您的 Wiimote 与电脑配对
- 点击“开始”按钮,在搜索框中输入“蓝牙”,然后选择“添加蓝牙设备”。
- 同时按下 Wiimote 上的 1 和 2 按钮。您应该看到 4 个蓝色 LED 闪烁。保持两个按钮按下直到配对完成。
- 现在您应该在“添加设备”窗口中看到 Wiimote。选择 Wiimote 并点击“下一步”。
- 在“选择配对选项”屏幕中,点击“不使用代码配对”。
- 接下来您将看到以下屏幕。但这并不意味着配对成功。如果您查看系统托盘,您会看到系统正在尝试查找并安装驱动程序。Windows 会首先尝试在 Windows Update 上查找设备的驱动程序,这通常需要一段时间才能完成——您可以通过取消 Windows Update 上的查找来加快此过程。
- 驱动程序安装完成后,如果一切顺利,您应该在系统托盘区域看到此内容。
在您的蓝牙设备窗口中,您应该看到 Wiimote 列为游戏控制器设备。 - 现在,您可能会看到 Wiimote 上的 4 个 LED 持续闪烁。我建议您现在运行 Brian Peek 的“WiimoteTest.exe”工具(该工具包含在本文的 下载 中提供的“工具”下载中),并只点亮其中一个 LED,以便您知道它已开启。此外,运行“WiimoteTest.exe”似乎会以某种方式初始化 Wiimote,并且需要它才能使传感器驱动程序与 Wiimote 通信。我希望有一天能弄清楚“WiimoteTest.exe”究竟如何初始化 Wiimote,并从传感器驱动程序中也执行此操作。
安装驱动程序
要安装 Wiimote 传感器驱动程序,使其在您的系统中显示为传感器,您需要执行以下操作
- 根据您的 CPU 在单个周期中可以处理的位数下载相关的二进制包。换句话说,如果您有 32 位 CPU,则下载 32 位版本 的二进制文件,如果您有 64 位 CPU,则下载 64 位版本。将其解压缩到某个地方——我假设您将其解压缩到“C:\Wiisensor”。
- 下载 tools ZIP 文件并将其解压缩到您在步骤 1 中使用的相同位置。
- 以管理员权限打开命令提示符并导航到“C:\Wiisensor”。
-
现在,根据您运行的是 32 位还是 64 位版本的 Windows 7,在命令窗口中进一步导航到“i386”文件夹或“amd64”文件夹。如果您使用的是 64 位版本的 Windows 7,则运行以下命令(全部在一行中)
C:\Wiisensor\amd64>..\tools\devcon\amd64\devcon.exe install WiimoteSensor.inf "HID\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306"
如果您使用的是 32 位版本的 Windows 7,则运行以下命令(同样,全部在一行中)
C:\Wiisensor\i386>..\tools\devcon\i386\devcon.exe install WiimoteSensor.inf "HID\{00001124-0000-1000-8000-00805f9b34fb}_VID&0002057e_PID&0306"
如果您看到这样一个令人不安的对话框,只需点击“无论如何安装此驱动程序软件”
您之所以看到此内容,是因为驱动程序需要使用受信任的数字证书进行签名——如果是商业驱动程序,它就会被签名。
- 安装完成后,您应该能够在设备管理器中的“传感器”类别下看到该设备(您可以通过点击“开始”并在搜索框中输入“设备管理器”来启动设备管理器)。
正如您所看到的,Wiimote 不只一次,而是两次被列出!我还没有完全弄清楚为什么会这样,但是可以安全地卸载旁边带有黄色感叹号的条目。只需右键单击该条目并选择“卸载”。
-
最后一步是在传感器控制面板中启用传感器。点击“开始”并在搜索框中输入“传感器”,然后选择“位置和其他传感器”。
-
在传感器控制面板中勾选“Nintendo Wiimote”条目旁边的“已启用”框,然后点击“应用”。
就是这样!现在您已准备好开始工作!我知道您认为所有这些都太复杂了,我同意。但在一个真实的应用程序中,所有这些理想情况下都应该由安装程序应用程序处理,最终用户无需处理这些事情。
带着她去兜风!
如果您已经跟随我到目前为止,那么您应该能够从“C:\Wiisensor\bin\Wiidiag\Wiidiag.exe”启动“Wiidiag”工具,并观看一个不起眼的小图表,当您挥舞 Wiimote 时它会响应您!
现在,很酷的是,如果您希望任何现成的应用程序都能与您的 Wiimote 加速度计配合使用,只要它设计为与传感器平台协同工作。例如,您可以从此处下载飞机演示应用程序,它应该**直接工作**!事实上,我已将该演示的二进制文件包含在本文的下载中,您应该在“C:\Wiisensor\tools\PlaneDemo\DemoApp.exe”中找到它。当您启动它时,您应该会看到屏幕上呈现一个小型飞机模型。只需勾选代表三个轴的三个复选框,当您移动 Wiimote 时,它就会立即开始旋转飞机。
Windows 7 上的传感器平台是什么?
Windows 传感器和位置平台 是 Windows 7 上的一个新框架,旨在让硬件供应商和应用程序开发人员轻松地与传感器设备无缝协作。以下是 Microsoft 的一些标准说辞
Windows 传感器和位置平台是 Windows 7 的新增功能,它使您的计算机和应用程序能够适应其当前环境。借助位置传感器——包括 GPS 设备、WWAN 无线电甚至三角测量技术——您的应用程序和小工具可以确切地知道它们在哪里,从而能够提供更具本地相关性的内容和功能。例如,环境光传感器可以让您的计算机根据当前光照条件自动调整屏幕亮度。它们还可以使应用程序优化其内容的可读性,使您的计算机在各种操作环境中比以往任何时候都更有用。
该平台提供了一种将传感器和位置设备集成到 Windows 的标准方法,以及一个供应用程序利用这些设备的标准编程接口。在 Windows 7 上,用户可以控制这些设备的数据如何暴露给应用程序。
如何编写传感器客户端?
传感器客户端应用程序可以使用新的传感器 API 编写,它基本上是一个基于 COM 的非托管 API。但是,Microsoft 在其 Windows 7 API 代码包中提供了有用的托管包装类,您可以从 此处 下载。例如,本文下载中附带的“Wiidiag”示例应用程序就是该 API 使用起来多么简单的例子。
您首先设置对“Microsoft.WindowsAPICodePack.dll”和“Microsoft.WindowsAPICodePack.Sensors.dll”文件的引用,然后使用 `SensorManager` 类访问系统上安装的传感器。以下是“Wiidiag”中初始化下拉列表的代码,其中包含系统上安装的所有“运动”传感器。
// // initialize the sensors drop down // _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); }
当传感器发生值得注意的事情时,例如当它可用或消失时,`StateChanged` 事件会触发。当传感器有数据要提供给应用程序时,`DataReportChanged` 事件会触发。例如,Wiimote 加速度计数据就是通过此事件提供的。
以下是“Wiidiag”中处理 `DataReportChanged` 事件的代码。它本质上只是将新的数据样本添加到图表用户控件中。
void Sensor_DataReportChanged(Sensor sender, EventArgs e) { if (InvokeRequired) { BeginInvoke(new MethodInvoker(delegate() { Sensor_DataReportChanged(sender, e); })); return; } if (_current.SensorId == sender.SensorId) { // // see if this is an accelerometer // Accelerometer3D accel = _current as Accelerometer3D; if (accel != null) { Acceleration3D data = accel.CurrentAcceleration; // // add these values to the graphs // historyGraphX.AddValue("x", data[AccelerationAxis.X]); historyGraphY.AddValue("y", data[AccelerationAxis.Y]); historyGraphZ.AddValue("z", data[AccelerationAxis.Z]); } } }
就是这样!它不能再简单了,不是吗?!
如何编写传感器驱动程序?
现在,编写传感器驱动程序要复杂一些,正如您所料,事实上,这也是本文其余部分的重点。话虽如此,您真的不应该因此而感到害怕,因为编写传感器驱动程序与传统意义上的驱动程序编写完全不同,也就是说,如果您正在考虑无休止的机器重启和 蓝屏死机 (BSOD)——请放心,这种体验完全不是那样,主要是因为传感器驱动程序是使用**用户模式驱动程序框架** (UMDF) 编写的。在我告诉您传感器驱动程序如何工作之前,我需要给您一个关于 UMDF 驱动程序如何工作的简要介绍。如果您已经知道 UMDF 驱动程序是如何构建的,那么请随意跳过下一节。
什么是 UMDF?
描述如何编写 UMDF 驱动程序足以单独写一篇文章,因此我不会试图解释它的每一个细微之处。事实上,我自己也只是一个初学者,不会假装我了解所有关于它的知识。但是,我将尝试描绘我脑海中对框架的设想。OSR 的人员在 此处 撰写了一篇关于实现 UMDF 驱动程序所需内容的精彩概述 文章。同一网站上提供了 此处 提供的架构概述。
UMDF 的基本思想是,对于某些类别的驱动程序,当在用户模式下运行的代码可以实现相同的功能时,实现内核模式驱动程序的痛苦是不值得的。以下是您的驱动程序在用户空间中运行的一些明显好处(取自 UMDF FAQ)。
- 稳定性提高。用户模式驱动程序只能使其自己的进程崩溃或挂起,而不能使整个系统崩溃或挂起。
- 开发简便。用户模式驱动程序可以用 C++ 编写,并使用用户模式调试器进行调试,而无需单独的调试系统。
- 安全性增强。用户模式驱动程序无法访问内核模式地址空间,因此无法暴露内核模式数据,例如系统信息或属于其他进程的数据。
最重要的好处当然是驱动程序崩溃并不意味着蓝屏死机和系统重启。相反,托管 UMDF 驱动程序的进程可以简单地终止并重新启动。作为开发人员,您可以使用您喜欢的调试器(例如 Visual Studio)进行常规用户模式应用程序开发,这是一个额外的奖励。
下一节仅介绍足够的 UMDF,以便您能够理解传感器驱动程序的工作原理。例如,它不会试图解释 UMDF 驱动程序处理 I/O 的所有不同方式。
UMDF 简介
UMDF 驱动程序是一个普通的 Windows DLL。该框架使用基于 COM 的开发环境,但实际上不使用 COM 运行时库。COM 连接几乎完全用于管理对象生命周期和类型自省,这意味着所有 UMDF 对象都必须实现 IUnknown
。
驱动程序初始化
UMDF 驱动程序中的生命始于一个实现接口 IDriverEntry
的对象。此接口中只有 3 个方法,其中关键方法是 OnDeviceAdd
,当系统中出现感兴趣的设备时,框架会调用它。驱动程序需要通过创建 IWDFDevice
对象并将其与驱动程序提供的对象连接来响应 OnDeviceAdd
调用,该对象反过来实现所有包含驱动程序感兴趣的事件通知的回调接口。
例如,在 Wiimote 传感器驱动程序中,CWiimoteDriver
类实现了 IDriverEntry
。在其 OnDeviceAdd
方法中,它调用静态方法 CWiimoteDevice::CreateInstance
,后者又实例化一个 IWDFDevice
对象并通过 IWDFDriver::CreateDevice
将其与 CWiimoteDevice
的实例关联起来。CWiimoteDevice
实现了 IPnpCallback
、IPnpCallbackHardware
和 IFileCallbackCleanup
接口,这些接口包含由框架在不同时间点调用的回调方法。例如,IPnpCallback
包含每当即插即用设备进入和退出系统时调用的方法。
这是一个序列图,可能有助于说明 UMDF 驱动程序加载时控制流。
设备对象实现
CWiimoteDevice
类实现了 IPnpCallback
、IPnpCallbackHardware
和 IFileCallbackCleanup
。这里的关键接口是 IPnpCallbackHardware
,它包含 2 个方法——OnPrepareHardware
和 OnReleaseHardware
。OnPrepareHardware
由框架调用,指示驱动程序应执行一切必要的准备工作以准备设备进行访问;OnReleaseHardware
由框架调用,指示驱动程序在设备停止可访问后执行清理。CWiimoteDevice::OnPrepareHardware
通过初始化所有传感器对象、传感器类扩展对象(本文后面解释)以及实现 ISensorDriver
的对象(同样,在其他地方解释)来引导传感器框架。
CWiimoteDevice
对 IPnpCallback
接口的实现仅在 CWiimoteDevice::OnD0Entry
和 CWiimoteDevice::OnD0Exit
方法中执行有用的操作,它分别指示驱动程序中的所有传感器设备启动和停止 I/O 操作。
驱动程序安装
与所有驱动程序一样,UMDF 驱动程序也使用 INF 文件作为源进行安装。除了其他事项,INF 文件描述了
- 驱动程序所属的驱动程序类
- 驱动程序正常工作所需的操作系统版本
- 驱动程序设计用于工作的设备
- 驱动程序包中必须包含哪些文件,以及在安装过程中必须将它们复制到何处
- 驱动程序版本和时间戳
Windows 设备管理器使用此信息来正确安装和配置驱动程序。对于 UMDF 驱动程序,另一项关键信息是实现 IDriverEntry
对象的 COM CLSID,因为没有它,框架就无法真正启动驱动程序。有关 INF 文件工作原理的详细解释,我建议您查看 MSDN 文档。
在此之后,控制权转移到传感器框架。然而,在我们深入研究之前,有必要简要了解一下 Wiimote 传感器驱动程序的构建系统是如何工作的。
设置构建环境
如果您像我一样,在开发、调试和测试应用程序方面被 Visual Studio 宠坏了,那么在处理 UMDF 时,您可能会像我最初一样感到茫然,因为您几乎被迫使用 Windows WDK 为您安装的构建环境。但是 OSR 的一些圣人费心构建了一个批处理文件,允许您将 WDK 与 Visual Studio 集成,并在 IDE 中直接构建和编码。在接下来的几节中,我将解释您在按 Ctrl+Shift+B(或者如果您的 VS 中设置了 VC++ 键盘绑定,则只需按 F7)之前需要在您的机器上设置的内容。
安装软件
首先,下载并安装以下所有内容
- Visual Studio 2008
- Windows WDK 7.0 或更高版本
- UnxUtils - 只需将其解压到您计算机上的某个位置
- Windows 7 API 代码包
您可能想知道下载 *UnxUtils* 有什么用。我将在本节后面解释它的确切作用,但我基本上使用了一些 UNIX 工具,例如“sed”来自动更新构建修订号。
设置 PATH 变量
请确保系统 PATH
环境变量已更新,指向您的 *UnxUtils* 安装的 "\usr\local\wbin" 文件夹。这是必需的,以便构建系统可以直接通过名称调用 UNIX 工具。
构建和调试
驱动程序的 Visual Studio 项目是一个 VC++ Makefile 项目。Makefile 项目基本上是将二进制文件的实际构建委托给外部脚本/批处理文件的项目。Visual Studio 可以配置为在您构建项目时调用外部命令(例如 *makefile*)。如果命令的输出以标准形式(例如 VC++ 编译器生成的那些)打印,VS 将自动解析输出并直接在 IDE 中列出错误和警告,这样双击错误将直接带您到有问题的行。接下来的几节将描述此过程的工作原理。
如何在 Visual Studio 中构建
正如我之前解释的,使用 WDK 构建驱动程序(用户模式或其他)需要您根据您所针对的 CPU 架构和操作系统启动相关的构建环境,并发出 *build* 命令。*build* 工具使用一个名为“sources”的文件来确定源文件是什么以及如何构建二进制文件。Wiimote 传感器驱动程序项目使用的“sources”文件是一个相当标准的文件,除了以下几点:
- 我添加了
USE_STL = 1
指令,因为我喜欢并喜爱 STL 容器类。 - 我使用
USER_C_FLAGS = /EHsc /wd4238
指令自定义了传递给 C++ 编译器的标志,因为我需要异常处理支持(/EHsc
启用)。/wd4238
标志阻止编译器发出警告 4238。这是因为项目中的某一行代码导致编译器发出此警告,并且 WDK 构建默认配置为将所有警告视为错误。我碰巧认为在这种情况下可以安全地忽略此特定警告。
现在我们已经准备好“sources”文件,我们应该能够启动 WDK 构建环境并运行 build -ceZ
来进行干净的重建。然而,为了能够在 Visual Studio 中执行此操作,我使用了 OSR 的 DDKBUILD 批处理文件作为 VS Makefile 项目的外部构建命令。该链接中的文章描述了批处理文件的工作原理。我为构建 x86 32 位和 64 位版本的驱动程序创建了单独的 VC++ 构建配置。因此,要构建 Wiimote 驱动程序的 32 位版本,只需选择当前配置为“Debug32”或“Release32”,具体取决于您想要调试版本还是发布版本(在驱动程序社区中也称为“checked”或“free”版本),然后按 F7(或 Ctrl+Shift+B)。您将选择“Debug64”或“Release64”来获取相应的 64 位版本。以下是所有 4 种配置的构建和重建设置
- Debug64 - 构建
- ddkbuild -W7X64 checked . -WDF
- Debug64 - 重建
- ddkbuild -W7X64 checked . -cZ -WDF
- Release64 - 构建
- ddkbuild -W7X64 free . -WDF
- Release64 - 重建
- ddkbuild -W7X64 free . -cZ -WDF
- Debug32 - 构建
- ddkbuild -W7 checked . -WDF
- Debug32 - 重建
- ddkbuild -W7 checked . -cZ -WDF
- Release32 - 构建
- ddkbuild -W7 free . -WDF
- Release32 - 重建
- ddkbuild -W7 free . -cZ -WDF
调试
作为驱动程序,您不能直接按 F5 启动调试会话,因为当需要该驱动程序的设备出现在系统上时,驱动程序会按需加载到 UMDF 主机进程中。MSDN 中已充分解释了 UMDF 驱动程序的调试,这是您了解如何进行调试的最佳选择。我个人使用 此处 描述的技术在驱动程序加载时附加到 UMDF 主机进程。主机进程名为“WUDFHost.exe”,您可以在使用 devcon.exe
安装驱动程序后立即附加到此进程。我将注册表中的 HostProcessDbgBreakOnStart
值设置为 60 秒,以便有足够的时间附加到主机进程。
这里唯一棘手的部分是弄清楚要附加到哪个“WUDFHost.exe”实例,因为很可能在给定时间有多个实例正在运行。我通常只是附加到所有正在运行的实例。有时我附加到所有正在运行的实例,然后发现驱动程序 DLL 没有加载——这通常意味着在我附加到所有现有主机之后启动了一个新的主机进程。
管理驱动程序版本
事实证明,Windows 设备管理器会在您第一次安装驱动程序时缓存驱动程序安装文件(在 Windows 7 上是“C:\windows\system32\DriverStore\FileRepository”),并且在后续重新安装/更新时始终从缓存中选择文件,除非新文件具有更新的版本号。这在标准的构建-部署-调试周期中有点麻烦,因为您每次构建时都必须记住手动更新 RC 文件和“WiimoteSensor.inf”文件的新版本号。我决定将这一小部分自动化,以便每次从 VS 创建新版本时,它都会自动增加版本号。以下是 Wiimote 传感器项目的工作方式
- DDKBUILD 通过在您所有源文件所在的文件夹中创建名为“ddkprebld.cmd”和“ddkpostbld.cmd”的文件来支持预/后构建任务的执行。在“ddkprebld.cmd”中,我调用“mbld”文件夹中的另一个批处理文件“incv.bat”来更新版本号。
- “incv.bat”使用另外两个名为“incm.bat”和“incr.bat”的批处理文件来增加“makefile.inc”和“WiimoteSensor.rc”文件中的版本号。
- 所有这些脚本文件主要使用 UNIX “sed”工具来查找和更新源文件中要更新的特定字符串。
Wiimote 传感器驱动程序设计
尽管 WDK 随附的传感器驱动程序示例驱动程序为您提供了编写传感器驱动程序的一般概念,但我个人发现代码结构非常混乱。在编写 Wiimote 传感器驱动程序时,我决定对事物进行一些重构。本节旨在解释我是如何实现核心驱动程序的。
传感器驱动程序必须做什么?
传感器驱动程序的主要任务是,嗯,感知事物并报告数据!从传感器驱动程序的角度来看,外部世界通过指向实现 ISensorClassExtension
接口的对象的指针表示。此对象由传感器平台提供,充当驱动程序与对传感器提供的数据感兴趣的客户端应用程序之间的中介。从 ISensorClassExtension
对象的角度来看,传感器驱动程序是一个实现 ISensorDriver
的对象。ISensorDriver
对象由驱动程序提供,并包含传感器平台在需要时在不同时间点调用的各种方法。传感器驱动程序的主要职责是
- 管理与传感器硬件设备的 I/O。
- 管理传感器属性和数据。
- 跟踪连接到传感器的客户端应用程序并遵守客户端应用程序偏好。
- 引发传感器数据事件。
管理传感器属性和数据
传感器驱动程序支持的每个传感器本质上是属性和数据的集合。“属性”与“数据”不同,因为“属性”通常是属性——例如传感器的友好名称、制造商名称、型号等——这些属性往往保持静态。而“数据”就是数据!对于 Wiimote 传感器驱动程序,Wiimote 报告的加速度值就属于数据。
传感器平台要求您通过实现 IPortableDeviceKeyCollection
和 IPortableDeviceValues
的对象报告和维护您的传感器支持的属性和数据。IPortableDeviceValues
基本上是一个字典对象,其中键是表示单个属性的 GUID 值,值是 PROPVARIANT
,它可以保存几乎任何东西(它与 COM 中的 VARIANT
类型相同)。IPortableDeviceKeyCollection
是表示属性键集合的 GUID 值集合。
为了使属性和数据的管理有些轻松,我创建了一个名为 CPortableDeviceProps
的类,它几乎完全屏蔽了用户直接处理 IPortableDeviceKeyCollection
和 IPortableDeviceValues
对象的需要。它使用 CDeviceProperty
对象以类型独立(不牺牲类型安全)的方式无缝指定属性值。例如,创建加速度计支持的数据属性集合如下所示
// // add sensor supported data fields // 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) )); }
在这里,m_Data
是 CPortableDeviceProps
类的一个实例。
“ISensorDriver”并非真正是一个接口
当您考虑实现 ISensorDriver
时,将此接口中的方法视为按相关功能分组会很有用。我怀疑所有方法都被归结为一个单一接口,以试图使驱动程序的要求保持相对简单。以下是此接口支持的方法按功能组分类的列表。
客户端管理
- OnClientConnect
- OnClientDisonnect
事件管理
- OnClientSubscribeToEvents
- OnClientUnsubscribeFromEvents
- OnGetSupportedEvents
数据管理
- OnGetSupportedDataFields
- OnGetSupportedProperties
- OnGetDataFields
- OnGetProperties
其他方法
- OnGetSupportedSensorObjects
- OnProcessWpdMessage
- OnSetProperties
传感器驱动程序的整体设计反映了这种功能分类,而 ISensorDriver
的实现大部分只是委托给负责相关功能领域的其他参与对象。
关键对象及其交互方式
传感器驱动程序可以被看作是传感器对象的集合,每个对象管理一个单独的传感器。ISensorDriver
接口由 CSensorDDI
类实现。正如预期的那样,所有传感器都共享可以整齐地分解到通用基类中的功能。在 Wiimote 传感器驱动程序中,这个类是抽象的 CSensor
类。下面将描述驱动程序实现中的关键对象以及它们如何相互作用。
CSensor
这个类是所有传感器类的基类。在 Wiimote 驱动程序中,另外两个类继承自 CSensor
- CSensorDevice
和 CAccelerometerSensor
。
CSensor
具有以下职责
- 管理传感器上的所有数据和属性。
- 创建并初始化所有传感器共有的属性。
- 管理状态变化和状态变化事件通知。
- 管理 I/O。
CSensor
类承担了为 ISensorDriver
接口上的“数据管理”方法提供核心实现的职责。每个传感器都由该类维护的唯一字符串标识符进行标识。
CSensorRegistry
该类维护一个继承自 CSensor
的对象集合。这是驱动程序支持的传感器对象的注册表。它维护传感器标识符和传感器对象之间的映射。它支持枚举所有支持的传感器,并在给定字符串标识符的情况下检索关联的传感器对象。每个具体的传感器类都需要在其实现源文件之一中使用 IMPLEMENT_SENSOR
宏向传感器注册表注册自己。
CSensorDevice
所有传感器驱动程序都应该支持一个名为“DEVICE”的伪传感器,该传感器支持一些通用属性。在 Wiimote 驱动程序中,此支持通过此类提供。CSensorDevice
继承自 CSensor
,只为一组属性提供值。
C加速度传感器
这是实现加速度计功能的核心类。它使用 CReadWriteRequest
类处理与实际 Wiimote 的 I/O。CAccelerometerSensor
继承自 CSensor
,并管理加速度计特有的属性和数据。当其 StartIO
方法被调用时,它会启动一个连续的 I/O 读取请求链,发送到堆叠在 Wiimote 驱动程序下方的下一个驱动程序。当 I/O 启动时,它还会向 Wiimote 发出“读取内存”请求以读取加速度计校准数据。
加速度计数据在 ParseAccelerationData
方法中解析。此方法还负责在有新数据可用时引发数据事件。有关 I/O 工作原理的更多信息,请参阅本文 后面 的部分。
CClientsManager
此类负责跟踪客户端应用程序与传感器的连接和断开。它实现了 ISensorDriver
接口的客户端管理和事件管理职责。
驱动程序 I/O 以及与 Wiimote 的通信方式
当我开始构建这个驱动程序时,我认为使用 Brian Peek 的 WiimoteLib 库作为设置 I/O 的参考会很有用。简要地说,以下是 WiimoteLib 中与 Wiimote 的 I/O 工作原理。
- 使用 HID API 枚举系统上的所有 HID 输入设备。
- 检查每个设备的生产商和供应商标识符,并与 Wiimote 的已知生产商和供应商标识符(分别为
0x057E
和0x0306
)进行匹配。 - 如果找到匹配项,那么我们知道它是一个 Wiimote。使用通过
SetupDiGetDeviceInterfaceDetail
获取的设备路径,打开设备的 Win32 文件句柄。文件句柄本身是使用标准CreateFile
API 创建的。 - 现在我们有了文件句柄,可以使用简单的
ReadFile
和WriteFile
调用(同步或异步)执行 I/O。 - 不幸的是,有时 Wiimote 似乎不响应
WriteFile
调用。Brian 可能对此得到了不确定的结果,这解释了为什么他放入了辅助 I/O 机制,以防WriteFile
不起作用。辅助机制是简单地使用 HIDHidD_SetOutputReport
API 向 Wiimote 发送数据。
Windows 如何知道要堆叠在传感器驱动程序下面的驱动程序
我希望总体上坚持相同的 I/O 机制。然而,作为驱动程序,事情有点不同(它在用户模式下运行的事实似乎并不重要——它仍然是一个驱动程序,并且具有与内核模式驱动程序相同的一些功能限制)。考虑文件“WiimoteSensor.inx”中的以下行(此文件在构建过程中用作生成“WiimoteSensor.inf”文件的模板——“WiimoteSensor.inf”包含 Windows 安装驱动程序时使用的安装说明)
[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
这条线基本上将此驱动程序与特定的硬件设备关联起来。字符串 HID\{000...
表示此驱动程序与 HID 设备通信,而 \
后面的内容用于识别此驱动程序所构建的特定 HID 设备。此子字符串可以视为用 _
字符分隔,第一个标记是一个 GUID——{00001124-0000-1000-8000-00805f9b34fb}
——指定这是一个蓝牙 HID 设备。第二个标记——VID&0002057e
——指定设备的供应商 ID,第三个标记——PID&0306
——指定产品 ID。此信息由操作系统的设备安装程序组件使用,以便将正确的低级驱动程序堆叠在传感器驱动程序下方。在我们的情况下,这将是蓝牙 HID 驱动程序。
传感器驱动程序如何进行标准 I/O
当应用程序向设备发出 I/O 请求时,该请求通常是在应用程序之前打开的文件句柄的上下文中进行的。设备驱动程序(无论是 UMDF 还是其他)在需要时可以访问文件句柄。现在,如果 UMDF 驱动程序需要向其下方堆叠的下一个驱动程序发出新的 I/O 请求——与客户端应用程序可能执行的任何操作分开——那么它应该首先使用 IWDFDevice::CreateWdfFile
方法获取指向实现 IWDFDriverCreatedFile
的对象的指针,然后像文件句柄一样使用该指针来引用必须将 I/O 指向的设备。该句柄将与 IWDFIoTarget
对象结合使用,该对象表示堆叠在 UMDF 驱动程序下方的低级驱动程序。例如,发出异步*读取*请求的执行方式如下
- 调用
IWDFDevice::CreateRequest
创建一个IWDFIoRequest
对象。 - 调用
IWDFIoRequest::SetCompletionCallback
以提供一个实现IRequestCallbackRequestCompletion
的回调对象,以便在 I/O 操作完成时通知它。 - 调用
IWDFDevice::GetDefaultIoTarget
获取IWDFIoTarget
对象的引用。 - 使用
IWDFDevice::CreatePreallocatedWdfMemory
和IWDFIoTarget::FormatRequestForRead
设置将在 I/O 期间使用的内存缓冲区。 - 调用
IWDFIoRequest::Send
实际发出请求。
在 Wiimote 驱动程序中,整个过程从 CAccelerometerSensor::StartIO
启动。执行 I/O 的核心逻辑在 CReadWriteRequest
类中实现。
CAccelerometerSensor::StartIO
方法还尝试从 Wiimote 上的预定义内存位置读取加速度计的校准数据。就像 WiimoteLib 一样,驱动程序首先通过使用 CReadWriteRequest::CreateAndSendWriteRequest
发出“读取内存”请求来尝试此操作。如果超时,即在合理的时间范围内未收到响应,则它通过向 HID 驱动程序发送 IOCTL_HID_SET_OUTPUT_REPORT
IOCTL 来使用替代方法。它通过调用 CReadWriteRequest::CreateAndSendIOCTLRequest
来完成此操作。
如何使用框架实现您自己的传感器驱动程序
Wiimote 传感器驱动程序中的代码可以作为实现您自己的传感器驱动程序的模板。需要注意的是 CSensor
类。实现一个新的传感器本质上应该涉及定义一个继承自 CSensor
并重写相关方法的新类。这使得驱动程序编写者可以专注于核心功能的实现,而不是与传感器平台和 UMDF 交互所需的底层结构。
未来增强功能
以下是我想在此实现中改进的一些事情- 解决安装后传感器显示两次的问题。
- 弄清楚在加载时对 Wiimote 执行哪些初始化,这样就不再需要在驱动程序工作之前运行“WiimoteTest.exe”。
- 如果可能的话,使 Wiimote 通过蓝牙配对无缝连接。
修订历史
- 2010 年 2 月 3 日:文章首次发表。
- 2010 年 2 月 16 日:扩展了驱动程序 I/O 工作原理的文本,将下载文件拆分为多个文件,并向二进制文件下载添加了一个缺失的文件。