(1)STM32 USB设备开发-基础知识

news/2025/1/22 6:25:34/

开篇感谢:

【经验分享】STM32 USB相关知识扫盲 - STM32团队 ST意法半导体中文论坛

单片机学习记录_桃成蹊2.0的博客-CSDN博客

USB_不吃鱼的猫丿的博客-CSDN博客

1、USB鼠标_哔哩哔哩_bilibili

usb_冰糖葫的博客-CSDN博客

USB_lqonlylove的博客-CSDN博客

USB -- STM32F103 USB AUDIO(音频)Speaker同步传输(Out传输)讲解(七)_stm32 usb audio-CSDN博客

基于SMT32的USB键盘制作(附稚晖君键盘方案分析)_大颜u的博客-CSDN博客

基础知识

等级划分:

接口类型:

STM32基础型(F1系列)所带的USB是全速。

电气属性

USB的通信都是由主机发起的,这一点与IIC协议是类似的。

数据线

USB使用差分传输模式,有两条数据线,分别是:

USB数据正信号线,USB Data Positive,即USB-DP线,简写为D+

USB数据负信号线,USB Data Minus, 即USB-DM线,简写为D-

剩下的就是电源线(5V-Vbus)和地线(GND)。

USB主机是如何识别设备是高速设备/全速设备/低速设备?

主机的D+和D-都接有15K下拉电阻。

全速USB设备的数据线D+接有1.5K的上拉电阻,一旦接入主机,主机的D+被拉高

低速USB设备的数据线D-接有1.5K的上拉电阻,一旦接入主机,主机的D-会被拉高

因此,主机就可以根据检测到自己的D+为高还是D-为高,从而判断接入的设备是一个全速还是低速设备。

所以你可以看到STM32板子上的USB口D+有一个上拉电阻,而且是必须有的:

USB设备分类

看一个CDC虚拟串口的设备分类:

在看一个MSC的(模拟U盘):

不同的类有不同的用途;不同的应用场合对应不同的产品形态;不同的产品形态可能会有自己特殊的描述符,比如: HID类有报告描述符、CDC类有ACM、Union描述符等。

常用USB设备类:大容量存储类(Mass Storage Class - MSC),人机接口设备类(Human Interface Device - HID),音频设备类(Audio Device Class),视频设备类(Video Device Class),成像设备类(Imaging Device Class),打印机设备类(Printer Device Class)

描述符详解

以STM32里的MSC设备为例,MSC类所需要的描述符有:设备描述符+配置描述符+接口描述符(数量由配置描述符里的bNumInterfaces字段决定)+端点描述符(数量由配置描述符里的bNumEndpoints决定)

设备描述符

其中1个USB设备只能有一个设备描述符,但是其他描述符可以有多个。

一般实现USB复合设备的方法主要是:一个设备描述符、一个配置描述符、多个接口描述符、多个端点描述符......

每个USB设备都必须且只有一个设备描述符,摘取一下STM32的MSC设备里的实例代码:

每个字段的含义:

bLength:描述符大小.固定为0x12;

bDescriptorType:设备描述符类型.固定为0x01;

bcdUSB:USB 规范发布号.表示了本设备能适用于那种协议,如2.0=0200,1.1=0110;

bDeviceClass:类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的;

bDeviceSubClass:子类型代码(由USB分配),如果 bDeviceClass值是0,一定要设置为0。其它情况就跟据USB-IF组织定义的编码;

bDeviceProtocol:协议代码(由USB分配),如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH;

bMaxPacketSize0:端点0最大分组大小(只有8,16,32,64有效);

idVendor:供应商ID(由USB分配);

idProduct : 产品ID(由厂商分配),由供应商ID和产品ID,就可以让操作系统加载不同的驱动程序

bcdDevice :设备出产编码.由厂家自行设置;

iManufacturer :厂商描述符字符串索引.索引到对应的字符串描述符. 为0则表示没有;

iProduct :产品描述符字符串索引.同上;

iSerialNumber:设备序列号字符串索引.同上;

bNumConfigurations :可能的配置数.指配置字符串的个数。

bDeviceClass、bDeviceSubClass和bDeviceProtocol可在USB官网进行查询,idVendor是向USB机构申请的,需要支付一笔费用,如果针对开发者,可以选择芯片厂商的idVendor进行开发。

