STM32F103 USB EP0通信数据详解

目录

1. SETUP阶段

【标准请求】

a. GET_STATUS

b. CLEAR_FEATURE

c. SET_FEATURE

d.  SET_ADDRESS

e. GET_DESCRIPTOR

f. SET_DESCRIPTOR

g. GET_CONFIGURATION

h. SET_CONFIGURATION

i. GET_INTERFACE

j. SET_INTERFACE

k. SYNCH_FRAME

2. DATA阶段

【Data In过程】

【Data Out过程】

3. STATUS过程

【Status In过程】

【Status Out过程】

实例


总结一下STM32F103的USB端点0的数据处理过程。一个请求过程可能包含三个过程。第一个阶段是SETUP阶段,主机发送SETUP数据;第二个阶段是可选的,主机和设备之间传输数据;第三个阶段,主机和设备之间传输状态,状态的方向和数据方向相反,如果没有数据(即没有第二阶段),状态的方向是从设备到主机。

更详细的内容可以参考USB规范第9章: https://usb.org/document-library/usb-20-specification

用一个变量gUsbDevState.state记录USB的状态,开始的时候USB的状态为USB_CTRL_IDLE。

1. SETUP阶段

HOST第一笔数据是SETUP数据,即告知Device接下来的数据要如何处理。从USB buffer中读入8字节数据获取Setup信息。获取到SETUP数据前把gUsbDevState.state设置为USB_CTRL_SETUP。

下面是这8个字节具体对应的含义。

#pragma pack (1)
typedef struct _stUSBReqInfo
{
    uint8_t bmRequestType;
    uint8_t bRequest;
    uint16_t wValue;
    uint16_t wIndex;
    uint16_t wLength;
}stUSBReqInfo;
#pragma pack ()

设备根据bmRequestType和bRequest解析执行USB命令。下图是获取设备描述符的SETUP数据:

bmRequestType:

Bit 7

Bit 6:5

Bit 4:0

表示后续数据包(必须是DATA0)的方向,0为OUT,1为IN

请求类型,0表示标准请求,1表示Class的请求,2表示厂商的请求,3是预留值

接收这个SETUP包的对象,0是设备,1是接口,2是端点,3是其他未知的,4-31是预留值。

bRequest、wValue、wIndex、wLength和具体的bmRequestType有关。bRequest一般表示请求类型,wValue一般表示传送参数给对象标明这是什么请求,wLength表示下一阶段发送数据的长度。

【标准请求】

USB2.0定义了如下的标准请求:

#define     GET_STATUS              0
#define     CLEAR_FEATURE           1
#define     SET_FEATURE             3
#define     SET_ADDRESS             5
#define     GET_DESCRIPTOR          6
#define     SET_DESCRIPTOR          7
#define     GET_CONFIGURATION       8
#define     SET_CONFIGURATION       9
#define     GET_INTERFACE           10
#define     SET_INTERFACE           11
#define     SYNCH_FRAME             12

a. GET_STATUS

返回选定接收者的状态。bmRequestType中的Bit 4:0指明接收对象。返回值是指定接收者的状态。

命令bmRequestTypebRequestwValuewIndexwLengthData
Get_Status10000000B
10000001B
10000010B
GET_STATUS00(返回设备状态)
接口号(对象是接口时)
端点号(对象是端点时)
2

设备状态
接口状态
端点状态

GET_STATUS固定返回2个字节。

对象是设备时:返回设备的Remote Wakeup使能和设备是总线供电还是自供电。

uint8_t status[2] = {0, 0};
if((gUsbDevInfo.feature & (1 << 5)) > 0) //Remote Wakeup, 1: enabled, 0: disabled
{
    status[0] |= 0x02;
}
if((gUsbDevInfo.feature & (1 << 6)) > 0) //1:Bus-powered, 0: Self-powered
{
    status[0] |= 0x01;                  
}

对象是接口时:固定返回2字节0x00

端点:主要是返回端点是否为STALL状态。wIndex表示哪个端点和端点方向。

uint8_t ep = (gUsbReqInfo.wIndex & 0x7f);
uint32_t epStatus = usbGetEpStatus(ep);
if(gUsbReqInfo.wIndex & 0x80)
{
    if(epStatus == USB_EP_TX_STALL)
        status[0] |= 0x01;
}
else
{
    if(epStatus == USB_EP_RX_STALL)
        status[0] |= 0x01;
}

b. CLEAR_FEATURE

用来清除或禁止某个指定的属性(属性与具体的实现有关,值的具体定义应该与驱动有关)。该命令不会返回数据。

命令bmRequestTypebRequestwValuewIndexwLengthData
Clear_Feature00000000B
00000001B
00000010B
CLEAR_FEATURE特性选择符(1表示设备,0表示端点)
接口号 
端点号
0

当对象是设备时:禁止Remote Wakeup

