主机环境:Windows 7 SP1函数
开发环境:MDK5.14测试
目标板:STM32F103C8T6ui
开发库:STM32F1Cube库和STM32_USB_Device_Libraryspa
如今来分析哈USB器件库代码,先来看usbd_core文件,其头文件只有一些函数声明,没啥可说的,只有一点,以前分析usbd_conf.c文件时里面USB中断回调函数中调用的底层接口都是在usbd_core.h文件中声明的,一样由用户实现的底层接口也是在该文件中声明的,在usbd_core.c文件中实现,该文件是很重要的一个文件,由于全部上层操做最终都会调用该文件中的API来实现。在器件库文档中提到了核心库的做用,以下:指针
第一个分析的函数是USB栈的初始化以及从新初始化,以下:code
/** * @brief USBD_Init * Initializes the device stack and load the class driver * @param pdev: device instance * @param pdesc: Descriptor structure address * @param id: Low level core index * @retval None */ USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev, USBD_DescriptorsTypeDef *pdesc, uint8_t id) { /* Check whether the USB Host handle is valid */ if(pdev == NULL) { USBD_ErrLog("Invalid Device handle"); return USBD_FAIL; } /* Unlink previous class*/ if(pdev->pClass != NULL) { pdev->pClass = NULL; } /* Assign USBD Descriptors */ if(pdesc != NULL) { pdev->pDesc = pdesc; } /* Set Device initial State */ pdev->dev_state = USBD_STATE_DEFAULT; pdev->id = id; /* Initialize low level driver */ USBD_LL_Init(pdev); return USBD_OK; } /** * @brief USBD_DeInit * Re-Initialize th device library * @param pdev: device instance * @retval status: status */ USBD_StatusTypeDef USBD_DeInit(USBD_HandleTypeDef *pdev) { /* Set Default State */ pdev->dev_state = USBD_STATE_DEFAULT; /* Free Class Resources */ pdev->pClass->DeInit(pdev, pdev->dev_config); /* Stop the low level driver */ USBD_LL_Stop(pdev); /* Initialize low level driver */ USBD_LL_DeInit(pdev); return USBD_OK; }USB初始化函数很简单,将USB句柄的设备类指针置NULL,同时将USB的描述符加载上去,将USB设备状态置为默认状态,该函数里面的id目前不清楚是做何用,最后调用USB_LL_Init()函数来初始化底层驱动。USB器件库中USB设备有四种状态,定义在usbd_def.h文件中,以下:
/* Device Status */ #define USBD_STATE_DEFAULT 1 #define USBD_STATE_ADDRESSED 2 #define USBD_STATE_CONFIGURED 3 #define USBD_STATE_SUSPENDED 4默认状态、地址状态、配置状态、挂起状态。在USB2.0协议文档的第9章节中规定了USB设备的6种状态:链接状态、上电状态、默认状态、地址状态、配置状态、挂起状态,六者之间的关系图以下所示:
在USB库中是省略了链接和上电两个状态,剩下四种状态的说明能够在USB2.0协议的第九章节找到,由此能够看出USB2.0协议中第九章节有多重要了。在USB分配地址以前其使用默认地址,处在默认状态下的USB设备不能响应正常的请求,当USB设备分配了惟一的地址后即进入地址状态,响应正常请求,USB设备配置完成后进入配置状态,USB设备在指定时间长度内没有检测到总线通讯时会进入挂起状态,但会保持任何内部状态,包括地址和配置。在USB从新初始化函数中,须要释放类资源,且中止USB底层驱动,从新初始化底层驱动。接着是注册类函数:blog
/** * @brief USBD_RegisterClass * Link class driver to Device Core. * @param pDevice : Device Handle * @param pclass: Class handle * @retval USBD Status */ USBD_StatusTypeDef USBD_RegisterClass(USBD_HandleTypeDef *pdev, USBD_ClassTypeDef *pclass) { USBD_StatusTypeDef status = USBD_OK; if(pclass != 0) { /* link the class to the USB Device handle */ pdev->pClass = pclass; status = USBD_OK; } else { USBD_ErrLog("Invalid Class handle"); status = USBD_FAIL; } return status; }
注册设备类函数也很简单,把设备类指针传递给USB设备句柄便可,经过指针USB句柄包含了咱们所用的全部资源,接着来看USB的一些基本操做,以下:接口
/** * @brief USBD_Start * Start the USB Device Core. * @param pdev: Device Handle * @retval USBD Status */ USBD_StatusTypeDef USBD_Start (USBD_HandleTypeDef *pdev) { /* Start the low level driver */ USBD_LL_Start(pdev); return USBD_OK; } /** * @brief USBD_Stop * Stop the USB Device Core. * @param pdev: Device Handle * @retval USBD Status */ USBD_StatusTypeDef USBD_Stop (USBD_HandleTypeDef *pdev) { /* Free Class Resources */ pdev->pClass->DeInit(pdev, pdev->dev_config); /* Stop the low level driver */ USBD_LL_Stop(pdev); return USBD_OK; } /** * @brief USBD_RunTestMode * Launch test mode process * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_RunTestMode (USBD_HandleTypeDef *pdev) { return USBD_OK; } /** * @brief USBD_SetClassConfig * Configure device and start the interface * @param pdev: device instance * @param cfgidx: configuration index * @retval status */ USBD_StatusTypeDef USBD_SetClassConfig(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { USBD_StatusTypeDef ret = USBD_FAIL; if(pdev->pClass != NULL) { /* Set configuration and Start the Class*/ if(pdev->pClass->Init(pdev, cfgidx) == 0) { ret = USBD_OK; } } return ret; } /** * @brief USBD_ClrClassConfig * Clear current configuration * @param pdev: device instance * @param cfgidx: configuration index * @retval status: USBD_StatusTypeDef */ USBD_StatusTypeDef USBD_ClrClassConfig(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { /* Clear configuration and De-initialize the Class process*/ pdev->pClass->DeInit(pdev, cfgidx); return USBD_OK; } /** * @brief USBD_LL_Reset * Handle Reset event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_SetSpeed(USBD_HandleTypeDef *pdev, USBD_SpeedTypeDef speed) { pdev->dev_speed = speed; return USBD_OK; }其中USBD_Start和USBD_Stop跟USBD的初始化相似都是调用usbd_conf中的底层基本操做,USBD_RunTestMode()函数为空,代表不支持测试模式,测试模式在USB2.0协议文档有说起,既然这里不支持就么有去细研究该功能。另外两个函数USBD_SetClassConfig()、USBD_ClrClassConfig()函数则是跟USB设备类相关,这里咱们尚未分析到USB设备类中,因此也略过,知道其功能便可。最后有一个USBD_LL_SetSpeed()函数,USB通讯有三种通讯速度:低速、全速、高速,STM32F103C8T6支持全速模式,USB速度的定义以下:
/* Following USB Device Speed */ typedef enum { USBD_SPEED_HIGH = 0, USBD_SPEED_FULL = 1, USBD_SPEED_LOW = 2, }USBD_SpeedTypeDef;usbd_core.c中剩下的一些函数体则是在usbd_conf.cUSB中断回调函数中调用的USB通讯处理的真正实现者,以下:
/** * @brief USBD_SetupStage * Handle the setup stage * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup) { USBD_ParseSetupRequest(&pdev->request, psetup); pdev->ep0_state = USBD_EP0_SETUP; pdev->ep0_data_len = pdev->request.wLength; switch (pdev->request.bmRequest & 0x1F) { case USB_REQ_RECIPIENT_DEVICE: USBD_StdDevReq (pdev, &pdev->request); break; case USB_REQ_RECIPIENT_INTERFACE: USBD_StdItfReq(pdev, &pdev->request); break; case USB_REQ_RECIPIENT_ENDPOINT: USBD_StdEPReq(pdev, &pdev->request); break; default: USBD_LL_StallEP(pdev , pdev->request.bmRequest & 0x80); break; } return USBD_OK; } /** * @brief USBD_DataOutStage * Handle data OUT stage * @param pdev: device instance * @param epnum: endpoint index * @retval status */ USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev , uint8_t epnum, uint8_t *pdata) { USBD_EndpointTypeDef *pep; if(epnum == 0) { pep = &pdev->ep_out[0]; if ( pdev->ep0_state == USBD_EP0_DATA_OUT) { if(pep->rem_length > pep->maxpacket) { pep->rem_length -= pep->maxpacket; USBD_CtlContinueRx (pdev, pdata, MIN(pep->rem_length ,pep->maxpacket)); } else { if((pdev->pClass->EP0_RxReady != NULL)&& (pdev->dev_state == USBD_STATE_CONFIGURED)) { pdev->pClass->EP0_RxReady(pdev); } USBD_CtlSendStatus(pdev); } } } else if((pdev->pClass->DataOut != NULL)&& (pdev->dev_state == USBD_STATE_CONFIGURED)) { pdev->pClass->DataOut(pdev, epnum); } return USBD_OK; } /** * @brief USBD_DataInStage * Handle data in stage * @param pdev: device instance * @param epnum: endpoint index * @retval status */ USBD_StatusTypeDef USBD_LL_DataInStage(USBD_HandleTypeDef *pdev ,uint8_t epnum, uint8_t *pdata) { USBD_EndpointTypeDef *pep; if(epnum == 0) { pep = &pdev->ep_in[0]; if ( pdev->ep0_state == USBD_EP0_DATA_IN) { if(pep->rem_length > pep->maxpacket) { pep->rem_length -= pep->maxpacket; USBD_CtlContinueSendData (pdev, pdata, pep->rem_length); /* Prepare endpoint for premature end of transfer */ USBD_LL_PrepareReceive (pdev, 0, NULL, 0); } else { /* last packet is MPS multiple, so send ZLP packet */ if((pep->total_length % pep->maxpacket == 0) && (pep->total_length >= pep->maxpacket) && (pep->total_length < pdev->ep0_data_len )) { USBD_CtlContinueSendData(pdev , NULL, 0); pdev->ep0_data_len = 0; /* Prepare endpoint for premature end of transfer */ USBD_LL_PrepareReceive (pdev, 0, NULL, 0); } else { if((pdev->pClass->EP0_TxSent != NULL)&& (pdev->dev_state == USBD_STATE_CONFIGURED)) { pdev->pClass->EP0_TxSent(pdev); } USBD_CtlReceiveStatus(pdev); } } } if (pdev->dev_test_mode == 1) { USBD_RunTestMode(pdev); pdev->dev_test_mode = 0; } } else if((pdev->pClass->DataIn != NULL)&& (pdev->dev_state == USBD_STATE_CONFIGURED)) { pdev->pClass->DataIn(pdev, epnum); } return USBD_OK; } /** * @brief USBD_LL_Reset * Handle Reset event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_Reset(USBD_HandleTypeDef *pdev) { /* Open EP0 OUT */ USBD_LL_OpenEP(pdev, 0x00, USBD_EP_TYPE_CTRL, USB_MAX_EP0_SIZE); pdev->ep_out[0].maxpacket = USB_MAX_EP0_SIZE; /* Open EP0 IN */ USBD_LL_OpenEP(pdev, 0x80, USBD_EP_TYPE_CTRL, USB_MAX_EP0_SIZE); pdev->ep_in[0].maxpacket = USB_MAX_EP0_SIZE; /* Upon Reset call user call back */ pdev->dev_state = USBD_STATE_DEFAULT; if (pdev->pClassData) pdev->pClass->DeInit(pdev, pdev->dev_config); return USBD_OK; } /** * @brief USBD_Suspend * Handle Suspend event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_Suspend(USBD_HandleTypeDef *pdev) { pdev->dev_old_state = pdev->dev_state; pdev->dev_state = USBD_STATE_SUSPENDED; return USBD_OK; } /** * @brief USBD_Resume * Handle Resume event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_Resume(USBD_HandleTypeDef *pdev) { pdev->dev_state = pdev->dev_old_state; return USBD_OK; } /** * @brief USBD_SOF * Handle SOF event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef *pdev) { if(pdev->dev_state == USBD_STATE_CONFIGURED) { if(pdev->pClass->SOF != NULL) { pdev->pClass->SOF(pdev); } } return USBD_OK; } /** * @brief USBD_IsoINIncomplete * Handle iso in incomplete event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum) { return USBD_OK; } /** * @brief USBD_IsoOUTIncomplete * Handle iso out incomplete event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum) { return USBD_OK; } /** * @brief USBD_DevConnected * Handle device connection event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_DevConnected(USBD_HandleTypeDef *pdev) { return USBD_OK; } /** * @brief USBD_DevDisconnected * Handle device disconnection event * @param pdev: device instance * @retval status */ USBD_StatusTypeDef USBD_LL_DevDisconnected(USBD_HandleTypeDef *pdev) { /* Free Class Resources */ pdev->dev_state = USBD_STATE_DEFAULT; pdev->pClass->DeInit(pdev, pdev->dev_config); return USBD_OK; }虽然函数有些多,但看具体函数的代码量就能够知道哪些函数是重要的,第一个函数是USBD_LL_SetupStage(),USB请求分为三个阶段:Setup阶段、可选的数据阶段、状态阶段。该函数用于处理Setup阶段,解析USB主机发来的请求,调用USBD_ParseSetupRequest()函数来获取setup请求,并赋给pdev->request变量,该函数实现以下:
/** * @brief USBD_ParseSetupRequest * Copy buffer into setup structure * @param pdev: device instance * @param req: usb request * @retval None */ void USBD_ParseSetupRequest(USBD_SetupReqTypedef *req, uint8_t *pdata) { req->bmRequest = *(uint8_t *) (pdata); req->bRequest = *(uint8_t *) (pdata + 1); req->wValue = SWAPBYTE (pdata + 2); req->wIndex = SWAPBYTE (pdata + 4); req->wLength = SWAPBYTE (pdata + 6); } #define SWAPBYTE(addr) (((uint16_t)(*((uint8_t *)(addr)))) + \ (((uint16_t)(*(((uint8_t *)(addr)) + 1))) << 8))函数实现很简单即获取Setup的8个成员变量值,至于Setup包中8个数据来源在usbd_conf.c文件中传递的是hpcd->Setup,其数据来源是在Cube库中USB的实现的,所以在这里并无关心。在获取完Setup包数据后将端点0状态置为USBD_EP0_SETUP,端点0比较重要是由于其是USB默认的控制端点用于接收USBSetup请求数据,在USB器件库中规定了端点0的几种状态,以下:
/* EP0 State */ #define USBD_EP0_IDLE 0 #define USBD_EP0_SETUP 1 #define USBD_EP0_DATA_IN 2 #define USBD_EP0_DATA_OUT 3 #define USBD_EP0_STATUS_IN 4 #define USBD_EP0_STATUS_OUT 5 #define USBD_EP0_STALL 6
能够看出端点0的状态跟USB请求息息相关。接着使用ep0_data_len来存储该Setup请求的数据长度,并根据bmRequest数据值来检测该请求的接收者,在USB2.0协议中规定了请求的接收者有三个:设备、接口、端点。根据接收者的不一样调用不一样的函数实现体,这些实现体在另外一个文件usbd_ctlreq.c中实现,因此这里不细说,等分析usbd_ctlreq.c文件时再细说,若是接收者不是以上三种则调用USBD_LL_StallEP()来将端点设置一个中止条件。USBD_LL_DataOutStage()和USBD_LL_DataInStage()是最重要的两个函数且代码行数较多,放到最后分析,先分析另外几个比较简单的函数,USBD_LL_Reset()函数是从新初始化,这里是从新设置了端点0的属性,并把设备状态置为默认状态,并调用相应类的DeInit()函数来从新初始化设备类。USB的挂起和唤醒函数更简单,只是设置设备状态便可。USBD_LL_SOF()函数是发出起始帧信号,SOF是一个数据包,而EOF是一种电平状态。后面有一个设备断开链接的实现,跟复位有些相似,将设备状态置为默认状态,并从新初始化设备类。如今来分析两个重要的函数,首先是USBD_LL_DataOutStage(),须要注意的是Setup阶段是只跟端点0相关,而数据阶段是能够跟每个端点相关的,由于任何端点均可以传输数据,因此该函数的参数中有epnum来传递传输数据的端点号,在函数实现中能够看到若是传递的epnum不是0,则代表是设备类中的端点传递数据,若是设备处于配置状态且设备类的DataOut指针非空则执行设备类中的DataOut函数,若是epnum为0则是端点0上的数据,DataOutStage上的数据是USB模块接收USB主机发来的,要明白其数据传输的方向。每一个端点都有设置其最大包大小即maxpacket,端点接收的数据大小必定是小于等于maxpacket的,当须要接收远多于maxpacket的数据时是须要分包发送/接收的,一个很形象的例子以下:ip
该图是STM32论坛中培训资料中获得的,根据该图能够方便于咱们分析DataOutStage以及DataInStage,在DataOutStage的处理中是获取该0号端点,且该端点处于DATA_OUT状态,端点的rem_length变量存储的是当次接收的数据总长度即在Setup函数中request.wLength,而maxpacket的值是在打开该端点时传递进来的,当咱们接收到的数据长度大于自身的最大包大小时,代表咱们还有数据须要继续接收,这里要注意的是该函数实际是USB中断函数中的DataStage的回调函数,即该函数执行时已经接收到了一包数据,所以这里才调用USBD_CtlContinueRx()即继续接收,这个跟串口的接收中断较相似,也由此,调用USBD_CtlContinueRx()中的参数是rem_length和maxpacket中的小值,当最后数据接收完毕时rem_length的值是小于等于maxpacket,就看传输的数据量是不是maxpacket的整数倍了。数据接收完成时这里调用了设备类的EP0_RxReady回调函数即交由对应的设备类对所收到的数据进行处理,对于本例的VCP类说即交由CDC类的接口文件中的CDC_Itf_Control()函数来处理(设置设备串口属性),最后调用USBD_CtlSendStatus()函数来执行请求的第三阶段:状态阶段。该函数咱们在后面另分析。资源
有了分析DataOutStage的基础,DataInStage就容易分析多了,二者是相反的过程,该函数是发送数据到USB主机,首先当传输数据的端点是非0端点时调用相应设备类的DataIn函数进行处理,当传输数据的端点是0端点时且端点0处于DATA_IN状态,同理此处的rem_length一样和Setup阶段的request.wLength相等,且该函数执行时已经有一包数据发送出去,所以这里更新完rem_length后继续发送数据且使用USBD_LL_PrepareReceive()接收USB主机发来的应答信息。当rem_length小于等于maxpacket时代表数据已经发送完毕,若是所须要发送的数据量是maxpacket的整数倍这里须要发送一个0字节数据包来通知USB主机数据发送完毕。这里一直不理解的是下面这条语句
(pep->total_length < pdev->ep0_data_len )由于按我的理解这两个值应该是相等的,经过检索可知ep0_data_len只有一处赋值即Setup阶段的request.wLength,而total_length的值在最开始的发送时与rem_length值相等也即为本次发送的数据长度值,不管怎么想两者都应该是相等才对。ep0_data_len的值只有在这里清零,下次进入该函数时total_length值即大于ep0_data_len(所以我的以为这里应该是ep0_data_len和0的判断而不是和total_length判断),数据发送完毕后,若是EP0_TxSent回调函数不为空则执行该回调,最后接收USB主机发来的状态信息。当端点0的状态不为DATA_IN时这里有个测试模式的调用,因为没有使能测试模式,因此这里不关心,若是有兴趣的话能够研究哈。至此,usbd_core文件分析完毕,USB器件库的核心文件还剩下两个usbd_ioreq和usbd_ctlreq。