CH582 使用USB模拟 HID设备,无法只读取数据,必须先写一帧才能读一帧

各位老师,我在使用HID设备时候出现问题,请百忙之中指导,现象如下:

1.我的上位机是使用hidapi 库的,仅仅通过hid_read读取。通过上位机读取数据,无法读到上传数据

2.另外我直接使用CH9326的DEBUG程序打开该设备,试着读取,效果一样。还是无法读到数据。

3.先写使用hid_write写一帧数据,则可以读到一帧数据,再写则可以再读一帧数据。意思是只能写一帧才能读一帧。

问题如下:

  1. 我的理解是我的main函数的while循环里面包含语句DevEP2_OUT_Deal(16);就是向PC机器上传数据,为什么无法读取。

  2. 我只需要实现每20ms向PC上上传一帧20个字节左右的自定义数据,如何实现?

  3. 我的回复帖是新的问题,请不吝指导。

    附件是对应的ch582下位机程序和上位机测试代码

  4. icon_rar.gifHIDapiTest.rar

  5. icon_rar.gifHID.rar

/********************************** (C) COPYRIGHT *******************************
 * File Name          : Main.c
 * Author             : WCH
 * Version            : V1.0
 * Date               : 2020/02/20
 * Description        : 模拟USB复合设备,键鼠,支持类命令
 *******************************************************************************/

#include "CH58x_common.h"

#define DevEP0SIZE  0x10
// 设备描述符
const UINT8 MyDevDescr[] = { 0x12, 0x01, 0x10, 0x01, 0x00, 0x00, 0x00, DevEP0SIZE, 0x3d, 0x41, 0x07, 0x21, 0x00, 0x00,
                             0x00, 0x00, 0x00, 0x01 };

// 配置描述符
const UINT8 MyCfgDescr[] = {     0x09,0x02,0x29,0x00,0x01,0x01,0x04,0xA0,0x23,               //配置描述符
                                 0x09,0x04,0x00,0x00,0x02,0x03,0x00,0x00,0x05,               //接口描述符
                                 0x09,0x21,0x00,0x01,0x00,0x01,0x22,0x22,0x00,               //HID类描述符

                                 0x07,0x05,0x82,0x03,DevEP0SIZE,0x00,0x01,              //端点描述符(全速间隔时间改成1ms)
                                 0x07,0x05,0x02,0x03,DevEP0SIZE,0x00,0x01,              //端点描述符

    };
// 语言描述符
const UINT8 MyLangDescr[] = { 0x04, 0x03, 0x09, 0x04 };
// 厂家信息
const UINT8 MyManuInfo[] = { 0x0E, 0x03, 'X', 0, 'i', 0, 'n', 0, 'L', 0, 'i', 0, 'n', 0 };
// 产品信息
const UINT8 MyProdInfo[] = { 0x0C, 0x03, 'M', 0, 'a', 0, 'g', 0, 'i', 0, 'c', 0 };
/*HID类报表描述符*/
const UINT8 CfgDesc[] = {     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, DevEP0SIZE,                                        //Report Counet
                                 0x81, 0x06,                                                   //Input
                                 0x09, 0x02,                                                   //Usage Page  用法
                                 0x15, 0x00,                                                   //Logical  Minimun
                                 0x26, 0x00,0xff,                                              //Logical  Maximun
                                 0x75, 0x08,                                                   //Report Size
                                 0x95, DevEP0SIZE,                                        //Report Counet
                                 0x91, 0x06,                                                   //Output
                                 0xC0 };


/**********************************************************/
UINT8 DevConfig, Ready;
UINT8 SetupReqCode;
UINT16 SetupReqLen;
const UINT8 *pDescr;
/*鼠标键盘数据*/
UINT8 HIDInOutData[DevEP0SIZE] = { 0 };

/******** 用户自定义分配端点RAM ****************************************/
__attribute__((aligned(4)))  UINT8 EP0_Databuf[64 + 64 + 64];    //ep0(64)+ep4_out(64)+ep4_in(64)
__attribute__((aligned(4)))  UINT8 EP1_Databuf[64 + 64];    //ep1_out(64)+ep1_in(64)
__attribute__((aligned(4)))  UINT8 EP2_Databuf[64 + 64];    //ep2_out(64)+ep2_in(64)
__attribute__((aligned(4)))  UINT8 EP3_Databuf[64 + 64];    //ep3_out(64)+ep3_in(64)

