目录

本程序改编自沁恒官网2022年1月更新的CH583EVT中的CompoundDev例程。这里只贴了main.c中的程序。
能够实现:①直接接电脑,在设备管理器中能够查到被电脑识别为HID-compliant game contorller;
②在CH582m单片机中自定义了回包内容,通过USB抓包工具可以抓到单片机模拟的JoyStick与电脑主机正常通信。

  1. #include "CH58x_common.h"
  2.  
  3. /*设备、配置、接口、类、端点和报表描述符见我前一篇随笔,这里就省略不写了*/
  4.  
  5. uint8_t DevConfig; //存放设备的配置
  6. uint8_t SetupReqCode; //这个值会在收到SETUP包时赋值。端口0用于控制传输,此变量只在控制传输中使用
  7. uint16_t SetupReqLen; //这个值会在收到SETUP包时赋值。端口0用于控制传输,此变量只在控制传输中使用
  8. const uint8_t *pDescr; //在控制传输的数据阶段,此地址中的数据会被拷贝到RAM中,由DMA发送
  9.           //在前面加了const表示(*pDescr)是常量不可赋值,pDescr可以赋值
  10. uint8_t Report_Value = 0x00; //USB设备所用协议,在收到SETUP包时赋值
  11. uint8_t Idle_Value = 0x00; //设备空闲时间,在收到SETUP包时赋值
  12. uint8_t USB_SleepStatus = 0x00; // USB睡眠状态
  13.  
  14. //JoyStick中断传输中上传给主机4字节的数据
  15. uint8_t HIDJoyStick[4] = {0x0, 0x0, 0x0, 0x0};
  16. /******** 用户自定义分配端点RAM ****************************************/
  17. __attribute__((aligned(4))) uint8_t EP0_Databuf[64 + 64 + 64]; //ep0(64)+ep4_out(64)+ep4_in(64) //端点4的缓存也在这里
  18. __attribute__((aligned(4))) uint8_t EP1_Databuf[64 + 64]; //ep1_out(64)+ep1_in(64)
  19. __attribute__((aligned(4))) uint8_t EP2_Databuf[64 + 64]; //ep2_out(64)+ep2_in(64)
  20. __attribute__((aligned(4))) uint8_t EP3_Databuf[64 + 64]; //ep3_out(64)+ep3_in(64)
  21.  
  22. /*********************************************************************
  23. * @fn USB_DevTransProcesswch
  24. *
  25. * @brief USB 传输处理函数
  26. *
  27. * @return none
  28. */
  29. void USB_DevTransProcess(void) //USB设备传输中断处理
  30. {
  31.   uint8_t len, chtype; //len用于拷贝函数,chtype用于存放数据传输方向、命令的类型、接收的对象等信息
  32.   uint8_t intflag, errflag = 0; //intflag用于存放标志寄存器值,errflag用于标记是否支持主机的指令
  33.  
  34.   intflag = R8_USB_INT_FG; //取得中断标识寄存器的值
  35.  
  36.   if(intflag & RB_UIF_TRANSFER) //判断_INT_FG中的USB传输完成中断标志位。若有传输完成中断了,进if语句
  37.   {
  38.     if((R8_USB_INT_ST & MASK_UIS_TOKEN) != MASK_UIS_TOKEN) // 非空闲 //判断中断状态寄存器中的5:4位,查看令牌的PID标识。若这两位不是11(表示空闲),进if语句
  39.     {
  40.       switch(R8_USB_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP)) //取得令牌的PID标识和设备模式下的3:0位的端点号。主机模式下3:0位是应答PID标识位
  41.       // 分析操作令牌和端点号
  42.       { //端点0用于控制传输。以下的端点0的IN和OUT令牌相应程序,对应控制传输的数据阶段和状态阶段。
  43.         case UIS_TOKEN_IN: //令牌包的PID为IN,5:4位为10。3:0位的端点号为0。IN令牌:设备给主机发数据。_UIS_:USB中断状态
  44.         { //端点0为双向端点,用作控制传输。 “|0”运算省略了
  45.           switch(SetupReqCode)//这个值会在收到SETUP包时赋值。在后面会有SETUP包处理程序,对应控制传输的设置阶段。
  46.           {
  47.  
  48.             case USB_GET_DESCRIPTOR: //USB标准命令,主机从USB设备获取描述
  49.               len = SetupReqLen >= DevEP0SIZE ? DevEP0SIZE : SetupReqLen; // 本次包传输长度。最长为64字节,超过64字节的分多次处理,前几次要满包。
  50.               memcpy(pEP0_DataBuf, pDescr, len);//memcpy:内存拷贝函数,从(二号位)地址拷贝(三号位)字符串长度到(一号位)地址中
  51.           //DMA直接与内存相连,会检测到内存的改写,而后不用单片机控制就可以将内存中的数据发送出去。如果只是两个数组互相赋值,不涉及与DMA匹配的物理内存,就无法触发DMA。
  52.               SetupReqLen -= len; //记录剩下的需要发送的数据长度
  53.               pDescr += len; //更新接下来需要发送的数据的起始地址,拷贝函数用
  54.               R8_UEP0_T_LEN = len; //端点0发送长度寄存器中写入本次包传输长度
  55.               R8_UEP0_CTRL ^= RB_UEP_T_TOG; // 同步切换。IN方向(对于单片机就是T方向)的PID中的DATA0和DATA1切换
  56.               break; //赋值完端点控制寄存器的握手包响应(ACK、NAK、STALL),由硬件打包成符合规范的包,DMA自动发送
  57.  
  58.             case USB_SET_ADDRESS: //USB标准命令,主机为设备设置一个唯一地址,范围0~127,0为默认地址
  59.               R8_USB_DEV_AD = (R8_USB_DEV_AD & RB_UDA_GP_BIT) | SetupReqLen;
  60.                 //7位地址+最高位的用户自定义地址(默认为1),或上“包传输长度”(这里的“包传输长度”在后面赋值成了地址位)
  61.               R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  62.                 //R响应OUT事务ACK,T响应IN事务NAK。这个CASE分支里是IN方向,当DMA相应内存中,单片机没有数据更新时,回NAK握手包。
  63.               break; //一般程序里的OUT事务,设备会回包给主机,不响应NAK。
  64.  
  65.             case USB_SET_FEATURE: //USB标准命令,主机要求启动一个在设备、接口或端点上的特征
  66.               break;
  67.  
  68.             default:
  69.               R8_UEP0_T_LEN = 0; //状态阶段完成中断或者是强制上传0长度数据包结束控制传输(数据字段长度为0的数据包,包里SYNC、PID、EOP字段都有)
  70.               R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  71.                 //R响应OUT事务ACK,T响应IN事务NAK。这个CASE分支里是OUT方向,当DMA相应内存中更新了数据且单片机验收正常时,回ACK握手包。
  72.               break;
  73.           }
  74.         }
  75.         break;
  76.  
  77.         case UIS_TOKEN_OUT: //令牌包的PID为OUT,5:4位为00。3:0位的端点号为0。OUT令牌:主机给设备发数据。
  78.         { //端点0为双向端点,用作控制传输。 “|0”运算省略了
  79.           len = R8_USB_RX_LEN; //读取当前USB接收长度寄存器中存储的接收的数据字节数 //接收长度寄存器为各个端点共用,发送长度寄存器各有各的
  80.         }
  81.         break;
  82.  
  83.         case UIS_TOKEN_OUT | 1: //令牌包的PID为OUT,端点号为1
  84.         {
  85.           if(R8_USB_INT_ST & RB_UIS_TOG_OK) //硬件会判断是否正确的同步切换数据包,同步切换正确,这一位自动置位
  86.           { // 不同步的数据包将丢弃
  87.             R8_UEP1_CTRL ^= RB_UEP_R_TOG; //OUT事务的DATA同步切换。设定一个期望值。
  88.             len = R8_USB_RX_LEN; //读取接收数据的字节数
  89.             DevEP1_OUT_Deal(len); //发送长度为len的字节,自动回ACK握手包。自定义的程序。
  90.           }
  91.         }
  92.         break;
  93.  
  94.         case UIS_TOKEN_IN | 1: //令牌包的PID为IN,端点号为1
  95.           R8_UEP1_CTRL ^= RB_UEP_T_TOG; //IN事务的DATA切换一下。设定将要发送的包的PID。
  96.           R8_UEP1_CTRL = (R8_UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK; //当DMA中没有由单片机更新数据时,将T响应IN事务置为NAK。更新了就发出数据。
  97.           break;
  98.  
  99.     }
  100.     R8_USB_INT_FG = RB_UIF_TRANSFER; //写1清零中断标志
  101.   }
  102.  
  103.   if(R8_USB_INT_ST & RB_UIS_SETUP_ACT) // Setup包处理
  104.   {
  105.     R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_NAK;
  106.       //R响应OUT事务期待为DATA1(DMA收到的数据包的PID要为DATA1,否则算数据错误要重传)和ACK(DMA相应内存中收到了数据,单片机验收正常)
  107.       //T响应IN事务设定为DATA1(单片机有数据送入DMA相应内存,以DATA1发送出去)和NAK(单片机没有准备好数据)。
  108.     SetupReqLen = pSetupReqPak->wLength; //数据阶段的字节数 //pSetupReqPak:将端点0的RAM地址强制转换成一个存放结构体的地址,结构体成员依次紧凑排列
  109.     SetupReqCode = pSetupReqPak->bRequest; //命令的序号
  110.     chtype = pSetupReqPak->bRequestType; //包含数据传输方向、命令的类型、接收的对象等信息
  111.  
  112.     len = 0;
  113.     errflag = 0;
  114.     if((pSetupReqPak->bRequestType & USB_REQ_TYP_MASK) != USB_REQ_TYP_STANDARD) //判断命令的类型,如果不是标准请求,进if语句
  115.     {
  116.       /* 非标准请求 */
  117.       /* 其它请求,如类请求,产商请求等 */
  118.       if(pSetupReqPak->bRequestType & 0x40) //取得命令中的某一位,判断是否为0,不为零进if语句
  119.       {
  120.         /* 厂商请求 */
  121.       }
  122.       else if(pSetupReqPak->bRequestType & 0x20) //取得命令中的某一位,判断是否为0,不为零进if语句
  123.       { //判断为HID类请求
  124.         switch(SetupReqCode) //判断命令的序号
  125.         {
  126.           case DEF_USB_SET_IDLE: /* 0x0A: SET_IDLE */ //主机想设置HID设备特定输入报表的空闲时间间隔
  127.             Idle_Value = EP0_Databuf[3];//这个一定要有
  128.             break;
  129.  
  130.           case DEF_USB_SET_REPORT: /* 0x09: SET_REPORT */ //主机想设置HID设备的报表描述符
  131.             break;
  132.  
  133.           case DEF_USB_SET_PROTOCOL: /* 0x0B: SET_PROTOCOL */ //主机想设置HID设备当前所使用的协议
  134.             Report_Value = EP0_Databuf[2];
  135.             break;
  136.  
  137.           case DEF_USB_GET_IDLE: /* 0x02: GET_IDLE */ //主机想读取HID设备特定输入报表的当前的空闲比率
  138.             EP0_Databuf[0] = Idle_Value;
  139.             len = 1;
  140.             break;
  141.  
  142.           case DEF_USB_GET_PROTOCOL: /* 0x03: GET_PROTOCOL */ //主机想获得HID设备当前所使用的协议
  143.             EP0_Databuf[0] = Report_Value;
  144.             len = 1;
  145.             break;
  146.  
  147.           default:
  148.             errflag = 0xFF;
  149.         }
  150.       }
  151.     }
  152.     else //判断为标准请求
  153.     {
  154.       switch(SetupReqCode) //判断命令的序号
  155.       {
  156.         case USB_GET_DESCRIPTOR: //主机想获得标准描述符
  157.         {
  158.           switch(((pSetupReqPak->wValue) >> 8)) //右移8位,看原来的高8位是否为0,为1表示方向为IN方向,则进s-case语句
  159.           {
  160.             case USB_DESCR_TYP_DEVICE: //不同的值代表不同的命令。主机想获得设备描述符
  161.             {
  162.               pDescr = MyDevDescr; //将设备描述符字符串放在pDescr地址中,“获得标准描述符”这个case末尾会用拷贝函数发送
  163.               len = MyDevDescr[0]; //协议规定设备描述符的首字节存放字节数长度。拷贝函数会用到len参数
  164.             }
  165.             break;
  166.  
  167.             case USB_DESCR_TYP_CONFIG: //主机想获得配置描述符
  168.             {
  169.               pDescr = MyCfgDescr; //将配置描述符字符串放在pDescr地址中,之后会发送
  170.               len = MyCfgDescr[2]; //协议规定配置描述符的第三个字节存放配置信息的总长
  171.             }
  172.             break;
  173.  
  174.             case USB_DESCR_TYP_HID: //主机想获得人机接口类描述符。此处结构体中的wIndex与配置描述符不同,意为接口号。
  175.               switch((pSetupReqPak->wIndex) & 0xff) //取低八位,高八位抹去
  176.               {
  177.               /* 选择接口 */
  178.                 case 0:
  179.                   pDescr = (uint8_t *)(&MyCfgDescr[18]); //接口1的类描述符存放位置,待发送
  180.                   len = 9;
  181.                   break;
  182.  
  183.                 default://就用了一个接口,出现其他值就不对了
  184.                   errflag = 0xff;
  185.                   break;
  186.               }
  187.             break;
  188.  
  189.             case USB_DESCR_TYP_REPORT: //主机想获得设备报表描述符
  190.             {
  191.               if(((pSetupReqPak->wIndex) & 0xff) == 0) //接口0报表描述符
  192.               {
  193.                 pDescr = JoyStickRepDesc; //数据准备上传
  194.                 len = sizeof(JoyStickRepDesc);
  195.               }
  196.               else
  197.                 len = 0xff; //本程序只有1个接口,这句话正常不可能执行
  198.             }
  199.             break;
  200.  
  201.             case USB_DESCR_TYP_STRING: //主机想获得设备字符串描述符
  202.             {
  203.               switch((pSetupReqPak->wValue) & 0xff) //根据wValue的值传递字符串信息
  204.               {
  205.               default:
  206.                 errflag = 0xFF; // 不支持的字符串描述符
  207.                 break;    //没有写字符串描述符,非必要
  208.               }
  209.             }
  210.             break;
  211.  
  212.             default:
  213.               errflag = 0xff;
  214.               break;
  215.           }
  216.           if(SetupReqLen > len)
  217.             SetupReqLen = len; //实际需上传总长度
  218.           len = (SetupReqLen >= DevEP0SIZE) ? DevEP0SIZE : SetupReqLen; //最大长度为64字节
  219.           memcpy(pEP0_DataBuf, pDescr, len); //拷贝函数
  220.           pDescr += len;
  221.         }
  222.         break;
  223.  
  224.         case USB_SET_ADDRESS: //主机想设置设备地址
  225.           SetupReqLen = (pSetupReqPak->wValue) & 0xff; //将主机分发的位设备地址暂存在SetupReqLen中
  226.         break; //控制阶段会赋值给设备地址参数
  227.  
  228.         case USB_GET_CONFIGURATION: //主机想获得设备当前配置
  229.           pEP0_DataBuf[0] = DevConfig; //将设备配置放进RAM
  230.           if(SetupReqLen > 1)
  231.             SetupReqLen = 1; //将数据阶段的字节数置1。因为DevConfig只有一个字节
  232.           break;
  233.  
  234.         case USB_SET_CONFIGURATION: //主机想设置设备当前配置
  235.           DevConfig = (pSetupReqPak->wValue) & 0xff; //取低八位,高八位抹去
  236.           break;
  237.  
  238.         case USB_CLEAR_FEATURE: //关闭USB设备的特征/功能。可以是设备或是端点层面上的。
  239.         {
  240.           if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) //判断是不是端点特征(清除端点停止工作的状态)
  241.           {
  242.             switch((pSetupReqPak->wIndex) & 0xff) //取低八位,高八位抹去。判断索引
  243.             { //16位的最高位判断数据传输方向,0为OUT,1为IN。低位为端点号。
  244.               case 0x81: //清零_TOG和_T_RES这三位,并将后者写成_NAK,响应IN事务NAK表示无数据返回
  245.                 R8_UEP1_CTRL = (R8_UEP1_CTRL & ~(RB_UEP_T_TOG | MASK_UEP_T_RES)) | UEP_T_RES_NAK;
  246.                 break;
  247.               case 0x01: //清零_TOG和_R_RES这三位,并将后者写成_ACK,响应OUT事务ACK表示接收正常
  248.                 R8_UEP1_CTRL = (R8_UEP1_CTRL & ~(RB_UEP_R_TOG | MASK_UEP_R_RES)) | UEP_R_RES_ACK;
  249.                 break;
  250.               default:
  251.                 errflag = 0xFF; // 不支持的端点
  252.                 break;
  253.             }
  254.           }
  255.           else if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE) //判断是不是设备特征(用于设备唤醒)
  256.           {
  257.             if(pSetupReqPak->wValue == 1) //唤醒标志位为1
  258.             {
  259.               USB_SleepStatus &= ~0x01; //最低位清零
  260.             }
  261.           }
  262.           else
  263.           {
  264.             errflag = 0xFF;
  265.           }
  266.         }
  267.         break;
  268.  
  269.         case USB_SET_FEATURE: //开启USB设备的特征/功能。可以是设备或是端点层面上的。
  270.           if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) //判断是不是端点特征(使端点停止工作)
  271.           {
  272.             /* 端点 */
  273.             switch(pSetupReqPak->wIndex) //判断索引
  274.             { //16位的最高位判断数据传输方向,0为OUT,1为IN。低位为端点号。
  275.               case 0x81: //清零_TOG和_T_RES三位,并将后者写成_STALL,根据主机指令停止端点的工作
  276.                 R8_UEP1_CTRL = (R8_UEP1_CTRL & ~(RB_UEP_T_TOG | MASK_UEP_T_RES)) | UEP_T_RES_STALL;
  277.                 break;
  278.               case 0x01: //清零_TOG和_R_RES三位,并将后者写成_STALL,根据主机指令停止端点的工作
  279.                 R8_UEP1_CTRL = (R8_UEP1_CTRL & ~(RB_UEP_R_TOG | MASK_UEP_R_RES)) | UEP_R_RES_STALL;
  280.                 break;
  281.               default:
  282.               /* 不支持的端点 */
  283.                 errflag = 0xFF; // 不支持的端点
  284.                 break;
  285.             }
  286.           }
  287.           else if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE) //判断是不是设备特征(使设备休眠)
  288.           {
  289.             if(pSetupReqPak->wValue == 1)
  290.             {
  291.               USB_SleepStatus |= 0x01; //设置睡眠
  292.             }
  293.           }
  294.           else
  295.           {
  296.             errflag = 0xFF;
  297.           }
  298.           break;
  299.  
  300.         case USB_GET_INTERFACE: //主机想获得接口当前工作的选择设置值
  301.           pEP0_DataBuf[0] = 0x00;
  302.           if(SetupReqLen > 1)
  303.             SetupReqLen = 1; //将数据阶段的字节数置1。因为待传数据只有一个字节
  304.           break;
  305.  
  306.         case USB_SET_INTERFACE: //主机想激活设备的某个接口
  307.           break;
  308.  
  309.         case USB_GET_STATUS: //主机想获得设备、接口或是端点的状态
  310.           if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) //判断是否为端点状态
  311.           {
  312.           /* 端点 */
  313.             pEP0_DataBuf[0] = 0x00;
  314.             switch(pSetupReqPak->wIndex)
  315.             { //16位的最高位判断数据传输方向,0为OUT,1为IN。低位为端点号。
  316.               case 0x81: //判断_TOG和_T_RES三位,若处于STALL状态,进if语句
  317.                 if((R8_UEP1_CTRL & (RB_UEP_T_TOG | MASK_UEP_T_RES)) == UEP_T_RES_STALL)
  318.                 {
  319.                   pEP0_DataBuf[0] = 0x01; //返回D0为1,表示端点被停止工作了。该位由SET_FEATURE和CLEAR_FEATURE命令配置。
  320.                 }
  321.                 break;
  322.  
  323.               case 0x01: //判断_TOG和_R_RES三位,若处于STALL状态,进if语句
  324.                 if((R8_UEP1_CTRL & (RB_UEP_R_TOG | MASK_UEP_R_RES)) == UEP_R_RES_STALL)
  325.                 {
  326.                   pEP0_DataBuf[0] = 0x01;
  327.                 }
  328.                 break;
  329.             }
  330.           }
  331.           else if((pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE) //判断是否为设备状态
  332.           {
  333.             pEP0_DataBuf[0] = 0x00;
  334.             if(USB_SleepStatus) //如果设备处于睡眠状态
  335.             {
  336.               pEP0_DataBuf[0] = 0x02; //最低位D0为0,表示设备由总线供电,为1表示设备自供电。 D1位为1表示支持远程唤醒,为0表示不支持。
  337.             }
  338.             else
  339.             {
  340.               pEP0_DataBuf[0] = 0x00;
  341.             }
  342.           }
  343.           pEP0_DataBuf[1] = 0; //返回状态信息的格式为16位数,高八位保留为0
  344.           if(SetupReqLen >= 2)
  345.           {
  346.             SetupReqLen = 2; //将数据阶段的字节数置2。因为待传数据只有2个字节
  347.           }
  348.           break;
  349.  
  350.         default:
  351.           errflag = 0xff;
  352.           break;
  353.         }
  354.       }
  355.       if(errflag == 0xff) // 错误或不支持
  356.       {
  357.         R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL; // STALL
  358.       }
  359.       else
  360.       {
  361.         if(chtype & 0x80) // 上传。最高位为1,数据传输方向为设备向主机传输。
  362.         {
  363.           len = (SetupReqLen > DevEP0SIZE) ? DevEP0SIZE : SetupReqLen;
  364.           SetupReqLen -= len;
  365.         }
  366.       else
  367.         len = 0; // 下传。最高位为0,数据传输方向为主机向设备传输。
  368.       R8_UEP0_T_LEN = len;
  369.       R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK; // 默认数据包是DATA1
  370.       }
  371.  
  372.       R8_USB_INT_FG = RB_UIF_TRANSFER; //写1清中断标识
  373.     }
  374.   }
  375.  
  376.   else if(intflag & RB_UIF_BUS_RST) //判断_INT_FG中的总线复位标志位,为1触发
  377.   {
  378.     R8_USB_DEV_AD = 0; //设备地址写成0,待主机重新分配给设备一个新地址
  379.     R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; //把端点0的控制寄存器,写成:接收响应响应ACK表示正常收到,发送响应NAK表示没有数据要返回
  380.     R8_UEP1_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  381.     R8_UEP2_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  382.     R8_UEP3_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  383.     R8_USB_INT_FG = RB_UIF_BUS_RST; //写1清中断标识
  384.   }
  385.   else if(intflag & RB_UIF_SUSPEND) //判断_INT_FG中的总线挂起或唤醒事件中断标志位。挂起和唤醒都会触发此中断
  386.   {
  387.     if(R8_USB_MIS_ST & RB_UMS_SUSPEND) //取得杂项状态寄存器中的挂起状态位,为1表示USB总线处于挂起态,为0表示总线处于非挂起态
  388.     {
  389.       ;
  390.     } // 挂起 //当设备处于空闲状态超过3ms,主机会要求设备挂起(类似于电脑休眠)
  391.     else //挂起或唤醒中断被触发,又没有被判断为挂起
  392.     {
  393.       ;
  394.     } // 唤醒
  395.     R8_USB_INT_FG = RB_UIF_SUSPEND; //写1清中断标志
  396.   }
  397.   else
  398.   {
  399.     R8_USB_INT_FG = intflag; //_INT_FG中没有中断标识,再把原值写回原来的寄存器
  400.   }
  401. }
  402.  
  403. /*********************************************************************
  404. * @fn DevHIDJoyStickReport
  405. *
  406. * @brief 上报JoyStick数据
  407. *
  408. * @return none
  409. */
  410. void DevHIDJoyStickReport(uint8_t jsdata0,uint8_t jsdata1,uint8_t jsdata2,uint8_t jsdata3)
  411. {
  412.   HIDJoyStick[0] = jsdata0;
  413.   HIDJoyStick[1] = jsdata1;
  414.   HIDJoyStick[2] = jsdata2;
  415.   HIDJoyStick[3] = jsdata3;
  416.   memcpy(pEP1_IN_DataBuf, HIDJoyStick, sizeof(HIDJoyStick));
  417.   DevEP1_IN_Deal(sizeof(HIDJoyStick));
  418. }
  419.  
  420. /*********************************************************************
  421. * @fn DevWakeup
  422. *
  423. * @brief 设备模式唤醒主机
  424. *
  425. * @return none
  426. */
  427. void DevWakeup(void)
  428. {
  429.   R16_PIN_ANALOG_IE &= ~(RB_PIN_USB_DP_PU); //清空标识,该位为0,表示由RB_UC_DEV_PU_EN控制D+线路上是否上拉,但是睡眠模式或者下电模式下不起作用。写1强制使能上拉。
  430.   R8_UDEV_CTRL |= RB_UD_LOW_SPEED; //选择低速模式
  431.   mDelaymS(2);
  432.   R8_UDEV_CTRL &= ~RB_UD_LOW_SPEED; //清空低速模式标识,即选择全速模式
  433.   R16_PIN_ANALOG_IE |= RB_PIN_USB_DP_PU; //RB_PIN_USB_DP_PU可以不受睡眠影响,强制启用D+引脚的上拉
  434. }
  435.  
  436. /*********************************************************************
  437. * @fn DebugInit
  438. *
  439. * @brief 调试初始化
  440. *
  441. * @return none
  442. */
  443. void DebugInit(void) //初始化串口1,用于在串口助手显示debug信息
  444. {
  445.   GPIOA_SetBits(GPIO_Pin_9);
  446.   GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
  447.   GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
  448.   UART1_DefInit();
  449. }
  450.  
  451. /*********************************************************************
  452. * @fn main
  453. *
  454. * @brief 主函数
  455. *
  456. * @return none
  457. */
  458. int main()
  459. {
  460.   SetSysClock(CLK_SOURCE_PLL_60MHz);
  461.  
  462.   DebugInit(); //配置串口1用来prinft来debug
  463.   printf("start\n");
  464.  
  465.   pEP0_RAM_Addr = EP0_Databuf; //配置缓存区64字节。CH582m所有端点的最大数据包长度都是64字节
  466.   pEP1_RAM_Addr = EP1_Databuf;
  467.  
  468.   USB_DeviceInit();
  469.  
  470.   PFIC_EnableIRQ(USB_IRQn); //启用中断向量
  471.  
  472.   while(1)
  473.   {  //对于鼠标来说按下某个键会将回包的某个字节写值,松开鼠标会将该值清零表示不再按下这个键。
  474.     //对于JoyStick来说可能也是如此,不过用单片机模拟JoyStick与电脑通信的目的已经达到,英文手册太长,就不查手册找表示释放按键的值了。
  475.     mDelaymS(1000);
  476.     DevHIDJoyStickReport(0x05, 0x10, 0x20, 0x11);
  477.     mDelaymS(1000);
  478.     DevHIDJoyStickReport(0x0A, 0x15, 0x25, 0x22);
  479.     mDelaymS(1000);
  480.     DevHIDJoyStickReport(0x0E, 0x1A, 0x2A, 0x44);
  481.     mDelaymS(1000);
  482.     DevHIDJoyStickReport(0x10, 0x1E, 0x2E, 0x88);
  483.   }
  484. }
  485.  
  486. /*********************************************************************
  487. * @fn DevEP1_OUT_Deal
  488. *
  489. * @brief 端点1数据处理
  490. *
  491. * @return none
  492. */
  493. void DevEP1_OUT_Deal(uint8_t l)
  494. { /* 用户可自定义 */
  495.   uint8_t i;
  496.  
  497.   for(i = 0; i < l; i++)
  498.   {
  499.     pEP1_IN_DataBuf[i] = ~pEP1_OUT_DataBuf[i]; //将OUT缓存(对应单片机接收)按位取反,赋值给IN缓存(对应单片机发送)。//自定义的操作
  500.   }
  501.   DevEP1_IN_Deal(l); //单片机通过端点1发送长度l字节的数据,自动回ACK握手包。此函数配置完了控制寄存器。
  502. }
  503.  
  504. /*********************************************************************
  505. * @fn USB_IRQHandler
  506. *
  507. * @brief USB中断函数
  508. *
  509. * @return none
  510. */
  511. __INTERRUPT
  512. __HIGH_CODE
  513. void USB_IRQHandler(void) /* USB中断服务程序,使用寄存器组1 */
  514. {
  515.   USB_DevTransProcess();
  516. }

