原创 USB的“JoyStickMouse”例程分析04

2010-4-17 21:09 2594 7 7 分类: MCU/ 嵌入式

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

 


四、USB的“JoyStickMouse”工作过程详细分析


1、枚举第一步:获取设备的描述符


USB_init()开始


1)先要允许数据传输完成中断


poweron()函数后面紧跟着几句话:


  PowerOn();  //这句执行完,设备被主机检测到,并且能够响应复位中断了。


  _SetISTR(0);               /* clear pending interrupts */


  wInterrupt_Mask = IMR_MSK;


  _SetCNTR(wInterrupt_Mask); /* set interrupts mask */


//以上这两句话将允许所有的USB中断


  bDeviceState = UNCONNECTED;  //设备状态置位为未连接状态。这里我不太理解。这时候即使复位中断未发生,最起码设备已经算是连接入总线了,为什么这个状态还要设置为 “未连接”呢


 


2)主机获取描述符


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


主机发出对地址0、端点0发出SETUP令牌包,首先端点0寄存器的11SETUP置位,表明收到了setup令牌包。


由于此时端点0数据接收有效,所以接下来主机的请求数据包被SIE保存到端点0描述附表的 RxADDR里面,收到的字节数保存到 RxCount里面


端点0寄存器的CTR_RX被置位为1ISTRCTR置位为1DIR=1EP_ID=0,表示端点0接收到主机来的请求数据。此时设备已经ACK主机,将触发正确传输完成中断,下面就进入中断看一看。


 


_SetISTR((u16)CLR_CTR); /*首先清除传输完成标志 */


EPindex = (u8)(wIstr & ISTR_EP_ID); //获取数据传输针对的端点号


 


if (EPindex == 0)  //如果是端点0,这里的确是端点0


    {


      SaveRState = _GetEPRxStatus(ENDP0); //保存端点0状态,原本是有效状态。


      SaveTState = _GetEPTxStatus(ENDP0);


      _SetEPRxStatus(ENDP0, EP_RX_NAK); //在本次数据处理好之前,对主机发来的数据包以NAK回应


      _SetEPTxStatus(ENDP0, EP_TX_NAK);


 


      if ((wIstr & ISTR_DIR) == 0) //如果是IN令牌,数据被取走


      {


        _ClearEP_CTR_TX(ENDP0);


        In0_Process();   //调用该程序处理固件数据输出后的工作。


        _SetEPRxStatus(ENDP0, SaveRState);


        _SetEPTxStatus(ENDP0, SaveTState);


        return;


      }


      Else   //DIR=1时,要么是SETUP包,要么是OUT包。


      {  //这里先分析SETUP包。


 


        wEPVal = _GetENDPOINT(ENDP0);  //获取整个端点0状态


        if ((wEPVal & EP_CTR_TX) != 0)  //这种情况一般不太可能,


        {        //如果出现表示同时 TXRX 同时置位。


        }


        else if ((wEPVal &EP_SETUP) != 0)  //我们的程序会执行到这里


        {


          _ClearEP_CTR_RX(ENDP0);


          Setup0_Process();   //主要是调用该程序来处理主机请求。


          _SetEPRxStatus(ENDP0, SaveRState);


          _SetEPTxStatus(ENDP0, SaveTState);


          return;


        }


        else if ((wEPVal & EP_CTR_RX) != 0) //暂时不执行的代码先删除掉。


        {


        }


      }


    }/* if(EPindex == 0) */


 


    后面处理其他端点的代码就先不看了。


  }/* while(...) */


3Setup0_Process()函数的执行分析


这个函数执行的时候,主机发来的请求数据包已经存在于RxADDR缓冲区了。大部分的标志位已经清除,除了SETUP位,这个味将由下一个令牌包自动清除。


进入处理函数:


 


pBuf.b = PMAAddr + (u8 *)(_GetEPRxAddr(ENDP0) * 2); //这是取得端点0接收缓冲区的起始地址。


PMAAddr是包缓冲区起始地址,_GetEPRxAddr(ENDP0)获得端点0描述符表里的接收缓冲区地址,为什么要乘以2呢?大概因为描述符表里地址项为16位,使用的是相对偏移。


 


  if (pInformation->ControlState != PAUSE)


  {


    pInformation->USBbmRequestType = *pBuf.b++; //请求类型,表明方向和接收对象(设备、接口还是端点)此时为80,表明设备到主机


    pInformation->USBbRequest = *pBuf.b++; /* 请求代码,第一次时应该是6,表明主机要获取设备描述符。 */


    pBuf.w++; 


    pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */


    pBuf.w++;  //我觉得这里可能有些问题。


    pInformation->USBwIndex  = ByteSwap(*pBuf.w++); /* wIndex */


    pBuf.w++;


    pInformation->USBwLength = *pBuf.w; /* wLength */


  }


  pInformation->ControlState = SETTING_UP;


  if (pInformation->USBwLength == 0)


  {


    NoData_Setup0();


  }


  else


  {


    Data_Setup0();  //这次是有数据传输的,所以有进入该该函数。


  }


  return Post0_Process();


