基於windows7的usb多點觸控設備


前段時間,朋友要做一個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點畫線,顯示多點觸摸事件的功能了。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章