STM32——USB详细使用说明 附件HID的双向通信

xiaoxiao2021-02-28  78

转自:http://blog.sina.com.cn/s/blog_98ee3a930100wn6m.html

说明:使用的是STM32F103ZET6

硬件原理图

在开始枚举设备的一些初始化

void bsp_USBInit(void) {

    GPIO_InitTypeDef  GPIO_InitStructure;

         RCC_APB2PeriphClockCmd(RCC_USB_PULL_UP, ENABLE);          USB_CABLE_DISABLE();

         GPIO_InitStructure.GPIO_Pin = PIN_USB_PULL_UP;     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;     GPIO_Init(GPIOB, &GPIO_InitStructure);          {       NVIC_InitTypeDef NVIC_InitStructure;             NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);        NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;       NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;       NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;       NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;       NVIC_Init(&NVIC_InitStructure);     }          RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);

        USB_Init();

}

现在开始分析真正的初始化

第一步:初始化,总线复位及向默认地址 0发送 GET_DESCRIPTOR 指令包,请求设备描述

1)Index[4 - 5]:表示 USB插入总线复位;

2)Index[7 - 8]:表示主机向默认地址发送GET_DESCRIPTOR指令包,详细信 息也抓出来了,如(图二)所示

3)Index[15 - 17]:表示设备向主机发送设备描述数据 Index[16] 4)Index[18 - 19]:表示主机完成 GET_DESCRIPTOR指令后,给设备发送一个 空应答

现在具体的分析103的usb的执行过程 按顺序向下执行

***************(1)**************

DEVICE_INFO *pInformation;

DEVICE_PROP *pProperty;

DEVICE_PROP Device_Property =   {     Joystick_init,     Joystick_Reset,     Joystick_Status_In,     Joystick_Status_Out,     Joystick_Data_Setup,     Joystick_NoData_Setup,     Joystick_Get_Interface_Setting,     Joystick_GetDeviceDescriptor,     Joystick_GetConfigDescriptor,     Joystick_GetStringDescriptor,     0,     0x40   };

USER_STANDARD_REQUESTS User_Standard_Requests =   {     Joystick_GetConfiguration,     Joystick_SetConfiguration,     Joystick_GetInterface,     Joystick_SetInterface,     Joystick_GetStatus,     Joystick_ClearFeature,     Joystick_SetEndPointFeature,     Joystick_SetDeviceFeature,     Joystick_SetDeviceAddress   };

//USB内核将主机发送过来的用于实现USB设备的设置包保存在设备信息结构表中 typedef struct _DEVICE_INFO {   uint8_t USBbmRequestType;              uint8_t USBbRequest;                   uint16_t_uint8_t USBwValues;           uint16_t_uint8_t USBwIndexs;           uint16_t_uint8_t USBwLengths;       

  uint8_t ControlState;                  uint8_t Current_Feature;   uint8_t Current_Configuration;       uint8_t Current_Interface;           uint8_t Current_AlternateSetting; 

  ENDPOINT_INFO Ctrl_Info; }DEVICE_INFO;

usb_init.c文件里面的

void USB_Init(void) {   pInformation = &Device_Info;   pInformation->ControlState = 2;   pProperty = &Device_Property;   pUser_Standard_Requests = &User_Standard_Requests;      pProperty->Init(); }

***************(2)**************通过函数指针指向这个初始化函数pProperty 在usb_prop.c文件里面

