/****************************************
**  名称: 模拟一个HID兼容设备，用于HID设备数据上下传输
**  版权：版权所有(C) 1999-2017    
**  网站: http://www.wch.com    
**  版本: V1.0
**  作者: GJ  2017.3.31
**  描述: 全速USB2.0 设备例程，适应于CH372&CH375&CH376
****************************************
*/

/* HID兼容设备，实现按下P3.3 HID上传64字节数据包，可配合提供的上位机软件“hid_recv.exe”调试 */

#pragma NOAREGS
#include <reg51.h>
#include "CH375INC.H"
#include "stdio.h"
unsigned char idata buf[64];
typedef	union _REQUEST_PACK{
	unsigned char  buffer[8];
	struct{
		unsigned char	 bmReuestType;    	 //标准请求字
		unsigned char	 bRequest;		   	//请求代码
		unsigned int     wValue;			//特性选择高
		unsigned int     wIndx;				//索引
		unsigned int     wLength;				//数据长度
	}r;
} mREQUEST_PACKET,	*mpREQUEST_PACKET;
//设备的描述符包含设备描述符、配置描述符、字符描述符；识别描述符的类型就是根据它的第二个字节值来识别；
//一般情况下设备描述符不需要用户去配置，因为可以直接用软件从现有的设备中读取；同种设备的描述符都相同；如优盘描述符都一样
/*以下是设备描述符，（为什么是这些数值，可查阅USB协议书籍）*/
unsigned char  code DevDes[]={
    0x12  //描述符大小，即此数组的长度			
   ,0x01  //常数表示描述符的类型；(数值为0x01表示设备描述符)
   ,0x10			
   ,0x01  //USB规范版本信息;2个字节；0x10、0x01表示USB1.1；USB2.0为0X00、0X02；
   ,0x00  //类别码，数值0X01~0XFE为USB定义的类，如打印机等，0XFF是由厂商指定的类别；0为HID类，它的类别在配置描述符中的接口描述符指定；
   ,0x00  //子类别码；这些码值的具体含义根据bDeviceClass 域来看如bDeviceClass 域为零此域也须为零；如bDeviceClass 域为FFH此域的所有值保留	
   ,0x00  //协议码
   ,0x08  //端点0的最大信息包大小
   ,0x31  //厂商ID
   ,0x51
   ,0x07  //产品ID	
   ,0x20
   ,0x00  //设备版本信息
   ,0x03
   ,0x01  //索引值	
   ,0x02
   ,0x00
   ,0x01  //可能配置的数目			
					};

//报表描述符
unsigned char code Hid_des[]={
  0x06, 0x00,0xff,
  0x09, 0x01,  
  0xa1, 0x01, 	  //集合开始
  
  0x09, 0x02, 		//Usage Page  用法
  0x15, 0x00, 	    //Logical  Minimun
  0x26, 0x00,0xff,  //Logical  Maximun
  0x75, 0x08,  		//Report Size
  0x95, 0x40,  		//Report Counet
  0x81, 0x06,  		//Input
	
  0x09, 0x02, 		//Usage Page  用法
  0x15, 0x00, 		//Logical  Minimun
  0x26, 0x00,0xff,		//Logical  Maximun
  0x75, 0x08,  		//Report Size
  0x95, 0x40,  		//Report Counet
  0x91, 0x06,  		//Output
  0xC0  
         };

//配置描述符；包括配置描述符、接口描述符、端点描述符，HID设备还包括HID描述符；
unsigned char   code ConDes[]={			
			   0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0xa0,  0x23,//配置描述符；第二字节为0X02
		       0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00,  0x05,//接口描述符；第二字节为0X04 
			   0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(Hid_des)&0xFF,(sizeof(Hid_des)>>8)&0xFF,//HID描述符；第二字节为0X21
		       0x07, 0x05, 0x82, 0x03, 0x40, 0x00, 0x01, //端点描述符；第二字节为0X05
			   0x07, 0x05, 0x02, 0x03, 0x40, 0x00, 0x01	 //端点描述符；
				};	

 //字符串描述符
unsigned char  code LangDes[]={0x04,0x03,0x09,0x04};		//语言描述符
unsigned char  code SerDes[]={0x0A,0x03,0x5F,0x6C,0xCF,0x82,0x81,0x6C,0x52,0x60, };		

//字符串描述符
unsigned char mVarSetupRequest;						//	;USB请求码
unsigned char mVarSetupLength;					//		;后续数据长度
unsigned char  code * VarSetupDescr;//	;描述符偏移地址
unsigned char buf1[8];
unsigned char VarUsbAddress	;					//
unsigned  char  Report_ID=0X01;

sbit	key	=	P3^3;

