当前位置: 首页 > news >正文

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

开篇感谢:

【经验分享】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原理和过程的有一定意义,但是对于只想快速做出东西的可以直接看后面的文章。

相关文章:

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

开篇感谢&#xff1a; 【经验分享】STM32 USB相关知识扫盲 - STM32团队 ST意法半导体中文论坛 单片机学习记录_桃成蹊2.0的博客-CSDN博客 USB_不吃鱼的猫丿的博客-CSDN博客 1、USB鼠标_哔哩哔哩_bilibili usb_冰糖葫的博客-CSDN博客 USB_lqonlylove的博客-CSDN博客 USB …...

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…...

缓存之美:万文详解 Caffeine 实现原理(上)

由于社区最大字数限制&#xff0c;本文章将分为两篇&#xff0c;第二篇文章为缓存之美&#xff1a;万文详解 Caffeine 实现原理&#xff08;下&#xff09; 大家好&#xff0c;我是 方圆。文章将采用“总-分-总”的结构对配置固定大小元素驱逐策略的 Caffeine 缓存进行介绍&…...

PHP语言的网络编程

PHP语言的网络编程 网络编程是现代软件开发中不可或缺的一部分&#xff0c;尤其是在日益发展的互联网时代。PHP&#xff08;Hypertext Preprocessor&#xff09;是一种广泛使用的开源脚本语言&#xff0c;专门用于Web开发。它的灵活性、易用性以及强大的社区支持使得PHP在网络…...

【技巧】优雅的使用 pnpm+Monorepo 单体仓库构建一个高效、灵活的多项目架构

单体仓库&#xff08;Monorepo&#xff09;搭建指南&#xff1a;从零开始 单体仓库&#xff08;Monorepo&#xff09;是一种将多个相关项目集中管理在一个仓库中的开发模式。它可以帮助开发者共享代码、统一配置&#xff0c;并简化依赖管理。本文将通过实际代码示例&#xff0…...

算法项目实时推流

1、搭建流媒体服务器 下载mediamtx 2、视频流直推 ffmpeg -stream_loop -1 -i DJI_20250109112715_0002_W.MP4 -r 30 -c:v libx264 -preset ultrafast -f flv rtmp://192.168.100.20:1935/live/test_chengdu1 3、硬件加速 如果硬件支持&#xff0c;可以使用硬件加速编码器&am…...

软件测试—— 接口测试(HTTP和HTTPS)

软件测试—— 接口测试&#xff08;HTTP和HTTPS&#xff09; HTTP请求方法GET特点使用场景URL结构URL组成部分URL编码总结 POST特点使用场景请求结构示例 请求标头和响应标头请求标头&#xff08;Request Headers&#xff09;示例请求标头 响应标头&#xff08;Response Header…...

PCL K4PCS算法实现点云粗配准【2025最新版】

目录 一、算法原理1、算法概述2、算法流程3、参考文献二、 代码实现1、原始版本2、2024新版三、 结果展示本文由CSDN点云侠原创,原文链接,首发于:2020年4月27日。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的抄袭狗。 博客长期更新,本文最近一次更新时间为…...

Docker 学习总结(85)—— docker cp 使用总结

前言 在现代软件开发中,Docker 已成为一种流行的容器化技术。无论是在开发、测试还是生产环境中,管理容器内的文件都是一项常见且重要的任务。本文将详细介绍如何使用 docker cp 命令在 Docker 容器与宿主机之间拷贝文件和目录,并结合一些实际使用场景,帮助您更高效地管理…...

《FMambaIR:一种基于混合状态空间模型和频域的方法用于图像恢复》学习笔记

paper&#xff1a;(PDF) FMambaIR: A Hybrid State Space Model and Frequency Domain for Image Restoration 目录 摘要 一、引言 二、相关工作 1、图像恢复 2、频率学习 3、状态空间模型&#xff08;SSM&#xff09; 三、框架 1、基本知识 2、整体框架 3、F-Mamba…...

