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