
/****************************************
**  名称: 模拟一个CDC设备，实现USB转串口功能
**  版权：版权所有(C) 1999-2017    
**  网站: http://www.wch.com    
**  版本: V1.0
**  作者: GJ  2017.3.31
**  描述: 全速USB2.0 设备例程，适应于CH372&CH375&CH376          
****************************************
*/

/* 本程序模拟一个CDC设备，可在电脑上显示一个串口实现串口数据收发，波特率可调, 需安装工程目录下的 UsbToUartDriver.inf 文件 */

#pragma NOAREGS
#include <reg52.h>
#include "CH375INC.H"
#include <stdio.h>

#define Baud    9600

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;


//设备描述符
unsigned char  code DevDes[]={
					0x12,0x01,0x10,0x01,0x02,0x00,0x00,0x08,
                    0x86,0x1a,0x55,0xff,0x00,0x01,0x01,0x02,
                    0x03,0x01

					};
//配置描述符
unsigned char   code ConDes[]={			  
    0x09,0x02,0x43,0x00,0x02,0x01,0x00,0xa0,0x32,             //配置描述符（两个接口）
	//以下为接口0（CDC接口）描述符	
    0x09,0x04,0x00,0x00,0x01,0x02,0x02,0x01,0x00,             //CDC接口描述符(一个端点)
	//以下为功能描述符
    0x05,0x24,0x00,0x10,0x01,                                 //功能描述符(头)
	0x05,0x24,0x01,0x00,0x00,                                 //管理描述符(没有数据类接口) 03 01
	0x04,0x24,0x02,0x02,                                      //支持Set_Line_Coding、Set_Control_Line_State、Get_Line_Coding、Serial_State	
	0x05,0x24,0x06,0x00,0x01,                                 //编号为0的CDC接口;编号1的数据类接口
	0x07,0x05,0x81,0x03,0x08,0x00,0xFF,                       //中断上传端点描述符
	//以下为接口1（数据接口）描述符
	0x09,0x04,0x01,0x00,0x02,0x0a,0x00,0x00,0x00,             //数据接口描述符
    0x07,0x05,0x02,0x02,0x40,0x00,0x00,                       //端点描述符	
	0x07,0x05,0x82,0x02,0x40,0x00,0x00,                       //端点描述符
					};		

unsigned char  code LangDes[]={0x04,0x03,0x09,0x04};		//语言描述符
unsigned char  code SerDes[]={ 0x14,0x03,
				0x32,0x00,0x30,0x00,0x31,0x00,0x37,0x00,0x2D,0x00,
				0x32,0x00,0x2D,0x00,
				0x32,0x00,0x35,0x00};		                     //字符串描述符
 unsigned char  code Prod_Des[]={                                //产品字符串描述符
				0x14,0x03,
				0x43,0x00,0x48,0x00,0x33,0x00,0x37,0x00,0x32,0x00,0x5F,0x00,
				0x43,0x00,0x44,0x00,0x43,0x00,
 };
 unsigned char  code Manuf_Des[]={  
				0x0A,0x03,
				0x5F,0x6c,0xCF,0x82,0x81,0x6c,0x52,0x60,
 };
 
unsigned char mVarSetupRequest;						//USB请求码
unsigned short mVarSetupLength;					    //后续数据长度
unsigned char  *VarSetupDescr;						//描述符偏移地址

unsigned char VarUsbAddress	;					    //分配的地址

bit CH375FLAGERR;						//错误清0
bit CH375TRANSBIT;
bit	CH375CONFLAG;
					                     //配置标志

unsigned char volatile xdata CH375_CMD_PORT _at_ 0xBDF1;		/* CH375命令端口的I/O地址 */
unsigned char volatile xdata CH375_DAT_PORT _at_ 0xBCF0;		/* CH375数据端口的I/O地址 */

mREQUEST_PACKET  request;
sbit  CH375ACT  = P1^4;

#define  SET_LINE_CODING                0X20            // Configures DTE rate, stop-bits, parity, and number-of-character
#define  GET_LINE_CODING                0X21            // This request allows the host to find out the currently configured line coding.
#define  SET_CONTROL_LINE_STATE         0X22            // This request generates RS-232/V.24 style control signals.

//cdc参数
unsigned char xdata LineCoding[7]={0x80,0x25,0x00,0x00,0x00,0x00,0x08};   //初始化波特率为57600，1停止位，无校验，8数据位。