PyQt5 超详细入门级教程上篇

PyQt5 超详细入门级教程 上篇&#xff1a;1-3部分&#xff1a;PyQt5基础与常用控件 第1部分&#xff1a;初识 PyQt5 和安装 1.1 什么是 PyQt5&#xff1f; PyQt5 是 Python 的图形用户界面 (GUI) 框架&#xff0c;它基于强大的 Qt 库。Qt 是一个跨平台的 C 框架&#xff0c;用…...

通信协议—WebSocket

一、WebSocket编程概念 1.1 什么是WebSocket WebSocket 是一种全双工通信协议&#xff0c;允许在客户端&#xff08;通常是浏览器&#xff09;和服务器之间建立持久连接&#xff0c;以实现实时的双向通信。它是 HTML5 标准的一部分&#xff0c;相比传统的 HTTP 请求&#xff…...

FFmpeg音视频采集

文章目录 音视频采集音频采集获取设备信息录制麦克风录制声卡 视频采集摄像机画面采集 音视频采集 DirectShow&#xff08;简称DShow&#xff09;是一个Windows平台上的流媒体框架&#xff0c;提供了高质量的多媒体流采集和回放功能&#xff0c;它支持多种多样的媒体文件格式&…...

【微机原理与接口技术】定时控制接口

文章目录 8253的引脚和工作方式内部结构和引脚工作方式方式0&#xff1a;计数结束中断方式1&#xff1a;可编程单稳脉冲方式2&#xff1a;周期性负脉冲输出方式3&#xff1a;方波发生器方式4&#xff1a;软件触发的单次负脉冲输出方式5&#xff1a;硬件触发的单次负脉冲输出各种…...

AG32 FPGA 的 Block RAM 资源:M9K 使用

1. 概述 AG32 FPGA 包含了 4 个 M9K 块&#xff0c;每个 M9K 块的容量为 8192 bits&#xff0c;总计为 4 个 M9K&#xff08;4K bytes&#xff09;。这使得 AG32 的内部存储非常适合嵌入式应用&#xff0c;能够有效地利用片上资源。 M9K 参数 参考自《AGRV2K_Rev2.0.pdf》。…...

第3天:阿里巴巴微服务解决方案概览

一、阿里巴巴微服务解决方案概述 阿里巴巴在微服务领域贡献了多个开源项目&#xff0c;形成了完整的微服务解决方案&#xff0c;广泛应用于分布式系统开发。其中&#xff0c;Spring Cloud Alibaba 是基于 Spring Cloud 构建的一站式微服务解决方案&#xff0c;集成了多个阿里巴…...

在Ubuntu上安装RabbitMQ教程

1、安装erlang 因为rabbitmq是基于erlang开发的&#xff0c;所以要安装rabbitmq&#xff0c;首先需要安装erlang运行环境 apt-get install erlang执行命令查是否安装成功&#xff1a;erl&#xff0c;疯狂 Ctrlc 就能退出命令行 2、安装rabbitmq 1、查看erlang与rabbitmq版本…...

WPF 引发类型为“System.Windows.Forms.AxHost+InvalidActiveXStateException”的异常 解决办法

本章讲述&#xff1a;引发类型为“System.Windows.Forms.AxHostInvalidActiveXStateException”的异常 解决办法。 这几天在做一个WPF功能时&#xff0c;因为引用了第三方的OCX控件&#xff0c;一般来说一个对象只要实例化就行了, 但是在引用这个控件时就报引发类型为“System.…...

Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制

在Vue 3中&#xff0c;导航守卫&#xff08;Navigation Guard&#xff09;用于拦截路由的变化&#xff0c;可以在用户访问页面前进行检查。结合Axios进行token认证机制时&#xff0c;我们可以通过导航守卫在路由跳转时&#xff0c;检查用户的认证状态&#xff0c;确保用户有有效…...

