技术标签: STM32 USB USB描述符 USB复合设备 USB设备描述符 单片机
本篇文章为我个人在学习USB相关知识的整理,可能有不对的地方,欢迎指正!
我学习USB相关知识参考了以下网友的帖子:
http://www.51hei.com/bbs/dpj-40953-1.html
https://blog.csdn.net/alien75/article/details/4622319
等级划分:
接口类型:
STM32基础型(F1系列)所带的USB是全速。
USB的通信都是由主机发起的,这一点与IIC协议是类似的。
USB使用差分传输模式,有两条数据线,分别是:
剩下的就是电源线(5V-Vbus)和地线(GND)。
主机的D+和D-都接有15K下拉电阻。
因此,主机就可以根据检测到自己的D+为高还是D-为高,从而判断接入的设备是一个全速还是低速设备。
所以你可以看到STM32板子上的USB口D+有一个上拉电阻,而且是必须有的:
类别详细描述在这里:https://www.usb.org/defined-class-codes。
看一个CDC虚拟串口的设备分类:
在看一个MSC的(模拟U盘):
不同的类有不同的用途;不同的应用场合对应不同的产品形态;不同的产品形态可能会有自己特殊的描述符,比如: HID类有报告描述符、CDC类有ACM、Union描述符等。
以STM32里的MSC设备为例,MSC类所需要的描述符有:设备描述符+配置描述符+接口描述符(数量由配置描述符里的bNumInterfaces字段决定)+端点描述符(数量由配置描述符里的bNumEndpoints决定),所以MSC类结构就是这样的:
配置描述符
{
接口描述符1
{
端点描述符1
{
}
端点描述符2
{
}
}
}
而CDC类是这样的:
配置描述符
{
接口描述符1(通信接口)
{
其他描述符(特殊描述符)
{
/*Header Functional Descriptor*/
/*Call Management Functional Descriptor*/
/*ACM Functional Descriptor*/
/*Union Functional Descriptor*/
}
端点描述符(命令端点)
{
}
}
接口描述符2(数据接口)
{
端点描述符1(输出端口)
{
}
端点描述符2(输入端口)
{
}
}
}
每个USB设备都必须且只有一个设备描述符,摘取一下STM32的MSC设备里的实例代码:
每个字段的含义:
配置描述符定义了设备的配置信息,一个设备可以有多个配置描述符。摘一个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;
每个字段含义如下:
接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的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;
每个字段含义如下:
USB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量。摘取STM32的MSC设备类的端口描述符:
用C语言组合就是这样的一个结构:
typedef struct _USB_ENDPOINT_DESCRIPTOR_
{
BYTE bLength,
BYTE bDescriptorType,
BYTE bEndpointAddress,
BYTE bmAttributes,
uint16_t wMaxPacketSize,
BYTE bInterval
}USB_ENDPOINT_DESCRIPTOR;
每个字段含义如下:
字符串描述符是可选的,如果不支持字符串描述符,其设备描述符、配置描述符、接口描述符内的所有字符串描述符索引都必须为0。
字符串描述符结构如下:
typedef struct _USB_STRING_DESCRIPTION_
{
BYTE bLength,
BYTE bDescriptionType,
BYTE bString[1];
}USB_STRING_DESCRIPTION;
各个字段含义:
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是0X08
BYTE 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
{
}
}
}
}
这个512字节SRAM叫做Packet Buffer Memory Area(简称PMA)
,这个很重要,后面会详细讲解。
根据描述可以,一共有8个端点,16个寄存器,一个端点关联两个寄存器,所以我们可以将他们规划为8个输入端点(0x80-0X87)+8个输出端点(0X00-0X07)。
先说一下USB的数据包大小,全速设备的最大包大小为64字节,高速最大为1024字节。
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的划分就是:
这里注意一点,缓冲区分配好之后访问是不会溢出的,也就是说缓冲区之间完全隔离。
文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。
文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退
文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet
文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程
文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据
文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端
文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库
文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密
文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error
文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库
文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame
文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量