//来自百合电子工作室:http://www.baiheee.com
想学习usb开发的可以去看看。是个开源的,挺详细的。我只是转载了一部分。
首页 > 开源项目 > USB开源项目:Easy USB 51 Programer
三、读写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 SetupDiEnumDeviceInterfa ces( -
IN HDEVINFO DeviceInfoSet, -
IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL, -
IN LPGUID InterfaceClassGuid, -
IN DWORD MemberIndex, -
OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
运行此函数的主要目的是取得第一个参数DeviceInfoSet的填充值,又将此值作为以下函数
- BOOL
SetupDiGetDeviceInterfac eDetail( -
HDEVINFO DeviceInfoSet, -
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, -
PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailDat a, -
DWORD DeviceInterfaceDetailDat aSize, -
PDWORD RequiredSize, -
PSP_DEVINFO_DATA DeviceInfoData);
的第一个参数,以便取得这个函数的第三个参数DeviceInterfaceDetailDat
- 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设备的查找,并实现了数据收发功能,同时具有设备拨插检测通知功能。点击这里下载
下面用实例说明如何使用这个类。
实例 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
7、修改Hid.c中的PID和VID宏定义来设置需要访问的HID设备,此处的PID和VID值分别为0x0666和0x0471:
- #define
VID 0x0471 - #define
PID 0x0666
8、在CEasyUSB51ProgramerTest1
- CHid
m_MyHidDevice;
当然您得包含头文件Hid.h。
9、在CEasyUSB51ProgramerTest1
- 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、在CEasyUSB51ProgramerTest1
- ON_MESSAGE(WM_DEVICECHANGE,
OnDeviceChange)
11、在CEasyUSB51ProgramerTest1
- LRESULT
OnDeviceChange(WPARAM wParam, LPARAM lParam);
12、成员函数OnDeviceChange的结构如下:
- LRESULT
CEasyUSB51ProgramerTest1 Dlg::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
CEasyUSB51ProgramerTest1 Dlg::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; - }
- void
CEasyUSB51ProgramerTest1 Dlg::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个发光二极管状态。
下位机已经规定了每帧数据的长度为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; -
} -
} -
} -
} - }
通过实例1的学习,其实上位机程序的编写非常简单,所以在这里只贴出源代码。