CH582m模拟JoyStick使用USB与电脑通信的更多相关文章

  1. 如何使用飞秋FeiQ实现两电脑通信(或传输文件)

    如何使用飞秋FeiQ实现两电脑通信(或传输文件) 1. 在两天电脑上,分别按照飞秋FeiQ 我使用的绿色飞秋2013正式版 2. 使用一根网线,将两电脑的网口连接一起 3. 设置飞秋FeiQ的端口号不 ...

  2. Cisco Packet Tracer中两台电脑通信设置

    Cisco Packet Tracer是网络初学者仿真模拟网络环境的必备工具.今天我们来模拟下两台电脑之间的通信. Cisco Packet Tracer版本6.2.0 一.添加设备 1.这里添加一个 ...

  3. WIFI环境下Android手机和电脑通信

    前面已经写过一篇java实现最基础的socket网络通信,这篇和之前那篇大同小异,只是将客户端代码移植到手机中,然后获取本机IP的方法略有不同. 先讲一下本篇中用到Android studio的使用吧 ...

  4. 真相:中国版BBB用USB连电脑没有盘符的根本原因分析

    很多网友在问:为什么中国版的装完驱动插上板子没有显示端口号和69M的盘符??楼主发现,在开机启动的时候,加载g_multi模块时出现错误提示 invalid argument.         Emb ...

  5. Windows与自定义USB HID设备通信说明.

    1 .   所使用的典型 Windows API CreateFile ReadFile WriteFile 以下函数是 DDK 的内容: HidD_SetFeature HidD_GetFeatur ...

  6. WebSocket 开发模拟客户端与有游戏服务器通信

    WebSocket 客户端测试功能 websocket是有标准的通信协议,在h2engine服务器引擎中继承了websocket通信协议,使用websocket通信协议的好处是很多语言或框架都内置了w ...

  7. USB插入电脑的硬件检测和枚举流程

    USB协议定义了设备的6种状态,仅在枚举过程种,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两 ...

  8. VC++ 6.0 C8051F340 USB PC侧通信 Demo

    // HelloWorld.cpp : Defines the entry point for the console application. // /*********************** ...

  9. 树莓派和STM32通过USB和串口通信记录

    不管怎样,为了简便开发,通信选择串口通信. 推荐文章:https://blog.csdn.net/magnetoooo/article/details/53564797 推荐测试工具:https:// ...

  10. USB上位机通信:CyAPI

    至今的工作中,有USB接口通信的需求,记录一下. 建立一个USB设备对象 CCyUSBDevice *USBDevice = new CCyUSBDev(Handle): 打开USB设备 一个USB设 ...

