USB的“Virtual_COM_Port”例程分析--枚举--获取设备描述符

二. GET_DESCRIPTOR主机获取描述符

 

 

  1. 建立事务阶段

主机进入控制传输的第一阶段:建立事务--发送setup令牌包,发送请求数据包,设备发送ACK包。

主机对地址0、端点0发出SETUP令牌包,首先端点0寄存器的第11SETUP位置位,表明收到了setup令牌包。由于此时端点0数据接收有效,所以接下来主机的请求数据包被SIE保存到端点0描述符表的RxADDR里面,收到的字节数保存到RxCount里面。

     端点0寄存器的CTR_RX被置位为1ISTRCTR置位为1DIR=1EP_ID=0,表示端点0接收到主机来的请求数据。此时设备已经ACK主机,将触发正确传输完成中断。接下来是建立事务阶段的整体流程图:

 

 

 

1.1 ISTR事件中断服务程序USB_Istr()

 

#if (IMR_MSK & ISTR_CTR)
    /* Correct TRansfer (clear-only bit)正确传输中断 */
  if (wIstr & ISTR_CTR & wInterrupt_Mask)
  {//正确传输中断,协议相关的数据处理 ,硬件使端点为 NAK 状态,让程序有足够时间
    /* servicing of the endpoint correct transfer interrupt */
    /* clear of the CTR flag into the sub */
    CTR_LP();//正确传输时的处理函数

 

1.2 低优先级中断CTR_LP()

接收到主机发送的SETUP令牌包所以进入Setup0_Process()来处理SETUP

void CTR_LP(void)
{
  __IO uint16_t wEPVal = 0;
  /* stay in loop while pending interrupts */
    //如果有传输正确的中断将继续处理直到处理完成,该位只读,USB_EpnR 寄存器相关中断挂起位清除时才会清除
  while (((wIstr = _GetISTR()) & ISTR_CTR) != 0)
  {
    /* extract highest priority endpoint number 提取高优先级的端点*/
    EPindex = (uint8_t)(wIstr & ISTR_EP_ID);   //读出端点ID
    if (EPindex == 0)   //如果是端点0成功传输 ,控制传输,有三个阶段,建立阶段,数据阶段(可能有),状态阶段
    {
      /* Decode and service control endpoint interrupt */
      /* calling related service routine */
      /* (Setup0_Process, In0_Process, Out0_Process) */

      /* save RX & TX status */
      /* and set both to NAK */  
 /* 保存当前的状态,在后面处理中根据情况选择是否重新设置状态 */     
        SaveRState = _GetENDPOINT(ENDP0);    //获取端点0的状态寄存器值
        SaveTState = SaveRState & EPTX_STAT; //保存发送状态位
        SaveRState &=  EPRX_STAT;               //保存接受状态位

            /* 硬件自动设置为NAK 注意:硬件只会在相应方向上设置为NAK,另一方向是不会进行设置的,所以之前的的保存是有用的 */
        _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);//设定发送和接收状态为NAK,此时设备就会一直以NAK回应主机,使设备有足够的时间处理数据 
      /* DIR bit = origin of the interrupt */
      else
      {
        /* DIR = 1 */

        /* DIR = 1 & CTR_RX       => SETUP or OUT int :DIR = 1时,是输出中断*/
        /* DIR = 1 & (CTR_TX | CTR_RX) => 2 int pending */

        wEPVal = _GetENDPOINT(ENDP0); //获取端点0的状态
        /* 令牌包:SETUP 数据包:DATA0 握手包:ACK (程序运行到这里,说明已经确认数据正确,在确认数据正确时已经由硬件自动发送了) */
        /* 如果数据错误,则会丢弃数据且不返回握手包程序不会到这里 */        
        if ((wEPVal &EP_SETUP) != 0) // 如果接收的是控制阶段数据  
        {
          _ClearEP_CTR_RX(ENDP0); /* SETUP bit kept frozen while CTR_RX = 1 */
          Setup0_Process();       //处理SETUP事件   //程序会进入到这个函数里面
          /* before terminate set Tx & Rx status */
   
              _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//将处理后的状态重新进行设置
          return;
        }

 

1.3 处理主机发来的SETUPSetup0_Process()

    先取得设备端点0接收缓冲区地址,然后从地址中读取主机发送过来的SETUP包,根据SETUP包中wLength数据发现设备需要向主机发送数据,因此进入Data_Setup0()

uint8_t Setup0_Process(void)
{//处理主机发来的Setup包
  union
  {
    uint8_t* b;
    uint16_t* w;
  } pBuf;
#if defined STM32F303xE || defined STM32F302x8 
  uint16_t offset = 0;
  pBuf.b = (uint8_t *)( PMAAddr + _GetEPRxAddr(ENDP0));
#else  
  uint16_t offset = 1;
  
  pBuf.b = PMAAddr + (uint8_t *)(_GetEPRxAddr(ENDP0) * 2); /* *2 for 32 bits addr 这是取得端点0接收缓冲区的起始地址*/
    //PMAAddr是包缓冲区起始地址,_GetEPRxAddr(ENDP0)获得端点0描述符表里的接收缓冲区地址。
  //乘以2的原因是,描述符里地址项为16位,使用的是相对偏移
    
#endif
  if (pInformation->ControlState != PAUSE)//如果不处于暂停状态,那么获取主机发送setup包数据
  {
    pInformation->USBbmRequestType = *pBuf.b++; /* bmRequestType  请求类型,表示数据传输方向 请求类型 请求的接收者 */
    pInformation->USBbRequest = *pBuf.b++; /* bRequest 标准请求及代码  */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */    //regs.c ByteSwap()
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwIndex  = ByteSwap(*pBuf.w++); /* wIndex */   //regs.c ByteSwap()
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwLength = *pBuf.w; /* wLength */
  }

  pInformation->ControlState = SETTING_UP;//设置控制状态为 SETTING_UP设置
  if (pInformation->USBwLength == 0)
  {
    /* 不需要向主机发送数据的处理 */
    NoData_Setup0();
  }
  else
  {
    /* 需要向主机发送数据的处理 */
    Data_Setup0(); 
  }
  return Post0_Process();
}

 

1.4 需要向主机发送数据Data_Setup0()

    根据SETUP包中得到标准请求为获取设备描述符,通过偏移来取出设备描述符,然后判断是否成功取得描述符。接着根据SETUP包来确定传输方向为设备向主机,根据传输的数据大小得出设备向主机发送一个数据包。最后进入描述符的输出准备DataStageIn()中。

 

void Data_Setup0(void)
{
  uint8_t *(*CopyRoutine)(uint16_t);
  RESULT Result;
  uint32_t Request_No = pInformation->USBbRequest;

  uint32_t Related_Endpoint, Reserved;
  uint32_t wOffset, Status;
    
  CopyRoutine = NULL;   //这是一个函数指针,由用户提供
  wOffset = 0;

  /*GET DESCRIPTOR   获得描述符*/
  if (Request_No == GET_DESCRIPTOR)
  { //如果是标准请求并且接收者是设备
    if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT))
    {
      uint8_t wValue1 = pInformation->USBwValue1;//得到描述符类型 
      if (wValue1 == DEVICE_DESCRIPTOR)          //如果是获取设备描述符
      {
        CopyRoutine = pProperty->GetDeviceDescriptor;//得到设备描述符函数
      }

 

进入prop.c

 

/*******************************************************************************
* Function Name  : Virtual_Com_Port_GetDeviceDescriptor.
* Description    : Gets the device descriptor.获取设备描述符
* Input          : Length.
* Output         : None.
* Return         : The address of the device descriptor.
*******************************************************************************/
uint8_t *Virtual_Com_Port_GetDeviceDescriptor(uint16_t Length)
{
  return Standard_GetDescriptorData(Length, &Device_Descriptor);
}
//通过偏移来取出描述符号
uint8_t *Standard_GetDescriptorData(uint16_t Length, ONE_DESCRIPTOR *pDesc)
{
  uint32_t  wOffset;

  wOffset = pInformation->Ctrl_Info.Usb_wOffset;//得到偏移
  if (Length == 0)
  {
    pInformation->Ctrl_Info.Usb_wLength = pDesc->Descriptor_Size - wOffset;//得到需要发送的数据长度
    return 0;
  }

  return pDesc->Descriptor + wOffset;   //通过偏移来取出
}

 

返回Data_Setup0()

  if (CopyRoutine)    //判断是否取得描述符
  {
    pInformation->Ctrl_Info.Usb_wOffset = wOffset;
    pInformation->Ctrl_Info.CopyData = CopyRoutine;//将函数指针传入到结构体变量中
    /* sb in the original the cast to word was directly */
    /* now the cast is made step by step */
    (*CopyRoutine)(0);   //得到发送数据大小
    Result = USB_SUCCESS;
  }
  if (ValBit(pInformation->USBbmRequestType, 7))//D7表示数据传输方向:设备向主机
  {
    /* Device ==> Host :设备到主机*/
    __IO uint32_t wLength = pInformation->USBwLength;  //要发送的数据  //这个一般是64  
    else if (pInformation->Ctrl_Info.Usb_wLength < pInformation->USBwLength)//如果实际的数据长度小于请求的数据长度
    {
      if (pInformation->Ctrl_Info.Usb_wLength < pProperty->MaxPacketSize)//可以一个包发送出去
      {
        Data_Mul_MaxPacketSize = FALSE;
      }
    pInformation->Ctrl_Info.PacketSize = pProperty->MaxPacketSize;//只有这一次确定了控制端点的发送包大小
    DataStageIn();   //调用这个函数实现描述符的输出准备

 

1.5 实现描述符的输出准备DataStageIn()

    因为此时后数据传输,对此与最大包长进行比较,得到此时控制状态为LAST_IN_DATA。设备开始确定发送地址,发送送字节的数目,更新后续包的数据长度,数据偏移。最后使能发送端点 只要主机的IN令牌包一来 设备就会将描述符返回给主机。

   

  Length = pEPinfo->PacketSize;//最大发送字节数//得到数据包大小 64字节
  //要发送的数据小于等于最大发送大小,则处于 LAST_IN_DATA    18字节<64字节  ControlState = LAST_IN_DATA
  ControlState = (save_wLength <= Length) ? LAST_IN_DATA : IN_DATA;

  if (Length > save_wLength)//如果要发送的数据小于最大包长
  {
    Length = save_wLength;//就按照实际大小发送
  }
  //否则以一个包的最大数据量进行发送
    //以下是主要执行代码:
  DataBuffer = (*pEPinfo->CopyData)(Length);                   //得到要发送的数据地址(前面已经确定了发送数据的大小),这里共18个字节
  
  UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length); //将要发送的数据放到发送缓冲区中

  SetEPTxCount(ENDP0, Length);       //设置发送字节的数目:18

  pEPinfo->Usb_wLength -= Length;//更新需要的数据长度//Usb_wLength等于0
  pEPinfo->Usb_wOffset += Length;//更新最新包数据偏移//偏移到18
  vSetEPTxStatus(EP_TX_VALID);//使能发送端点 只要主机的IN令牌包一来 SIE就会将描述符返回给主机
/* 设置端点为接收有效 */
  USB_StatusOut();/* Expect the host to abort the data IN stage :使能接收也有效,主机可以取消IN*/

Expect_Status_Out:
  pInformation->ControlState = ControlState;//保存控制状态
}

 

1.6 设置端点为接收有效USB_StatusOut()

//这样设置是为了将所有的数据都处理完了才可真正使能发送和接收
#define vSetEPRxStatus(st) (SaveRState = st)
#define vSetEPTxStatus(st) (SaveTState = st)
/* IN令牌包后发送0长度的数据,表明这次数据传输过程结束 */
#define USB_StatusIn() Send0LengthData()
/* OUT令牌包后在数据传输结束后设置端点为接收有效,保持数据接收的持续性    */
#define USB_StatusOut() vSetEPRxStatus(EP_RX_VALID)

 

2. 输入事务(数据阶段)

    获取描述符的控制传输进入第二阶段,主机首先发送一个IN令牌包,由于端点0发送有效,SIE将数据返回主机。 主机方返回一个ACK后,主机发送数据的CTR标志置位,DIR = 0EP_ID = 0,表明主机正确收到了用户发过去的描述符。固件程序由此进入中断。

此时是由IN引起的。主要是调用In0_Process()完成剩下的工作。

 

 低优先级中断CTR_LP()

      if ((wIstr & ISTR_DIR) == 0)   //如果是IN令牌包,数据被取走。也可以通过判断端点发送状态位 EP_CTR_TX 进行判断
      {/* 这里需要处理数据阶段和状态阶段的数据 ,如果没有数据需要发送(也就是说发送了 IN 却没有数据),那么直接返回 NAK 就行了*/
        /* DIR = 0 */

        /* DIR = 0      => IN  int :DIR = 0时,是输入中断*/
        /* DIR = 0 implies that (EP_CTR_TX = 1) always  */

        _ClearEP_CTR_TX(ENDP0);    //清除发送标志,同时 ISTR_CTR 位也会清除 
                //在这里会设置发送的数据大小和接收发送状态SaveRState\SaveTState
        In0_Process();             //处理IN事件

           /* before terminate set Tx & Rx status */

            _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//将处理后的状态重新进行设置
          return;
      }

 


2.1 处理主机发来的INIn0_Process()

    因为此时控制状态为LAST_IN_DATA,所以直接进入DataStageIn()

 

uint8_t In0_Process(void)
{
  uint32_t ControlState = pInformation->ControlState;

    //有数据发送,继续发送数据,或者已经发送完成了
  if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))//进入到这里
  {
    DataStageIn();//第一次取设备描述符只取一次 当前的状态变为WAIT_STATUS_OUT 
                      //表明设备等待状态过程 主机输出0字节
    /* ControlState may be changed outside the function */
    ControlState = pInformation->ControlState;
  }
    

 

2.2 控制传输的发送数据阶段DataStageIn()

因为在1.4中已经得出一个数据包发送,Data_Mul_MaxPacketSize = FALSE。所以控制状态成为WAIT_STATUS_OUT,关闭设备的发送端点。

 

void DataStageIn(void)
{/* 有可能数据已经发送完成了,主机没有发送状态数据,而是继续发送了 IN 令牌包,那么设备的状态设为 WAIT_STATUS_OUT,也就是说端点的反应为 NAK  */    
    /* 此时当主机收到 NAK 握手包时就不会再发 IN 令牌包过来了,而是发送OUT令牌包,同时把 0 字节数据发送过来了 */
    
  ENDPOINT_INFO *pEPinfo = &pInformation->Ctrl_Info;  //端点信息保存在指针变量中
  uint32_t save_wLength = pEPinfo->Usb_wLength;       //得到字符的长度
  uint32_t ControlState = pInformation->ControlState; //得到当前的状态

  uint8_t *DataBuffer;
  uint32_t Length;
/* 如果要发送的数据长度为0并且是最后一次数据,也就是说数据都发送完了 */
  if ((save_wLength == 0) && (ControlState == LAST_IN_DATA))
  {//程序运行到这里说明数据都发送完了,这里分两种情况:整包发送和非整包发送,两种处理情况不一样
    else //字符的长度比数据包要小
    {//数据已经发送完
      /* No more data to send so STALL the TX Status*/
      ControlState = WAIT_STATUS_OUT;//发送完成了,等待接收状态数据包
      vSetEPTxStatus(EP_TX_STALL);//设置为STALL,既然已经发送完了,发送端点就可以停止了
    }
    goto Expect_Status_Out;//因为数据发送完成了,那么直接可以结束了
  }

  3.输出事务(状态阶段)

   主机收到18字节描述符后,进入状态事务过程,此时过程的令牌包为OUT,字节数为0,只需要用户返回一个ACK。所以中断处理程序会进入Out0_Process()

 

 

 低优先级中断CTR_LP()

        /* 令牌包:OUT 数据包:DATA1/DATA0  */
        else if ((wEPVal & EP_CTR_RX) != 0)//接收到的是数据包,有可能是有数据的包,也可能是0字节的数据包
        {/* 这里需要处理数据阶段和状态阶段的数据 */
          _ClearEP_CTR_RX(ENDP0);  //清除正确接收标志位
          Out0_Process();         //处理OUT事件
          /* before terminate set Tx & Rx status */
     
             _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
          return;
        }

 

3.1 处理主机发来的OUTOut0_Process()

 

   //由于此时状态为WAIT_STATUS_OUT,所以执行下面代码
  else if (ControlState == WAIT_STATUS_OUT) //之前在等待状态包
  {/* 程序运行到这里,说明状态包已经发送过来 */
    (*pProperty->Process_Status_OUT)();//状态包发送过来后的处理函数,也可以不处理,什么都不做
    ControlState = STALLED;     //状态变成了终止发送和接受       
  }

 

返回Post0_Process()

3.2 暂停端点的发送和接收Post0_Process()

 

uint8_t Post0_Process(void)
{
   
  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置最大接收包为64字节

  if (pInformation->ControlState == STALLED) 
  {
    vSetEPRxStatus(EP_RX_STALL); 
    vSetEPTxStatus(EP_TX_STALL);//将端点0的发送和接收都设置为:STALL,这样只接收SETUP令牌包。
  }

  return (pInformation->ControlState == PAUSE);
}

 

posted @ 2021-03-29 14:06  万事兴  阅读(829)  评论(0)    收藏  举报