gUsbDevInfo.feature = (uint8_t)(~(1 << 5)); //Disable Remote Wakeup

当对象是端点时:清除对应端点的STALL状态。

if(gUsbReqInfo.wValue == ENDPOINT_HALT)
{
    uint8_t ep = gUsbReqInfo.wIndex & 0x7F;
    if((gUsbReqInfo.wIndex & 0x80) > 0)
    {//IN Endpoint
        usbSetEpStatus(ep, USB_EP_TX_VALID);
    }
    else
    {//OUT Endpoint
        usbSetEpStatus(ep, USB_EP_RX_VALID);
    }
}

c. SET_FEATURE

用来设置或使能某个指定的属性(与CLEAR_FEATURE对应)。该命令不会返回数据。

命令bmRequestTypebRequestwValuewIndexwLengthData
Set_Feature00000000B
00000001B
00000010B
SET_FEATURE特性选择符(1表示设备,0表示端点)
接口号 
端点号
0

这里wIndex是一直为0,如果不为0(同时bmRequestType为0),则表示TEST_MODE。这里不支持这种情况。具体可以参考USB2.0的规格书。

当对象是设备时:使能Remote Wakeup

gUsbDevInfo.feature |= (1 << 5); //Enable Remote Wakeup

当对象是端点时:设置对应端点为STALL状态。

uint8_t ep = gUsbReqInfo.wIndex & 0x7F;
if((gUsbReqInfo.wIndex & 0x80) > 0)
{//IN Endpoint
    usbSetEpStatus(ep, USB_EP_TX_STALL);
}
else
{//OUT Endpoint
    usbSetEpStatus(ep, USB_EP_RX_STALL);
}

d.  SET_ADDRESS

设置设备的USB地址。

命令bmRequestTypebRequestwValuewIndexwLengthData
SET_ADDRESS00000000BSET_ADDRESS设备地址00

这里不要直接设置新的地址,等状态传输(第三阶段结束)完后再设置新的地址。

e. GET_DESCRIPTOR

如果描述符存在,则此请求返回指定的描述符。

命令bmRequestTypebRequestwValuewIndexwLengthData
Get_Descriptor10000000BGET_DESCRIPTOR描述表种类(高字节)和索引(低字节)0或语言标志描述表长度描述表

wValue描述表种类(高字节)和索引(低字节),

uint16_t type = gUsbReqInfo.wValue & 0xFF00;
uint16_t index = gUsbReqInfo.wValue & 0x00FF;

其中有7种标准描述符(USB2.0):

#define DEVICE_DESC_TYPE                        0x0100
#define CONFIGURATION_DESC_TYPE                 0x0200
#define STRING_DESC_TYPE                        0x0300
#define INTERFACE_DESC_TYPE                     0x0400
#define ENDPOINT_DESC_TYPE                      0x0500
#define DEV_QUALIFIER_DESC_TYPE                 0x0600
#define SPEED_CONFIGURATION_DESC_TYPE           0x0700

其中DEV_QUALIFIER_DESC_TYPE和SPEED_CONFIGURATION_DESC_TYPE是USB2.0设备才有的描述符。

f. SET_DESCRIPTOR

此请求是可选的,可用于更新现有描述符或添加新描述符。

g. GET_CONFIGURATION

此请求返回当前设备配置值。

命令bmRequestTypebRequestwValuewIndexwLengthData
Get_Configuration10000000BGET_CONFIGURATION001配置值

固定返回1字节配置值。

h. SET_CONFIGURATION

此请求设备配置值。

命令bmRequestTypebRequestwValuewIndexwLengthData
Set_Configuration00000000BSET_CONFIGURATION配置值(高字节为0,低字节表示要设置的配置值)00
if (gUsbReqInfo.wValue <= usbDeviceDescriptor[USB_DEVICE_DESC_LEN - 1]
    && (gUsbReqInfo.wIndex == 0)) /*call Back usb spec 2.0*/
{
    gUsbDevInfo.curConfig = gUsbReqInfo.wValue;
    if (gUsbDevInfo.curConfig != 0)
    {
        gUsbDevState.state = USB_STATE_CONFIGURED;
    }
}

i. GET_INTERFACE

此请求返回指定接口的备用设置。某些USB设备的配置具有互斥设置的接口。此请求允许主机确定当前选定的备用设置。

命令bmRequestTypebRequestwValuewIndexwLengthData
Get_Interface10000001BGET_INTERFACE0接口号1可选设置

j. SET_INTERFACE

此请求允许主机为指定的接口选择备用设置。如果设备只支持指定接口的默认设置,则可以在请求状态阶段返回STALL。

命令bmRequestTypebRequestwValuewIndexwLengthData
Set_Interface00000001BSET_INTERFACE可选设置接口号0

k. SYNCH_FRAME