随机推荐

  1. bugku-source-wp详解

    bugku-source-wp详解 F12先看源代码 base64解码 提交flag 发现这个flag是假的 根据提示打开kali直接扫 命令:gobuster dir -u http://114.6 ...

  2. (20)go-micro微服务Elasticsearch使用

    目录 一 Elasticsearch介绍 二 Elasticsearch的主要功能及应用场景 1.Elasticsearch 主要具有如下功能: 2.Elasticsearch 的主要应用场景如下: ...

  3. Python从零到壹丨图像增强及运算:图像掩膜直方图和HS直方图

    摘要:本章主要讲解图像直方图相关知识点,包括掩膜直方图和HS直方图,并通过直方图判断黑夜与白天,通过案例分享直方图的实际应用. 本文分享自华为云社区<[Python从零到壹] 五十二.图像增强及 ...

  4. JDK9对集合添加的优化-Debug追踪

    JDK9对集合添加的优化 通常,我们在代码中创建一个集合(例如,List或Set ),并直接用一些元素填充它.实例化集合,几add方法调用,使得代码重复. package A_Lian_one.dem ...

  5. GPS定位解决偏差

    目录 GPS定位解决偏差 开篇 实践 1.解决思路以及步骤 2.实践出真理! 3.上坐标系之间的代码. 希望大家:点赞,留言,关注咯~ 唠家常 今日推荐都在文章中了 GPS定位解决偏差 开篇 大家都知 ...

  6. Jenkins CLI命令行

    Jenkins CLI命令行 jenkins不光可以UI操作还提供了命令行接口 位置 首页->系统管理->工具和动作->Jenkins 命令行接口 在这个界面下载一个jenkins- ...

  7. 【学习笔记】Http请求方法总结

    Http常用请求方法对比 请求方法 常见参数传递方式 是否幂等 说明 API举例 GET URL,注意:Http协议对URL长度没有限制,所谓的限制是浏览器和处理服务器的 幂等 用于查询 批量查询:/ ...

  8. redis实现分布式锁(包含代码以及分析利弊)

    redis实现分布式锁(基础版) 使用redis实现分布式锁的方法有多种,基础版本是基于setnx命令,即如果不存在则设置.这个命令可以保证只有一个客户端能够成功设置一个key,从而获得锁.设置key ...

  9. Typora下载与安装 0.9.75版本

    Typora下载与安装 效果图 一.简介 一款 Markdown 编辑器和阅读器 (0.9.75 版本 不需购买) 二.下载 下载地址:Typora 三.安装 1.下载文件后双击安装 2. 选择存放的 ...

  10. 转载:屎人-->诗人系列--码农之歌

    转贴经常关注的一个博主的文,感觉还挺有趣: https://goofegg.github.io/content.html?id=141 ************************** 这个系列第 ...