代码随想录算法【Day28】

Day28 122.买卖股票的最佳时机 II 最终利润是可以分解的 假如第 0 天买入&#xff0c;第 3 天卖出&#xff0c;那么利润为&#xff1a;prices[3] - prices[0]。 相当于(prices[3] - prices[2]) (prices[2] - prices[1]) (prices[1] - prices[0])。 所以把利润分解为每天…...

【21】Word:德国旅游业务❗

目录 题目 NO1.2.3 NO4 NO5.6 NO7 NO8.9.10.11 题目 NO1.2.3 F12&#xff1a;另存为布局→页面设置→页边距&#xff1a;上下左右选中“德国主要城市”→开始→字体对话框→字体/字号→文本效果&#xff1a;段落对话框→对齐方式/字符间距/段落间距 NO4 布局→表对话框…...

基于 MDL 行情插件的中金所 L1 数据处理最佳实践

本文介绍了如何通过 DolphinDB 的 MDL 插件订阅并处理中金所 Level 1 实时数据。首先&#xff0c;文章简要介绍了 MDL 插件的功能和作用。它是基于 MDL 官方提供的行情数据服务 C SDK&#xff08;即 TCP 版本 MDL &#xff09;实现&#xff0c;提供了实时数据获取和处理的能力。…...

在 vscode + cmake + GNU 工具链的基础上配置 JLINK

安装 JLINK JLINK 官网链接 下载安装后找到安装路径下的可执行文件 将此路径添加到环境变量的 Path 中。 创建 JFlash 项目 打开 JFlash&#xff0c;选择新建项目 选择单片机型号 在弹出的窗口中搜索单片机 其他参数根据实际情况填写 新建完成&#xff1a; 接下来设置…...

靶机复现-pikachu靶机文件包含漏洞

本篇文章旨在为网络安全渗透测试靶机复现学习。通过阅读本文&#xff0c;读者将能够对渗透pikachu靶场文件包含漏洞复现有一定的了解 原文学习链接 CSDN博主&#xff1a;One_Blanks主页地址 靶机资源下载 PHPStudy pikachu 一、前言 文件包含漏洞是编程中的一种安全隐患&a…...

如何写出优秀的提示词?ChatGPT官方的六种方法

使用ChatGPT时&#xff0c;提示词&#xff08;Prompt&#xff09;的质量直接影响到生成结果的好坏。ChatGPT官方文档中提供了六种优化提示词的方法&#xff0c;这些方法能够帮助用户更好地利用ChatGPT&#xff0c;提升其生成内容的准确性和实用性。本文将结合中文习惯和新的示例…...

【数据结构】顺序表和链表

线性表 线性表是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。但是在物理结构上并不一定是连续的&#xff0c;线…...

StarRocks强大的实时数据分析

代码仓库&#xff1a;https://github.com/StarRocks/starrocks?tabreadme-ov-file StarRocks | A High-Performance Analytical Database 快速开始&#xff1a;StarRocks | StarRocks StarRocks 是一款高性能分析型数据仓库&#xff0c;使用向量化、MPP 架构、CBO、智能物化…...

20250121在Ubuntu20.04.6下使用Linux_Upgrade_Tool工具给荣品的PRO-RK3566开发板刷机

sudo upgrade_tool uf update.img 20250121在Ubuntu20.04.6下使用Linux_Upgrade_Tool工具给荣品的PRO-RK3566开发板刷机 2025/1/21 11:54 百度&#xff1a;ubuntu RK3566 刷机 firefly rk3566 ubuntu upgrade_tool烧写详解 https://wiki.t-firefly.com/Core-3566JD4/03-upgrad…...

python学opencv|读取图像(四十一 )使用cv2.add()函数实现各个像素点BGR叠加

【1】引言 前序已经学习了直接在画布上使用掩模&#xff0c;会获得彩色图像的多种叠加效果&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;四十&#xff09;掩模&#xff1a;三通道图像的局部覆盖-CSDN博客 这时候如果更进一步&#xff0c;直接…...