此请求用于设置和报告端点的同步帧。

命令bmRequestTypebRequestwValuewIndexwLengthData
Synch_Frame10000010BSYNCH_FRAME0端点号2帧号

其他Class和Vender的请求根据具体的应用另外处理。

switch(gUsbReqInfo.bmRequestType & USB_REQ_TYPE_MASK)
{
    case USB_REQ_TYPE_STANDARD:
        usbSetupStdReq();
        break;
    case USB_REQ_TYPE_CLASS:
        gUsbDataInfo.dataBuf = usbUsrClassReq();
        break;
    case USB_REQ_TYPE_VENDOR:
        gUsbDataInfo.dataBuf = usbUsrVenderReq();
        break;
    default:
        gUsbReqInfo.wLength = USB_UNSUPPORT;
        break;
}

2. DATA阶段

bmRequestType的bit7表示接下来数据的方向。如果方向是IN(例如Get Descripter的情况),STM32F103需要立刻发送数据给主机,即使数据长度为0也需要发送一个空包。如果是OUT,分2种情况,如果数据长度为0(例如Set Address的情况),则表示下一个阶段是STATUS IN,如果大于0(标准USB请求没有这个情况,虚拟串口里面的SET_LINE_CODING会是这种情况),STM32F103不是即时接收主机的数据,需要等下一次CTR中断判断进入OUT中断才读取接收缓冲中的数据。

