USB Device应用笔记(基于STM32F103)

常见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)
同步传输与中断传输是周期性的,控制传输和批量传输是突发的

通道类型

  1. 数据流:单向 ->批量,中断,同步传输
  2. 消息: 双向 -> 控制传输

事务(Transaction)

分类:SETUP, OUT, IN
这里写图片描述

Packet格式

SYNC Packet Content EOP
其中,Packet Content的组成
PID 地址 帧号 数据 CRC
注意:不是每种包都包含完整Packet Content

Packet种类及其组成

  1. 命令包 token
    PID + ADDR(设备地址,端点地址) + CRC5
  2. 帧首包 SOF(Start of frame)
    PID + 帧号 + CRC5
  3. 数据包 DATA
    PID + 数据 + CRC16
  4. 握手包 Handshake
    PID

PID域的类型

  1. Token
    IN, OUT, SETUP
  2. SOF
    SOF
  3. Data
    DATA0, DATA1, DATA2, DATAM
  4. 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命令:写寄存器 -> 设置各端点地址
如果端点地址需要设置成与端点号不一致,需要修改本函数的部分

@<usb_core.c>
for(i=0; i<nEP; i++){
_SetEPAddress((uint8_t)i, (uint8_t)i);
}

修改为:

@<usb_core.c>
_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 – 滚轮变化。
  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值