配置描述符

配置描述符定义了设备的配置信息,一个设备可以有多个配置描述符。摘一个STM32的MSC设备的配置描述符:

用C语言组合就是这样的一个结构:

typedef struct _USB_CONFIGURATION_DESCRIPTOR_
{BYTE      bLength,BYTE      bDescriptorType,uint16_t  wTotalLength,BYTE      bNumInterfaces,BYTE      bConfigurationValue,BYTE      iConfiguration,BYTE      bmAttributes,BYTE      MaxPower
}USB_CONFIGURATION_DESCRIPTOR;

每个字段含义如下:

bLength:描述符大小,固定为0x09.

bDescriptorType:配置描述符类型,固定为0x02.

wTotalLength:返回整个数据的长度,指此配置返回的配置描述符,接口描述符以及端点描述符的全部大小

bNumInterfaces:配置所支持的接口数。指该配置配备的接口数量也表示该配置下接口描述符数量

bConfigurationValue:作为Set Configuration的一个参数选择配置值

iConfiguration:用于描述该配置字符串描述符的索引

bmAttributes:供电模式选择,Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒

MaxPower:总线供电的USB设备的最大消耗电流.以2mA为单位,0x32表示 100mA

接口描述符

接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定,摘取STM32的MSC设备类的接口描述符:

用C语言组合就是这样的一个结构:

typedef struct _USB_INTERFACE_DESCRIPTOR_
{BYTE      bLength,BYTE      bDescriptorType,BYTE      bInterfaceNumber,BYTE      bAlternateSetting,BYTE      bNumEndpoint,BYTE      bInterfaceClass,BYTE      bInterfaceSubClass,BYTE      bInterfaceProtocol,BYTE      iInterface
}USB_INTERFACE_DESCRIPTOR;

每个字段含义如下:

bLength : 描述符大小.固定为0x09

bDescriptorType : 接口描述符类型.固定为0x04

bInterfaceNumber: 该接口的编号

bAlternateSetting : 用于为上一个字段选择可供替换的位置.即备用的接口描述符标号

bNumEndpoint : 使用的端点数目.端点0除外

bInterfaceClass : 类型代码(由USB分配)

bInterfaceSubClass : 子类型代码(由USB分配)

bInterfaceProtocol : 协议代码(由USB分配)

iInterface : 字符串描述符的索引

端点描述符

USB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量。摘取STM32的MSC设备类的端口描述符:

用C语言组合就是这样的一个结构:

typedef struct _USB_ENDPOINT_DESCRIPTOR_ {BYTE        bLength,BYTE        bDescriptorType,BYTE        bEndpointAddress,BYTE        bmAttributes,BYTE        bInterval
}USB_ENDPOINT_DESCRIPTOR;

每个字段含义如下:

bLength : 描述符大小.固定为0x07

bDescriptorType : 接口描述符类型.固定为0x050

bEndpointType : USB设备的端点地址.Bit7决定方向,1为IN端点,0为OUT端点,对于控制端点可以忽略;Bit6-4,保留;BIt3-0:端点号

bmAttributes : 端点属性.Bit7-2,保留.BIt1-0:00控制,01同步,02批量,03中断

wMaxPacketSize : 本端点接收或发送的最大信息包大小

bInterval : 轮训数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255

字符串描述符

字符串描述符是可选的,如果不支持字符串描述符,其设备描述符、配置描述符、接口描述符内的所有字符串描述符索引都必须为0。

字符串描述符结构如下:

typedef struct _USB_STRING_DESCRIPTION_BYTE      bLength,BYTE      bDescriptionType,BYTE      bString[1];
}USB_STRING_DESCRIPTION;

各个字段含义:

bLength : 描述符大小.由整个字符串的长度加上bLength和bDescriptorType的长度决定.

bDescriptorType : 接口描述符类型,固定为0x03.

bString[1] : Unicode编码字符串

IAD描述符

USB组合设备一般用Interface Association Descriptor(IAD)实现,就是在要合并的接口前加上IAD描述符。例如你想用一个硬件USB接口实现两个功能,又能到U盘又能当虚拟串口,那么在USB配置描述符中就需要加上IAD描述符来指明。

