USB Device应用笔记(基于STM32F103)

2023-11-01 118浏览
百检网是一家专业的第三方检测平台,汇聚众多拥有权威资质的第三方检测机构为你提供一站式的检测服务,做检测就上百检网。百检网让检测从此检测,一份报告全国通用,专业值得信赖。

常见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((uint8_t)i, (uint8_t)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 – 滚轮变化。


百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台