150 Linux 网络编程6 ,从socket 到 epoll整理。listen函数参数再研究

一 . 只能被一个client 链接 socket例子 此例子用于socket 例子&#xff0c; 该例子只能用于一个客户端连接server。 不能用于多个client 连接 server socket_server_support_one_clientconnect.c /* 此例子用于socket 例子&#xff0c; 该例子只能用于一个客户端连接server。…...

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证9)

测试数据库中只有之前记录温湿度及烟雾值的表中数据较多&#xff0c;在该数据库中增加AppUser表&#xff0c;用于登录用户身份查询&#xff0c;数据库表如下所示&#xff1a;   项目中安装SqlSugarCore包&#xff0c;然后修改控制器类的登录函数及分页查询数据函数&#xff…...

【数据分析(二)】初探 Pandas

目录 引言1. 基本数据结构1.1. Series 的初始化和简单操作1.2. DataFrame 的初始化和简单操作1.2.1. 初始化与持久化1.2.2. 读取查看1.2.3. 行操作1.2.4. 列操作1.2.5. 选中筛查 2. 数据预处理2.0. 生成样例表2.1. 缺失值处理2.2. 类型转换和排序2.3. 统计分析 3. 数据透视3.0.…...

大数据与AI驱动的商业查询平台:企业市场拓展的变革引擎​

在竞争白热化的商业环境里&#xff0c;企业对准确市场信息的高效获取能力&#xff0c;直接关系到业务拓展的成败。商业查询平台借助大数据和人工智能技术&#xff0c;为企业提供精准客户筛选、市场拓展分析以及风险评估服务&#xff0c;正逐渐成为企业市场开拓的得力助手。本文…...

k8s namespace绑定节点

k8s namespace绑定节点 1. apiserver 启用准入控制 PodNodeSelector2. namespace 添加注解 scheduler.alpha.kubernetes.io/node-selector3. label node 1. apiserver 启用准入控制 PodNodeSelector vim /etc/kubernetes/manifests/kube-apiserver.yaml spec:containers:- co…...

ChatGPT被曝存在爬虫漏洞,OpenAI未公开承认

OpenAI的ChatGPT爬虫似乎能够对任意网站发起分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;而OpenAI尚未承认这一漏洞。 本月&#xff0c;德国安全研究员Benjamin Flesch通过微软的GitHub分享了一篇文章&#xff0c;解释了如何通过向ChatGPT API发送单个HTTP请求…...

2024微短剧行业生态洞察报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p39072 本报告合集洞察从多个维度全面解读微短剧行业。在行业发展层面&#xff0c;市场规模与用户规模双增长&#xff0c;创造大量高收入就业岗位并带动产业链升级。内容创作上&#xff0c;精品化、品牌化趋势凸显&#xff0c;题材走…...

【JavaSE】(8) String 类

一、String 类常用方法 1、构造方法 常用的这4种构造方法&#xff1a;直接法&#xff0c;或者传参字符串字面量、字符数组、字节数组。 在 JDK1.8 中&#xff0c;String 类的字符串实际存储在 char 数组中&#xff1a; String 类也重写了 toString 方法&#xff0c;所以可以直…...

算法竞赛之差分进阶——等差数列差分 python

目录 前置知识进入正题实战演练 前置知识 给定区间 [ l, r ]&#xff0c;让我们把数组中的[ l, r ] 区间中的每一个数加上c,即 a[ l ] c , a[ l 1 ] c , a[ l 2] c , a[ r ] c; 怎么做&#xff1f;很简单&#xff0c;差分一下即可 还不会的小伙伴点此进入学习 进入正题 …...

细说STM32F407单片机电源低功耗StopMode模式及应用示例