bit CH375FLAGERR;						//错误清0
bit	CH375CONFLAG;
unsigned char flag=0;
mREQUEST_PACKET  request;

unsigned char volatile xdata CH375_CMD_PORT _at_ 0xBDF1;		/* CH375命令端口的I/O地址 */
unsigned char volatile xdata CH375_DAT_PORT _at_ 0xBCF0;		/* CH375数据端口的I/O地址 */
#define	CH375_INT_WIRE		INT0   	                            /* P3.2, INT0, 连接CH375的INT#引脚,用于查询中断状态 */
							 
void	mInitSTDIO( )
{
	SCON = 0x50;
	PCON = 0x80;
	TMOD = 0x20;
	TH1 = 0xf3;  /* 24MHz晶振, 9600bps */
	TR1 = 1;
	TI = 1;
}
void delay10ms()
{ 
    unsigned char i,j;
    for(i=10;i!=0;i--)
       for(j=100;j!=0;j--) ;
}

void Delay1us(){;}
/* 延时2微秒,不精确 */
void	Delay2us( )
{
	unsigned char i;
#define DELAY_START_VALUE	1  								/* 根据单片机的时钟选择初值,20MHz以下为0,30MHz以上为2 */
	for ( i=DELAY_START_VALUE; i!=0; i-- );
}
/* 延时50毫秒,不精确 */
void	Delay50ms( )
{
	unsigned char i, j;
	for ( i=200; i!=0; i-- ) for ( j=250; j!=0; j-- );
}
/*以下是带P0、P1、P2口的单片机外部读写方式、地址的确定和外部扩展I/O口地址一样，见硬件电路*/
void CH375_WR_CMD_PORT( unsigned char cmd ) { 				//向CH375的命令端口写入命令,周期不小于4uS,如果单片机较快则延时
	CH375_CMD_PORT=cmd;
	Delay2us( );
}

void CH375_WR_DAT_PORT( unsigned char dat ) { 				// 向CH375的数据端口写入数据,周期不小于1.5uS,如果单片机较快则延时 
	CH375_DAT_PORT=dat;
	Delay1us();  										// 因为MCS51单片机较慢所以实际上无需延时
}

unsigned char CH375_RD_DAT_PORT() {  					//从CH375的数据端口读出数据,周期不小于1.5uS,如果单片机较快则延时 
	Delay1us( );  									// 因为MCS51单片机较慢所以实际上无需延时 
	return( CH375_DAT_PORT );

}

