前段时间,朋友要做一个windows7的usb多点触控设备,我就帮了个小忙,负责搞定了设备 与PC通信相关的这块。整个项目我做了两个东西,一是下位机的usb设备描述符,一个是上位机的测试软件,下面我会把这两个过程都写一下,跟大家共享!!!
一、下位机部分
我仔细查了不少关于windows7的usb多点触控设备的资料,这里先跟大家共享一下
http://blog.csdn.net/cazicaquw/article/details/6771582
http://blog.csdn.net/yunwen3344/article/details/8107439
http://msdn.microsoft.com/en-us/windows/hardware/gg487437.aspx
http://msdn.microsoft.com/en-us/library/ff553745(v=vs.85).aspx
http://msdn.microsoft.com/library/windows/hardware/jj248722.aspx
http://msdn.microsoft.com/en-us/library/windows/hardware/dn383592.aspx
我主要参考的是微软官方的几个网址,大家多点一下旁边的选项有很多资料在里面,这里并没有全贴出来。
对于这个项目来讲,首先要知道usb的枚举过程以及usb描述符的意义,这个网上有太多的教程了,我也是现学的不敢卖弄,大家可以百度一下。
下面我们讲主要的:
硬件平台: stm32f103ZE
软件平台:keil MDK-ARM 4.70.0.0
为了开发方便我们找了keil官方带的usb工程,在这个工程上修改,减少了不小的工作量。如果大家跟我装同一个版本的话应该都可以找到这个工程。这个工程是一个自定义的HID设备,我们所要做的就是在这个工程基础上,把自定义HID设备的描述符改成多点触控的描述符。
描述符在usbdesc.c这个文件中,我们修改的仅仅是设备描述符,其他的都不用动。直接贴修改后的描述符:
const U8 HID_ReportDescriptor[] =
{
//
//多点触控设备
//
HID_UsagePage ( HID_USAGE_PAGE_DIGITIZER), //0x05, 0x0d,// USAGE_PAGE (Digitizers)
HID_Usage ( 0x04 ), //0x09, 0x04,// USAGE (Touch Screen)
HID_Collection ( HID_Application ), //0xa1, 0x01,// COLLECTION (Application)
HID_ReportID ( TOUCH_REPORT_ID ), //0x85, 0x01,//REPORTID_MTOUCH, // REPORT_ID (Touch)
//第一点
//手指和z轴
HID_Usage ( 0x22 ), //0x09, 0x22,// USAGE (Finger)
HID_Collection ( HID_Logical ), //0xa1, 0x02,// COLLECTION (Logical)
HID_Usage ( 0x42 ), //0x09, 0x42,// USAGE (Tip Switch)
HID_LogicalMin ( 0x00 ), //0x15, 0x00,// LOGICAL_MINIMUM (0)
HID_LogicalMax ( 0x01 ), //0x25, 0x01,// LOGICAL_MAXIMUM (1)
HID_ReportSize ( 0x01 ), //0x75, 0x01,// REPORT_SIZE (1)
HID_ReportCount ( 0x01 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_Usage ( 0x32 ), //0x09, 0x32,// USAGE (In Range)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_ReportCount ( 6 ), //0x95, 0x06,// REPORT_COUNT (6)
HID_Input(HID_Constant|HID_Array|HID_Absolute),//0x81, 0x01,// INPUT (Cnst,Ary,Abs)
//手指ID
HID_ReportSize ( 8 ), //0x75, 0x08,// REPORT_SIZE (8)
HID_Usage ( 0x51 ), //0x09, 0x51,// USAGE ( Contact Identifier)
HID_LogicalMax ( 0xff ), //0x25, 0xFF,// LOGICAL_MAXIMUM (255)
HID_ReportCount ( 0x01 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
//X和Y
HID_UsagePage ( HID_USAGE_PAGE_GENERIC ), //0x05, 0x01,// USAGE_PAGE (Generic Desk)
HID_LogicalMinS ( 0 ), //0x16, 0x00,0x00,//HID_LogicalMinS (0)
HID_LogicalMaxS ( 1365 ), //0x26, 0x56, 0x05,// LOGICAL_MAXIMUM (1366)
HID_ReportSize ( 16 ), //0x75, 0x10,// REPORT_SIZE (16)
HID_UnitExponent( 0 ), //0x55, 0x00,// UNIT_EXPONENT (0)
HID_Unit ( 0 ), //0x65, 0x00,// UNIT (00)
HID_Usage ( HID_USAGE_GENERIC_X ), //0x09, 0x30,// USAGE (X)
HID_PhysicalMinS( 0 ), //0x36, 0x00,0x00,// PHYSICAL_MINIMUM (0)
HID_PhysicalMaxS( 1365 ), //0x46, 0x56, 0x05,// PHYSICAL_MAXIMUM (1366)
HID_ReportCount ( 0x01 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_LogicalMaxS ( 767 ), //0x26, 0x00, 0x03,// LOGICAL_MAXIMUM (4095)
HID_PhysicalMaxS( 767 ), //0x46, 0x00, 0x03,// PHYSICAL_MAXIMUM (4095)
HID_Usage ( HID_USAGE_GENERIC_Y ), //0x09, 0x31,// USAGE (Y)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_EndCollection, //0xc0, // END_COLLECTION
//第二点
//手指和z轴
HID_UsagePage ( HID_USAGE_PAGE_DIGITIZER), //0x05, 0x0d,// USAGE_PAGE (Digitizers)
HID_Usage ( 0x22 ), //0x09, 0x22,// USAGE (Finger)
HID_Collection ( HID_Logical ), //0xa1, 0x02,// COLLECTION (Logical)
HID_Usage ( 0x42 ), //0x09, 0x42,// USAGE (Tip Switch)
HID_LogicalMin ( 0x00 ), //0x15, 0x00,// LOGICAL_MINIMUM (0)
HID_LogicalMax ( 0x01 ), //0x25, 0x01,// LOGICAL_MAXIMUM (1)
HID_ReportSize ( 0x01 ), //0x75, 0x01,// REPORT_SIZE (1)
HID_ReportCount ( 0x01 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_Usage ( 0x32 ), //0x09, 0x32,// USAGE (In Range)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_ReportCount ( 6 ), //0x95, 0x06,// REPORT_COUNT (6)
HID_Input(HID_Constant|HID_Array|HID_Absolute),//0x81, 0x01,// INPUT (Cnst,Ary,Abs)
//手指ID
HID_ReportSize ( 8 ), //0x75, 0x08,// REPORT_SIZE (8)
HID_Usage ( 0x51 ), //0x09, 0x51,// USAGE ( Contact Identifier)
HID_ReportCount ( 1 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_LogicalMax ( 0xff ), //0x25, 0xFF,// LOGICAL_MAXIMUM (255)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
//X和Y
HID_UsagePage ( HID_USAGE_PAGE_GENERIC ), //0x05, 0x01,// USAGE_PAGE (Generic Desk..
HID_LogicalMaxS ( 1365 ), //0x26, 0x56, 0x05, // LOGICAL_MAXIMUM (1366)
HID_ReportSize ( 16 ), //0x75, 0x10,// REPORT_SIZE (16)
HID_UnitExponent( 0 ), //0x55, 0x00,// UNIT_EXPONENT (0)
HID_Unit ( 0 ), //0x65, 0x00,// UNIT (0)
HID_Usage ( HID_USAGE_GENERIC_X ), //0x09, 0x30,// USAGE (X)
HID_PhysicalMinS( 0 ), //0x36, 0x00,0x00,// PHYSICAL_MINIMUM (0)
HID_PhysicalMaxS( 1365 ), //0x46, 0x56, 0x05,// PHYSICAL_MAXIMUM (1366)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_LogicalMaxS ( 767 ), //0x26, 0x00, 0x03,
HID_PhysicalMaxS( 767 ), //0x46, 0x00, 0x03,// PHYSICAL_MAXIMUM (0)
HID_Usage ( HID_USAGE_GENERIC_Y ), //0x09, 0x31,// USAGE (Y)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
HID_EndCollection, //0xc0, //END_COLLECTION
//实际点数
HID_UsagePage ( HID_USAGE_PAGE_DIGITIZER), //0x05, 0x0d,// USAGE_PAGE (Digitizers)
HID_Usage ( 0x54 ), //0x09, 0x54,// USAGE (Actual count)
HID_ReportCount ( 1 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_ReportSize ( 8 ), //0x75, 0x08,// REPORT_SIZE (8)
HID_LogicalMin ( 0x00 ), //0x15, 0x00,// LOGICAL_MINIMUM (0)
HID_LogicalMax ( 0x08 ), //0x25, 0x08,// LOGICAL_MAXIMUM (8)
HID_Input(HID_Data|HID_Variable|HID_Absolute),//0x81, 0x02,// INPUT (Data,Var,Abs)
//硬件支持点数
HID_ReportID ( FEATURE_REPORT_ID ), //0x85, 0x02,// REPORT_ID (Feature)
HID_ReportSize ( 8 ), //0x75, 0x08,// REPORT_SIZE (8)
HID_ReportCount ( 1 ), //0x95, 0x01,// REPORT_COUNT (1)
HID_LogicalMin ( 0x01 ), //0x15, 0x01,// LOGICAL_MINIMUM (1)
HID_LogicalMax ( 0x08 ), //0x25, 0x08,// LOGICAL_MAXIMUM (8)
HID_Usage ( 0x55 ), //0x09, 0x55,// USAGE(Maximum Count)
HID_Feature(HID_Data|HID_Variable|HID_Absolute),//0xB1, 0x02,// FEATURE (Data,Var,Abs)
HID_EndCollection, //0xc0, // END_COLLECTION
//
//上位机特性设定
//
HID_Usage ( 0x0e ), //0x09, 0x0E,// USAGE (Device Configuration)
HID_Collection ( HID_Application ), //0xa1, 0x01,// COLLECTION (Application)
HID_ReportID ( SET_FEATURE_REPORT_ID ), //0x85, 0x04,// REPORT_ID (Configuration)
HID_Usage ( 0x23 ), //0x09, 0x23,// USAGE (Device Settings)
HID_Collection ( HID_Logical ), //0xa1, 0x02,// COLLECTION (logical)
HID_Usage ( 0x52 ), //0x09, 0x52,// USAGE (Device Mode)
HID_LogicalMin ( 0 ), //0x15, 0x00,// LOGICAL_MINIMUM (0)
HID_LogicalMax ( 10 ), //0x25, 0x0a,// LOGICAL_MAXIMUM (10)
HID_ReportSize ( 8 ), //0x75, 0x08,// REPORT_SIZE (8)
HID_Usage ( 0x53 ), //0x09, 0x53,// USAGE (Device Identifier)
HID_ReportCount ( 2 ), //0x95, 0x01,// REPORT_COUNT (2)
HID_Feature(HID_Data|HID_Variable|HID_Absolute),//0xb1, 0x02,// FEATURE (Data,Var,Abs
HID_EndCollection, //0xc0, // END_COLLECTION
HID_EndCollection, //0xc0, // END_COLLECTION
}
对上面描述符几个说明:
1.手指和z轴部分:里面有效的是bit0和bit1两位,但是由于我们属于平面设备,没有z轴,因此值关心bit0就可以了。bit0=1说明有触摸,bit0=1说明手指已经离开。
2.X和Y部分:注意两者的最大值和最小值要与设备相匹配
3.手指ID部分: 手指ID指的是多点触控过程中,每个手指都要有一个ID,来表明这个手指的身份。同一跟手指,在一次触控过程中只能有一个ID。
4.实际点数: 表示的是,此数据包中具体包含几个有效点
5.硬件支持点数:此硬件设备最大支持几个点。相比于鼠标等单点设备,多点触控设备枚举的时候,上位机会发送一个特性包来询问次设备的具体信息,因此要在枚举过程中回复过去,这里回复硬件支持点数。对于在哪里回复,我们找到了具体的代码在hiduser.c中:
* 在 BOOL HID_GetReport (void)函数中添加以下代码
*
* case HID_REPORT_FEATURE:
* if(SetupPacket.wValue.WB.L==FEATURE_REPORT_ID)
* {
* nVersionFlag=1;
* EP0Buf[0] = FEATURE_REPORT_ID;
* EP0Buf[1] = MAX_POINT;
* }
* break;
关于这里的nVersionFlag:特性包是支持多点触控的windows才会有的,也就是只有windows7 及以上版本才会发送这个包,如果我们设备枚举过程中没有收到这个包,那表明我们的pc可能是xp或者vista,在后期可以做一定的处理,比如将数据转换成鼠标发送上去。
6.上位机特性设定:多点出控设备枚举时候,上位机还会对下位机进行一次配置,这里定义了配置包的信息。由于这个包里的信息对于我们没有任何意义,我们可以不响应这个包。如果需要的话,相应的函数在hiduser.c的HID_SetReport (void) 函数中,大家根据实际情况自己修改。
修改完这些,烧写到我们的设备中,这个时候windows应该就可以识别出多点触控了,具体识别成功与否怎么看呢?
首先在设备管理器中可以看到Microsoft Input Configuration Device、HID-compliant device和USB输入设备三个设备的PID和VID与定义的设备的相同,如果是的话说明已经识别成功了
这时候可以打开控制面板 ,并且以图标方式显示, 看是否有 “笔和触摸”这个选项。如下图
在开始菜单的搜索框中输入 “笔和触摸” 看能否搜索出相同的选项。打开这个选项卡,勾上 “在通知区域显示比势图标” 然后确定
这个时候再任务栏的右下角应该可以看到这个图标
此时说明设备已经识别成功了,下面是发送触摸的数据包,
// 对于本代码2点触控每个数据包由14个字节组成分别为 // InReport[0] 设备ID 固定值为TOUCH_REPORT_ID // InReport[1] 第一点触摸情况 bit1:1有触摸 0没触摸 bit2: 1在范围内 0不在范围内 // InReport[2] 第一点ID 每个点在抬起之前不能改变 0~255 // InReport[3] 第一点X坐标低8位 // InReport[4] 第一点X坐标高8位 // InReport[5] 第一点Y坐标低8位 // InReport[6] 第一点Y坐标高8位 // InReport[7] 第二点触摸情况 bit1:1有触摸 0没触摸 bit2: 1在范围内 0不在范围内 // InReport[8] 第二点ID 每个点在抬起之前不能改变 0~255 // InReport[9] 第二点X坐标低8位 // InReport[10] 第二点X坐标高8位 // InReport[11] 第二点Y坐标低8位 // InReport[12] 第二点Y坐标高8位 // InReport[13] 此数据包中有效的点数
二、上位机部分
我参考的资料:
http://msdn.microsoft.com/zh-cn/library/ee663093.aspx
我是在MFC dialog基础上做的,新建一个MFC程序。这里有几个函数跟多点触控有关
GetSystemMetrics(SM_DIGITIZER)用来检测是否有touch设备
GetSystemMetrics(SM_MAXIMUMTOUCHES)用来检测触控设备支持几个点
BOOL CTouchDlg::OnTouchInputs( UINT nInputsCount, PTOUCHINPUT pInputs)这是个虚函数,重载用来响应touch事件
这几个函数的用法具体见参考资料吧。下面把我的代码贴出来
新建一个定时器,定时扫描设备更新,把是否有出控设备存在,如果存在就把设备支持点数显示出来
void CTouchDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CString str;
BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);
if ((digitizerStatus & (0x80 + 0x40)) == 0) //堆栈就绪+多触点
{
this->GetDlgItem(IDC_STATIC_EE)->SetWindowTextA("没有可用的触控设备!");
}
else
{
BYTE nInputs = (BYTE) GetSystemMetrics(SM_MAXIMUMTOUCHES);
str.Format("当前设备支持%d个点",nInputs);
this->GetDlgItem(IDC_STATIC_EE)->SetWindowTextA(str);
}
CDialogEx::OnTimer(nIDEvent);
}
touch输入事件相应函数,接收所有touch事件,把事件信息打印到文本框
BOOL CTouchDlg::OnTouchInputs( UINT nInputsCount, PTOUCHINPUT pInputs)
{
CString str,strtemp;
while((int)nInputsCount>0)
{
if ((pInputs[nInputsCount-1].dwFlags & TOUCHEVENTF_DOWN) == TOUCHEVENTF_DOWN) // 触摸按下事件
{
this->GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
// if(str.GetLength()>500)
// str.Empty();
strtemp.Format("Touch down! ID:%d X:%d Y:%d\r\n",pInputs[nInputsCount-1].dwID,pInputs[nInputsCount-1].x/100,pInputs[nInputsCount-1].y/100);//打印信息
str.Append(strtemp);
this->GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
OnTouchInputDown(&pInputs[nInputsCount-1]);
}
else if ((pInputs[nInputsCount-1].dwFlags & TOUCHEVENTF_MOVE) == TOUCHEVENTF_MOVE) // 触摸移动事件
{
this->GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
//if(str.GetLength()>500)
// str.Empty();
strtemp.Format("Touch move! ID:%d X:%d Y:%d\r\n",pInputs[nInputsCount-1].dwID,pInputs[nInputsCount-1].x/100,pInputs[nInputsCount-1].y/100);//打印信息
str.Append(strtemp);
this->GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
OnTouchInputMove( &pInputs[nInputsCount-1]);
}
else if ((pInputs[nInputsCount-1].dwFlags & TOUCHEVENTF_UP) == TOUCHEVENTF_UP) // 触摸移动事件
{
this->GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
// if(str.GetLength()>500)
// str.Empty();
strtemp.Format("Touch up! ID:%d X:%d Y:%d\r\n",pInputs[nInputsCount-1].dwID,pInputs[nInputsCount-1].x/100,pInputs[nInputsCount-1].y/100);//打印信息
str.Append(strtemp);
this->GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
OnTouchInputUp(&pInputs[nInputsCount-1]);
}
else
{
this->GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
// if(str.GetLength()>500)
// str.Empty();
strtemp.Format("Touch in! ID:%d X:%d Y:%d\r\n",pInputs[nInputsCount-1].dwID,pInputs[nInputsCount-1].x/100,pInputs[nInputsCount-1].y/100);//打印信息
str.Append(strtemp);
this->GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
}
nInputsCount--;
}
return true;
}
BOOL CTouchDlg::OnTouchInputMove(PTOUCHINPUT pInputs)
{
CDC* pDC = GetDC();
pDC->SelectObject(&m_pen);
POINT point;
point.x=pInputs->x/100;
point.y=pInputs->y/100;
for(int i=0;i<4;i++)
{
if(m_CurrentPoint[i]==pInputs->dwID)
{
pDC->MoveTo(m_OldPoint[i]);
pDC->LineTo(point);
m_OldPoint[i]=point;
}
}
ReleaseDC(pDC);
return TRUE;
}
BOOL CTouchDlg::OnTouchInputDown(PTOUCHINPUT pInputs)
{
POINT point;
point.x=pInputs->x/100;
point.y=pInputs->y/100;
for(int i=0;i<4;i++)
{
if((m_nPointNum & (1<<i))==0 )
{
m_CurrentPoint[i]=pInputs->dwID; //保存当前点的ID
m_OldPoint[i]=point; //保存当前点位置
m_nPointNum |= (1<<i); //保存点标志
break;
}
}
CString str;
str.Format("PtFlag:%d %d %d %d %d",m_nPointNum,m_CurrentPoint[0],m_CurrentPoint[1],m_CurrentPoint[2],m_CurrentPoint[3]);
this->GetDlgItem(IDC_STATI1_DD)->SetWindowTextA(str);
return TRUE;
}
BOOL CTouchDlg::OnTouchInputUp(PTOUCHINPUT pInputs)
{
for(int i=0;i<4;i++)
{
if(m_CurrentPoint[i]==pInputs->dwID)
{
m_CurrentPoint[i]=0;
m_nPointNum &=( ~(1<<i));
}
}
CString str;
str.Format("PtFlag:%d %d %d %d %d",m_nPointNum,m_CurrentPoint[0],m_CurrentPoint[1],m_CurrentPoint[2],m_CurrentPoint[3]);
this->GetDlgItem(IDC_STATI1_DD)->SetWindowTextA(str);
return TRUE;
}
到了这里,这个程序已经可以完成最多4点画线,显示多点触摸事件的功能了。