#define UART_REV_LEN 128
unsigned char Receive_Uart0_Buf[UART_REV_LEN];   //串口接收缓冲区
volatile unsigned char Uart0_Input_Point = 0;   //循环缓冲区写入指针，总线复位需要初始化为0
volatile unsigned char Uart0_Output_Point = 0;  //循环缓冲区取出指针，总线复位需要初始化为0
volatile unsigned char Uart0ByteCount = 0;      //当前缓冲区剩余待取字节数

unsigned char USB_RX_Buf[64];                  //USB接收缓冲区
volatile unsigned char USB0ByteCount = 0;      //代表USB端点接收到的数据
volatile unsigned char USB0BufOutPoint = 0;    //取数据指针

volatile unsigned char UpPoint_Busy0  = 0;     //上传端点是否忙标志


/* 延时2微秒,不精确 */
void Delay1us(){
	;
}

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-- );
}

/*******************************************************************************
* Function Name  : Uart0_ISR()
* Description    : 串口接收中断函数，实现循环缓冲接收
*******************************************************************************/
void Uart0_ISR(void) interrupt 4
{
	if(RI)   //收到数据
	{
		Receive_Uart0_Buf[Uart0_Input_Point++] = SBUF;
		Uart0ByteCount++;                    //当前缓冲区剩余待取字节数
		if(Uart0_Input_Point>=UART_REV_LEN)
			Uart0_Input_Point = 0;           //写入指针
		RI =0;		
	}
	
}
//串口初始化
void Init_Uart(void)
{
	unsigned long temp = 0;
	unsigned char s;
	TMOD &= 0x0f;
	TMOD |= 0x20;
	PCON |= 0x80;
	
	s = 125000/Baud;
	temp = 125000%Baud;
	if(temp>(Baud/2))
		s++;
	TH1   = 256-s;	
	TL1   = TH1;
	SCON  = 0x50;
	TR1   = 1;
	ES = 1;	
}
/*******************************************************************************
* Function Name  : UARTSendByte(UINT8 SendDat)
* Description    : UARTSendByte发送一个字节
* Input          : UINT8 SendDat；要发送的数据
* Output         : None
* Return         : None
*******************************************************************************/
void UARTSendByte(unsigned char SendDat)
{
	SBUF = SendDat;                                                              //查询发送，中断方式可不用下面2条语句,但发送前需TI=0
	while(TI ==0);
	TI = 0;
}


void CH375_WR_CMD_PORT( unsigned char cmd ) { 				 /* 向CH375的命令端口写入命令,周期不小于4uS,如果单片机较快则延时 */
	//delay2us();
	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_WR_DAT_PORT( 1 );  							    /* 设置为使用外置固件的USB设备方式 */
	for ( ;; ) {  											/* 等待操作成功,通常需要等待10uS-20uS */
		if ( CH375_RD_DAT_PORT( )==CMD_RET_SUCCESS ) break;
	}
	CH375_WR_CMD_PORT(CMD_CHK_SUSPEND);                     /* 检测挂起事件 */
	CH375_WR_DAT_PORT( 0x10 );
	CH375_WR_DAT_PORT( 4 );                                 /* 以50mS为间隔检查USB挂起 */
	
/* 下述启用中断,假定CH375连接在INT0 */
	IT0 = 0;  /* 置外部信号为低电平触发 */
	IE0 = 0;  /* 清中断标志 */
	EX0 = 1;  /* 允许CH375中断 */
}
//*********************************************************

//*********************************************************
//端点0数据上传
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长度数据，这是一个状态阶段
	}
}
/*******************************************************************************
* Function Name  : Config_Uart(UINT8 *cfg_uart)
* Description    : 配置串口参数
* Input          : 串口配置参数 四位波特率、停止位、校验、数据位
* Output         : None
* Return         : None
*******************************************************************************/
void Config_Uart(unsigned char *cfg_uart)
{
	unsigned long uart0_buad = 0;
	unsigned long temp = 0;
	unsigned char s;
	*((unsigned char *)&uart0_buad) = cfg_uart[3];
	*((unsigned char *)&uart0_buad+1) = cfg_uart[2];
	*((unsigned char *)&uart0_buad+2) = cfg_uart[1];
	*((unsigned char *)&uart0_buad+3) = cfg_uart[0];
	ES = 0;
	TR1 = 0; 
	s = 125000/uart0_buad;
	temp = 125000%uart0_buad;
	if(temp>(uart0_buad/2))
		s++;
	TH1   = 256-s;	
	TL1   = TH1;
	TR1 = 1; 
	ES = 1;
}