/* CH375初始化子程序； */
void	CH375_Init( )
{
/* 设置USB工作模式, 必要操作 */
	CH375_WR_CMD_PORT( CMD_SET_USB_MODE );//向数据端口写设置CH375命令的数据CMD_SET_USB_MODE，数值大小在头文件中有定义，说明书中有规定值
	CH375_WR_DAT_PORT( 1 );  	/* 0X01设置为使用外置固件的USB设备方式，DS1中有定义 */
	for ( ;; ) {  				/* 等待操作成功,通常需要等待10uS-20uS */
		if ( CH375_RD_DAT_PORT( )==CMD_RET_SUCCESS ) break;
	}
/* 下述启用中断,假定CH375连接在INT0 */
	IT0 = 0;  /* 置外部信号为低电平触发 */
	IE0 = 0;  /* 清中断标志 */
	EX0 = 1;  /* 允许CH375中断 */	
}
//串口发送程序，用于监视程序运行情况；
void Uart_send( unsigned char i	)
{
	TI=0;
	SBUF=i;
	while(TI==0);
	TI=0;
}
//*********************************************************
//端点0数据上传；CH375有端点0、1、2等缓冲区与计算机通讯，个缓冲区大小不一样；详细见CH375说明书；
void mCh375Ep0Up(){
	unsigned char i,len;
	if(mVarSetupLength){												//长度不为0传输具体长度的数据
		if(mVarSetupLength<=8){
			len=mVarSetupLength;
			mVarSetupLength=0;
        }	//长度小于8则长输要求的长度
		else{
			len=8;
			mVarSetupLength-=8;
		}							                        		//长度大于8则传输8个，切总长度减8
	    CH375_WR_CMD_PORT(CMD_WR_USB_DATA3);						//发出写端点0的命令
       	CH375_WR_DAT_PORT(len);										//写入长度
    	for(i=0;i!=len;i++)
        CH375_WR_DAT_PORT(request.buffer[i]);	              		//循环写入数据
    }
	else{
		CH375_WR_CMD_PORT(CMD_WR_USB_DATA3);						//发出写端点0的命令
		CH375_WR_DAT_PORT(0);					                   //上传0长度数据，这是一个状态阶段

	}
}
//*********************************************************
//复制描述符以便上传
void mCh375DesUp(){
	unsigned char k;        
	for (k=0; k!=8; k++ ) {
         request.buffer[k]=*VarSetupDescr;  								//依次复制8个描述符，
         VarSetupDescr++;
    }
}
/* CH375中断服务程序INT0,使用寄存器组1 */
void	mCH375Interrupt( ) interrupt 0 using 1
{
	unsigned char InterruptStatus;
	unsigned char length, c1, len;
	unsigned char   mBuf[8];
	
	CH375_WR_CMD_PORT(CMD_GET_STATUS);  /* 向CH375芯片发送获取中断状态的命令；CMD_GET_STATUS为命令码，数值大小在CH375INC.H 头文件中*/
	InterruptStatus =CH375_RD_DAT_PORT();  	/* 读取中断状态 */
	IE0 = 0; 
	
	switch(InterruptStatus){  // 分析中断状态，
		case  USB_INT_EP2_OUT:  											// 批量端点下传成功 
			CH375_WR_CMD_PORT(CMD_RD_USB_DATA);									//发出读数据命令
			if(length=CH375_RD_DAT_PORT()){										//长度为0跳出
				for(len=0;len!=length;len++)
				{
					mBuf[len]=CH375_RD_DAT_PORT();					//取出下传数据
				}
				for(len=0;len!=length;len++)
				{
					Uart_send(mBuf[len]);
				}
				CH375_WR_CMD_PORT (CMD_UNLOCK_USB);
			}
			
			break;
		case   USB_INT_EP2_IN:												 //批量端点上传成功,未处理
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);								//释放缓冲区
			break;
		case   USB_INT_EP0_SETUP: 											//控制端点建立成功
	    	CH375_WR_CMD_PORT(CMD_RD_USB_DATA);
			length=CH375_RD_DAT_PORT();
			for(len=0;len!=length;len++)request.buffer[len]=CH375_RD_DAT_PORT();  // 取出数据
			if(length==0x08){						  //命令长度为8	
				mVarSetupLength=(request.buffer[6] | (unsigned short)request.buffer[7]<<8);
				mVarSetupRequest=request.r.bRequest;							//暂存标准请求码				
				if((c1=request.r.bmReuestType)&0x40){         					 //厂商请求，未处理
				}
				if((c1=request.r.bmReuestType)&0x20){// printf("b\n");         					//类请求，未处理
					if(request.buffer[1]==0x0a){//printf("c\n");
						}												//SET_IDLE
					else if(request.buffer[1]==0x09){
					}
				}
				if(!((c1=request.r.bmReuestType)&0x60)){          				//标准请求					
					switch(request.r.bRequest){  // 分析标准请求
						case DEF_USB_CLR_FEATURE:									//清除特性
							if((c1=request.r.bmReuestType&0x1F)==0X02){					//不是端点不支持
								switch(request.buffer[4]){
									case 0x82:
										CH375_WR_CMD_PORT(CMD_SET_ENDP7);					//清除端点2上传
										CH375_WR_DAT_PORT(0x8E);                			//发命令清除端点
										break;
									case 0x02:
										CH375_WR_CMD_PORT(CMD_SET_ENDP6);
										CH375_WR_DAT_PORT(0x80);							//清除端点2下传
										break;
									case 0x81:
										CH375_WR_CMD_PORT(CMD_SET_ENDP5);					//清除端点1上传
										CH375_WR_DAT_PORT(0x8E);
										break;
									case 0x01:
										CH375_WR_CMD_PORT(CMD_SET_ENDP4);					//清除端点1下传
										CH375_WR_DAT_PORT(0x80);
										break;
									default:
										break;
								}
							}
							else{
								CH375FLAGERR=1;								//不支持的清除特性，置错误标志
							}
							break;
						case DEF_USB_GET_STATUS:								//获得状态
							request.buffer[0]=0;
							request.buffer[1]=0;								//上传状态
							break;
						case DEF_USB_SET_ADDRESS:								//设置地址
							VarUsbAddress=request.buffer[2];					//暂存USB主机发来的地址
							break;
						case DEF_USB_GET_DESCR: 								//获得描述符
							if(request.buffer[3]==1){							//设备描述符上传
								VarSetupDescr=DevDes;
								if(mVarSetupLength>sizeof(DevDes))
									mVarSetupLength = sizeof(DevDes);
								CH375FLAGERR=0;
								}
							else if(request.buffer[3]==2){		 					//配置描述符上传
								VarSetupDescr=ConDes;
								if(mVarSetupLength>sizeof(ConDes))
									mVarSetupLength = sizeof(ConDes);
								CH375FLAGERR=0;
							
								}
							else if(request.buffer[3]==0x22) {
								VarSetupDescr=Hid_des;
								if(mVarSetupLength>sizeof(Hid_des))
									mVarSetupLength = sizeof(Hid_des);							
								CH375FLAGERR=0;
								}
							else if(request.buffer[3]==3) {
								if ( request.buffer[4]== 0 ) 
								{
									VarSetupDescr=LangDes;
									if(mVarSetupLength>sizeof(LangDes))
										mVarSetupLength = sizeof(LangDes);										
								}
								else 
								{
									VarSetupDescr=SerDes;
									if(mVarSetupLength>sizeof(SerDes))
										mVarSetupLength = sizeof(SerDes);																		
								} 						
							}
							
							else CH375FLAGERR=1;
								mCh375DesUp();											//其余描述符不支持					          							
							break;
						case DEF_USB_GET_CONFIG:									//获得配置
							request.buffer[0]=0;									//没有配置则传0
							if(CH375CONFLAG) request.buffer[0]=1;					//已经配置则传1；这是在描述符里规定的
							break;
						case DEF_USB_SET_CONFIG:                 					//设置配置
							CH375CONFLAG=0;
							if ( request.buffer[2] != 0 ) {
								CH375CONFLAG=1;											//设置配置标志
							}
							break;
						case DEF_USB_GET_INTERF:										//得到接口
							request.buffer[0]=1;									//上传接口数，本事例只支持一个接口
							break;
						default :
							CH375FLAGERR=1;											//不支持的标准请求
							break;
					}
				}
			}
			else {                                                            //不支持的控制传输，不是8字节的控制传输
				CH375FLAGERR=1;
			}
			if(!CH375FLAGERR) mCh375Ep0Up();										//没有错误/调用数据上传，，长度为0上传为状态
			else {
				CH375_WR_CMD_PORT(CMD_SET_ENDP3);								//设置端点1为STALL，指示一个错误
				CH375_WR_DAT_PORT(0x0F);
			}
			break;
		case   USB_INT_EP0_IN:													//控制端点上传成功
			if(mVarSetupRequest==DEF_USB_GET_DESCR){								//描述符上传
				mCh375DesUp();
				mCh375Ep0Up();															
			}
			else if(mVarSetupRequest==DEF_USB_SET_ADDRESS){							//设置地址
				CH375_WR_CMD_PORT(CMD_SET_USB_ADDR);
				CH375_WR_DAT_PORT(VarUsbAddress);								//设置USB地址,设置下次事务的USB地址
			}
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);								//释放缓冲区
			break;
		case   USB_INT_EP0_OUT:													//控制端点下传成功
			CH375_WR_CMD_PORT(CMD_RD_USB_DATA);									//发出读数据命令
			if(length=CH375_RD_DAT_PORT()){										//长度为0跳出
				for(len=0;len!=length;len++){buf1[len]=CH375_RD_DAT_PORT();					//取出下传数据
				   }
			}
			break;
		default:
			if((InterruptStatus&0x03)==0x03){									//总线复位
				CH375FLAGERR=0;													//错误清0
				CH375CONFLAG=0;													//配置清0
				mVarSetupLength=0;
																               //清配置完成输出
			}
			else{																//命令不支持
				;
			}
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);									//释放缓冲区
			CH375_RD_DAT_PORT();
			break;
	}
}

