历史上的今天
今天是:2024年09月14日(星期六)
2018年09月14日 | STM32 USB学习笔记8
2018-09-14 来源:eefocus
主机环境:Windows 7 SP1
开发环境:MDK5.14
目标板:STM32F103C8T6
开发库:STM32F1Cube库和STM32_USB_Device_Library
现在分析USB器件库核心文件的最后一个文件usbd_ctlreq,该文件提供了标准请求的处理,跟USB2.0协议的第九章节紧密关联。在Setup阶段根据bmRequest字段的内容分为:设备请求、接口请求、端点请求。并根据不同的请求调用不同的函数体,USB2.0协议中定义的标清请求有以下几种
通过第一个竖栏可以看出同一个请求代码可以对应多个接收者,例如CLEAR_FEATURE请求可以是设备请求也可以是接口请求,同样也可以是端点请求。对应的各个请求代码,如下
在usbd_def.h文件中可以找到与之对应的定义,如下:
#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
#define USB_REQ_SET_ADDRESS 0x05
#define USB_REQ_GET_DESCRIPTOR 0x06
#define USB_REQ_SET_DESCRIPTOR 0x07
#define USB_REQ_GET_CONFIGURATION 0x08
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_REQ_GET_INTERFACE 0x0A
#define USB_REQ_SET_INTERFACE 0x0B
#define USB_REQ_SYNCH_FRAME 0x0C
首先看一下标准设备请求的处理,
/**
* @brief USBD_StdDevReq
* Handle standard usb device requests
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
USBD_StatusTypeDef USBD_StdDevReq (USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef *req)
{
USBD_StatusTypeDef ret = USBD_OK;
switch (req->bRequest)
{
case USB_REQ_GET_DESCRIPTOR:
USBD_GetDescriptor (pdev, req) ;
break;
case USB_REQ_SET_ADDRESS:
USBD_SetAddress(pdev, req);
break;
case USB_REQ_SET_CONFIGURATION:
USBD_SetConfig (pdev , req);
break;
case USB_REQ_GET_CONFIGURATION:
USBD_GetConfig (pdev , req);
break;
case USB_REQ_GET_STATUS:
USBD_GetStatus (pdev , req);
break;
case USB_REQ_SET_FEATURE:
USBD_SetFeature (pdev , req);
break;
case USB_REQ_CLEAR_FEATURE:
USBD_ClrFeature (pdev , req);
break;
default:
USBD_CtlError(pdev , req);
break;
}
return ret;
}
符合标准的设备请求通过Table 9-3可以看出一共有8种,这里只处理了7种,SET_DESCRIPTOR是没有处理的,当请求不是这7种请求的任何一种时通过USBD_CtlError()函数回应给USB主机一个STALL表明请求错误发生。首先看第一个请求获取描述符请求的处理,如下:
/**
* @brief USBD_GetDescriptor
* Handle Get Descriptor requests
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_GetDescriptor(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
uint16_t len;
uint8_t *pbuf;
switch (req->wValue >> 8)
{
#if (USBD_LPM_ENABLED == 1)
case USB_DESC_TYPE_BOS:
pbuf = pdev->pDesc->GetBOSDescriptor(pdev->dev_speed, &len);
break;
#endif
case USB_DESC_TYPE_DEVICE:
pbuf = pdev->pDesc->GetDeviceDescriptor(pdev->dev_speed, &len);
break;
case USB_DESC_TYPE_CONFIGURATION:
if(pdev->dev_speed == USBD_SPEED_HIGH )
{
pbuf = (uint8_t *)pdev->pClass->GetHSConfigDescriptor(&len);
pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
}
else
{
pbuf = (uint8_t *)pdev->pClass->GetFSConfigDescriptor(&len);
pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
}
break;
case USB_DESC_TYPE_STRING:
switch ((uint8_t)(req->wValue))
{
case USBD_IDX_LANGID_STR:
pbuf = pdev->pDesc->GetLangIDStrDescriptor(pdev->dev_speed, &len);
break;
case USBD_IDX_MFC_STR:
pbuf = pdev->pDesc->GetManufacturerStrDescriptor(pdev->dev_speed, &len);
break;
case USBD_IDX_PRODUCT_STR:
pbuf = pdev->pDesc->GetProductStrDescriptor(pdev->dev_speed, &len);
break;
case USBD_IDX_SERIAL_STR:
pbuf = pdev->pDesc->GetSerialStrDescriptor(pdev->dev_speed, &len);
break;
case USBD_IDX_CONFIG_STR:
pbuf = pdev->pDesc->GetConfigurationStrDescriptor(pdev->dev_speed, &len);
break;
case USBD_IDX_INTERFACE_STR:
pbuf = pdev->pDesc->GetInterfaceStrDescriptor(pdev->dev_speed, &len);
break;
default:
#if (USBD_SUPPORT_USER_STRING == 1)
pbuf = pdev->pClass->GetUsrStrDescriptor(pdev, (req->wValue) , &len);
break;
#else
USBD_CtlError(pdev , req);
return;
#endif
}
break;
case USB_DESC_TYPE_DEVICE_QUALIFIER:
if(pdev->dev_speed == USBD_SPEED_HIGH )
{
pbuf = (uint8_t *)pdev->pClass->GetDeviceQualifierDescriptor(&len);
break;
}
else
{
USBD_CtlError(pdev , req);
return;
}
case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
if(pdev->dev_speed == USBD_SPEED_HIGH )
{
pbuf = (uint8_t *)pdev->pClass->GetOtherSpeedConfigDescriptor(&len);
pbuf[1] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
break;
}
else
{
USBD_CtlError(pdev , req);
return;
}
default:
USBD_CtlError(pdev , req);
return;
}
if((len != 0)&& (req->wLength != 0))
{
len = MIN(len , req->wLength);
USBD_CtlSendData (pdev,
pbuf,
len);
}
}
也是一大串switch-case语句,这样的代码虽然多但容易分析,在GET_DESCRIPTOR请求中,根据Table 9-3可知wValue字段的含义是描述符类型和描述符索引,在USB2.0协议的9.4.3章节中可以看到wValue的高字节标记了描述符的类型,wValue的低字节标记了描述符的索引。在USBD_GetDescriptor()函数中首先是检测描述符类型,第一个分支是设备描述符,通过pdev句柄的pDesc指针下的GetDeviceDescriptor指针调用所需要的函数即我们之前分析过的usbd_desc.c文件中的对应函数。第二个分支是获取配置描述符,这个是通过设备类指针获取的,放在后面分析,且这里分出了高速和全速模式。接着一个大分支是获取字符串描述符,这里就需要用到wValue的低字节描述符索引来细分各个字符串描述符,获取字符串描述符的处理跟获取设备描述符类似,不再赘述,索引号错误返回请求错误状态。DEVICE_QUALIFIER和OTHER_SPEED_CONFIGURATION描述符的获取都是在高速模式下才会有,由于STM32F103C8T6只支持全速模式,因此这里不再分析,直接返回请求错误。在最后发送这些数据到USB主机时有判断len和wLenth的大小,且返回的数据长度是两者的最小值,USB2.0协议的9.3.5章节中提到在一个输入请求中设备返回的数据不可以大于wLength字段指示的值,在一个输出请求中wLength字段的值一定是USB主机发出的数据长度值。如下:
至此,描述符的获取分析完毕,返回到标准设备请求函数USBD_StdDevReq()中查看第二个请求设置地址,如下:
/**
* @brief USBD_SetAddress
* Set device address
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_SetAddress(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
uint8_t dev_addr;
if ((req->wIndex == 0) && (req->wLength == 0))
{
dev_addr = (uint8_t)(req->wValue) & 0x7F;
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
USBD_CtlError(pdev , req);
}
else
{
pdev->dev_address = dev_addr;
USBD_LL_SetUSBAddress(pdev, dev_addr);
USBD_CtlSendStatus(pdev);
if (dev_addr != 0)
{
pdev->dev_state = USBD_STATE_ADDRESSED;
}
else
{
pdev->dev_state = USBD_STATE_DEFAULT;
}
}
}
else
{
USBD_CtlError(pdev , req);
}
}
一个USB总线上可以支持128个地址,地址0作为默认地址,且每个设备地址是唯一的,当USB主机为USB设备分配完地址后,设备即进入地址状态,等待配置完成,由Table 9-3可以看出SET_ADDRESS请求中wIndex和wLength字段值为0,wValue字段存储USB设备地址值,当设备处于配置状态时表明其已经分配了设备地址,因此返回请求错误,其他状态下通过USBD_LL_SetUSBAddress()函数使能该地址,并发送状态信息给USB主机(这里是没有数据阶段,只有状态阶段),当地址不为0时,标记设备进入地址状态。返回到USBD_StdDevReq()函数中查看SET_CONFIGURATION请求,如下:
/**
* @brief USBD_SetConfig
* Handle Set device configuration request
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_SetConfig(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
static uint8_t cfgidx;
cfgidx = (uint8_t)(req->wValue);
if (cfgidx > USBD_MAX_NUM_CONFIGURATION )
{
USBD_CtlError(pdev , req);
}
else
{
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
if (cfgidx)
{
pdev->dev_config = cfgidx;
pdev->dev_state = USBD_STATE_CONFIGURED;
if(USBD_SetClassConfig(pdev , cfgidx) == USBD_FAIL)
{
USBD_CtlError(pdev , req);
return;
}
USBD_CtlSendStatus(pdev);
}
else
{
USBD_CtlSendStatus(pdev);
}
break;
case USBD_STATE_CONFIGURED:
if (cfgidx == 0)
{
pdev->dev_state = USBD_STATE_ADDRESSED;
pdev->dev_config = cfgidx;
USBD_ClrClassConfig(pdev , cfgidx);
USBD_CtlSendStatus(pdev);
}
else if (cfgidx != pdev->dev_config)
{
/* Clear old configuration */
USBD_ClrClassConfig(pdev , pdev->dev_config);
/* set new configuration */
pdev->dev_config = cfgidx;
if(USBD_SetClassConfig(pdev , cfgidx) == USBD_FAIL)
{
USBD_CtlError(pdev , req);
return;
}
USBD_CtlSendStatus(pdev);
}
else
{
USBD_CtlSendStatus(pdev);
}
break;
default:
USBD_CtlError(pdev , req);
break;
}
}
}
代码稍微长了一些,该请求下wIndex和wLength字段的值同样为0,wValue字段值为配置ID,USB2.0协议中有关该请求的描述如下:
本例在usbd_conf.h文件中限制了最大配置数为1,该段代码中没有对wIndex和wLength进行限制,不科学那。。。当设备处于地址状态时可以对设备进行配置,记录此时的配置id号,切换设备状态,调用usbd_core.c文件中的USBD_SetClassConfig()函数执行真正的配置工作,该函数会链接到设备类的处理函数中。配置成功后发送状态信息到USB主机。如果设备处于配置状态时,发送0号配置ID设备进入地址状态,发送非0号配置ID且跟当前配置ID不同时重新加载该配置并发送状态信息到USB主机。设置配置流程比较简单,最重要的是它设备类中配置的实现。返回到标准设备请求函数中查看GET_CONFIGURATION请求,该请求返回设备的配置值,其中wValue、wIndex均为0,wLength为1,获取配置的实现如下:
/**
* @brief USBD_GetConfig
* Handle Get device configuration request
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_GetConfig(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
if (req->wLength != 1)
{
USBD_CtlError(pdev , req);
}
else
{
switch (pdev->dev_state )
{
case USBD_STATE_ADDRESSED:
pdev->dev_default_config = 0;
USBD_CtlSendData (pdev,
(uint8_t *)&pdev->dev_default_config,
1);
break;
case USBD_STATE_CONFIGURED:
USBD_CtlSendData (pdev,
(uint8_t *)&pdev->dev_config,
1);
break;
default:
USBD_CtlError(pdev , req);
break;
}
}
}
这里同样没有对wIndex和wIndex进行检测,只对wLength进行了检测,配置ID只占用了一个字节,因此返回的数据长度为1,设备处于地址状态时说明没有配置生效,因此返回默认的配置号0,而处于配置状态时返回当前设备的配置ID,该函数也比较简单。查看下一个标准设备请求GET_STATUS,如下:
/**
* @brief USBD_GetStatus
* Handle Get Status request
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_GetStatus(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
case USBD_STATE_CONFIGURED:
#if ( USBD_SELF_POWERED == 1)
pdev->dev_config_status = USB_CONFIG_SELF_POWERED;
#else
pdev->dev_config_status = 0;
#endif
if (pdev->dev_remote_wakeup)
{
pdev->dev_config_status |= USB_CONFIG_REMOTE_WAKEUP;
}
USBD_CtlSendData (pdev,
(uint8_t *)& pdev->dev_config_status,
2);
break;
default :
USBD_CtlError(pdev , req);
break;
}
}
GET_STATUS请求的结构如下:
wValue和wLength分别为0和2,该请求支持三个接收者,这里我们只看对设备的请求,其返回的信息如下:
设备返回的状态只有两个有效位:是否支持自供电,是否支持远程唤醒,其中Self Powered特性是不能够通过SetFeature()或ClearFeature()请求修改的。在本例中设备支持自供电模式,因此该字段为1,如果支持远程唤醒则相应字段也为1,最后把设备状态信息发送到USB主机上。
接着查看SET_FEATURE请求的处理,如下:
/**
* @brief USBD_SetFeature
* Handle Set device feature request
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_SetFeature(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
if (req->wValue == USB_FEATURE_REMOTE_WAKEUP)
{
pdev->dev_remote_wakeup = 1;
pdev->pClass->Setup (pdev, req);
USBD_CtlSendStatus(pdev);
}
}
该请求结构如下:
同样支持三个接收者,其中特性选择器的取值如下:
对于设备请求来说,有1和2可选,其中TEST_MODE中还有更多测试可选,如下:
但本例是不支持测试模式的,因此这里只关心远程唤醒特性,该段代码没有对参数进行检测也没有对设备状态进行检测,感觉不够严谨了,当使能远程唤醒时调用设备类的相应函数进行处理,并发送状态信息到USB主机。最后看一下CLEAR_FEATURE请求的处理,
/**
* @brief USBD_ClrFeature
* Handle clear device feature request
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
static void USBD_ClrFeature(USBD_HandleTypeDef *pdev ,
USBD_SetupReqTypedef *req)
{
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
case USBD_STATE_CONFIGURED:
if (req->wValue == USB_FEATURE_REMOTE_WAKEUP)
{
pdev->dev_remote_wakeup = 0;
pdev->pClass->Setup (pdev, req);
USBD_CtlSendStatus(pdev);
}
break;
default :
USBD_CtlError(pdev , req);
break;
}
}
CLEAR_FEATURE请求跟SET_FEATURE请求相反,处理流程相同,但这里比之SET_FEATURE请求处理要严谨多了,对设备状态进行了检测。至此,标准设备请求的处理就分析完毕了,接着产看标准接口请求的处理,
/**
* @brief USBD_StdItfReq
* Handle standard usb interface requests
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
USBD_StatusTypeDef USBD_StdItfReq (USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef *req)
{
USBD_StatusTypeDef ret = USBD_OK;
switch (pdev->dev_state)
{
case USBD_STATE_CONFIGURED:
if (LOBYTE(req->wIndex) <= USBD_MAX_NUM_INTERFACES)
{
pdev->pClass->Setup (pdev, req);
if((req->wLength == 0)&& (ret == USBD_OK))
{
USBD_CtlSendStatus(pdev);
}
}
else
{
USBD_CtlError(pdev , req);
}
break;
default:
USBD_CtlError(pdev , req);
break;
}
return USBD_OK;
}
标准接口请求一共有5个,这里处理比较简单,只对调用设备类的处理函数来处理某些请求,其他请求均返回请求错误状态。现在只剩下标准端点请求的处理了,也是本文件的最后一个需要分析的函数,如下:
/**
* @brief USBD_StdEPReq
* Handle standard usb endpoint requests
* @param pdev: device instance
* @param req: usb request
* @retval status
*/
USBD_StatusTypeDef USBD_StdEPReq (USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef *req)
{
uint8_t ep_addr;
USBD_StatusTypeDef ret = USBD_OK;
USBD_EndpointTypeDef *pep;
ep_addr = LOBYTE(req->wIndex);
/* Check if it is a class request */
if ((req->bmRequest & 0x60) == 0x20)
{
pdev->pClass->Setup (pdev, req);
return USBD_OK;
}
switch (req->bRequest)
{
case USB_REQ_SET_FEATURE :
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
if ((ep_addr != 0x00) && (ep_addr != 0x80))
{
USBD_LL_StallEP(pdev , ep_addr);
}
break;
case USBD_STATE_CONFIGURED:
if (req->wValue == USB_FEATURE_EP_HALT)
{
if ((ep_addr != 0x00) && (ep_addr != 0x80))
{
USBD_LL_StallEP(pdev , ep_addr);
}
}
pdev->pClass->Setup (pdev, req);
USBD_CtlSendStatus(pdev);
break;
default:
USBD_CtlError(pdev , req);
break;
}
break;
case USB_REQ_CLEAR_FEATURE :
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
if ((ep_addr != 0x00) && (ep_addr != 0x80))
{
USBD_LL_StallEP(pdev , ep_addr);
}
break;
case USBD_STATE_CONFIGURED:
if (req->wValue == USB_FEATURE_EP_HALT)
{
if ((ep_addr & 0x7F) != 0x00)
{
USBD_LL_ClearStallEP(pdev , ep_addr);
pdev->pClass->Setup (pdev, req);
}
USBD_CtlSendStatus(pdev);
}
break;
default:
USBD_CtlError(pdev , req);
break;
}
break;
case USB_REQ_GET_STATUS:
switch (pdev->dev_state)
{
case USBD_STATE_ADDRESSED:
if ((ep_addr & 0x7F) != 0x00)
{
USBD_LL_StallEP(pdev , ep_addr);
}
break;
case USBD_STATE_CONFIGURED:
pep = ((ep_addr & 0x80) == 0x80) ? &pdev->ep_in[ep_addr & 0x7F]:\
&pdev->ep_out[ep_addr & 0x7F];
if(USBD_LL_IsStallEP(pdev, ep_addr))
{
pep->status = 0x0001;
}
else
{
pep->status = 0x0000;
}
USBD_CtlSendData (pdev,
(uint8_t *)&pep->status,
2);
break;
default:
USBD_CtlError(pdev , req);
break;
}
break;
default:
break;
}
return ret;
}
代码比较长,标准端点请求一共有4个:CLEAR_FEATURE、GET_STATUS、SET_FEATURE、SYNCH_FRAME。这几个请求中wIndex字段的值为端点号,因此首先通过该字段获取端点号,根据bmRequest来检测是否是类请求,如果是类请求则调用设备类的相应函数进行处理,如果不是类请求则正常处理,这里只处理了前三个标准请求,首先看SET_FEATURE请求,在之前的特性选择器中端点对应的只有EP_HALT,在地址状态下如果指定的端点号非0,则设备回应请求错误,在配置状态下该请求有效,并调用设备类的处理函数实现特性的设置,CLEAR_FEATURE请求的处理跟SET_FEATURE类似,而GET_STATUS请求返回的信息如下:
有一个有效位,检测所请求端点的状态发送给USB主机,至此,usbd_ctlreq文件分析完毕,该文件需对照USB2.0协议的第9章节来分析,现在就只剩下USB的设备类文件没有分析了,下次分析usbd_cdc文件。
下一篇:STM32 USB学习笔记2
史海拾趣
|
2440WINCE下做用中断做了个按键驱动,却不知道在哪里加延时去抖. 2440WINCE下做用中断做了个按键驱动,却不知道在哪里加延时去抖. 在流驱动里开了一个线程 DWORD UserKeyProcessThread(void) { while(1) { &n ...… 查看全部问答> |
|
我是用platform builder 用wince自带的CETK,来测试刚编程完的驱动程序,然后创建主干Tux模块,最后在编译工程的时候出现了错误: :cannot open program database: c:/wince500\\pbworkspaces\\HID3\\WINCE500\\maistonei ...… 查看全部问答> |
|
8253处于方式5硬件触发选通方式下,对于Gate而言是要每次上升沿到来才会计数?还是说Gate从低电平调到高电平后,只要维持在高电平就会计数? 请高人指点一下,谢谢!… 查看全部问答> |
|
我在试着在PPC做一个改变PIN2码的东东 可是pin2码的输入次数是有限制的,我应该可以通过API获得我所剩余的输入次数.请问这个API是哪个,我需要怎么做才能获得这个次数呢?? PS: 我截获了SIMCAPS这个结构体的值,好像没多大用 3Q … 查看全部问答> |
|
小弟想学习驱动程序开发,但不知道如何入手?做人要踏实,做事也要踏实,小弟我想循序渐进的学习驱动开发,不求取巧,但求步步为营。那位高手给小弟指一条循序渐进学习驱动开发的明路,或者推荐一些教材,小弟不胜感激……… 查看全部问答> |
|
调试环境:万利STM32-E板 4G卡,使用 um0427文档的参考代码:sdcard.c V2.0.3 09/22/2008 MCD Application TeamSDIO 模式是 4bits DMA模式;1. 连续多 ...… 查看全部问答> |




