/**************************************** ** Copyright (C) W.ch 1999-2004 ** ** Web: http://www.winchiphead.com ** **************************************** ** USB 1.1 Host Examples for CH375 ** ** KC7.0@MCS-51 ** **************************************** */ /* 用CH375操作HID设备,比如键盘,鼠标*/ #include "stc90.h" #include #include "CH375INC.H" //#define TEST_LOW_SPEED 1 unsigned char volatile xdata CH375_CMD_PORT _at_ 0xBeF1; /* CH375命令端口的I/O地址 */ unsigned char volatile xdata CH375_DAT_PORT _at_ 0xBcF0; /* CH375数据端口的I/O地址 */ //unsigned char volatile xdata CH375_CMD_PORT _at_ 0xBDF1; /* CH375命令端口的I/O地址 */ //unsigned char volatile xdata CH375_DAT_PORT _at_ 0xBCF0; /* CH375数据端口的I/O地址 */ //UINT8XV CH375_CMD_PORT _at_ 0xBeF1; /* CH375命令端口的I/O地址 */ //UINT8XV CH375_DAT_PORT _at_ 0xBcF0; /* CH375数据端口的I/O地址 */ sbit CH375_INT_WIRE = 0xB0^2; /* P3.2, INT0, 连接CH375的INT#引脚,用于查询中断状态 */ sbit P1_0=P1^0; sbit P1_2=P1^2; #define TRUE 1 #define FALSE 0 unsigned char endp_int; //中断端点号 unsigned char num_interfaces; //接口数 unsigned char config_value; //配置值 unsigned char report_descr_len=0;//REPORT描述符长度 bit flag_config_2=0; //第二次获取描述符标志位 bit flag_interface_2=0; //多个接口标志位 unsigned char endp6_mode=0x80, endp7_mode=0x80;//同步标志位初值 unsigned char status=0xff; //全局状态 unsigned char idata data_buf[96];//描述符缓冲区可以适当减小 union _REQUEST //请求包结构 { struct { unsigned char bmRequestType; unsigned char bRequest; unsigned int wValue; unsigned int wIndex; unsigned int wLength; }Req; unsigned char Req_buf[8]; }Request; unsigned char report_cou=0; //REPORT描述符长度计数 bit flag_output=0; //串口输出标志位 unsigned char xdata data_in[1000];//串口输出缓冲区 void delay2us( ) { unsigned char i; #ifdef TEST_LOW_SPEED for ( i = 20; i != 0; i -- ); #else for ( i = 2; i != 0; i -- ); #endif } void delay1us( ) { unsigned char i; #ifdef TEST_LOW_SPEED for ( i = 10; i != 0; i -- ); #else for ( i = 1; i != 0; i -- ); #endif } void delayms(unsigned char delay) { unsigned char i; do{ for(i=0;i!=250;i++){;} for(i=0;i!=250;i++){;} for(i=0;i!=150;i++){;} }while(--delay); } void mInitSTDIO( ) { // SCON = 0x50; // PCON = 0x80; // TMOD = 0x21; TH1 = 0xf3; /* 22.1184MHz晶振, 115200bps */ SCON = 0x50; //8???,????? AUXR &= 0xBF; //???1???Fosc/12,?12T AUXR &= 0xFE; //??1?????1??????? TMOD &= 0x0F; //?????1?16??????? TL1 = 0xE8; //?????? TH1 = 0xFF; //?????? ET1 = 0; //?????1?? TR1 = 1; //?????1 TR1 = 1; TI = 1; // EX0=1; EA=1; } 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 ); } unsigned char set_usb_mode( unsigned char mode ) { /* 设置CH37X的工作模式 */ unsigned char i; CH375_WR_CMD_PORT( CMD_SET_USB_MODE ); CH375_WR_DAT_PORT( mode ); endp6_mode=endp7_mode=0x80; /* 主机端复位USB数据同步标志 */ for( i=0; i!=100; i++ ) { /* 等待设置模式操作完成,不超过30uS */ if ( CH375_RD_DAT_PORT()==CMD_RET_SUCCESS ) return( TRUE ); /* 成功 */ } return( FALSE ); /* CH375出错,例如芯片型号错或者处于串口方式或者不支持 */ } void set_freq(void) { CH375_WR_CMD_PORT(0x0b); /* 切换使375B进入低速模式 */ CH375_WR_DAT_PORT(0x17); CH375_WR_DAT_PORT(0xd8); } /* 数据同步 */ /* USB的数据同步通过切换DATA0和DATA1实现: 在设备端, CH372/CH375可以自动切换; 在主机端, 必须由SET_ENDP6和SET_ENDP7命令控制CH375切换DATA0与DATA1. 主机端的程序处理方法是为SET_ENDP6和SET_ENDP7分别提供一个全局变量, 初始值均为80H, 每执行一次成功事务后将位6取反, 每执行一次失败事务后将其复位为80H. */ void toggle_recv() { /* 主机接收成功后,切换DATA0和DATA1实现数据同步 */ CH375_WR_CMD_PORT( CMD_SET_ENDP6 ); CH375_WR_DAT_PORT( endp6_mode ); endp6_mode^=0x40; delay2us(); } void toggle_send() { /* 主机发送成功后,切换DATA0和DATA1实现数据同步 */ CH375_WR_CMD_PORT( CMD_SET_ENDP7 ); CH375_WR_DAT_PORT( endp7_mode ); endp7_mode^=0x40; delay2us(); } //void clr_stall6() { /* 主机接收失败后,复位设备端的数据同步到DATA0 */ // CH375_WR_CMD_PORT( CMD_CLR_STALL ); // CH375_WR_DAT_PORT( 2 | 0x80 ); /* 如果设备端不是CH37X芯片,那么需要修改端点号 */ // endp6_mode=0x80; // status=0xff; //} //void clr_stall7() { /* 主机发送失败后,复位设备端的数据同步到DATA0 */ // CH375_WR_CMD_PORT( CMD_CLR_STALL ); // CH375_WR_DAT_PORT( 2 ); /* 如果设备端不是CH37X芯片,那么需要修改端点号 */ // endp7_mode=0x80; // status=0xff; //} unsigned char rd_usb_data( unsigned char *buf ) { /* 从CH37X读出数据块 */ unsigned char i, len; CH375_WR_CMD_PORT( CMD_RD_USB_DATA ); /* 从CH375的端点缓冲区读取接收到的数据 */ len=CH375_RD_DAT_PORT(); /* 后续数据长度 */ for ( i=0; i!=len; i++ ) *buf++=CH375_RD_DAT_PORT(); return( len ); } void wr_usb_data( unsigned char len, unsigned char *buf ) { /* 向CH37X写入数据块 */ CH375_WR_CMD_PORT( CMD_WR_USB_DATA7 ); /* 向CH375的端点缓冲区写入准备发送的数据 */ CH375_WR_DAT_PORT( len ); /* 后续数据长度, len不能大于64 */ while( len-- ) CH375_WR_DAT_PORT( *buf++ ); } void issue_token( unsigned char endp_and_pid ) { /* 执行USB事务 */ CH375_WR_CMD_PORT( CMD_ISSUE_TOKEN ); CH375_WR_DAT_PORT( endp_and_pid ); /* 高4位目的端点号, 低4位令牌PID */ status=0xff; } void intt0() interrupt 0 { unsigned char len_temp,i; CH375_WR_CMD_PORT( CMD_GET_STATUS ); /* 产生操作完成中断, 获取中断状态 */ status=CH375_RD_DAT_PORT(); if(status!=USB_INT_SUCCESS)//&&((status&0xf0)==0x20)) { CH375_WR_CMD_PORT( CMD_CLR_STALL ); CH375_WR_DAT_PORT(1); /* 如果设备端不是CH37X芯片,那么需要修改端点号 */ endp6_mode=0x80; endp7_mode=0x80; toggle_recv(); issue_token(( endp_int << 4 ) | DEF_USB_PID_IN);//发送从中断端点读数据的令牌 } else { len_temp=rd_usb_data(data_buf); //键盘中断端点数据长度一般为8字节,鼠标为4字节 for(i=0;i!=len_temp;i++)data_in[i]=data_buf[i]; flag_output=1; toggle_recv(); issue_token(( endp_int << 4 ) | DEF_USB_PID_IN);//发送从中断端点读数据的令牌 } } unsigned char wait_interrupt() { /* 主机端等待操作完成, 返回操作状态 */ while( CH375_INT_WIRE ); /* 查询等待CH375操作完成中断(INT#低电平) */ CH375_WR_CMD_PORT( CMD_GET_STATUS ); /* 产生操作完成中断, 获取中断状态 */ return( CH375_RD_DAT_PORT() ); } void set_retry(unsigned char num) { CH375_WR_CMD_PORT( CMD_SET_RETRY); CH375_WR_DAT_PORT( 0x25); CH375_WR_DAT_PORT( num); delay2us(); } unsigned char set_config_ex() { Request.Req.bmRequestType=0x00; Request.Req.bRequest=0x09;config_value=config_value; Request.Req.wValue=0x0000|((unsigned int)config_value<<8); Request.Req.wIndex=0x0000; Request.Req.wLength=0x0000; endp7_mode=0x80; toggle_send(); wr_usb_data(8,Request.Req_buf);/* SETUP数据总是8字节 */ issue_token(( 0 << 4 ) | DEF_USB_PID_SETUP);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* SETUP阶段操作成功 */ { endp6_mode=0xc0; toggle_recv(); issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* 状态阶段操作成功 */ { if(rd_usb_data(data_buf)!=0) return(0); } else return(0); } else return(0); return(1) ; } unsigned char set_idle() { Request.Req.bmRequestType=0x21; Request.Req.bRequest=0x0a; Request.Req.wValue=0x0000; Request.Req.wIndex=0x0000;if(flag_interface_2==1)Request.Req.wIndex=0x0100; Request.Req.wLength=0x0000; endp7_mode=0x80; toggle_send(); wr_usb_data(8,Request.Req_buf);/* SETUP数据总是8字节 */ issue_token(( 0 << 4 ) | DEF_USB_PID_SETUP);status=wait_interrupt(); if(status==USB_INT_SUCCESS) /* SETUP阶段操作成功 */ { endp6_mode=0xc0; toggle_recv(); issue_token(( 0 << 4 ) | DEF_USB_PID_IN );status=wait_interrupt(); if(status==USB_INT_SUCCESS) /* 状态阶段操作成功 */ { if(rd_usb_data(data_buf)!=0) return(0); } else return(0); } else return(0); return(1); } unsigned char set_report() { Request.Req.bmRequestType=0x21; Request.Req.bRequest=0x09; Request.Req.wValue=0x0002; Request.Req.wIndex=0x0000; Request.Req.wLength=0x0100; endp7_mode=0x80; toggle_send(); wr_usb_data(8,Request.Req_buf);/* SETUP数据总是8字节 */ issue_token(( 0 << 4 ) | DEF_USB_PID_SETUP);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* SETUP阶段操作成功 */ { toggle_send(); Request.Req_buf[0]=0x01; wr_usb_data(1,Request.Req_buf); issue_token(( 0 << 4 ) | DEF_USB_PID_OUT);status=wait_interrupt(); if(status==USB_INT_SUCCESS) /* DATA阶段操作成功 */ { endp6_mode=0xc0; toggle_recv(); issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* 状态阶段操作成功 */ { if(rd_usb_data(data_buf)!=0) return(0); } else return(0); } else return(0); } else return(0); return(1); } unsigned char get_descr_ex() { unsigned char descr_len; unsigned char *p=data_buf; endp7_mode=0x80; toggle_send(); wr_usb_data(8,Request.Req_buf); issue_token(( 0 << 4 ) | DEF_USB_PID_SETUP);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* SETUP阶段操作成功 */ { endp6_mode=0xc0; toggle_recv(); } else return(0); issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* DATA阶段操作成功 */ { if(flag_config_2)descr_len=data_buf[2]-rd_usb_data(data_buf); else descr_len=data_buf[0]-rd_usb_data(data_buf); while(descr_len>0) { toggle_recv(); p+=0x08; issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS) /* DATA阶段操作成功 */ descr_len-=rd_usb_data(p); else return(0); } } else return(0); endp7_mode=0xc0; toggle_send(); wr_usb_data(0,Request.Req_buf); issue_token(( 0 << 4 ) | DEF_USB_PID_OUT);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* 状态阶段操作成功 */ return(1); else return(0); } unsigned char get_report_descr_ex() { unsigned char descr_len; unsigned char *p=data_buf; unsigned char report_cou_temp=0; report_cou=0; endp7_mode=0x80; toggle_send(); wr_usb_data(8,Request.Req_buf); issue_token(( 0 << 4 ) | DEF_USB_PID_SETUP);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* SETUP阶段操作成功 */ { endp6_mode=0xc0; toggle_recv(); } else return(0); issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* DATA阶段操作成功 */ { rd_usb_data(data_buf); report_cou+=8; descr_len=Request.Req_buf[6]-0x08;/*剩余描述符长度计算*/ while(descr_len>0) { toggle_recv(); p+=0x08; issue_token(( 0 << 4 ) | DEF_USB_PID_IN);status=wait_interrupt(); if(status==USB_INT_SUCCESS) /* DATA阶段操作成功 */ { report_cou_temp=rd_usb_data(p); if(report_cou_temp!=0x08){report_cou+=report_cou_temp;break;} else {descr_len-=0x08;report_cou+=8;} } else return(0); } } else return(0); endp7_mode=0xc0; toggle_send(); wr_usb_data(0,Request.Req_buf); issue_token(( 0 << 4 ) | DEF_USB_PID_OUT);status=wait_interrupt(); if(status==USB_INT_SUCCESS)/* 状态阶段操作成功 */ return(1); else return(0); } void get_int_in() { endp6_mode=0x80; toggle_recv(); issue_token(( endp_int << 4 ) | DEF_USB_PID_IN);//status=wait_interrupt(); } void reset_device() { /* USB规范中未要求在USB设备插入后必须复位该设备,但是计算机的WINDOWS总是这样做,所以有些USB设备也要求在插入后必须先复位才能工作 */ set_usb_mode( 7 ); /* 复位USB设备,CH375向USB信号线的D+和D-输出低电平 */ delayms(10); set_usb_mode( 6 ); /* 结束复位 */ while ( wait_interrupt()!=USB_INT_CONNECT ); /* 等待复位之后的设备端再次连接上来 */ } unsigned char get_descr( unsigned char type ) { /* 从设备端获取描述符 */ status=0xff; CH375_WR_CMD_PORT( CMD_GET_DESCR ); CH375_WR_DAT_PORT( type ); /* 描述符类型, 只支持1(设备)或者2(配置) */ status=wait_interrupt(); /* 等待CH375操作完成 */ if ( status==USB_INT_SUCCESS ) { /* 操作成功 */ unsigned char i, len; len=rd_usb_data( data_buf ); printf( "%s描述符是:", type==1?"设备":"配置" ); for ( i=0; i!=len; i++ ) printf( "%02x ", (unsigned int)data_buf[i] ); printf( "\n" ); } return( status ); } unsigned char set_addr( unsigned char addr ) { /* 设置设备端的USB地址 */ unsigned char status; CH375_WR_CMD_PORT( CMD_SET_ADDRESS ); /* 设置USB设备端的USB地址 */ CH375_WR_DAT_PORT( addr ); /* 地址, 从1到127之间的任意值, 常用2到20 */ status=wait_interrupt(); /* 等待CH375操作完成 */ if ( status==USB_INT_SUCCESS ) { /* 操作成功 */ CH375_WR_CMD_PORT( CMD_SET_USB_ADDR ); /* 设置USB主机端的USB地址 */ CH375_WR_DAT_PORT( addr ); /* 当目标USB设备的地址成功修改后,应该同步修改主机端的USB地址 */ } return( status ); } unsigned char set_config( unsigned char cfg ) { /* 设置设备端的USB配置 */ CH375_WR_CMD_PORT( CMD_SET_CONFIG ); /* 设置USB设备端的配置值 */ CH375_WR_DAT_PORT( cfg ); /* 此值取自USB设备的配置描述符中 */ return( wait_interrupt() ); /* 等待CH375操作完成 */ } void parse_config_descr() { unsigned char i; num_interfaces=data_buf[4]; //保存接口数 config_value=data_buf[5]; //保存配置值 for(i=0;i!=data_buf[2];i++) { if((data_buf[i]==0x09)&&(data_buf[i+1]==0x04))//接口描述符 { } if((data_buf[i]==0x09)&&(data_buf[i+1]==0x21))//HID描述符 { report_descr_len=data_buf[i+7];} //REPORT描述符长度 if((data_buf[i]==0x07)&&(data_buf[i+1]==0x05))//端点描述符 { endp_int=data_buf[i+2]&0x0f;} //中断端点号 } } void main() { unsigned char i; mInitSTDIO( ); delayms(250); printf("%02x ",(unsigned int)data_buf[i]); set_usb_mode( 6 ); /* 设置USB主机模式, 如果设备端是CH37X, 那么5和6均可 */ #ifdef TEST_LOW_SPEED set_freq( ); //使375B进入低速模式 #endif while(1) { while ( wait_interrupt()!=USB_INT_CONNECT ); /* 等待设备端连接上来 */ delayms(5); reset_device(); delayms(5); set_freq( ); //使375B进入低速模式 // get_descr(0x01);//获取设备描述符 // printf("device\n"); Request.Req.bmRequestType=0x80; Request.Req.bRequest=0x06; Request.Req.wValue=0x0001; Request.Req.wIndex=0x0000; Request.Req.wLength=0x1200; if(get_descr_ex()==1) { for(i=0;i!=data_buf[0];i++) printf("%02x ",(unsigned int)data_buf[i]); printf("\n"); } else printf("get device descr failed\n"); set_addr(5);//设置地址 // get_descr(0x02);//获取配置描述符 printf("config\n"); Request.Req.bmRequestType=0x80; Request.Req.bRequest=0x06; Request.Req.wValue=0x0002; Request.Req.wIndex=0x0000; Request.Req.wLength=0x0900; if(get_descr_ex()==1) { for(i=0;i!=data_buf[0];i++) printf("%02x ",(unsigned int)data_buf[i]); printf("\n"); if(data_buf[4]!=0x01)flag_interface_2=1;//有多个接口 } else printf("get config descr failed\n"); printf("config 2\n"); if(data_buf[2]>0x09) { flag_config_2=1; Request.Req.bmRequestType=0x80; Request.Req.bRequest=0x06; Request.Req.wValue=0x0002; Request.Req.wIndex=0x0000; Request.Req.wLength=0x0000|((unsigned int)data_buf[2]<<8); if(get_descr_ex()==1) { for(i=0;i!=data_buf[2];i++) printf("%02x ",(unsigned int)data_buf[i]); printf("\n"); flag_config_2=0; } else printf("get device descr again failed\n"); } parse_config_descr();//保存描述符中一些值 set_config(config_value);//设置配置 // printf("set config\n"); // if(set_config_ex()!=1)printf("set config failed\n"); printf("set idle\n"); if(set_idle()!=1)printf("set idle failed\n"); printf("report\n"); Request.Req.bmRequestType=0x81; Request.Req.bRequest=0x06; Request.Req.wValue=0x0022; Request.Req.wIndex=0x0000; Request.Req.wLength=0x0000|((unsigned int)(report_descr_len+0x40)<<8); if(get_report_descr_ex()==1) { for(i=0;i!=report_cou;i++) printf("%02x ",(unsigned int)data_buf[i]); printf("\n"); } else printf("get report descr failed\n"); printf("set report\n"); //对于键盘这一步,是点亮指示灯 if(set_report()!=1)printf("set report failed\n"); delayms(250); EA=1; //打开中断,从中断端点接收数据 get_int_in(); //发送从中断端点读数据的令牌 do{ if(flag_output==1) { for(i=0;i!=8;i++)printf("%02x ",(unsigned int)data_in[i]); printf("\n"); flag_output=0; } }while(1); while ( wait_interrupt()!=USB_INT_DISCONNECT ); /* 等待设备端连接上来 */ } }