如何与HID设备通讯(二)
三、读写HID设备的步骤
读写HID设备步骤如下:
①、得到系统HID设备结构数组指针
②、对设备进行遍历
③、得到指定HID设备的句柄
④、readfile/writefile进行读写
下面分别对各步骤及其所涉及的相关API函数进行介绍。
1、得到设备句柄:这步用到的两面个主要API函数原型为:
A、通过以下函数
- VOID HidD_GetHidGuid(OUT LPGUID HidGuid );
得到HID设备的GUID。
B、再通过以下函数
- HDEVINFO SetupDiGetClassDevs(
- CONST LPGUID ClassGuid,
- PCTSTR Enumerator,
- HWND hwndParent,
- DWORD Flags
- );
取得HID设备结构数组指针,以便下一步利用这个数组对所有HID设备进行遍列。
2、对设备进行遍历:遍历过程如下
A、首先执行以下函数:
- WINSETUPAPI BOOL WINAPI SetupDiEnumDeviceInterfaces(
- IN HDEVINFO DeviceInfoSet,
- IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL,
- IN LPGUID InterfaceClassGuid,
- IN DWORD MemberIndex,
- OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
运行此函数的主要目的是取得第一个参数DeviceInfoSet的填充值,又将此值作为以下函数
- BOOL SetupDiGetDeviceInterfaceDetail(
- HDEVINFO DeviceInfoSet,
- PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
- PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
- DWORD DeviceInterfaceDetailDataSize,
- PDWORD RequiredSize,
- PSP_DEVINFO_DATA DeviceInfoData);
的第一个参数,以便取得这个函数的第三个参数DeviceInterfaceDetailData的填充值,然后利用这个值传递给CreateFile函数,此时CreateFile会返回一个指向HID设备的句柄,再根据以下函数
- BOOLEAN HidD_GetAttributes(
- IN HANDLE HidDeviceObject,
- OUT PHIDD_ATTRIBUTES Attributes
- );
取得此HID设备的属性(第二个参数的填充值),然后判断属性里的PID(Attributes->ProductID)和VID(Attributes->VendorID)是否是我们要查找的设备的PID和VID。PID和VID在下位机固件代码的设备描述符里提供(设备描述里的idProduct域和idVendor,参考百合电子工作室发表的文章《USB开发基础--USB命令(请求)和USB描述符》一文中表4),当然您也可以通过一些工具查询得到PID和VID,您可以到USB组织官方网站www.usb.org下载这类工具。
3、根据得到的设备句柄利用ReadFile和WriteFile对设备进行读写操作。
百合电子工作室已经将以上步骤封装成了一个HID类(参考了其它一些实例代码),它能实现对指定PID和VID设备的查找,并实现了数据收发功能,同时具有设备拨插检测通知功能。点击这里下载
下面用实例说明如何使用这个类。
四、读写HID设备实例
实例 1
1、找开Visual C++ 6.0,新建一基于MFC的工程名为:Easy USB 51 Programer Test1。
2、MFC AppWizard Step 1对话框中选择基于对话框的应用程序,然后点“Finish”按钮,如图所示:
3、创建3个静态文本标签(Static Text),文本内容分别为:Write、Read和Message;创建两个文本框和一个列表框,ID分别为:IDC_EDIT_TX、IDC_EDIT_RX和IDC_LIST_MESSAGE;两个按钮ID和文本分别为:IDC_BTN_WRITE(Write)和IDCANCEL(Exit)。界面如下:
4、添加控件所对应的变量,如下图所示:
5、将Hid.c和Hid.h导入工程,并将“要用到的windows ddk里的几个文件”文件夹内的文件复制到工程所在目录,在Procect->Settings->Link页的“Object/Library moudles”设置中添加“hid.lib setupapi.lib”,如下图所示:
6、在stdafx.h文件中包含头文件语句前添加:#define WINVER 0x0500
7、修改Hid.c中的PID和VID宏定义来设置需要访问的HID设备,此处的PID和VID值分别为0x0666和0x0471:
- #define VID 0x0471
- #define PID 0x0666
8、在CEasyUSB51ProgramerTest1Dlg类中添加成员变量m_MyHidDevice,其定义如下
当然您得包含头文件Hid.h。
9、在CEasyUSB51ProgramerTest1Dlg类的OnInitDialog函数中添加如下语句:
- m_MyHidDevice.m_hParentWnd = (HANDLE*) this->GetSafeHwnd( );
- if(m_MyHidDevice.FindHid()) //找到指定HID设备
- {
- m_ctrlMessage.InsertString(-1,"My hid device detected");
- }
- else //没有找到指定HID设备
- {
- m_ctrlMessage.InsertString(-1,"My hid device not detected");
- m_ctrlWrite.EnableWindow(FALSE); //禁用"write"按钮
- }
10、在CEasyUSB51ProgramerTest1Dlg的消息映射中(“BEGIN_MESSAGE_MAP(CEasyUSB51ProgramerTest1Dlg, CDialog)” 与 “END_MESSAGE_MAP()”之间)添加如下代码:
- ON_MESSAGE(WM_DEVICECHANGE, OnDeviceChange)
11、在CEasyUSB51ProgramerTest1Dlg类中添加成员函数:
- LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam);
12、成员函数OnDeviceChange的结构如下:
- LRESULT CEasyUSB51ProgramerTest1Dlg::OnDeviceChange(WPARAM wParam, LPARAM lParam)
- {
-
-
- switch(LOWORD(wParam))
- {
-
-
- case DBT_DEVICEARRIVAL:
- {
-
- if(m_MyHidDevice.FindHid())
- {
-
-
- }
- break;
- }
-
-
- case DBT_DEVICEREMOVECOMPLETE:
- {
- if(!m_MyHidDevice.FindHid())
- {
-
-
- }
- break;
- }
- }
-
- return true;
- }
13、这里为了实现在Message信息框里显示HID设备的拨插操作,现对OnDeviceChange函数作如下填充:
- LRESULT CEasyUSB51ProgramerTest1Dlg::OnDeviceChange(WPARAM wParam, LPARAM lParam)
- {
-
-
- switch(LOWORD(wParam))
- {
-
-
- case DBT_DEVICEARRIVAL:
- {
-
- if(m_MyHidDevice.FindHid())
- {
-
-
- unsigned short nIndex = m_ctrlMessage.InsertString(-1,"My hid device detected");
- m_ctrlMessage.SetCurSel(nIndex); //流动信息窗口
- m_ctrlWrite.EnableWindow(TRUE); //启用"write"按钮
- }
- break;
- }
-
-
- case DBT_DEVICEREMOVECOMPLETE:
- {
- if(!m_MyHidDevice.FindHid())
- {
-
-
- unsigned short nIndex = m_ctrlMessage.InsertString(-1,"My hid device removed");
- m_ctrlMessage.SetCurSel(nIndex); //流动信息窗口
- m_ctrlWrite.EnableWindow(FALSE); //禁用"write"按钮
- }
- break;
- }
- }
-
- return true;
- }
14、对HID的读写可通过Hid类的成员函数WriteHid和ReadHid。以下是"write"按钮的响应函数,实现对HID设备的读写操作:
- void CEasyUSB51ProgramerTest1Dlg::OnBtnWrite()
- {
- unsigned char ucTxBuffer[64]; //发送缓冲
- unsigned char ucRxBuffer[64]; //接收缓冲
-
- UpdateData(TRUE);
-
- //判断发送框中内容是否超过64字节
- if(m_strTx.GetLength()>64)
- {
- AfxMessageBox("发送字节数不能超过64个字节");
- }
-
- //准备发送缓冲区中的内容
- for(int i=0; i<64 ; i++)
- {
- if(i <= (m_strTx.GetLength()-1) )
- ucTxBuffer[i] = m_strTx.GetAt(i);
- else
- ucTxBuffer[i] = 0;
- }
-
- //写操作
- m_MyHidDevice.WriteHid(ucTxBuffer,64);
- //读操作
- m_MyHidDevice.ReadHid(ucRxBuffer,64);
-
- m_strRx = ucRxBuffer;
- UpdateData(FALSE);
- }
完成后的实际效果:
下载程序
下载源代码
实例 2
此例子需要用到扩展板:EXT-BOARD-A。实现功能为通过上位机设定 EXT-BOARD-A 上的8个发光二极管状态。
1、命令及数据定义
下位机已经规定了每帧数据的长度为64个字节,我们现在需要对每一个字节的含义作出定义。在这个实例中,我们可作如下规定:每帧数据前5个字节为命令,命令后面紧跟数据(从第6个字节开始)。命令为ASCII编码,在此实例中只有一个命令“ENLED”,代表设备LED状态。命令后面的一个字节为数据,代表D0~D7八个LED的状态,后面的字节无意义。
2、修改下位机程序
修改main.c文件,其内容如下:
-
-
- //#include <at89x52.h>
- #include <reg51.h>
- #include "D12Config.h"
- #include "Descriptor.h"
- #include "Chap_9.h"
- #include "D12Driver.h"
- #include <string.h>
-
- main()
- {
- unsigned char ucLedState;
-
- if (Init_D12()!=0) //初始化D12
- return; //如果初始化不成功,返回
-
- IT0 = 0; //外部中断0为电平触发方式
-
- EX0 = 1; //开外部中断0
- PX0 = 0; //设置外部中断0中断优先级
- EA = 1; //开80C51总中断
-
- while(1)
- {
- usbserve(); //处理USB事件
- if(bEPPflags.bits.configuration)
- {
- //在这里添加端点操作代码
-
- if(bEPPflags.bits.ep2_rxdone ) //主端点接收到数据(从主机发往设备的数据)
- {
- bEPPflags.bits.ep2_rxdone = 0;
-
- //判断是否是 ENLED 命令(EpBuf的前5个字节为”ENLED“)
- if(strncmp("ENLED",EpBuf,5)==0)
- {
- //取得LED状态设定值
- ucLedState = EpBuf[5];
-
- //设定LED状态
- P0 = ucLedState;
- }
- }
- }
- }
- }
点击这里下载修改好的源代码
3、上位机程序
通过实例1的学习,其实上位机程序的编写非常简单,所以在这里只贴出源代码。
上位机界面 LED的状态由上位机控制
http://blog.sina.com.cn/s/blog_484dff580100kq62.html
下载程序路径:D:\Yifei\Download\USB\应用实例\httpblog.sina.com.cnsblog_484dff580100kq62.html