常见USB设备类
音频类(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)
同步传输与中断传输是周期性的,控制传输和批量传输是突发的
通道类型
数据流:单向 ->批量,中断,同步传输
消息: 双向 -> 控制传输
事务(Transaction)
分类:SETUP, OUT, IN
Packet格式
SYNC Packet Content EOP
其中,Packet Content的组成
PID 地址 帧号 数据 CRC
注意:不是每种包都包含完整Packet Content
Packet种类及其组成
命令包 token
PID + ADDR(设备地址,端点地址) + CRC5
帧首包 SOF(Start of frame)
PID + 帧号 + CRC5
数据包 DATA
PID + 数据 + CRC16
握手包 Handshake
PID
PID域的类型
Token
IN, OUT, SETUP
SOF
SOF
Data
DATA0, DATA1, DATA2, DATAM
Handshake
ACK, NAK, STALL, NYET/ERR(HS)
帧格式
说明:
- USB规范规定SETUP分组不能以非ACK握手分组应答,如果SETUP分组失败,则会引起下一个SETUP分组。因此,以NAK或STALL分组响应主机的SETUP分组是被禁止的。
- 控制传输使用双向端点
举例: Control Transfer的SETUP Transaction的组成
SETUP Packet + DATA0 Packet + ACK Package
SETUP包只跟以DATA0为PID的数据包,且数据包的方向为Host到Device。
USB设备状态
插入 -> 供电 -> 复位 -> 地址 -> 配置 挂起
实现一个USB设备的软件流程(以Mass Storage为例)
1. 系统初始化
(1)初始化系统时钟,配置USB时钟为48MHz;
(2)清除挂起的中断标志;
(3)复位USB模块
(4)配置本应用关心的中断,设备状态为UNCONNECTED
(5)初始化媒介层(SD卡,Flash)
2. USB复位(RESET中断)
(1)设置分组缓冲区基地址;
(2)配置端点的类型,发送状态,接收状态,端点地址(EP_TYPE, EP_KIND, EA),发送缓冲区或接收缓冲区地址,接收端点还需要设置接收长度;
(3)初始化设备地址为0,置位USB_DADDR. EF使能USB模块;
(4)初始化BOT状态机,CBW.dSignature;
(5)设备状态为ATTACHED
3. SETUP阶段和数据阶段
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;
描述符(Descriptor)分类
设备描述符(Device Descriptor)
配置描述符(Configuration Descriptor)
接口描述符(Interface Descriptor)
端点描述符(Endpoint Descriptor)
字符描述符(String Descriptor)
报告描述符(Report Descriptor)
(*pProperty->Init)()
(*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
...
}
Device_Property.Reset()完成以下工作:
Device_Info.Current_Configuration = 0(同前)
pInformation->Current_Feature = MASS_ConfigDescriptor[7]
设置Buffer table的地址(BTABLE_ADDRESS) ,设备地址默认为0
初始化EP
EP0:控制类型;发送NAK、接收Valid;设置接收buffer地址和长度;设置发送buffer地址
EP1:批量类型;发送NAK、接收Disable;设置发送buffer地址
EP2:批量类型;发送Disable、接收VALID;设置接收buffer地址和长度
bDeviceState = ATTACHED 全局变量,表示设备当前已被插入主机
关于Setup0_Process()中调用DataStageIn()的原因
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请求格式 USB请求由SETUP transaction完成,一条完整的USB请求存放在其中的DATA0 packet中,字节序为小端模式。 偏移量域长度描述 0bmRequestType1请求特征 D7:传输方向 0=主机至设备 1=设备至主机 D6..5:种类 0=标准 1=类 2=厂商 3=保留 D4..0:接受者 0=设备 1=接口 2=端点 3=其他 4..31:保留 1bRequest1命令类型编码值 2USBwValue2根据不同的命令,含义不同 4USBwIndex2根据不同的命令,含义不同,主要用于传送索引或偏移 6USBwLength2数据阶段的字节数,如果没有数据阶段则填0 USB Mass Storage Class 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 关于获取LUN数量的修改 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。 从Bulk端点到存储器访问函数的流程 1. OUT(Mass_Storage_Write) 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 2. IN(Mass_Storage_Read) 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) 这里的uint32_t也要修改成uint64_t USB HID Class 1. JoyStick Mouse 计算机显示屏坐标增长方向 X坐标 自左至右增大 Y坐标 自上至下增大 鼠标发送给PC的数据每次4个字节 BYTE0 BYTE1 BYTE2 BYTE3 定义如下: BYTE0 bit7: 1 表示 Y坐标的变化量超出 -256~255 的范围,0表示没有溢出 bit6: 1 表示 X坐标的变化量超出 -256~255 的范围,0表示没有溢出 bit5: Y坐标变化的符号位,1表示负数,即鼠标向上移动 bit4: X坐标变化的符号位,1表示负数,即鼠标向左移动 bit3: 恒为1 bit2: 1表示中键按下 bit1: 1表示右键按下 bit0: 1表示左键按下 BYTE1 – X坐标变化量,与BYTE1的bit4组成9位符号数,负数表示向左移,正数表右移。用补码表示变化量 BYTE2 – Y坐标变化量,与BYTE1的bit5组成9位符号数,负数表示向下移,正数表上移。用补码表示变化量 BYTE3 – 滚轮变化。
百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台