void up_data( unsigned char key_num)
{
	unsigned char i;
		buf[0]=0X01;													
		buf[1]=0x06;
		buf[2]=key_num;
		buf[3]=0x00;
		buf[4]=0x00;
		buf[5]=0x00;
		buf[6]=0x00;
		buf[7]=0x00;														 //演示数据上传
		buf[48]=0xFF;
		buf[49]=0xFF;
		buf[63]=0xFF;
		CH375_WR_CMD_PORT(CMD_WR_USB_DATA7);								//发出写上传端点命令
	    CH375_WR_DAT_PORT(64);
		for(i=0;i!=64;i++)
			CH375_WR_DAT_PORT(buf[i]);
}
void mCh375key( )  
{   
	unsigned char k=0;
	if(key!=0)  return;
    while(1) {
	   if(key==0)  continue;
	   delay10ms();
	   if(key==1) {up_data(0x04);break;}    
	 }
}

main( ) {
	Delay50ms( );	/* 延时等待CH375初始化完成,如果单片机由CH375提供复位信号则不必延时 */
	mInitSTDIO( );
	CH375_Init( );  /* 初始化CH375 */
	EA = 1;         /* 开总中断 */
	while(1)
	{		
		if(CH375CONFLAG)
		{
			if(key==0) mCh375key(); 
		}			
	}  	
}