typedef struct _USBInterfaceAssociationDescriptor
{BYTE  bLength:                  0x08        //描述符大小,固定BYTE  bDescriptorType:          0x0B        //IAD描述符类型,固定BYTE  bFirstInterface:          0x00        //起始接口编号BYTE  bInterfaceCount:          0x02        //本个IAD下设备类的接口数量BYTE  bFunctionClass:           0x0E        //类型代码,本个IAD指示的是什么类型的设备,例如CDC是0X02,MSC是0X08BYTE  bFunctionSubClass:        0x03        //子类型代码BYTE  bFunctionProtocol:        0x00        //协议代码BYTE  iFunction:                0x04        //描述字符串索引
}

以MSC+CDC为例,他的配置描述符结构就是这样的:

配置描述符
{IAD描述符1(CDC){接口描述符1(通信接口){其他描述符(特殊描述符){/*Header Functional Descriptor*//*Call Management Functional Descriptor*//*ACM Functional Descriptor*//*Union Functional Descriptor*/}端点描述符(命令端点){}}接口描述符2(数据接口){端点描述符1(输出端口){}端点描述符2(输入端口){ }}}IAD描述符(MSC){接口描述符1{端点描述符1{}端点描述符2{}}}
}

其它描述符

Packet的组成

Packet主要由下面四部分组成:

SOP:起始帧,从DILE状态(J状态)切换到K状态

SYNC:同步域,3个重复的KJ状态切换,后跟随2个位时间的K状态

Packet Connent:内容域,主要包括以下内容:

PID:包标识

地址:设备地址

帧号:11位帧号

数据:通信的数据

CRC:校验

EOP:结束帧,持续2个位时间的SE0信号,后跟随1个位时间的J状态

接下来重点讲解一下Packet Connent域里面的内容。

Packet的内容

Packet包的内容--PID域

PID:Packet Identifier,包标识,LSB在前,前4个字节为PID码,后四个字节是前四个字节的取反。

以下是PID的类型码。

Packet包的内容--地址域

地址由7位的设备地址和4位的端点地址组成。

Packet包的内容--帧号域

帧号域由以下特性:

11位

主机每发出一个帧,帧号都会自动加1(全速和低速设备1ms发出一帧, 高速设备0.125ms发出一帧)

当帧号达到0x7FFF的时候,将清零帧号重新计数

仅在每个帧的帧首才传输一次SOF包

Packet包的内容--数据域

根据传输类型的不同,数据域的数据长度从0-1024字节不等。

Packet包的内容--CRC域

Packet的类型

Packet包的类型--令牌包

令牌包的组成为:PID+地址+CRC

PID:IN、OUT、SETUP令牌

地址:7位的设备地址和4位的端点号组成

CRC:这里是对地址域计算

Packet包的类型--SOF包

SOF包的组成为:PID+帧号+CRC

PID:SOF令牌

帧号:LS/FS每1ms一个帧,HS每0.125ms一个帧

CRC:这里是对帧号域计算

Packet包的类型--数据包

数据包的组成为:PID+数据+CRC

PID:DATA0、DATA1、DATA2、MDATA令牌

数据:传输类型不同,数据包的最大长度有所不同

CRC:这里是对数据域计算

Packet包的类型--握手包

握手包的组成为:PID

PID:ACK、NAK、STALL、NYET令牌

ACK:表示正确接收数据,并且有足够的空间来容纳数据。主机和设备都可以用ACK来确认,而NAK、STALLA、NYET只能设备返回,主机不能使用这些握手包。

NAK:表示没有数据需要返回,或者数据正确接收但是没有足够的空间来容忍它们。 当主机收到NAK时,知道设备还未准备好,主机会在以后的合适的时机进行重新传输。

STALL:表示设备无法执行这个请求,或者端点已经被挂起,它表示一种错误的状态。 设备返回STALL后,需要主机进行干预才能解除这种STALL状态。

NYET:只在USB2.0的高速设备输出事物中使用,表示设备本次数据成功接收,但是没有 足够的空间来接收下一次数据。主机在下一次输出数据时,将先使用PING令牌包来试探 设备是否有空间接收数据,以避免不必要的带宽浪费。

注意:

1. 当收到SETUP包的时候,设备只能回复ACK;

2. 当同步传输的时候,没有握手包。

传输类型

USB的传输类型分为控制传输、中断传输、批量传输和同步传输,每种传输类型用在特定的场合。

控制传输