//*********************************************************

//复制描述符以便上传
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   *pBuf;
	CH375_WR_CMD_PORT(CMD_GET_STATUS);  									/* 获取中断状态并取消中断请求 */
	InterruptStatus =CH375_RD_DAT_PORT();  									/* 获取中断状态 */
	IE0 = 0;  																/* 清中断标志,对应于INT0中断 */
	
	switch(InterruptStatus){  // 分析中断状态
		case USB_INT_EP1_IN:
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);	
			break;
		case  USB_INT_EP2_OUT:  											// 批量端点下传成功
			CH375_WR_CMD_PORT(CMD_SET_ENDP6);
			CH375_WR_DAT_PORT(0x0E);                                        //响应NAK		
			CH375_WR_CMD_PORT(CMD_RD_USB_DATA);									//发出读数据命令
			USB0ByteCount=CH375_RD_DAT_PORT();											//首先读出的是长度														
			for(len=0;len!=USB0ByteCount;len++,pBuf++) 
				USB_RX_Buf[len] = CH375_RD_DAT_PORT();	                    //将数据读入到缓冲区			 
			USB0BufOutPoint = 0;                                             //取数据指针复位		
			break;
		case   USB_INT_EP2_IN:												 //批量端点上传成功,未处理
			UpPoint_Busy0 = 0;
			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();  // 取出数据
			mVarSetupRequest = 0xff;										//清请求状态
			CH375TRANSBIT = 0;												//清标志
			if(length==0x08){
			    mVarSetupLength=(request.buffer[6] | (unsigned short)request.buffer[7]<<8);	
				mVarSetupRequest=request.r.bRequest;							//暂存标准请求码
				if( !(request.r.bmReuestType&0x80) ){									//表示需要上传数据
						CH375FLAGERR = 2;																	//上传数据置标志
				}
				else CH375FLAGERR = 0;																//下传数据清标志
				if((c1=request.r.bmReuestType)&0x40){         					 //厂商请求，未处理
				}
				else if((c1=request.r.bmReuestType)&0x20){          					//类请求
					switch( mVarSetupRequest ) 
					{
						case GET_LINE_CODING:   //0x21  currently configured
							if(mVarSetupLength>sizeof(LineCoding))
								mVarSetupLength = sizeof(LineCoding);
							VarSetupDescr=LineCoding;
							mCh375DesUp();
							break;						
						case SET_CONTROL_LINE_STATE:  //0x22  generates RS-232/V.24 style control signals										
							break;
						case SET_LINE_CODING:        //0x20  Configure
							break;
						default:				
								 break;
				  }						
				
				}
				else 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;
							else if(request.buffer[3]==2){		 					//配置描述符上传
								if(mVarSetupLength > (ConDes[2] | (unsigned short)ConDes[3]<<8))
									mVarSetupLength = ConDes[2] | (unsigned short)ConDes[3]<<8;
								VarSetupDescr=ConDes;
							}
							else if(request.buffer[3]==3) {
								if ( request.buffer[2]== 0 ) 
								{
									if(mVarSetupLength>sizeof(LangDes))
										mVarSetupLength = sizeof(LangDes);
									VarSetupDescr=LangDes;
								}
								else if ( request.buffer[2]== 1 ) 
								{
									if(mVarSetupLength>sizeof(Manuf_Des))
										mVarSetupLength = sizeof(Manuf_Des);
									VarSetupDescr=Manuf_Des; 						//做字符串处理
								}
								else if( request.buffer[2]== 2 )
								{
									if(mVarSetupLength>sizeof(Prod_Des))
										mVarSetupLength = sizeof(Prod_Des);
									VarSetupDescr=Prod_Des; 						//做字符串处理
								}
								else
								{
									if(mVarSetupLength>sizeof(SerDes))
										mVarSetupLength = sizeof(SerDes);
									VarSetupDescr=SerDes; 						//做字符串处理									
								}
							}
							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;
							CH375ACT=1;
							if ( request.buffer[2] != 0 ) {
								CH375CONFLAG=1;											//设置配置标志
								CH375ACT=0;												//输出配置完成信号
							}
							break;
						case DEF_USB_GET_INTERF:										//得到接口
							request.buffer[0]=1;									//上传接口数，本事例只支持一个接口
							break;
						default :
							CH375FLAGERR=1;											//不支持的标准请求
							break;
					}
				}
			}
			else {  //不支持的控制传输，不是8字节的控制传输
				CH375FLAGERR=1;
			}
			if(CH375FLAGERR==0) mCh375Ep0Up();										//没有错误/调用数据上传，，长度为0上传为状态
			else if( CH375FLAGERR == 2 ) {
				if( mVarSetupLength == 0 ){											//上传0包数据
					CH375FLAGERR = 0;
					mCh375Ep0Up();	
				}
			}
			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跳出
				mVarSetupLength -= length;
				for(len=0;len!=length;len++)
				{	
					c1=CH375_RD_DAT_PORT();					//取出下传数据
					if(mVarSetupRequest == SET_LINE_CODING )
						LineCoding[len] = c1;
				}
				if(mVarSetupRequest == SET_LINE_CODING)
					Config_Uart(LineCoding);	
				CH375TRANSBIT = 1;
			}
			if( CH375TRANSBIT ){											
				if( (length<8) || (mVarSetupLength == 0) ){
					CH375TRANSBIT = 0;
					CH375_WR_CMD_PORT(CMD_WR_USB_DATA3);						//发出写端点0的命令
					CH375_WR_DAT_PORT(0);					                   //上传0长度数据，这是一个状态阶段
				}
			}
			break;
		case USB_INT_USB_SUSPEND:
			CH375_WR_CMD_PORT (CMD_ENTER_SLEEP);	                            //睡眠			
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);									//释放缓冲区
			break;
		case USB_INT_WAKE_UP:			
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);									//释放缓冲区
			break;
		default:
			if((InterruptStatus&0x03)==0x03){									//总线复位
				CH375FLAGERR=0;													//错误清0
				CH375CONFLAG=0;													//配置清0
				mVarSetupLength=0;
				CH375TRANSBIT = 0;
				CH375ACT=1;														//清配置完成输出
				Uart0_Input_Point = 0;   //循环缓冲区输入指针
				Uart0_Output_Point = 0;  //循环缓冲区读出指针
				Uart0ByteCount = 0;      //当前缓冲区剩余待取字节数
				USB0ByteCount = 0;       //USB端点收到的长度		
				UpPoint_Busy0 = 0;
			}
			CH375_WR_CMD_PORT (CMD_UNLOCK_USB);									//释放缓冲区
			break;
	}
}