目录 一、停止模式基础知识 1、进入停止模式 2、停止模式的状态 3、退出停止模式 4、SysTick定时器的影响 二、停止模式应用示例 1、示例功能和CubeMX项目配置 &#xff08;1&#xff09;时钟 &#xff08;2&#xff09;RTC &#xff08;3&#xff09;ADC1 &#xf…...

LeetCode hot 力扣热题100 二叉树的中序遍历(非递归)

以下是代码中每行的详细注释以及整体思路&#xff1a; class Solution { public:vector<int> inorderTraversal(TreeNode* root) {// 定义存储结果的向量&#xff0c;用于存储中序遍历结果vector<int> result;// 定义一个栈&#xff0c;存储节点和访问状态。pair的…...

Flink底层架构与运行流程

这张图展示了Flink程序的架构和运行流程。 主要组件及功能&#xff1a; Flink Program&#xff08;Flink程序&#xff09;&#xff1a; 包含Program code&#xff08;程序代码&#xff09;&#xff0c;这是用户编写的业务逻辑代码。经过Optimizer / Graph Builder&#xff08…...

人工智能之深度学习_[4]-神经网络入门

文章目录 神经网络基础1 神经网络1.1 神经网络概念1.1.1 什么是神经网络1.1.2 如何构建神经网络1.1.3 神经网络内部状态值和激活值 1.2 激活函数1.2.1 网络非线性因素理解1.2.2 常见激活函数1.2.2.1 Sigmoid 激活函数1.2.2.2 Tanh 激活函数1.2.2.3 ReLU 激活函数1.2.2.4 SoftMa…...

ASP.NET Blazor部署方式有哪些?

今天我们来说说Blazor的三种部署方式&#xff0c;如果大家还不了解Blazor&#xff0c;那么我先简单介绍下Blazor Blazor 是一种 .NET 前端 Web 框架&#xff0c;在单个编程模型中同时支持服务器端呈现和客户端交互性&#xff1a; ● 使用 C# 创建丰富的交互式 UI。 ● 共享使用…...

Cyber Security 101-Security Solutions-Firewall Fundamentals(防火墙基础)

了解防火墙并亲身体验 Windows 和 Linux 内置防火墙。 任务1&#xff1a;防火墙的用途是什么 我们看到商场、银行、 餐馆和房屋。这些警卫被安置在 这些区域用于检查进出人员。这 维护此检查的目的是确保没有人在没有 被允许。这个警卫充当了他所在区域和访客之间的一堵墙。 …...

本地仓库管理之当前分支内的操作

以刚搭建好的git仓库为例&#xff0c;刚搭建完的仓库只有master分支&#xff0c;使用git branch查看当前的分支情况。 elfubuntu:~/work/example/hello$ git branch *所在分支为当前分支&#xff0c;即master分支 当前分支进行源码修改时简单流程图如下&#xff1a; 在当前分…...

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

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

ORACLE 12C开启EM EXPRESS过程

1 首先启动监听和关闭ORACLE/LINUX防火墙&#xff08;这一步略过&#xff09; lsnrctl start 2 然后查看http和https端口 select dbms_xdb_config.gethttpsport() from dual; select dbms_xdb_config.gethttpport() from dual; 3 设置em端口 exec dbms_xdb_config.setHTTPSPo…...

JavaScript学习笔记(1)

html 完成了架子&#xff0c; css 做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习 JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 一、引入方式 1.内部脚本 将 JS 代码定义在 HTML 页面中 Jav…...

【PCL】Segmentation 模块—— 欧几里得聚类提取(Euclidean Cluster Extraction)

1、简介 PCL 的 Euclidean Cluster Extraction&#xff08;欧几里得聚类提取&#xff09; 是一种基于欧几里得距离的点云聚类算法。它的目标是将点云数据分割成多个独立的簇&#xff08;clusters&#xff09;&#xff0c;每个簇代表一个独立的物体或结构。该算法通过计算点与点…...

CMake技术细节:解决未定义,提供参数

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…...