非周期性传输,用于命令和状态的传输。每个USB设备都必须有控制端点,支持控制传输来进行命令和状态的传输。USB主机驱动将通过控制传输与USB设备的控制端点通讯,完成USB设备的枚举和配置。

控制传输是双向的传输,必须有IN和OUT两个方向上的特定端点号的控制端点来完成两个方向上的控住传输。

中断传输

周期性,低频率传输,允许有限延迟的通信,中断传输用于那些频率不高,但对周期有一定要求的数据传输。具有保证的带宽,并能在下个周期对先前错误的传输进行重传;对于全速端点,中断传输的时间间隔在1ms到255ms之间,对于低速端点,时间间隔限制在10ms到255ms之间,对于高速端点, 时间间隔为2bInterval-1*125us,bInterval的值在1到16之间。

中断传输总算单向的,可以用单向的中断端点来实现某个方向上的中断传输。

批量传输

非周期性,大容量数据的通信,数据可以占用任意带宽。大容量数据传输适用于那些需要大数据量传输,但是对实时性,对延迟性和带宽没有严格要求的应用。大容量传输可以占用任意可用的数据带宽。

大容量传输是单向的,可以用单向的大容量传输端点来实现某个方向的大容量传输。

同步传输

周期性,持续的传输,用于传输与时效相关的信息,并且在数据中保存时间戳的信息。同步传输用于传输那些需要保证带宽,并且不能忍受延迟的信息。整个带宽都将用于保证同步传输的数据完整,并且不支持出错重传。

同步传输总是单向的,可以使用单向的同步端点来实现某个方向上的同步传输。

复位挂起和唤醒机制

USB使用的差分传输模式,两个数据线D+和D-(VOH:2.8V VOL:0.3V)

差分信号1:D+ > VOH and D- < VOL

差分信号0:D- > VOH and D+ < VOL

IDLE状态:J状态

复位信号:D+ and D- < VOL for >= 10ms

挂起信号:USB主机3m内不发送任何信号(3ms以上的IDLE状态)

唤醒信号:K状态持续20ms以上,并以低速EOP信号结尾。

USB的枚举过程

枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。

枚举通信过程具体如下:

step1:检测电压变化,报告主机

首先,USB设备上电后,一直监测USB设备接口电平变化HUB检测到有电压变化,将利用自己的中断端点将信息反馈给主控制器有设备连接。

Step2:主机了解连接设备

主机在知道有设备接入后会发送一个Get_Port_Status请求(request)给hub以了解此次状态改变的确切含义。

Step3:Hub检测所插入的设备是高速还是低速

hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。USB 2.0规范要求速度检测要先于复位(Reset)操作。

Step4:hub复位设备

主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms。当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。

Step5: Host检测所连接的全速设备是否是支持高速模式

因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。

同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一直以全速工作。

Step6:Hub建立设备和主机之间的信息通道

主机不停地向hub发送Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。

当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)

Step7:主机发送Get_Descriptor请求获取默认管道的最大包长度

默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。

设备描述符的第8字节代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。

Step8:主机给设备分配一个地址

主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。

Step9:主机获取设备的信息

主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。

之后主机发送Get_Descriptor请求,读取配置描述符(Configuration Descriptor),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。

接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。

如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。

Step10: 主机给设备挂载驱动(复合设备除外)

主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。 然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。

对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。

Step11:设备驱动选择一个配置

驱动(注意,这里是驱动,之后的事情都是有驱动来接管负责与设备的通信)根据前面设备回复的信息,发送Set_Configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。

对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。

以下是一个简单的枚举过程,帮助大家理解软件层面的协议。