4Data_Setup0()函数的执行分析


  CopyRoutine = NULL; //这是一个函数指针,由用户提供。


  wOffset = 0;


 


  if (Request_No == GET_DESCRIPTOR) //如果是获取设备描述符


  {


    if(Type_Recipient==(STANDARD_REQUEST| EVICE_RECIPIENT))


    {


      u8 wValue1 = pInformation->USBwValue1;


      if (wValue1 == DEVICE_DESCRIPTOR)


      {


        CopyRoutine = pProperty->GetDeviceDescriptor;


      } //获取设备描述符的操作由用户提供。


 


  if (CopyRoutine)


  {


    pInformation->Ctrl_Info.Usb_wOffset = wOffset;


    pInformation->Ctrl_Info.CopyData = CopyRoutine;


    (*CopyRoutine)(0); //这个函数这里调用的目的只是设置了pInformation中需要写入的描述符的长度。


    Result = USB_SUCCESS;


  }


 


  if (ValBit(pInformation->USBbmRequestType, 7))  //此时为80


  {   //上面这个语句主要是判断传输方向。如果为1,则是设备到主机


    vu32 wLength = pInformation->USBwLength; 这个一般是64


    if (pInformation->Ctrl_Info.Usb_wLength > wLength)


    {   //设备描述符长度18


      pInformation->Ctrl_Info.Usb_wLength = wLength;


    }  //有些细节暂时先放着


   pInformation->Ctrl_Info.PacketSize = pProperty->MaxPacketSize;


    DataStageIn();  //最主要是调用这个函数完成描述符的输出准备


  }


 


5DataStageIn()函数的执行分析


以下是主要执行代码:


  DataBuffer = (*pEPinfo->CopyData)(Length); //这个是取得用户描述符缓冲区的地址。这里共18个字节


  UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length);//这个函数将设备描述符复制到用户的发送缓冲区。


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


 


  pEPinfo->Usb_wLength -= Length; 等于0


  pEPinfo->Usb_wOffset += Length; 偏移到18


  vSetEPTxStatus(EP_TX_VALID); //使能端点发送,只要主机的IN令牌包一来,SIE就会将描述符返回给主机。


  USB_StatusOut();/* 这个实际上是使接收也有效,主机可取消IN */


Expect_Status_Out:


  pInformation->ControlState = ControlState;


 


6)执行流程返回到CTR_LP(void)


 


_SetEPRxStatus(ENDP0, SaveRState); 


_SetEPTxStatus(ENDP0, SaveTState);             


//由于vSetEPTxStatus(EP_TX_VALID)实际改变了SaveTState,所以此时端点发送已经使能。


return;


 


7)主机的IN令牌包


获取描述符的控制传输进入第二阶段,主机首先发一个IN令牌包,由于端点0发送有效,SIE将数据返回主机。


主机方返回一个ACK后,主机发送数据的CTR标志置位,DIR=0EP_ID=0,表明主机正确收到了用户发过去的描述符。固件程序由此进入中断。


此时是由IN引起的。


主要是调用In0_Process()完成剩下的工作。


 


7)追踪进入函数In0_Process()


此时实际上设备返回描述符已经成功了。


这一次还是调用DataStageIn()函数,但是目的只是期待主机的0状态字节输出了。


  if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))


  {  第一次取设备描述符只取一次


    DataStageIn();  //此次调用后,当前状态变成WAIT_STATUS_OUT,表明设备等待状态过程,主机输出0字节。


    /* ControlState may be changed outside the function */


    ControlState = pInformation->ControlState;


  }返回时调用Post0_Process(void)函数,这个函数没做什么事。


 


8)进入状态过程


主机收到18个字节的描述符后,进入状态事务过程,此过程的令牌包为OUT,字节数为0.只需要用户回一个ACK


所以中断处理程序会进入Out0_Process()。


由于此时状态为WAIT_STATUS_OUT,所以执行以下这段。


  else if (ControlState == WAIT_STATUS_OUT)


  {


    (*pProperty->Process_Status_OUT)();  //这是个空函数,什么也不做。


    ControlState = STALLED;  //状态转为STALLED


  }


获取设备描述符后,主机再一次复位设备。设备又进入初始状态


 


 


 


 

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
7
关闭 站长推荐上一条 /3 下一条