基于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点画线,显示多点触摸事件的功能了。







  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值