Host [ 80 06 00 01 00 00 40 00 ] 主机:你是什么设备?
Device [ 12 01 00 02 0A 00 00 40 70 34 08 00 00 02 01 02 03 01 ] 设备:我是CDC设备
Host [ 00 05 21 00 00 00 00 00 ] 主机设置唯一设备地址(地址只能从0-127),以后使用此地址和设备通讯
Host [ 80 06 00 01 00 00 12 00 ] 主机:你是什么设备?
Device [ 12 01 00 02 02 02 02 40 70 34 08 00 00 02 01 02 03 01 ] 设备:我是CDC设备
Host [ 80 06 00 02 00 00 FF 00 ] 主机:你有几个接口?每个接口使用了哪几个端点?
Device [ 09 02 43 00 02 01 00 C0 32 09 04 00 00 01 02 02 01 00 05 24 00 10 01 05 24 01 00 01 04 24 02 02 05 24 06 00 01 07 05 82 03 08 00 FF 09 04 01 00 02 0A 00 00 00 07 05 03 02 40 00 00 07 05 81 02 ]
Device [ 40 00 00 ] 设备:我有2个接口,一个接口使用了输入端点2,一个接口使用了输入端点1和输出端点3
Host [ 80 06 00 03 00 00 FF 00 ] 主机:你的字符串使用的是什么编码格式?
Device [ 04 03 09 04 ] 设备:我使用的美国的编码格式
Host [ 80 06 02 03 09 04 FF 00 ] 主机:你的产品名称是什么?
Device [ 32 03 53 00 54 00 4D 00 33 00 32 00 20 00 56 00 69 00 72 00 74 00 75 00 61 00 6C 00 20 00 43 00 4F 00 4D 00 20 00 50 00 6F 00 72 00 74 00 20 00 20 00 ] 设备:我的产品名称是…
Host [ 80 06 03 03 09 04 FF 00 ] 主机:你的设备UID是什么?
Device [ 1A 03 35 00 43 00 44 00 45 00 35 00 38 00 34 00 30 00 33 00 32 00 33 00 30 00 ] 设备:我的

UID是....

STM32相关讲解

STM32-USB详解

这个512字节SRAM叫做Packet Buffer Memory Area(简称PMA),这个很重要,后面会详细讲解。

根据描述可以,一共有8个端点,16个寄存器,一个端点关联两个寄存器,所以我们可以将他们规划为8个输入端点(0x80-0X87)+8个输出端点(0X00-0X07)。

STM32-PMA详解

先说一下USB的数据包大小,全速设备的最大包大小为64字节,高速最大为1024字节,STM32是USB全速设备,所以最大为64字节。

Packet Buffer Memory Area(简称PMA)

STM32F1/F3/L1系列都有且结构相同(其他系列暂未考证),译过来就是包数据缓存区,大小为512字节,按2字节进行寻址。

这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。

说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。

很多人利用ST官方的USB库修改自己的USB应用时候卡住,获取改完之后懵懵懂懂出现错误,估计大多数原因就在此处的修改!

摘取一下STM32F1参考手册里的PMA描述表:

名称含义:

ADDR0_TX:输出端点0发送缓冲区地址

COUNT0_TX:输出端点0发送缓冲区大小

ADDR0_RX:输入端点0发送缓冲区地址

COUNT0_RX:输入端点0发送缓冲区大小

可以看到一个完整的端点描述包括:缓冲区地址+缓冲区大小。

PMA的头部为端点的描述,每个端点占8个字节,实际使用了几个端点就有几个描述头,例如使用了0、1、2这三个连续端点(这里注意是连续端点),那么PMA头部的3x8=24(十六进制的0X18)字节就是描述,倘若你使用的是0、1、3这三个端点,其中编号为2的端点虽然没使用但是占用空间,那么PMA头部的端点描述就是4x8=32字节,编号为2的端点8字节的空间就浪费了(严格来说没浪费,就是不方便使用)。

头部的端点描述之后就是各个端点的缓冲区了,例如使用了0、1、2三个端点,占用了PMA头部3x8=24字节的空间,那么这三个端点的缓冲区地址就是从PMA偏移24字节开始的,当然只要是大于24就都可以,这里就是最关键的地方了,很多人修改ST官方库实现自己USB应用时候就是没改这里的地址,导致缓冲区的使用覆盖了PMA头部的端点描述从而出错!

下面摘取一个STM32官方MSC设备的实例进行分析:

可以看到一共用到了4个端点,分别是输入端点0X80和0X81,输出端点0X00和0X01,其中0X00端点和0X80端点是供USB使用必须有的,0X81和0X01端点则是MSC设备输入输出端点。

那么一共使用了4个端点,按理来说PMA头部的端点描述大小应该是4X8=32(十六进制的0X20)字节,0X20之后的才是各个端点缓冲区,但是ST这里的却是从0X18开始,也就是说使用了三个端点,这个地方我还没有搞明白为什么,欢迎各位补充!