void USB_DevTransProcess( void )
{
  UINT8 len, chtype;
  UINT8 intflag, errflag = 0;

  intflag = R8_USB_INT_FG;
  if ( intflag & RB_UIF_TRANSFER )
  {
    if ( ( R8_USB_INT_ST & MASK_UIS_TOKEN ) != MASK_UIS_TOKEN )    // 非空闲
    {

      switch ( R8_USB_INT_ST & ( MASK_UIS_TOKEN | MASK_UIS_ENDP ) )
      // 分析操作令牌和端点号
      {
        case UIS_TOKEN_IN :
        {
          switch ( SetupReqCode )
          {
            case USB_GET_DESCRIPTOR :
              len = SetupReqLen >= DevEP0SIZE ?
                  DevEP0SIZE : SetupReqLen;    // 本次传输长度
              memcpy( pEP0_DataBuf, pDescr, len ); /* 加载上传数据 */
              SetupReqLen -= len;
              pDescr += len;
              R8_UEP0_T_LEN = len;
              R8_UEP0_CTRL ^= RB_UEP_T_TOG;                             // 翻转
              break;
            case USB_SET_ADDRESS :
              R8_USB_DEV_AD = ( R8_USB_DEV_AD & RB_UDA_GP_BIT ) | SetupReqLen;
              R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
              break;
            default :
              R8_UEP0_T_LEN = 0;                                      // 状态阶段完成中断或者是强制上传0长度数据包结束控制传输
              R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
              break;
          }
        }
          break;

        case UIS_TOKEN_OUT :
        {
            //GPIOB_InverseBits(GPIO_Pin_19);
          len = R8_USB_RX_LEN;
          //if ( SetupReqCode == 0x09 )
          {
              GPIOB_WriteBit(GPIO_Pin_19, pEP0_DataBuf[0]);
          }
        }
          break;
        case UIS_TOKEN_OUT | 1 :
        {
          if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
          {                       // 不同步的数据包将丢弃
            len = R8_USB_RX_LEN;
            DevEP1_OUT_Deal( len );
          }
        }
          break;
        case UIS_TOKEN_IN | 1 :
          R8_UEP1_CTRL = ( R8_UEP1_CTRL & ~MASK_UEP_T_RES ) | UEP_T_RES_NAK;
          break;

        case UIS_TOKEN_OUT | 2 :
        {
          if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
          {                       // 不同步的数据包将丢弃
            len = R8_USB_RX_LEN;
            DevEP2_OUT_Deal( len );
          }
        }
          break;
        case UIS_TOKEN_IN | 2 :
          //GPIOB_InverseBits(GPIO_Pin_19);
//          if ( !pEP2_IN_DataBuf[0] )
//          {
//              GPIOB_SetBits(GPIO_Pin_19);
//          }
//          else
//          {
//             GPIOB_ResetBits(GPIO_Pin_19);
//          }
        GPIOB_WriteBit(GPIO_Pin_19, pEP2_OUT_DataBuf[1]);
          R8_UEP2_CTRL = ( R8_UEP2_CTRL & ~MASK_UEP_T_RES ) | UEP_T_RES_NAK;
          break;

        case UIS_TOKEN_OUT | 3 :
        {
          if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
          {                       // 不同步的数据包将丢弃
            len = R8_USB_RX_LEN;
            DevEP3_OUT_Deal( len );
          }
        }
          break;

        case UIS_TOKEN_IN | 3 :
          R8_UEP3_CTRL = ( R8_UEP3_CTRL & ~MASK_UEP_T_RES ) | UEP_T_RES_NAK;
          break;

        case UIS_TOKEN_OUT | 4 :
        {
          if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
          {
            R8_UEP4_CTRL ^= RB_UEP_R_TOG;
            len = R8_USB_RX_LEN;
            DevEP4_OUT_Deal( len );
          }
        }
          break;

        case UIS_TOKEN_IN | 4 :
          R8_UEP4_CTRL ^= RB_UEP_T_TOG;
          R8_UEP4_CTRL = ( R8_UEP4_CTRL & ~MASK_UEP_T_RES ) | UEP_T_RES_NAK;
          break;

        default :
          break;
      }
      R8_USB_INT_FG = RB_UIF_TRANSFER;
    }
    if ( R8_USB_INT_ST & RB_UIS_SETUP_ACT )                  // Setup包处理
    {
        //GPIOB_InverseBits(GPIO_Pin_19);
      R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_NAK;
      SetupReqLen = pSetupReqPak->wLength;
      SetupReqCode = pSetupReqPak->bRequest;
      chtype = pSetupReqPak->bRequestType;

      len = 0;
      errflag = 0;
      if ( ( pSetupReqPak->bRequestType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )
      {
        switch ( SetupReqCode )
        {
          case 0x0a :
            break;        //这个一定要有
          case 0x09 :
            break;
          default :
            errflag = 0xFF;
        }
      }
      else /* 标准请求 */
      {
        switch ( SetupReqCode )
        {

          case USB_GET_DESCRIPTOR :
          {
            switch ( ( ( pSetupReqPak->wValue ) >> 8 ) )
            {
              case USB_DESCR_TYP_DEVICE :
              {
                pDescr = MyDevDescr;
                len = MyDevDescr[0];
              }
                break;

              case USB_DESCR_TYP_CONFIG :
              {
                pDescr = MyCfgDescr;
                len = MyCfgDescr[2];
              }
                break;

              case USB_DESCR_TYP_REPORT :
              {
                if ( ( ( pSetupReqPak->wIndex ) & 0xff ) == 0 )                             //接口0报表描述符
                {
                  pDescr = CfgDesc;                                  //数据准备上传
                  len = sizeof( CfgDesc );
                }
                else
                  len = 0xff;                                           //本程序只有2个接口,这句话正常不可能执行
              }
                break;

              case USB_DESCR_TYP_STRING :
              {
                switch ( ( pSetupReqPak->wValue ) & 0xff )
                {
                  case 1 :
                    pDescr = MyManuInfo;
                    len = MyManuInfo[0];
                    break;
                  case 2 :
                    pDescr = MyProdInfo;
                    len = MyProdInfo[0];
                    break;
                  case 0 :
                    pDescr = MyLangDescr;
                    len = MyLangDescr[0];
                    break;
                  default :
                    errflag = 0xFF;                               // 不支持的字符串描述符
                    break;
                }
              }
                break;

              default :
                errflag = 0xff;
                break;
            }
            if ( SetupReqLen > len )
              SetupReqLen = len;      //实际需上传总长度
            len = ( SetupReqLen >= DevEP0SIZE ) ?
                DevEP0SIZE : SetupReqLen;
            memcpy( pEP0_DataBuf, pDescr, len );
            pDescr += len;
          }
            break;

          case USB_SET_ADDRESS :
            SetupReqLen = ( pSetupReqPak->wValue ) & 0xff;
            break;

          case USB_GET_CONFIGURATION :
            pEP0_DataBuf[0] = DevConfig;
            if ( SetupReqLen > 1 )
              SetupReqLen = 1;
            break;

          case USB_SET_CONFIGURATION :
            DevConfig = ( pSetupReqPak->wValue ) & 0xff;
            break;

          case USB_CLEAR_FEATURE :
          {
            if ( ( pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )    // 端点
            {
              switch ( ( pSetupReqPak->wIndex ) & 0xff )
              {
                case 0x82 :
                  R8_UEP2_CTRL = ( R8_UEP2_CTRL & ~( RB_UEP_T_TOG | MASK_UEP_T_RES ) ) | UEP_T_RES_NAK;
                  break;
                case 0x02 :
                  R8_UEP2_CTRL = ( R8_UEP2_CTRL & ~( RB_UEP_R_TOG | MASK_UEP_R_RES ) ) | UEP_R_RES_ACK;
                  break;
                case 0x81 :
                  R8_UEP1_CTRL = ( R8_UEP1_CTRL & ~( RB_UEP_T_TOG | MASK_UEP_T_RES ) ) | UEP_T_RES_NAK;
                  break;
                case 0x01 :
                  R8_UEP1_CTRL = ( R8_UEP1_CTRL & ~( RB_UEP_R_TOG | MASK_UEP_R_RES ) ) | UEP_R_RES_ACK;
                  break;
                default :
                  errflag = 0xFF;                                 // 不支持的端点
                  break;
              }
            }
            else
              errflag = 0xFF;
          }
            break;

          case USB_GET_INTERFACE :
            pEP0_DataBuf[0] = 0x00;
            if ( SetupReqLen > 1 )
              SetupReqLen = 1;
            break;

          case USB_GET_STATUS :
            pEP0_DataBuf[0] = 0x00;
            pEP0_DataBuf[1] = 0x00;
            if ( SetupReqLen > 2 )
              SetupReqLen = 2;
            break;

          default :
            errflag = 0xff;
            break;
        }
      }
      if ( errflag == 0xff )        // 错误或不支持
      {
//                  SetupReqCode = 0xFF;
        R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;    // STALL
      }
      else
      {
        if ( chtype & 0x80 )     // 上传
        {
          len = ( SetupReqLen > DevEP0SIZE ) ?
              DevEP0SIZE : SetupReqLen;
          SetupReqLen -= len;
        }
        else
          len = 0;        // 下传
        R8_UEP0_T_LEN = len;
        R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;    // 默认数据包是DATA1
      }

      R8_USB_INT_FG = RB_UIF_TRANSFER;
    }
  }
  else if ( intflag & RB_UIF_BUS_RST )
  {
    R8_USB_DEV_AD = 0;
    R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    R8_UEP1_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_UEP2_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_UEP3_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_USB_INT_FG = RB_UIF_BUS_RST;
  }
  else if ( intflag & RB_UIF_SUSPEND )
  {
    if ( R8_USB_MIS_ST & RB_UMS_SUSPEND )
    {
      ;
    }    // 挂起
    else
    {
      ;
    }               // 唤醒
    R8_USB_INT_FG = RB_UIF_SUSPEND;
  }
  else
  {
    R8_USB_INT_FG = intflag;
  }
}

void DebugInit( void )
{
  GPIOA_SetBits( GPIO_Pin_9 );
  GPIOA_ModeCfg( GPIO_Pin_8, GPIO_ModeIN_PU );
  GPIOA_ModeCfg( GPIO_Pin_9, GPIO_ModeOut_PP_5mA );
  UART1_DefInit();
}

int main()
{
  SetSysClock( CLK_SOURCE_PLL_60MHz );

  DebugInit();
  printf( "start\n" );

  pEP0_RAM_Addr = EP0_Databuf;
  pEP1_RAM_Addr = EP1_Databuf;
  pEP2_RAM_Addr = EP2_Databuf;
  pEP3_RAM_Addr = EP3_Databuf;

  USB_DeviceInit();

  PFIC_EnableIRQ( USB_IRQn );
  GPIOB_ModeCfg(GPIO_Pin_19,GPIO_ModeOut_PP_20mA);
  GPIOB_ModeCfg(GPIO_Pin_19,GPIO_ModeOut_PP_20mA);
  GPIOB_ModeCfg(GPIO_Pin_18,GPIO_ModeOut_PP_20mA);
  mDelaymS(1000);
  while(1)
  {
      mDelaymS(100);
      DevEP2_OUT_Deal(32);
  }
}

void DevEP1_OUT_Deal( UINT8 l )
{ /* 用户可自定义 */
  UINT8 i;

  for ( i = 0; i < l; i++ )
  {
    pEP1_IN_DataBuf[i] = ~pEP1_OUT_DataBuf[i];
  }
  DevEP1_IN_Deal( l );
}

void DevEP2_OUT_Deal( UINT8 l )
{ /* 用户可自定义 */
  UINT8 i;

  for ( i = 0; i < l; i++ )
  {
    pEP2_IN_DataBuf[i] = pEP2_OUT_DataBuf[i]+1;
  }
  DevEP2_IN_Deal( l );
}

void DevEP3_OUT_Deal( UINT8 l )
{ /* 用户可自定义 */
  UINT8 i;

  for ( i = 0; i < l; i++ )
  {
    pEP3_IN_DataBuf[i] = ~pEP3_OUT_DataBuf[i]+2;
  }
  DevEP3_IN_Deal( l );
}

void DevEP4_OUT_Deal( UINT8 l )
{ /* 用户可自定义 */
  UINT8 i;

  for ( i = 0; i < l; i++ )
  {
    pEP4_IN_DataBuf[i] = ~pEP4_OUT_DataBuf[i]+3;
  }
  DevEP4_IN_Deal( l );
}

__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void USB_IRQHandler( void ) /* USB中断服务程序,使用寄存器组1 */
{
  USB_DevTransProcess();
}


目前进一步测试,发现,我只有通过hid_write写一部分数据进去之后,才能通过hid_read读一帧数据出来,如果不写则无法读出数据,我的USB初始化函数里面一个while循环在哪,发现LED是在闪烁。但是就是没有数据读出来。请教各位版主如何解决?我的目的很简单,就是实现一个HID设备,每隔20毫秒能向电脑传输一帧数据,大概有10来个字节左右。

void USB_Init()
{
  pEP0_RAM_Addr = EP0_Databuf;
  pEP1_RAM_Addr = EP1_Databuf;
  pEP2_RAM_Addr = EP2_Databuf;
  pEP3_RAM_Addr = EP3_Databuf;

  USB_DeviceInit();

  PFIC_EnableIRQ( USB_IRQn );
  mDelaymS(1000);
  while(1)
  {
      mDelaymS(100);
      GPIOB_InverseBits(GPIO_Pin_18);//LED闪烁
      for (int i = 0; i < 0x20; i++ )
        {
          pEP2_IN_DataBuf[i] = i*2;
        }
        DevEP2_IN_Deal( 0X20);//发送数据到端口2

  }

}



另外请教各位老师第三个问题:我在上位机使用hid_read读端口2数据时候,如下的程序段好像都没有被执行到,对应的LED没有发生闪烁,这是什么原因?我的理解是这是向PC机上传的中断,当PC发出hid_read命令时候,肯定会被执行到:

        case UIS_TOKEN_IN | 2 :
          GPIOB_InverseBits(GPIO_Pin_19);
          R8_UEP2_CTRL = ( R8_UEP2_CTRL & ~MASK_UEP_T_RES ) | UEP_T_RES_NAK;
          break;

当我在上位机执行hid_write命令,发现如下的程序段是执行的,因为对应的LED在闪烁。

case UIS_TOKEN_OUT | 2 :
          if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
          {                       // 不同步的数据包将丢弃
            len = R8_USB_RX_LEN;
            //DevEP2_OUT_Deal( len );
            for ( int i = 0; i < 64; i++ )
                       {
                          pEP2_IN_DataBuf[i] =0x33;
                       }
                       DevEP2_IN_Deal( len );
          }
          GPIOB_InverseBits(GPIO_Pin_18);
          break;



DevEP4_IN_Deal( l ); 这句里面的 l 必须是64,也就是 上传数据必须是64个字节,我测试的时候,也发现了这个问题,发送字节要是不够64,上位机就收不到USB发来的数据


问题1:端点_OUT_Deal这个函数,是设备接收到主机发来的OUT包之后,对于OUT数据的处理,处理好后再通过IN方向的数据处理返回给主机;USB低速和全速传输中从机设备不能主动向主机PC上传数据。

问题2:可以修改IN方向端点2的描述符的最后一个字节,值改为20,即中断传输中,每隔20ms,主机发一个IN包给从机,从机设备收到PC发来的IN包,上传自定义数据。报表描述符中规定了上传数据的格式,DevEP0SIZE的值宏定义为了16,配合report size表示16个字节。这里中断使用端点2的话,报表描述符中DevEP0SIZE这个值可以不用通过宏定义,直接改成想要的字节数,最大为64。


感谢回复,关于问题1和2,我的理解:端点_OUT_Deal 这个函数就是将当前的数据提前准备好,等待上位机发命令下来就开始上传,当我发送了一个读的命令之后,相当于就是主机发送了一个IN的命令,那么准备好的数据就被上传到主机上了。因此理论上我应该得到数据的。

   请问:我以上的描述中是哪个环节出了问题而导致我的数据没有被收到,也就是读失败了.


我下载了附件中的程序,主函数复制于帖子中的主函数。

使用BUS HOUND抓包,能够抓到按程序设置并返回的IN数据。

6{SIZPC_ZH0]R9W0U`PV48S.png 8{TCTU4HDS{][LWI{{DI~IC.png7O2C0OXK~QW914CTXYZCDZV.png

此过程是否即是按6楼的说法进行的,OUT方向作为触发指令下达给从机设备,再设置数据后IN方向上传给主机。




是这样的,中断里面的确实说明了6楼的工作流程。

但是我在main 函数里面也一直在 循环调用

 DevEP2_IN_Deal( 0X20);//发送数据到端口,

那么我在上位机中:不调用hid_write的情况下,一直调用hid_read,应该是可以得到数据的,但是事实上读取的时候没有返回数据。如何才能不调用hid_write 只调用hid_read能够返回数据呢?我现在将上传周期修改为20ms,仍然不能读取数据。

下面是下位机main函数中的while循环:

  while(1)
  {
      mDelaymS(100);
      GPIOB_InverseBits(GPIO_Pin_18);//LED闪烁
      for (int i = 0; i < 0x20; i++ )
        {
          pEP2_IN_DataBuf[i] = i*2;
        }
        DevEP2_IN_Deal( 0X20);//发送数据到端口2
 
  }
 
}




3楼中程序没有运行到IN方向case内,建议确定一下,调用hid_read,主机PC是否在端点2的IN方向向从机设备发送IN指令,指令是否下达到了单片机。建议在相应case内添加串口打印  PRINT("debug字符串注意最后加\n换行"),由串口打印信息来debug。

当设置好中断函数后,每隔20ms主机会自动于端点2的IN方向管道,向从机设备发送IN指令。若要实现每20ms发送一段数据,收到这个IN指令前,要向pEP2_IN_DataBuf写好数据,而后调用 端点_IN_Deal()函数设置相应寄存器,即可在收到IN指令时发出去。

若收到IN指令前pEP2_IN_DataBuf内没有写数据,判断为设备没有准备好数据,会回NAK包。

若要咨询API的使用,可以致电025-52638360。


只有登录才能回复,可以选择微信账号登录