if((gUsbReqInfo.bmRequestType & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN)
{
    gUsbDevState.ctrlState = USB_CTRL_IN;
}
else
{
    gUsbDevState.ctrlState = USB_CTRL_DATA_OUT_WAIT;
    if(gUsbReqInfo.wLength == 0)
        gUsbDevState.ctrlState = USB_CTRL_STATUS_IN;
}

【Data In过程】

Data In是设备发送数据给主机的过程。USB通讯过程都是由主机发起的,主机会在SETUP阶段通知设备接下来的数据类型和长度,SETUP阶段把数据长度设置成3种情况:错误或不支持(-1)、0字节(即NO DATA的情况)、需要返回数据(即有DATA的情况)。

a. 错误或不支持的情况

如果SETUP数据有错误,这个阶段应该将端点设置为STALL状态通知主机发生错误。如果是SETUP中要求的数据类型不支持,则可以返回空包或者将端点设置为STALL状态?

if(gUsbReqInfo.wLength == USB_UNSUPPORT)
{
    usbSetEpStatus(USB_EP0, USB_EP_TX_STALL);
    gUsbDevState.ctrlState = USB_CTRL_IDLE;
    return;
}

b. NO DATA的情况

gUsbReqInfo.wLength有2种情况下回等于0,一种是主机发送的请求没有数据(例如Set Address),这种情况下SETUP阶段(gUsbReqInfo.bmRequestType & USB_REQ_DIR_MASK) == USB_REQ_DIR_OUT,所以此次IN的过程实际是USB_CTRL_STATUS_IN, USB协议要求设备要在50ms内完成这个阶段。另外一种情况是最后一笔数据正好是64字节,这时候需要多发一个空包。

if(gUsbReqInfo.wLength == 0)
{
    if(gUsbDevState.ctrlState == USB_CTRL_DATA_IN_LAST)
    {
        usbWriteBuf(USB_EP0, 0, 0); //Send Empty Packet
        gUsbDevState.ctrlState = USB_CTRL_STATUS_OUT;
    }
    else
    {
        usbWriteBuf(USB_EP0, 0, 0); //Send Empty Packet
        gUsbDevState.ctrlState = USB_CTRL_IDLE;
    }
    return;
}

c. DATA的情况

STM32F103的端点0一次最大只能传输64字节,所以如果数据大于64字节,主机会发送多次Data In请求。当数据正好是64字节时,需要发送一个空包。

count = usbWriteBuf(USB_EP0, (uint8_t *)gUsbDataInfo.dataBuf + gUsbDataInfo.offset, 
        gUsbReqInfo.wLength);
gUsbReqInfo.wLength -= count;
gUsbDataInfo.offset += count;
if(gUsbReqInfo.wLength == 0)
{
    if(count < EP0_MEM_SIZE)
        gUsbDevState.ctrlState = USB_CTRL_STATUS_OUT;
    else//count == EP0_MEM_SIZE
        gUsbDevState.ctrlState = USB_CTRL_DATA_IN_LAST_IDLE;
}
else
    gUsbDevState.ctrlState = USB_CTRL_DATA_IN_WAIT;

【Data Out过程】

标准的EP0过程基本没有Out数据的过程。具体的Class则需要另外处理。

if(gUsbDevState.ctrlState == USB_CTRL_OUT)
{
    usbUsrOut0();
}

3. STATUS过程

【Status In过程】

Status In一般情况是只要发送一个空包即可,特殊情况是Set Address。Set Address在SETUP、DATA IN和STATUS IN都不会改变设备的USB地址,而是在Set Address通信结束后再设置地址。

if(gUsbDevState.ctrlState == USB_CTRL_STATUS_IN)
{
    if(gUsbDevState.state == USB_STATE_ADDRESS)
    {
        usbWriteBuf(USB_EP0, 0, 0); //Send Empty Packet
        gUsbDevState.state = USB_STATE_ADDRESSED;
    }
    else if(gUsbDevInfo.address != 0) //SetAddress
    {
        Printf("Set Address:%d\r\n", gUsbDevInfo.address);
        usbSetAddress(gUsbAddress);
        gUsbDevInfo.address = 0;
        gUsbDevState.ctrlState = USB_CTRL_IDLE;
    }
    else
    {
        usbWriteBuf(USB_EP0, 0, 0); //Send Empty Packet
        gUsbDevState.ctrlState = USB_CTRL_IDLE;
    }
}

【Status Out过程】

Status Out只需要设置RX有效即可。

if(gUsbDevState.ctrlState == USB_CTRL_STATUS_OUT)
{
    usbSetEpStatus(USB_EP0, USB_EP_RX_VALID);
    gUsbDevState.ctrlState = USB_CTRL_IDLE;
}

实例

主机第一笔数据是要求设备提供设备描述符,这个通信过程包括Setup + Data In With Data + Status Out。

        1. STM32F103产生一个Setup Out中断,从STM32F103的USB Buffer中读入8字节,即Setup数据

usbReadBuf(USB_EP0, (uint8_t *)(&gUsbReqInfo), sizeof(gUsbReqInfo));

其中gUsbReqInfo.bRequest == USB_GET_DESCRIPTOR,

(uint8_t)((gUsbReqInfo.wValue & 0xFF00) >> 8) == USB_DEVICE_DESCRIPTOR_TYPE

STM32F103需要返回数组usbDeviceDescriptor,长度固定为18字节。

        2. State由USB_CTRL_SETUP转为USB_CTRL_IN,STM32F103随即发送18字节给主机。由于设备描述符长度小于64字节,所以一次发送完成,状态由USB_CTRL_IN改为USB_CTRL_STATUS_OUT,等待EP0 Out中断。

        3. Out中断后设置Rx为Valid,并将State改为USB_CTRL_IDLE。

        4. STM32F103产生一个IN中断表明数据已经接收到,即如果当前状态为USB_CTRL_IDLE,IN中断不需要做处理。

随后主机会设置USB地址(即Set Address),这个通信过程包括Setup + Data In With No Data + Status In + Set Address.

        1. 设置USB地址成立条件gUsbReqInfo.bRequest == USB_SET_ADDRESS。

        2. State由USB_CTRL_SETUP转为USB_CTRL_STATUS_IN,即发送一个空包结束Set Address通信过程。

        3. STM32F103产生一个IN中断表明主机也确认OK,这里要将设备的地址改为新的地址。

主机会再次读入设备描述符确认地址是否设置成功,如果再次读入设备描述符不正确,则会再尝试写新的地址+读入设备描述符,失败则会停止枚举。如果是正确的,接下来就需要读入配置描述符。主机会读2次配置描述符,第一次固定读入9字节,获取全部配置描述符的长度,然后再读入全部的配置描述符。

第一读配置描述符数据:

第二次读入配置描述符:

        1. gUsbReqInfo.bRequest == USB_GET_DESCRIPTOR, (uint8_t)((gUsbReqInfo.wValue & 0xFF00) >> 8) == USB_CONFIGURATION_DESCRIPTOR_TYPE, STM32F103返回数组usbDeviceConfigDescriptor,长度与具体应用有关。这里虚拟串口的配置描述符长度为67字节。

        2. State由USB_CTRL_SETUP转为USB_CTRL_IN,STM32F103随即发送64字节给主机,由于还有3字节还没有发送,所以State还是USB_CTRL_IN,等待下一次的IN中断。

        3. STM32F103产生一个IN中断继续发送剩下的3字节数据。然后状态由USB_CTRL_IN改为USB_CTRL_STATUS_OUT,等待EP0 Out中断。

        4. Out中断后设置Rx为Valid,并将State改为USB_CTRL_IDLE。

        5. STM32F103产生一个IN中断表示主机已经接收到数据,即如果当前状态为USB_CTRL_IDLE,IN中断不需要做处理。

如果主机得到正确的配置描述符,会发Get Status获取设备状态。

然后主机会读一次265字节长度的配置描述符确认通信是否正常。主机还会读取3个字符串描述符,可以把Serial Number的字符串描述符设置为64字节验证一下正好长度为64字节的情况。

当主机确认枚举成功后会发送SET_CONFIGURATION。

Response是空包,所以没有返回数据。

这样就完成了枚举过程。

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值