至于为什么0X18之后是0X58,是因为USB全速设备的最大包是64字节(十进制的0X40),所以这里PMA的划分就是:

头部0X18字节为各个端点的描述

0X18地址开始的64字节为输出端点0的缓冲区

0X58地址开始的64字节为输入端点0的缓冲区

0X98地址开始的64字节为输入端点1的缓冲区

0XD8地址开始的64字节为输出端点1的缓冲区

这里注意一点,缓冲区分配好之后访问是不会溢出的,也就是说缓冲区之间完全隔离。

STM32F1-HAL库中 USB外设库的文件介绍

STM32_USB_Host_Library 中的文件介绍

STM32_USB_Device_Library 中的文件介绍

这篇内容很多,很难完全看完,对于像弄明白USB原理和过程的有一定意义,但是对于只想快速做出东西的可以直接看后面的文章。


http://www.ppmy.cn/news/1565154.html

相关文章

Android系统开发(六):从Linux到Android:模块化开发,GKI内核的硬核科普

引言&#xff1a; 今天我们聊聊Android生态中最“硬核”的话题&#xff1a;通用内核镜像&#xff08;GKI&#xff09;与内核模块接口&#xff08;KMI&#xff09;。这是内核碎片化终结者的秘密武器&#xff0c;解决了内核和供应商模块之间无尽的兼容性问题。为什么重要&#x…

iOS-YModel

YModel 是一个高效的 iOS/OSX 的模型转换框架&#xff0c;可以轻松地将 JSON 转换成 Model&#xff0c;或者将 Model 转换成 JSON。以下是详细的使用指南&#xff1a; 导入 YYModel: 确保在你的项目中导入了 YYModel。使用 CocoaPods 的话可以在 Podfile 中加入以下代码&#…

Java 在包管理与模块化中的优势:与其他开发语言的比较

在开发复杂的、规模庞大的软件系统时&#xff0c;包管理和模块化设计起着至关重要的作用。它们不仅决定了代码的组织和可维护性&#xff0c;还直接影响到团队协作效率、扩展性和性能。在众多编程语言中&#xff0c;Java 凭借其成熟的生态系统、强类型系统和标准化的包管理机制&…

JupyterLab 安装以及部分相关配置

安装 JupyterLab pip install jupyter启动 JupyterLab jupyter lab [--port <指定的端口号>] [--no-browser] # --port 指定端口 # --no-browser 启动时不打开浏览器安装中文 首先安装中文包 pip install jupyterlab-language-pack-zh-CN安装完成后重启 JupyterLab 选…

【STM32-学习笔记-15-】MAX7219点阵屏模块

文章目录 MAX7219点阵模块一、MAX7219Ⅰ、 概述Ⅱ、功能特点Ⅲ、引脚功能Ⅳ、典型应用电路Ⅴ、内部组成结构Ⅵ、时序图Ⅶ、寄存器 二、STM32控制点阵屏Ⅰ、程序框图Ⅱ、程序编写①、MAX7219.c②、MAX7219.h③、MAX7219_Img.h④、main.c MAX7219点阵模块 一、MAX7219 Ⅰ、 概述…

性能调优篇 四、JVM运行时参数

目录 一、三种JVM参数选项1、标准参数选项1&#xff09;特点2&#xff09;各种选项3&#xff09;-server 和 -client 2、-X参数选项3、-XX参数选项 二、添加JVM参数选项1、idea 如何添加jvm参数 三、常见的JVM参数选项1、打印设置的参数选项及其值2、堆、栈、方法区等内存大小设…

1. 基于图像的三维重建

1. 基于图像的三维重建 核心概念三维重建中深度图、点云的区别&#xff1f;深度图点云总结 深度图到点云还需要什么步骤&#xff1f;1. **获取相机内参**2. **生成相应的像素坐标**3. **计算三维坐标**4. **构建点云**5. **处理颜色信息&#xff08;可选&#xff09;**6. **去除…

【Unity3D】3D物体摆放、场景优化案例Demo

目录 PlaceManager.cs(放置管理类) Ground.cs(地板类) 和 GroundData.cs(地板数据类) 额外知识点说明 1、MeshFilter和MeshRenderer的Bounds区别 2、Gizmos 绘制一个平行于斜面的立方体 通过网盘分享的文件&#xff1a;PlaceGameDemo2.unitypackage 链接: https://pan.baid…