main( ) {
	unsigned char lenth,i;
	unsigned short Uart0_Timeout = 0;
	Delay50ms( );	/* 延时等待CH375初始化完成,如果单片机由CH375提供复位信号则不必延时 */
	Init_Uart();
	CH375_Init( );  /* 初始化CH375 */
    EA=1;
    while(1)		/* 主程序 */
	{
		if(CH375CONFLAG)
		{			
			if(USB0ByteCount)   //USB接收端点有数据
			{
				UARTSendByte(USB_RX_Buf[USB0BufOutPoint++]);
				USB0ByteCount--;
				if(USB0ByteCount==0) 
				{
					CH375_WR_CMD_PORT(CMD_SET_ENDP6);
					CH375_WR_DAT_PORT(0); 					
				}
			}
			if(Uart0ByteCount)
				Uart0_Timeout++;
			if(!UpPoint_Busy0)   //端点不繁忙（空闲后的第一包数据，只用作触发上传）
			{
				lenth = Uart0ByteCount;
				if(lenth>0)
				{
					if(lenth>39 || Uart0_Timeout>2500)                                     //超时发送+聚包发送
					{
						Uart0_Timeout = 0;
						UpPoint_Busy0 = 1;
						if(Uart0_Output_Point+lenth>UART_REV_LEN)
							lenth = UART_REV_LEN-Uart0_Output_Point;
						Uart0ByteCount -= lenth;			
						//写上传端点
						CH375_WR_CMD_PORT(CMD_WR_USB_DATA7);								//发出写上传端点命令
						CH375_WR_DAT_PORT(lenth);
						for(i=0;i!=lenth;i++)
						{
							CH375_WR_DAT_PORT(Receive_Uart0_Buf[Uart0_Output_Point+i]);
						}
						Uart0_Output_Point+=lenth;
						if(Uart0_Output_Point>=UART_REV_LEN)
							Uart0_Output_Point = 0;													
					}
				}
			}
		}
	}
	
}
