音频类(Audio),通信设备类(CDC),设备固件升级类(DFU),人机接口类(HID),大容量存储设备类(Mass Storage)
USB的数据由Packet(包)组成Transaction(事务),Transaction组成Transfer(传输),不同传输类型每Frame(帧)占用带宽的特性不同。同步传输每帧占用固定带宽;中断传输每帧都占用带宽,但所占带宽不固定;控制传输和批量传输在需要时才占用帧带宽,批量传输将会占用帧的所有剩余带宽。除同步传输外,一个Transaction由token包,数据包,握手包构成,STM32的每次中断都完成一个Transaction,token包和握手包的收发由硬件完成,数据包由应用程序完成。
用于描述端点(Endpoint)或通道(Pipe)的特性
1. 中断传输(Interrupt Transfer)
2. 控制传输(Control Transfer)
3. 同步传输(Isochronous Transfer)
4. 批量传输(Bulk Transfer)
同步传输与中断传输是周期性的,控制传输和批量传输是突发的
SYNC Packet Content EOP
其中,Packet Content的组成
PID 地址 帧号 数据 CRC
注意:不是每种包都包含完整Packet Content
说明:
- USB规范规定SETUP分组不能以非ACK握手分组应答,如果SETUP分组失败,则会引起下一个SETUP分组。因此,以NAK或STALL分组响应主机的SETUP分组是被禁止的。
- 控制传输使用双向端点
举例: Control Transfer的SETUP Transaction的组成
SETUP Packet + DATA0 Packet + ACK Package
SETUP包只跟以DATA0为PID的数据包,且数据包的方向为Host到Device。
(1)初始化系统时钟,配置USB时钟为48MHz;
(2)清除挂起的中断标志;
(3)复位USB模块
(4)配置本应用关心的中断,设备状态为UNCONNECTED
(5)初始化媒介层(SD卡,Flash)
(1)设置分组缓冲区基地址;
(2)配置端点的类型,发送状态,接收状态,端点地址(EP_TYPE, EP_KIND, EA),发送缓冲区或接收缓冲区地址,接收端点还需要设置接收长度;
(3)初始化设备地址为0,置位USB_DADDR. EF使能USB模块;
(4)初始化BOT状态机,CBW.dSignature;
(5)设备状态为ATTACHED
SETUP阶段使用标准请求和类特定请求完成设备枚举,由端点0的控制传输完成。
IN过程
(1)CTR_TX置位,发生中断,根据USB_ISTR的EP_ID和DIR位识别出端点号和方向;
(2)清除USB_EPnR. CTR_TX位;
(3)填写发送缓冲区,设置COUNTn_TX;
(4)将STAT_TX设置为”11”,使能该端点的TX。
OUT过程
注意:SL_SIZE和NUM_BLOCK决定了最大接收字节数
STAT_RX在接收数据后变为10(NAK)
(1)CTR_RX置位,发生中断,根据USB_ISTR. EP_ID位和USB_ISTR. DIR位识别出端点号和方向;
(2)根据USB_EPnR. SETUP位确定事务类型,是OUT还是SETUP,同时清除USB_EPnR. CTR_RX位;
(3)读出缓冲区描述表指向的COUNTn_RX,获得此次传输的字节数;
(4)从ADDRn_RX处获得数据;
(5)使能下次接收,即设置USB_EPnR. STAT_RX位为”11”,使能该端点。
说明:
- USB_CNTR. USB置位后所有的配置寄存器不会被复位,但设备的地址寄存器和端点寄存器会被USB复位所复位;
- 使用双缓冲机制时,STAT_TX和STAT_RX不会因为完成一次IN/OUT分组而被置为NAK;
- 对于同步端点,端点的状态只能是有效或者禁用,因此硬件不会在数据传输结束时改变端点的状态;
- 端点地址不一定要与端点号一致,由于用4位二进制表示(EA[3:0]),故端点地址取值为0 - 15,端点0作为控制端点,必须使其端点地址为0;
(*pProperty->Init)()完成了全速/低速设备上拉电阻检测,复位,设置应用关心的中断屏蔽位
CNTR. CTRM,CNTR. RESETM,未完成端点传输类型,缓冲区描述表的配置,设备地址DADDR的配置(仅置0并置位USB_DADDR. EF来使能USB),在函数的最后:bDeviceState = UNCONNECTED
而USB_Init()的下一条语句是while (bDeviceState != CONFIGURED)
,显然接下来应该在中断中完成缓冲区描述表填写(USB_BTABLE),端点传输类型(USB_EPnR),枚举过程的工作。
//USB中断服务函数
USB_Istr()
{
...
#if (IMR_MSK & ISTR_RESET)
if (wIstr & ISTR_RESET & wInterrupt_Mask)
{
_SetISTR((uint16_t)CLR_RESET);
Device_Property.Reset();
#ifdef RESET_CALLBACK
RESET_Callback();
#endif
}
#endif
...
}
Setup0_Process() - > 处理标准request / class相关request -> 根据数据阶段:如果是IN -> DataSatgeIn()
USB Device收到IN Packet且地址正确后,访问ADDRn_TX和COUNTn_TX,将相应缓冲区的内容通过移位寄存器发送出去,并等待Host发送ACK Package。Device收到ACK后toggle DTOG_TX位,硬件设置STAT_TX位为”10”(NAK),使端点无效,CTR_TX置位。应用程序需要清除中断标志CTR_TX,把下次要发送的内容写进ADDRn_TX指向的hw_buf,更新COUNTn_TX为需要发送的字节数,然后设置STAT_TX为”11”(VALID),使能数据传输。当STAT_TX为”10”(NAK)时,所有IN请求都会被NAK,Host不断重发IN请求,直到该端点有效。
每次处理CTR_TX中断时,写进hw_buf的内容都是下次要发送的内容,因此,在首次处理IN请求之前需要准备好hw_buf,如果SETUP的数据阶段是IN,就要在Setup0_Process()中调用DataStageIn()填充首次需要准备的内容。
In0_Process() -> WAIT_STATUS_IN -> 如果是SET_ADDR命令:写寄存器 -> 设置各端点地址
如果端点地址需要设置成与端点号不一致,需要修改本函数的部分
@
for(i=0; i
修改为:
@
_SetEPAddress(0,0);
_SetEPAddress(1,EP_ADDR1);
_SetEPAddress(2,EP_ADDR2);
USB请求由SETUP transaction完成,一条完整的USB请求存放在其中的DATA0 packet中,字节序为小端模式。
偏移量 | 域 | 长度 | 描述 |
---|---|---|---|
0 | bmRequestType | 1 | 请求特征 D7:传输方向 0=主机至设备 1=设备至主机 D6..5:种类 0=标准 1=类 2=厂商 3=保留 D4..0:接受者 0=设备 1=接口 2=端点 3=其他 4..31:保留 |
1 | bRequest | 1 | 命令类型编码值 |
2 | USBwValue | 2 | 根据不同的命令,含义不同 |
4 | USBwIndex | 2 | 根据不同的命令,含义不同,主要用于传送索引或偏移 |
6 | USBwLength | 2 | 数据阶段的字节数,如果没有数据阶段则填0 |
USB Mass Storage使用BOT(Bulk only Transfer)协议和SCSI指令来处理传输。相对于CBI(Control Bulk Interrupt)协议,BOT协议仅需要控制端点,一个Bulk IN端点和一个Bulk OUT端点即可完成命令、数据、状态的传输,BOT状态机如下图所示:
CBW(Command Block Wrapper)是一个31字节长的包,由Host发起,格式如下:
dCBWSignature: 0x43425355
dCBWTag: 用户定义标签,dCSWTag应当原样返回
dCBWDataTransferLength: 主机期望传输的数据长度。
bmCBWFlags: 主要定义数据的传输方向,由bit 7定义(0-out, 1-in),其他比特默认为0
bCBWLUN: 逻辑单元号
bCBWCBLength: CB的有效长度
CBWCB: 设备执行的命令块,这里是SCSI命令,一般是16字节
CSW(Command Status Wrapper)是Device收到Host发送的CBW并完成数据传输后向Host发送有关状态信息的包,长度为13字节,格式如下:
dCSWSignature: 0x53425355
dCSWTag: 应当与dCBWTag一致
dCSWDataResidue:
bCSWStatus: CBW传输的成功或失败状态,为0表示传输成功,非0表示传输失败, 如下表所示
Class-Specific requests
BOT协议要求支持两个类相关请求:
1. Bulk-only mass storage reset
该请求用于复位Mass Storage设备及与其相关的接口。Device接收到请求后,清除两个Bulk端点的data toggle,初始化CBW signature到默认值,设置BOT状态机到BOT_IDLE状态,以准备接收下一个CBW。
该请求在Mass_NoDataSetup()@usb_prop.c中处理。
2. Get Max LUN request
一个Mass storage设备可能管理多个共享同一device特性的逻辑单元,host使用CBW中的bCBWLUN域决定当前使用哪一个逻辑单元。
该请求在Mass_DataSetup()@usb_prop.c中处理。
Standard request requirements
@usb_prop.c
BOT协议规定,device在接收到以下两个标准请求时必须响应相应的requirement:
Mass_Storage_SetConfiguration()
当device从unconfigured状态转换为configured状态时,所有端点的data toggle都必须清零
Mass_Storage_ClearFeature()
当host发送了一个带有非法signature或length的CBW时,device必须设置两个Bulk端点的状态为STALL,直到收到mass storage reset请求。
Mass_Storage_SetDeviceAddress()
设置device的地址
bDeviceState = ADDRESSED
USB Host获取LUN数量是通过class-specified请求处理函数uint8_t *Get_Max_Lun(uint16_t Length)完成的,定位到Get_Max_Lun()可以看到LUN的数量由Max_Lun变量来确定,定位到变量定义处并修改,注意Max_Lun表示最大的LUN,LUN号从0开始,有两个LUN,Max_Lun应该填1而不是2。
EP2_OUT_Callback()@usb_endp.c
Mass_Storage_Out()@usb_bot.c
Data_Len = USB_SIL_Read(EpAddr, Bulk_Data_Buff)@usb_sil.c
-> CBW_IDLE : CBW_Decode() ; CBW_DATA_OUT
SCSI_Write10_Cmd(CBW.bLUN, SCSI_LBA, SCSI_BlkLen)@usb_scsi.c
-> 检查地址合法性;BOT状态转换;EP2_RX设置为Valid
Write_Memory(lun, LBA, BlockNbr)@memory.c
-> 处理packet到block的转换,block地址到byte地址转换
MAL_Write(lun, ByteAddr, NumOfByte)@mass_mal.c
-> 写入一个block
EP1_IN_Callback()@usb_endp.c
Mass_Storage_In()@usb_bot.c
-> CBW_IDLE : CBW_Decode() ; BOT_DATA_IN
SCSI_Read10_Cmd(CBW.bLUN, SCSI_LBA, SCSI_BlkLen)@usb_scsi.c
-> 检查地址合法性;BOT状态转换;在首次IN前调用Read_Memory()填充hw_buf
Read_Memory(lun, LBA, BlockNbr)@memory.c
-> 处理block到packet的转换,block地址到byte地址的转换
MAL_Read(lun, ByteAddr, NumOfByte)@mass_mal.c
-> 读出一个block
USB_SIL_Write(EP1_IN, (uint8_t *)Data_Buffer, BULK_MAX_PACKET_SIZE)@usb_sil.c
-> 分多次向EP1写入packet
说明:
@memory.c
Write_Memory()函数和Read_Memory()函数定义的用于block到byte的地址、长度转换的变量是uint32_t类型的,而32 bit无符号变量最大寻址能力是4G,也就是说sd card超过4G的部分无法访问,所以需要修改类型为uint64_t。同时注意其调用的MAL_Write()和MAL_Read()函数的单位和数据类型。
@mass_mal.c
MAL_Write()和MAL_Read()函数的Input参数Memory_Offset和Transfer_Length单位是字节,注意Mass Storage的API,特别是SD卡的API,有些的单位是BlockAddr和NumOfBlock。
uint32_t Mass_Memory_Size[2];
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
计算机显示屏坐标增长方向
X坐标 自左至右增大
Y坐标 自上至下增大
鼠标发送给PC的数据每次4个字节
BYTE0 BYTE1 BYTE2 BYTE3