
1、基础知识; I! ~5 Q- z) R" ?( ^' t% w6 } 等级划分: j' Q; T+ C* A ![]() - d% X* |* M2 N! L; e7 [0 m/ E 接口类型:( a3 L0 t2 O, R* _0 i* `4 W, j Z . K# g% f, V5 a# e ![]() 9 V3 p8 G$ A$ I6 b. D3 _$ r" X# O STM32基础型(F1系列)所带的USB是全速。0 n+ U' i4 ^9 C3 }' }% t7 h . O9 x. Z: @: u0 i 2、电气属性0 z- B1 i4 s9 A4 t) N2 d- X+ l USB的通信都是由主机发起的,这一点与IIC协议是类似的。" f2 G3 m1 P) d, T7 C 0 t4 N3 W. v" K 2.1 数据线 a2 c# \; Z& p' i! R1 R8 t USB使用差分传输模式,有两条数据线,分别是: # m2 v2 a: X6 |. B USB数据正信号线,USB Data Positive,即USB-DP线,简写为D+! D6 Q1 n% y/ R( [5 E5 k USB数据负信号线,USB Data Minus, 即USB-DM线,简写为D-7 }4 v! S* X+ a, s 9 R7 b0 Z5 i3 m0 c3 [0 b$ d4 r 剩下的就是电源线(5V-Vbus)和地线(GND)。 2.2 USB主机是如何识别设备是高速设备/全速设备/低速设备?0 ]7 r$ o# \! g. X, { 主机的D+和D-都接有15K下拉电阻。 全速USB设备的数据线D+接有1.5K的上拉电阻,一旦接入主机,主机的D+被拉高 低速USB设备的数据线D-接有1.5K的上拉电阻,一旦接入主机,主机的D-会被拉高 4 C+ _9 N% q& |7 A, U t6 ^- \4 t1 u 因此,主机就可以根据检测到自己的D+为高还是D-为高,从而判断接入的设备是一个全速还是低速设备。& O2 B; Y; i: h( D* E: |' Y4 j7 x 所以你可以看到STM32板子上的USB口D+有一个上拉电阻,而且是必须有的: ![]() 3、USB设备分类 ![]() 看一个CDC虚拟串口的设备分类: ![]() / f; x0 G9 ?( ? 在看一个MSC的(模拟U盘): % d% e% k/ t+ X! ]+ s( p& K, ?3 K0 I ![]() 不同的类有不同的用途;不同的应用场合对应不同的产品形态;不同的产品形态可能会有自己特殊的描述符,比如: HID类有报告描述符、CDC类有ACM、Union描述符等。' P$ a" u h4 n( n 4、描述符详解" }# l1 Y# e1 t1 R% I 以STM32里的MSC设备为例,MSC类所需要的描述符有:设备描述符+配置描述符+接口描述符(数量由配置描述符里的bNumInterfaces字段决定)+端点描述符(数量由配置描述符里的bNumEndpoints决定),所以MSC类结构就是这样的:! N R/ e) ~- X/ ]; A l* [" n ' F% z# D; k/ S- h8 E; a" ?& F
而CDC类是这样的:
4.1 设备描述符 每个USB设备都必须且只有一个设备描述符,摘取一下STM32的MSC设备里的实例代码: ![]() ; P8 a) k8 W# y# J 每个字段的含义: bLength:描述符大小.固定为0x12;2 e# \# A0 P2 m* ^+ \ bDescriptorType:设备描述符类型.固定为0x01;! a) ^1 s- J6 D; l$ f3 E5 O bcdUSB:USB 规范发布号.表示了本设备能适用于那种协议,如2.0=0200,1.1=0110; bDeviceClass:类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的;9 B# X2 r6 d- J3 e1 a# G# ~ bDeviceSubClass:子类型代码(由USB分配),如果 bDeviceClass值是0,一定要设置为0。其它情况就跟据USB-IF组织定义的编码;5 P B' v$ [% J8 H+ L0 Q! S4 D bDeviceProtocol:协议代码(由USB分配),如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH; bMaxPacketSize0:端点0最大分组大小(只有8,16,32,64有效);4 j! Z6 x* i- w2 b idVendor:供应商ID(由USB分配); idProduct : 产品ID(由厂商分配),由供应商ID和产品ID,就可以让操作系统加载不同的驱动程序 bcdDevice :设备出产编码.由厂家自行设置;0 L6 O$ t4 l5 W+ b C \0 W iManufacturer :厂商描述符字符串索引.索引到对应的字符串描述符. 为0则表示没有;, p" M1 r- ~) T: O iProduct :产品描述符字符串索引.同上; iSerialNumber:设备序列号字符串索引.同上; bNumConfigurations :可能的配置数.指配置字符串的个数。 4.2 配置描述符, ]- C; U% z- X' }- l/ g; ? 配置描述符定义了设备的配置信息,一个设备可以有多个配置描述符。摘一个STM32的MSC设备的配置描述符: ![]() 用C语言组合就是这样的一个结构:& P0 Q/ [' l' Y( w& d1 M
每个字段含义如下:' y5 x. Z3 |2 }5 g8 @ bLength:描述符大小,固定为0x09. bDescriptorType:配置描述符类型,固定为0x02.7 V* o& A8 K8 O* ~* f wTotalLength:返回整个数据的长度,指此配置返回的配置描述符,接口描述符以及端点描述符的全部大小' a' ^% W- _; Q+ ^! S bNumInterfaces:配置所支持的接口数。指该配置配备的接口数量也表示该配置下接口描述符数量( [/ {; p5 [- l8 v) ]! m" X3 i' ~ bConfigurationValue:作为Set Configuration的一个参数选择配置值4 S) z3 c5 r7 F4 i' b- V! n iConfiguration:用于描述该配置字符串描述符的索引 bmAttributes:供电模式选择,Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒& }2 p/ l3 [+ Y MaxPower:总线供电的USB设备的最大消耗电流.以2mA为单位 - i9 u: s( B3 r% u3 x& ?$ O 4.3 接口描述符 接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定,摘取STM32的MSC设备类的接口描述符:& [2 x0 i. I6 E: O& H e) r1 _ ![]() 用C语言组合就是这样的一个结构:) D7 J( D" H) R3 K) J
每个字段含义如下:! R1 ~/ p& W! F* H6 H3 |0 @ ( d; X1 J0 |( g+ {2 S7 @% |9 M bLength : 描述符大小.固定为0x09.2 K5 K' H) p* H" x! K/ L# G bDescriptorType : 接口描述符类型.固定为0x04.( A, e3 ]9 R6 R: F/ m, ~ m# w bInterfaceNumber: 该接口的编号. bAlternateSetting : 用于为上一个字段选择可供替换的位置.即备用的接口描述符标号. bNumEndpoint : 使用的端点数目.端点0除外. bInterfaceClass : 类型代码(由USB分配).6 i/ b9 e0 T$ E! W( E8 t6 a bInterfaceSubClass : 子类型代码(由USB分配).2 J& a! r+ Z1 F! L$ S7 t3 w2 ? bInterfaceProtocol : 协议代码(由USB分配).: v, _# I8 i6 c6 Z6 k iInterface : 字符串描述符的索引 4.4 端点描述符( l/ g2 }$ Q5 v1 |; w* c USB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量。摘取STM32的MSC设备类的端口描述符:6 I3 d5 C! O$ g3 G9 o: W' | I) v% s. w7 G6 e& `* t: v5 Y ![]() # h+ h2 v" G' |2 J) Y% s 用C语言组合就是这样的一个结构:: R6 }- t* ]) p. S5 h& }: i1 n- R9 n 6 ?: H8 U8 o1 L
每个字段含义如下: bLength : 描述符大小.固定为0x07 bDescriptorType : 接口描述符类型.固定为0x057 T" p* y2 M) F- `! R- X bEndpointType : USB设备的端点地址.Bit7决定方向,1为IN端点,0为OUT端点,对于控制端点可以忽略;Bit6-4,保留;BIt3-0:端点号 bmAttributes : 端点属性.Bit7-2,保留.BIt1-0:00控制,01同步,02批量,03中断8 b. c9 U) D ^ wMaxPacketSize : 本端点接收或发送的最大信息包大小 bInterval : 轮训数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255 7 m% |9 S" A- J7 r; o3 z) O 4.5 字符串描述符 字符串描述符是可选的,如果不支持字符串描述符,其设备描述符、配置描述符、接口描述符内的所有字符串描述符索引都必须为0。 Q6 ^& v6 C' y0 f. A' ~ 2 q! N d4 q( [/ H) x 字符串描述符结构如下:/ @0 z' U- h8 V' A1 G
各个字段含义: bLength : 描述符大小.由整个字符串的长度加上bLength和bDescriptorType的长度决定.( t% U" }$ P: i0 A! }1 n9 I4 E. W bDescriptorType : 接口描述符类型,固定为0x03.4 s6 H% ~ D5 \3 ^ bString[1] : Unicode编码字符串$ n! w7 t1 n$ ?* [. w 4.6 IAD描述符% v% H# A9 C6 G/ q USB组合设备一般用Interface Association Descriptor(IAD)实现,就是在要合并的接口前加上IAD描述符。例如你想用一个硬件USB接口实现两个功能,又能到U盘又能当虚拟串口,那么在USB配置描述符中就需要加上IAD描述符来指明。
以MSC+CDC为例,他的配置描述符结构就是这样的:
5、STM32-USB详解" B5 _3 I' { w# \4 q+ }" s& e $ o; J O; P/ A ![]() / N# w! R. |+ z% O% x/ P* ?( A 这个512字节SRAM叫做Packet Buffer Memory Area(简称PMA),这个很重要,后面会详细讲解。# _8 a9 P! C& v: ^& d5 q # v8 G7 X5 I1 `5 n+ G( c9 N& W ![]() - J, x" i5 ?! Y- s) b 根据描述可以,一共有8个端点,16个寄存器,一个端点关联两个寄存器,所以我们可以将他们规划为8个输入端点(0x80-0X87)+8个输出端点(0X00-0X07)。 5 y# c, d3 L- y5 z; Q: R 6、STM32-PMA详解 先说一下USB的数据包大小,全速设备的最大包大小为64字节,高速最大为1024字节。6 N, w% C( u6 L( k Packet Buffer Memory Area(简称PMA)# y, f; R8 S$ |+ t- G6 O7 ]" w3 ~ " E! ?2 L% f5 M! x% s/ `1 w STM32F1/F3/L1系列都有且结构相同(其他系列暂未考证),译过来就是包数据缓存区,大小为512字节,按2字节进行寻址。* _! \2 }; M {; ~0 v0 S8 ~ 这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。 " `% }% H0 P3 Y! T5 y 说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。 3 \% p( n6 N: [3 u0 Q5 Z 很多人利用ST官方的USB库修改自己的USB应用时候卡住,获取改完之后懵懵懂懂出现错误,估计大多数原因就在此处的修改! 摘取一下STM32F1参考手册里的PMA描述表:- l8 B0 b1 {7 m5 I+ M 9 o0 F6 d; m( T0 S7 A ![]() ?3 I- y* P3 a7 H; T$ N 名称含义: ADDR0_TX:输出端点0发送缓冲区地址) p2 y6 ~8 x* T1 v- ^& n# b5 | COUNT0_TX:输出端点0发送缓冲区大小 ADDR0_RX:输入端点0发送缓冲区地址6 b- O) ]" @# S/ Q; g# l+ u' e! p& B2 M COUNT0_RX:输入端点0发送缓冲区大小1 _ x; v0 x8 S( p' H 可以看到一个完整的端点描述包括:缓冲区地址+缓冲区大小。2 _8 f$ u8 ?5 D * B- l7 b! m# t- g+ ?/ i PMA的头部为端点的描述,每个端点占8个字节,实际使用了几个端点就有几个描述头,例如使用了0、1、2这三个连续端点(这里注意是连续端点),那么PMA头部的3x8=24(十六进制的0X18)字节就是描述,倘若你使用的是0、1、3这三个端点,其中编号为2的端点虽然没使用但是占用空间,那么PMA头部的端点描述就是4x8=32字节,编号为2的端点8字节的空间就浪费了(严格来说没浪费,就是不方便使用)。" ~+ e% c: x' n( W9 g7 |. M % q& q5 k5 o& V+ o) i3 q1 b 头部的端点描述之后就是各个端点的缓冲区了,例如使用了0、1、2三个端点,占用了PMA头部3x8=24字节的空间,那么这三个端点的缓冲区地址就是从PMA偏移24字节开始的,当然只要是大于24就都可以,这里就是最关键的地方了,很多人修改ST官方库实现自己USB应用时候就是没改这里的地址,导致缓冲区的使用覆盖了PMA头部的端点描述从而出错! 4 \" O% n1 R' D0 _: x 下面摘取一个STM32官方MSC设备的实例进行分析:3 M- y( [* L% T5 U : x; u2 v- ]& H+ U/ Q$ ] ![]() 8 ~4 J8 F0 ^1 H" b5 o C 可以看到一共用到了4个端点,分别是输入端点0X80和0X81,输出端点0X00和0X01,其中0X00端点和0X80端点是供USB使用必须有的,0X81和0X01端点则是MSC设备输入输出端点。 + N% v& U4 H$ ^/ d7 K0 P 那么一共使用了4个端点,按理来说PMA头部的端点描述大小应该是4X8=32(十六进制的0X20)字节,0X20之后的才是各个端点缓冲区,但是ST这里的却是从0X18开始,也就是说使用了三个端点,这个地方我还没有搞明白为什么,欢迎各位补充! 至于为什么0X18之后是0X58,是因为USB全速设备的最大包是64字节(十进制的0X40),所以这里PMA的划分就是:/ x0 m0 Z8 w( v6 @ 9 T. B- }( }5 N. |1 ~" i; F 头部0X18字节为各个端点的描述8 z# y, b/ h* I6 P8 _ 0X18地址开始的64字节为输出端点0的缓冲区 0X58地址开始的64字节为输入端点0的缓冲区- ]3 S; R- M, k 0X98地址开始的64字节为输出端点1的缓冲区 0XD8地址开始的64字节为输入端点1的缓冲区 1 ?' ^0 i0 e2 j/ O . ^4 ~$ W% y( W, c3 [, f: w2 h 这里注意一点,缓冲区分配好之后访问是不会溢出的,也就是说缓冲区之间完全隔离。/ q7 p- w9 m2 b# B |
最全USB HID开发资料,悉心整理一个月,亲自测试
实战经验 | 选择USBX模块生成USB CDC ACM无PD的项目
STM32 USB HID键盘例程
刘氓兔的杂谈【001】-片上USB 高速PHY
【经验分享】在进行 USB CDC 类开发时,无法发送 64整数倍的数据
【源码】STLINK-V3MINI 高速USB仿真器,成功改刷【高速CMSIS-DAP】
在线直播|无需编写任何代码即可在STM32上实现USB-C Power Delivery
STM32 USB CDC 虚拟多串口
圈圈发布USB图书第二版有感,以及分享一些我学习USB过程...
USB Audio设计与实现