void Joystick_init(void) {

    Get_SerialNum();                                      //得到串行号

  pInformation->Current_Configuration = 0;          //     PowerOn();                                            //将USB上电 连接设备

    USB_SIL_Init();                                       //主要是CNTR寄存器的初始化   bDeviceState = UNCONNECTED;                        //设备状态标志 当前状态未连接 }

hw_config.c文件里面 这个和标准的不一样有改动,获取设备版本号,将其存入到版本号字符串。

void Get_SerialNum(void)   //得到串行号 {     uint32_t Device_Serial0, Device_Serial1, Device_Serial2;     Device_Serial0 = *(__IO uint32_t*)(0x1FFFF7E8);     Device_Serial1 = *(__IO uint32_t*)(0x1FFFF7EC);     Device_Serial2 = *(__IO uint32_t*)(0x1FFFF7F0);     Device_Serial0 += Device_Serial2;     if (Device_Serial0 != 0)     {         IntToUnicode (Device_Serial0, &Joystick_StringSerial[2] , 8);         IntToUnicode (Device_Serial1, &Joystick_StringSerial[18], 4);     } }

usb_pwr.c文件里面 在这个文件里面只是使能了复位,挂起,唤醒中断,在PowerOn函数使能了复位中断以后,将进入到USB的复位中断里面去。

然后再执行函数USB_SIL_Init 将所有的USB中断都打开。在D+被接通上拉以后,设备就能被主机检测到。

RESULT PowerOn(void) { #ifndef STM32F10X_CL   uint16_t wRegVal;

   USB_Cable_Config(ENABLE);                    //将USB上电连接

   //对USB模块强制复位,类似于USB总线上的复位信号。USB模块将一直保持在复位状态下 //直到软件清除此位。如果USB复位中断被使能,将产生一个复位中断。   wRegVal = CNTR_FRES;                      //强制复位                                  _SetCNTR(wRegVal);

     wInterrupt_Mask = 0;   _SetCNTR(wInterrupt_Mask);                 //清除复位信号      _SetISTR(0);  

//复位中断屏蔽位 挂起中断屏蔽位 唤醒中断屏蔽位使能     wInterrupt_Mask = CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM;

  _SetCNTR(wInterrupt_Mask); #endif

  return USB_SUCCESS; }

usb_istr.c文件里面,下面只写了进入到复位中断函数,进入到USB连接状态

void USB_Istr(void)

{

   wIstr = _GetISTR();

  #if (IMR_MSK & ISTR_RESET)                      //USB复位请求中断   if (wIstr & ISTR_RESET & wInterrupt_Mask)        {     _SetISTR((uint16_t)CLR_RESET);                //清楚复位中断标志     Device_Property.Reset();                      //进入到复位中断   #ifdef RESET_CALLBACK     RESET_Callback();   #endif   } #end

}

usb_prop.c文件里面,实现对端点的设置。

void Joystick_Reset(void) {      pInformation->Current_Configuration = 0;   pInformation->Current_Interface = 0;                                 

 

    pInformation->Current_Feature = Joystick_ConfigDescriptor[7];        //供电模式选择

#ifdef STM32F10X_CL          OTG_DEV_EP_Init(EP1_IN, OTG_DEV_EP_TYPE_INT, 4); #else

  SetBTABLE(BTABLE_ADDRESS);                  //分组缓冲区描述表地址设置

    SetEPType(ENDP0, EP_CONTROL);               //初始化为控制端点类型   SetEPTxStatus(ENDP0, EP_TX_STALL);          //端点以STALL分组响应所有的发送请求。

  //也就是端点状态设置成发送无效,也就是主机的IN令牌包来的时候,回送一个STALL。       SetEPRxAddr(ENDP0, ENDP0_RXADDR);          //设置端点0描述符的接受地址,

  SetEPTxAddr(ENDP0, ENDP0_TXADDR);           //设置端点0描述符的发送地址

  Clear_Status_Out(ENDP0);                      

  //仅用于控制端点 如果STATUS_OUT位被清除,OUT分组可以包含任意长度的数据   SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); 

  //设置端点0的接受字节寄存器的最大值是64   SetEPRxValid(ENDP0);                                //设置接受端点有效

    SetEPType(ENDP1, EP_INTERRUPT);                     //初始化为中断端点类型   SetEPTxAddr(ENDP1, ENDP1_TXADDR);                   //设置发送数据的地址   SetEPTxCount(ENDP1, 4);                              //设置发送的长度   SetEPRxStatus(ENDP1, EP_RX_DIS);                    //设置接受端点关闭   SetEPTxStatus(ENDP1, EP_TX_NAK);                    //设置发送端点端点非应答

    SetDeviceAddress(0);                                //设置设备用缺省地址相应 #endif

  bDeviceState = ATTACHED;                          //当前状态连接 }

usb_sil.c的文件里面,主要是使能了如下这些中断

CNTR_CTRM   正确传输(CTR)中断使能   CNTR_WKUPM 唤醒中断使能 CNTR_SUSPM  挂起(SUSP)中断使能      CNTR_ERRM  出错中断使能 CNTR_SOFM   帧首中断使能            CNTR_ESOFM 期望帧首中断使能CNTR_RESETM 设置此位将向PC主机发送唤醒请求。根据USB协议,如果此位在1ms到15ms内保持有效,主机将对USB模块实行唤醒操作。

uint32_t USB_SIL_Init(void) { #ifndef STM32F10X_CL        _SetISTR(0);                              //清除中断标志   wInterrupt_Mask = IMR_MSK;    //这组寄存器用于定义USB模块的工作模式,中断的处理,设备的地址和读取当前帧的编号   _SetCNTR(wInterrupt_Mask);              //设置相应的控制寄存器  #else      OTG_DEV_Init(); #endif

  return 0; }

***************(3)**************

1.获取设备描述符

usb_int.c的文件里面

低优先级中断  在控制 中断  批量传输下使用(在单缓冲模式下使用) 当一次正确的OUT,SETUP,IN数据传输完成后,硬件会自动设置此位为NAK状态,使应用程序有足够的时间处理完当前传输的数据后,响应下一个数据分组

void CTR_LP(void) {   __IO uint16_t wEPVal = 0;      while (((wIstr = _GetISTR()) & ISTR_CTR) != 0)   {          EPindex = (uint8_t)(wIstr & ISTR_EP_ID);                 //读出端点ID     if (EPindex == 0)                                        //如果是端点0     {                                       SaveRState = _GetENDPOINT(ENDP0);                    //读取端点0寄存器USB_EP0R         SaveTState = SaveRState & EPTX_STAT;                 //保存发送状态位         SaveRState &=  EPRX_STAT;                            //保存接受状态位         _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);        //端点以NAK分组响应所有的发送和接受请求(解释在上面)             if ((wIstr & ISTR_DIR) == 0)    //IN令牌,数据被取走                       

     {                                  _ClearEP_CTR_TX(ENDP0);                                //清除正确发送标志位         In0_Process();                                         //处理INT事件                  _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);         return;       }       else       {       

               

        wEPVal = _GetENDPOINT(ENDP0);                   //得到端点0寄存器的数据         if ((wEPVal &EP_SETUP) != 0)                    //SETUP分组传输完成标志            {           _ClearEP_CTR_RX(ENDP0);           Setup0_Process();                           //处理SETUP事件

                                                      //程序会进入到这个函数里面                    _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);           return;         }

        else if ((wEPVal & EP_CTR_RX) != 0)         {           _ClearEP_CTR_RX(ENDP0);           Out0_Process();                              //处理OUT事件                     _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);           return;         }       }     }     else                                            //如果是除端点0以外的端点     {      

                             wEPVal = _GetENDPOINT(EPindex);               //得到相应端点寄存器值       if ((wEPVal & EP_CTR_RX) != 0)                //检测正确接收标志 PC-USB OUT int       {                  _ClearEP_CTR_RX(EPindex);                 //清除相应的标志

                (*pEpInt_OUT[EPindex-1])();               //调用OUT int服务功能

      }

      if ((wEPVal & EP_CTR_TX) != 0)               //检测正确发送标志  USB-PC IN int       {                 _ClearEP_CTR_TX(EPindex);                 //清除相应的标志

                 (*pEpInt_IN[EPindex-1])();                 //调用IN int服务功能       }

    }

  } }

usb_coer.c的文件里面,主要是得到主机发来的标准请求命令

uint8_t Setup0_Process(void) {

  union   {     uint8_t* b;     uint16_t* w;   } pBuf;

#ifdef STM32F10X_CL   USB_OTG_EP *ep;   uint16_t offset = 0;   ep = PCD_GetOutEP(ENDP0);   pBuf.b = ep->xfer_buff; #else    uint16_t offset = 1;                              //得到接受缓冲区地址寄存器地址   pBuf.b = PMAAddr + (uint8_t *)(_GetEPRxAddr(ENDP0) * 2); #endif

  if (pInformation->ControlState != PAUSE)   {     pInformation->USBbmRequestType = *pBuf.b++;           

    pInformation->USBbRequest = *pBuf.b++;                    

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

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

    pBuf.w += offset;       pInformation->USBwLength = *pBuf.w;                        

  }

  pInformation->ControlState = SETTING_UP;   if (pInformation->USBwLength == 0)   {          NoData_Setup0();   }   else   {          Data_Setup0();  //由于是有数据的传输,所有要进入到这个函数   }   return Post0_Process(); }

usb_core.c的文件里面,这里只是选取了GET DESCRIPTOR

的程序部分,其他的部分删除了

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;

                           //看标准请求码格式就知道了   if (Request_No == GET_DESCRIPTOR)   {      //pInformation->USBbmRequestType是下面的两种 标准请求或设备请求     if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT))     {       uint8_t wValue1 = pInformation->USBwValue1;   //高一字节得到描述表种类 一共有5种       if (wValue1 == DEVICE_DESCRIPTOR)             //设备描述       {         CopyRoutine = pProperty->GetDeviceDescriptor;       }       else if (wValue1 == CONFIG_DESCRIPTOR)       {         CopyRoutine = pProperty->GetConfigDescriptor; //配置描述       }       else if (wValue1 == STRING_DESCRIPTOR)       {         CopyRoutine = pProperty->GetStringDescriptor; //字符串描述        }      }   }

  if (CopyRoutine)   {     pInformation->Ctrl_Info.Usb_wOffset = wOffset;  //本子程序的wOffset是0     pInformation->Ctrl_Info.CopyData = CopyRoutine; //使指针pInformation->Ctrl_Info.CopyData指向CopyRoutine             (*CopyRoutine)(0);                      //第一次执行时Length=0 返回的是有效数据的长度 存储到pInformation->Ctrl_Info.Usb_wLength     Result = USB_SUCCESS;   }   else   {                                       //如果标准请求不存在  看类 厂商请求中是否有     Result = (*pProperty->Class_Data_Setup)(pInformation->USBbRequest);     if (Result == USB_NOT_READY)     {       pInformation->ControlState = PAUSE;       return;     }   }

  if (pInformation->Ctrl_Info.Usb_wLength == 0xFFFF)   //如果字符的长度是0xffff   {          pInformation->ControlState = PAUSE;     return;   }   if ((Result == USB_UNSUPPORT) || (pInformation->Ctrl_Info.Usb_wLength == 0))   {          pInformation->ControlState = STALLED;     return;   }

  if (ValBit(pInformation->USBbmRequestType, 7))                                      //D7表示数据传输方向 1:设备向主机   {          __IO uint32_t wLength = pInformation->USBwLength;         //设置使其为USB主机设置的长度  本程序HID 鼠标  pProperty->MaxPacketSize是0x40     if (pInformation->Ctrl_Info.Usb_wLength > wLength)                               

      //字符的长度大于主机要求的长度

    {       pInformation->Ctrl_Info.Usb_wLength = wLength;                               

                                       //将其设置为主机要求的     }     else if (pInformation->Ctrl_Info.Usb_wLength < pInformation->USBwLength)                           //字符的长度小于主机要求的     {       if (pInformation->Ctrl_Info.Usb_wLength < pProperty->MaxPacketSize)                       //如果字符的长度长度小于每包数据最大字节数       {         Data_Mul_MaxPacketSize = FALSE;       }       else if ((pInformation->Ctrl_Info.Usb_wLength % pProperty->MaxPacketSize) == 0)  //如果是其整数倍       {         Data_Mul_MaxPacketSize = TRUE;       }     }  

    pInformation->Ctrl_Info.PacketSize = pProperty->MaxPacketSize;     DataStageIn();   }   else                             //主机向设备   {     pInformation->ControlState = OUT_DATA;     vSetEPRxStatus(EP_RX_VALID);    }

  return; }

usb_coer.c的文件里面

void DataStageIn(void) {   ENDPOINT_INFO *pEPinfo = &pInformation->Ctrl_Info;       //端点信息保存在指针变量中   uint32_t save_wLength = pEPinfo->Usb_wLength;            //得到字符的长度   uint32_t ControlState = pInformation->ControlState;      //得到当前的状态

  uint8_t *DataBuffer;   uint32_t Length;

  if ((save_wLength == 0) && (ControlState == LAST_IN_DATA)) //如果字符长度为0 且控制状态是最后输入的数据   {     if(Data_Mul_MaxPacketSize == TRUE)         //如果字符的长度是数据包的整数倍     {             Send0LengthData();       ControlState = LAST_IN_DATA;       Data_Mul_MaxPacketSize = FALSE;          //这一次发送0字节 状态转为最后输入阶段     }     else                                     //字符的长度比数据包要小     {                                         //数据已经发送完             ControlState = WAIT_STATUS_OUT;

    #ifdef STM32F10X_CL            PCD_EP_Read (ENDP0, 0, 0);     #endif      #ifndef STM32F10X_CL       vSetEPTxStatus(EP_TX_STALL);           //设置端点的发送状态停止     #endif      }     goto Expect_Status_Out;   }

  Length = pEPinfo->PacketSize;              //得到数据包大小 64字节   ControlState = (save_wLength <= Length) ? LAST_IN_DATA : IN_DATA;//比较大小得到是LAST_IN_DATA还是IN_DATA    18字节<64字节  ControlState = LAST_IN_DATA

  if (Length > save_wLength)   {     Length = save_wLength;   }

  DataBuffer = (*pEPinfo->CopyData)(Length); //DataBuffer指向要复制数据的地址 这个地址是随Usb_wOffset变化的

#ifdef STM32F10X_CL   PCD_EP_Write (ENDP0, DataBuffer, Length); #else                                 //GetEPTxAddr(ENDP0) 得到发送缓冲区相应端点的地址                                  //将DataBuffer中的数据复制到相应的发送缓冲区中     UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length); #endif

  SetEPTxCount(ENDP0, Length);   //设置相应的端点要发送的字节数

  pEPinfo->Usb_wLength -= Length;//等于0   pEPinfo->Usb_wOffset += Length;//偏移到18   vSetEPTxStatus(EP_TX_VALID);  //使能发送端点 只要主机的IN令牌包一来 SIE就会将描述符返回给主机

  USB_StatusOut();                                               //设置接收端点有效 这个实际上使接受也有效,

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

***************(4)**************

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

  if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))//进入到这里

  {     DataStageIn();//第一次取设备描述符只取一次 当前的状态变为WAIT_STATUS_IN 表明设备等待状态过程 主机输出0字节          ControlState = pInformation->ControlState;   }

  else if (ControlState == WAIT_STATUS_IN)            //设置地址状态阶段进入这个程序   {     if ((pInformation->USBbRequest == SET_ADDRESS) &&         (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)))     {       SetDeviceAddress(pInformation->USBwValue0);    //设置使用新的地址       pUser_Standard_Requests->User_SetDeviceAddress();     }     (*pProperty->Process_Status_IN)();     ControlState = STALLED;                         //变为这个状态   }

  else   {     ControlState = STALLED;   }

  pInformation->ControlState = ControlState;

  return Post0_Process(); }

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

  if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))   {           //主机在完成传输前终止传输       ControlState = STALLED;   }   else if ((ControlState == OUT_DATA) || (ControlState == LAST_OUT_DATA))   {     DataStageOut();     ControlState = pInformation->ControlState;   }

  else if (ControlState == WAIT_STATUS_OUT)    //进入到这个里面   {     (*pProperty->Process_Status_OUT)(); //这个函数其实什么也没做   #ifndef STM32F10X_CL     ControlState = STALLED;                 //状态变成了终止发送和接受       

  #endif                   }

    else   {     ControlState = STALLED;   }

  pInformation->ControlState = ControlState;

  return Post0_Process(); }

***************(5)**************

获取设备描述符以后,主机再一次的复位设备,设备又进入初始状态。开始枚举的第二步设置地址。

void NoData_Setup0(void) {   RESULT Result = USB_UNSUPPORT;   uint32_t RequestNo = pInformation->USBbRequest;   uint32_t ControlState;

  if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT))              //设备请求   {

   else if (RequestNo == SET_ADDRESS)                                       /设置地址    {      if ((pInformation->USBwValue0 > 127) || (pInformation->USBwValue1 != 0)          || (pInformation->USBwIndex != 0)          || (pInformation->Current_Configuration != 0))             {        ControlState = STALLED;        goto exit_NoData_Setup0;      }      else      {        Result = USB_SUCCESS;

     #ifdef STM32F10X_CL         SetDeviceAddress(pInformation->USBwValue0);      #endif       }    }

ControlState = WAIT_STATUS_IN;

  USB_StatusIn();//准备好发送0字节的状态数据包 SetEPTxCount(ENDP0, 0);

//vSetEPTxStatus(EP_TX_VALID);建立阶段后直接的进入状态阶段

exit_NoData_Setup0:   pInformation->ControlState = ControlState;   return; }

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

  if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))  //控制状态   {     DataStageIn();//第一次取设备描述符只取一次 当前的状态变为WAIT_STATUS_IN 表明设备等待状态过程 主机输出0字节         ControlState = pInformation->ControlState;   }

  else if (ControlState == WAIT_STATUS_IN)         //设置地址状态阶段进入这个程序   {     if ((pInformation->USBbRequest == SET_ADDRESS) &&         (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)))     {       SetDeviceAddress(pInformation->USBwValue0);  //设置使用新的地址       pUser_Standard_Requests->User_SetDeviceAddress();     }     (*pProperty->Process_Status_IN)();     ControlState = STALLED;                   //终止发送和接受

  }

  else   {     ControlState = STALLED;   }

  pInformation->ControlState = ControlState;

  return Post0_Process(); }

uint8_t Post0_Process(void) { #ifdef STM32F10X_CL    USB_OTG_EP *ep; #endif   SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);   //设置端点0 要接受的字节数

  if (pInformation->ControlState == STALLED)            //这种状态下只接受SETUP命令包   {     vSetEPRxStatus(EP_RX_STALL);                  //终止端点0接受     vSetEPTxStatus(EP_TX_STALL);                       //终止端点0发送

  }

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

***************(6)*************

从新地址获取设备描述符

 

转载请注明原文地址: https://www.6miu.com/read-71206.html

最新回复(0)