当前位置: 代码迷 >> 综合 >> STM32F103 USB EP0通信数据详解
  详细解决方案

STM32F103 USB EP0通信数据详解

热度:41   发布时间:2024-02-24 22:01:57.0

目录

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指明接收对象。返回值是指定接收者的状态。

命令 bmRequestType bRequest wValue wIndex wLength Data
Get_Status 10000000B
10000001B
10000010B
GET_STATUS 0 0(返回设备状态)
接口号(对象是接口时)
端点号(对象是端点时)
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

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Clear_Feature 00000000B
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 EndpointusbSetEpStatus(ep, USB_EP_TX_VALID);}else{//OUT EndpointusbSetEpStatus(ep, USB_EP_RX_VALID);}
}

c. SET_FEATURE

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Set_Feature 00000000B
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 EndpointusbSetEpStatus(ep, USB_EP_TX_STALL);
}
else
{//OUT EndpointusbSetEpStatus(ep, USB_EP_RX_STALL);
}

d.  SET_ADDRESS

设置设备的USB地址。

命令 bmRequestType bRequest wValue wIndex wLength Data
SET_ADDRESS 00000000B SET_ADDRESS 设备地址 0 0

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

e. GET_DESCRIPTOR

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Get_Descriptor 10000000B GET_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

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Get_Configuration 10000000B GET_CONFIGURATION 0 0 1 配置值

固定返回1字节配置值。

h. SET_CONFIGURATION

此请求设备配置值。

命令 bmRequestType bRequest wValue wIndex wLength Data
Set_Configuration 00000000B SET_CONFIGURATION 配置值(高字节为0,低字节表示要设置的配置值) 0 0
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设备的配置具有互斥设置的接口。此请求允许主机确定当前选定的备用设置。

命令 bmRequestType bRequest wValue wIndex wLength Data
Get_Interface 10000001B GET_INTERFACE 0 接口号 1 可选设置

j. SET_INTERFACE

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Set_Interface 00000001B SET_INTERFACE 可选设置 接口号 0

k. SYNCH_FRAME

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

命令 bmRequestType bRequest wValue wIndex wLength Data
Synch_Frame 10000010B SYNCH_FRAME 0 端点号 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 PacketgUsbDevState.ctrlState = USB_CTRL_STATUS_OUT;}else{usbWriteBuf(USB_EP0, 0, 0); //Send Empty PacketgUsbDevState.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_SIZEgUsbDevState.ctrlState = USB_CTRL_DATA_IN_LAST_IDLE;
}
elsegUsbDevState.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 PacketgUsbDevState.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 PacketgUsbDevState.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是空包,所以没有返回数据。

这样就完成了枚举过程。