电脑基础知识单片机读写U盘FAT32或FAT16文件系统.docx
- 文档编号:28182073
- 上传时间:2023-07-09
- 格式:DOCX
- 页数:53
- 大小:971.36KB
电脑基础知识单片机读写U盘FAT32或FAT16文件系统.docx
《电脑基础知识单片机读写U盘FAT32或FAT16文件系统.docx》由会员分享,可在线阅读,更多相关《电脑基础知识单片机读写U盘FAT32或FAT16文件系统.docx(53页珍藏版)》请在冰豆网上搜索。
电脑基础知识单片机读写U盘FAT32或FAT16文件系统
[电脑基础知识]单片机读写U盘FAT32或FAT16文件系统
第1章U盘的逻辑结构
1.1U盘的逻辑结构
U盘可以看成是以扇区(1扇区,512Bytes)为单位线性排列的实体,即0号扇区,1号扇区,2号扇区,„„这样按顺序地排列下去。
U盘是flash,对flash的操作总是以块为单位的,因此单片机对U盘的操作是以扇区为单位,整个扇区地读取,或整个扇区地写入。
第2章USB通信协议
2.1USB设备开工的机理
USB是即插即用的,涵盖海量存储器(如U盘、移动硬盘)、人机交互设备(如鼠标键盘游戏杆)、扫描仪、打印机等等各种各样功能的设备,那么USB主机是如何判断目前接入的设备到底是怎么样的呢,答案是USB描述符,以及USB的枚举。
2.2USB描述符
这个概念很简单,就是对各种纷繁芜杂的USB外设按功能划分大类(class),大类下又再细分小类(subclass),每个类别给予一串特定的符号(Descriptor)供主机辨识。
每个USB设备只能有一个DEVICE描述符,它指明了该设备属于哪一大类,是海量存储器类,还是人机交互设备类,还是打印机或者扫描仪类,等等。
每个DEVICE下可以有1个或多个配置描述符(configuration),以说明该设备含有哪些功能。
如一个USB接口的CDROM可以同时具有读写光盘的功能和播放CD的功能。
有几个功能,就有几个配置描述符。
每种配置对应若干个接口描述符(Interface),以描述该配置使用哪些接口与主机进行通信。
每个Interface又都是端点(EndPoint)的集合,端点就是设备与USB主机交换数据的最原子单位了。
每个Interface用到的端点可以是一个或多个。
下图摘自USBMASSSTORAGECBITransportSpecification第6页,清楚说明各种描述符的组织情况。
2.3USB设备的枚举过程(开工过程)
有了完善的分类后,USB设备上电即可通过枚举过程告诉USB主机自己的详细信息,
这很类似一个一问一答的过程,如下:
主机(下称H):
你是甚么设备,
设备(下称D):
我是12010100„„(这就是DEVICE描述符了)
H:
你有几种功能,
D:
我有090209„„(配置描述符)
H:
每种功能有几个接口,
D:
090400„„(接口描述符)
H:
每个接口用到哪些端点,
D:
070581„„(端点描述符)
H:
好了,我知道你是谁了,开始传数据吧~
D:
OK.READYGO!
具体而言,USB枚举过程有以下步骤:
(1)集线器检测新设备
主机集线器监视着每个端口的信号电压,当有新设备接入时便可觉察。
(集线器端口的两根信号线的每一根都有15kΩ的下拉电阻,而每一个设备在D+都有一个1.5kΩ的上拉电阻。
当用USB线将PC和设备接通后,设备的上拉电阻使信号线的电位升高,因此被主机集线器检测到。
)
(2)主机知道了新设备连接后
每个集线器用中断传输来报告在集线器上的事件。
当主机知道了这个事件,它给集线器发送一个Get_Status请求来了解更多的消息。
返回的消息告诉主机一个设备是什么时候连接的。
(3)集线器重新设置这个新设备
当主机知道有一个新的设备时,主机给集线器发送一个Set_Feature请求,请求集线器来重新设置端口。
集线器使得设备的USB数据线处于重启(RESET)状态至少10ms。
(4)集线器在设备和主机之间建立一个信号通路
主机发送一个Get_Status请求来验证设备是否激起重启状态。
返回的数据有一位表示设备仍然处于重启状态。
当集线器释放了重启状态,设备就处于默认状态了,即设备已经准备好通过Endpoint0的默认流程响应控制传输。
即设备现在使用默认地址0x0与主机通信。
(5)集线器检测设备速度
集线器通过测定那根信号线(D+或D-)在空闲时有更高的电压来检测设备是低速设备还是全速设备。
(全速和高速设备D+有上拉电阻,低速设备D-有上拉电阻)。
以下,需要USB的firmware进行干预
(6)获取最大数据包长度
PC向address0发送USB协议规定的Get_Device_Descriptor命令,以取得缺省控制管道所支持的最大数据包长度,并在有限的时间内等待USB设备的响应,该长度包含在设备描述符的bMaxPacketSize0字段中,其地址偏移量为7,所以这时主机只需读取该描述符的前8个字节。
注意,主机一次只能列举一个USB设备,所以同一时刻只能有一个USB设备使用缺省地址0。
(7)主机分配一个新的地址给设备
主机通过发送一个Set_Address请求来分配一个唯一的地址给设备。
设备读取这个请求,返回一个确认,并保存新的地址。
从此开始所有通信都使用这个新地址。
(8)主机向新地址重新发送Get_Device_Descriptor命令,此次读取其设备描述符的全部字段,以了解该设备的总体信息,如VID,PID。
(9)主机向设备循环发送Get_Device_Configuration命令,要求USB设备回答,以读取全部配置信息。
(10)主机发送Get_Device_String命令,获得字符集描述(unicode),比如产商、产品描述、型号等等。
(11)如果主机是PC电脑,此时主机将会弹出窗口,展示发现新设备的信息,产商、产品描述、型号等。
(12)根据Device_Descriptor和Device_Configuration应答,PC判断是否能够提供USB的Driver,一般win2k能提供几大类的设备,如游戏操作杆、存储、打印机、扫描仪等,操作就在后台运行。
(13)加载了USB设备驱动以后,主机发送Set_Configuration(x)命令请求为该设备选择一个合适的配置(x代表非0的配置值)。
如果配置成功,USB设备进入“配置”状态,并可以和客户软件进行数据传输。
此时,常规的USB完成了其必须进行的配置和连接工作。
查看注册表,能够发现相应的项目已经添加完毕,至此设备应当可以开始使用。
以上是PC电脑为主机的枚举过程,对于单片机作为主机的情形,过程要简单一些,以枚举U盘为例:
(1)芯片SL811监视USB总线电平,当发现有U盘插入后,给单片机一个中断信号。
(2)单片机给SL811发出端口复位命令,持续100毫秒以上。
(3)单片机发出Get_Device_descriptor命令,从默认的端口0和地址0发出。
该命令先假设了包传送的大小是64字节,在获得命令返回时修正MaxPacketSize。
此步同PC。
(4)单片机发送Set_Address请求来分配一个唯一的地址给U盘,我们实际应用中固定分配了地址2。
此步同PC。
(5)单片机向新地址2重新发送Get_Device_Descriptor命令,此次读取U盘设备描述符的全部字段,以了解该设备的总体信息,如VID,PID。
此步同PC。
(6)单片机发送Get_Configuration_Descriptor命令获取配置描述符。
(7)根据获取的配置信息,单片机发送SetConfig和SetInterface命令对U盘进行配置。
(8)对获取的Interface描述符和Endpoint描述符进行分析,判断是否大容量存储设备、是否支持SCSI命令集、是否BULK_ONLY传输、端口的最大包长等内容。
(9)发送Get_Max_LUN命令获取U盘的进一步信息(根据协议看此步非必须,有些U盘此步会返回STALL,即不支持,也没有关系)。
但是建议在枚举过程中不省略此步,因为不同品牌U盘其固件可能不一样,有些固件可能不允许省略此步。
(10)完成上述步骤后,U盘的枚举过程完成,接着需要发送几条SCSI命令来对U盘进行初始化,这几条命令依次是Inquiry、ReadFormatCapacity、ReadCapacity。
完成后,U盘已经准备好接收单片机发出的任何读写命令(读写命令也是来自SCSI命令集)。
如果你有兴趣知道USB协议一些更细节的内容,请往下看。
否则可以直接跳到第四章的文件系统部分。
2.4USB1.1协议
本节内容主要涵盖USB1.1Specification的第4、5、8、9章。
并且主要描述代码中无法注释或者在代码中注释会太麻烦的内容。
USB是一种主从的结构。
所有传输由Host来发起。
当主机发起一次传输时,这次传输的包(Packets)通常包括三个阶段。
主机首先是发送一个TokenPacket,内里包含本次传输的命令类型(type)、方向(direction)、设备的地址(deviceaddress)以及端点号(Endpoint)。
紧接着是数据包(datapacket),就是包含数据了。
最后将由device返回握手信号包(handshakepacket),表示是正确收到了(ACK)还是其他的失败原因。
三个包如下图所示。
USB的传输模型:
Host和设备的某个端点之间可以看成有一条逻辑管道(pipe)。
Pipe分两种:
业务数据流和信令消息。
业务流即指纯粹的数据,信令流指控制信息。
其实通信协议很多都如此,分业务流和信令流,例如电信网中的7号信令。
在信令管道中,有一条默认的管道,那就是零地址处的零号端点,这条管道在USB设备上电复位或总线复位后就存在了,便于Host统一利用这个地址向USB设备进行配置。
显然对于USB集线器,即使同时插入几个设备,Host也只能一次对一个设备进行配置。
USB设备只有配置(configured)后,才可使用。
USB的传输类型有四种:
控制传输(controltransfer),通常只用于在设备复位后Host通过端点0进行配置。
块传输(BulkTransfer),譬如U盘的大量数据传输即用此方式。
中断传输(InterruptTransfer),一般用于人机设备如USB鼠标键盘等。
等时传输(IsochronousTransfer),可以进行带宽控制的实时传输形式。
2.4.1重新认识枚举过程
枚举过程事实上是USB设备复位后,恢复到0地址0号端点,然后主机通过一系列控制传输命令对USB设备进行配置,同时也获取一些信息。
使用BUShound这个工具可以把完整的USB设备枚举过程抓下来。
网上很容易找到安装包。
BUS软件的设置如下:
可以确保抓下所有的数据包信息。
利用BUShound的软件抓一下爱国者行业特供型1G的U盘,其在PC下的枚举过程完
全在下图中表现了出来。
让我们逐一分析。
由设备16.0抓到的数据包属于USB集线器的行为,在无使用集线器的单片机系统中可以无视之。
设备21.0的含义是:
usb设备地址是21,目前管道是跟它的端点0打交道。
数字1处是枚举过程的开始,主机用控制传输发送GetDescriptor获取设备描述符(具体为何是设备描述符可以对照USB1.1技术规范的第九章来分析左边的那串08060002„„的二进制数据,下同),这条命令假设了未知设备的端点0的最大包长值64字节,然后在命令中要求设备返回0x12(十进制18)个字节的device描述符,如图中圈起来的12。
值得一提的是,这条命令无论假设设备的端点0的最大包长(Payload)是8,16,32,64,都是可以获得想要的数据的(图中的40)。
40H指明该设备的端点0的最大包长是64字节,Host此后的控制传输可以使用64字节的数据包跟设备通信了。
64字节数据包的细节后面会介绍。
数字2处表示主机发送GetDescriptor获取配置描述符。
但是类似设备描述符的处理方法,主机也先试探性的获取配置描述符的前9个字节(图中带圈的09),以获悉整个配置描述符有多长,因为长度信息就位于描述符的第3个字节,如图中带圈的20。
根据此20h的长度信息,数字3处开始正式请求设备完整的配置描述符了,可以在图中看到两个20是对
应的。
整个配置描述符包含32个字节(一般的U盘都是这样)。
这32个字节中,包含3部分内容,包含设备的重要信息。
数字5所代表的第一个框表示第一部分:
配置描述符。
数字6代表第二部分:
接口描述符(Interface)。
其中第5字节02表示该设备有2个端点(Bulk_IN和Bulk_OUT),第6字节08代表这是大容量存储设备(MassStorageDevice)。
第7字节06表示支持SCSI命令,不过我调试过MP3播放器这个地方是05,但也支持SCSI命令。
第8字节50表示数据只支持使用Bulk传输(BulkOnly)。
(更详细的内容可参考USBMassStorage
ClassBulk-OnlyTransportRevision1.0)。
数字7和8代表第三部分:
端点描述符。
第三字节都是代表端点地址,一般情况是地址1和地址2。
留意图中8框第三字节是82H,这表示该端点地址就是地址2,最高位被置1以表示这个端点是Bulk_IN端点,所以整个数值变成了82H。
但是并非地址2就一定是Bulk_IN,不同的U盘不一样,所以在程序中要根据描述符的实际值,用变量记录下来的,后面要用到。
继续看8框,第五和第六字节组成一个16位的数值表示该端点的最大包长度(payload)。
对于只支持1.1协议的设备,第六字节是其高8位,都是0,第五字节才是真正的payload数值,1.1协议规定只能是8,16,32,64之一,由厂家固定。
至于我们在上图看到第六字节是02第五字节是00,组合成200H,512,那是因为U盘控制器认出了这个Host(PC电脑)支持usb2.0,所以就回应了512,而不是64。
Payload值非常重要,后面要依据此值进行判断和计算。
下面接着的4个getdescriptor都是获取设备的string描述符。
实际的单片机系统也许不需要获取这些描述符,而且有些U盘也不支持获取这个描述符(返回STALL)。
再往下的就是set_configuration、set_interface、get_MAX_LUN等。
有些U盘,在set_interface处会stall;有些U盘,如果Host不发送set_interface命令,往后的命令都不响应,所以这个牵涉到兼容性问题,后面再解释。
在上图中可以看到,这个爱国者U盘在遇到get_MAX_LUN时返回了stall,PC的处理方法是clearfeature,然后重试,三次后仍然stall则跳过。
这个牵涉到如何进行差错处理,后面再详细分析。
2.4.2基于SL811的USB底层传输函数实现要点
前面枚举过程介绍的各种命令,如截图中的数字1处的“8006000100001200”到底是如何发送出去的,这也许是大家比较感兴趣的问题。
有必要先简单认识一下SL811的功能,虽然这跟USB1.1协议几乎无关。
SL811提供了15个寄存器供使用,实际在进行USB传输时最少只需要用到其中6个,另外还需要用到SL811内建的240字节RAM作为数据缓冲。
启动SL811发送/接收一次数据(注意不是一帧数据,一帧数据,一个数据包)的步骤如下:
目标U盘的端点地址和pid?
SL811寄存器地址0x03
目标U盘的地址?
地址0x04
811内部RAM中数据缓冲的地址?
地址0x01
该次数据的长度(不是该帧数据的长度)?
地址0x02
0xff?
地址0x0d
启动发送的命令字?
地址0x05
当这一次数据成功了后,如果pid是“发送”,则SL811内部RAM中缓冲处的数据都被发出去了;如果pid是“接收”,则SL811内部RAM中缓冲处会填满来自U盘的数据,长度等于上面黑体字第四行之设定。
单片机应该在此时及时把SL811的内建RAM中这些数据读出来,放进单片机自己开辟的内存区域。
看的出来SL811对USB的物理层已经完全封装了,但是设计者还是需要关心很多细节。
对SL811总线式的读写函数太简单,这里不提了。
那是我们这种解决方案下最底层的函数。
关键是usb.c中的usbXfer()和ep0Xfer()这两个函数。
2.4.3usbXfer()函数
借助分析这个函数的实现可以了解USB的传输模型及差错处理。
分析一下它的输入参数。
intusbXfer(BYTEusbaddr,//USB设备地址0-127
BYTEendpoint,//端点的地址0-15,对于U盘无非就是0,1,2
BYTEpid,//数据包token类型,包括setup,pid_IN,pid_OUT
BYTEiso,//是否使用等时传输,对于U盘,该项恒否
WORDwPayload,//本数据包的最大包长,又称净荷。
WORDwLen,//待发送或接收的数据的实际长度
BYTE*buffer//待发送的数据的缓冲首址,或者将要接收数据的缓冲首址
)
首先要了解pid。
Setup型的pid只出现在控制传输阶段,即usb设备复位配置阶段。
Pid_IN和pid_OUT可能出现在控制传输阶段和此后的Bulk传输阶段。
顾名思义,pid_IN表示Host打算通过这次usbXfer,从U盘读进来wLen长度的数据,放入buffer中。
Pid_OUT表示Host打算向U盘控制器发送wLen长度的数据或者写入U盘wLen长度的数据,这些数据已经在buffer准备好。
其次是要理清wPayload和wLen的关系。
wPayload传递过来的是该端点的最大包长,在前面枚举中分析那32字节的配置描述符时应该已经记录了下来。
在USB1.1的规范里只能是8,16,32,64这四个值其中之一,实际上我根据SL811的打印只见过8字节和64字节payload的U盘,而且那些8字节U盘只是端点0是8字节,BULK端点也是64字节的。
应该目前来说多数U盘都是64字节的(1.1的范围内)。
言归正传,由于USB设备的端点有最大包长的限制,SL811启动一次数据传输时必须保证不超过这一限制,因此,在往SL811的0x02地址发送数据长度前,应作一判断,取wPayload和wLen之中的较小者。
C代码为:
xferLen=(BYTE)((wLen>=wPayload)?
wPayload:
wLen);
当wLen 当wLen>payload的时候,一帧数据就需要SL811启动多次传输才能完成。 接着让我们根据代码来分析usbXfer()函数的流程。 建议对照着附件中usb.c的代码来看。 代码的图在前,分析在后,下同。 简单的函数说明,列出了返回值意义。 函数开始的变量定义,有些可以顾名思义,有些等后面用到了再解释。 红框处需要解释一下。 EP0_Buf值为0x10,它的意义是SL811内建RAM的起始地址。 对于SL811的内建RAM只需要用到其中2×payload个字节,而且是掰开两半来轮换使用。 SL811的应用笔记称之为乒乓缓冲。 举个例子,假设手头遇到一个U盘其端点0是8字节payload的。 在枚举U盘时host请求U盘返回它的32字节配置描述符。 此时属于wLen大于payload的情形,需要SL811启动4次传输才能完全把32字节数据收回来。 首先如上图所示,我们初始化data0指向SL811的RAM起始地址,data1初始化为指向data0+payload处,并初始化SL811的缓冲指针寄存器为data0。 然后启动SL811发起第一次传输,让SL811把第一批8字节收到data0处,单片机立即读走这8字节;修改SL811的缓冲指针寄存器为data1,启动第二次传输,把第二批8字节内容收到data1处,单片机也立即读走此8个字节。 依次轮换,直到4次传输过后,32字节完全收了回来。 平心而论这个作法有点多余,既然是单片机立即读走数据的,一直用data0就可以了。 不过上述做法是Cypress公司提供的例程,很多人都照搬了„„大家知道是怎么回事就行了。 这就是前面说的取wLen和Payload中的较小者作为一次传输的长度。 等效于前面那行C代码: xferLen=(BYTE)((wLen>=wPayload)? wPayload: wLen); Cmd变量是等会要写入SL811的控制寄存器0x00地址的值。 对于Pid_IN的token类型在这里进行预置。 sData0_RD=00100011B,其含义指: 该次数据包的sequencebit定为DATA0,产生同步帧SOF,数据方向是IN(即读U盘),使能传输(Enable,ARM)。 具体参考SL811的数据手册。 解释一下其中的Sequencebit,USB1.1协议规定,每个数据包(datapacket)都必须包含一个sequencebit,用于纠错。 收发双方的软件也要各自维护自己的sequencebit。 Sequence位要么是DATA0,用0表示,要么是DATA1,用1表示(注意此DATA0与前面的乒乓缓冲的data0没有半点关系,重名纯属巧合)。 USB1.1协议第185页描述了何为一次成功的数据包收发。 在第i个数据包发送前(左图),TX方的seqbit是DATA0,于是它填充数据包的seqbit为DATA0。 RX方成功收到了这个数据包,于是将自己的seqbit切换到1即DATA1,并返回一个ACK应答表示成功收到了。 当TX方收到ACK后,也切换自己的seqbit到DATA1。 右图的第i,1个数据包就类似了。 1.1协议在第186页描述了一次重发数据包的情形。 同样先看左图,第i个数据包由TX发出,但是由于各种原因RX收到坏(corrupted)了的包。 于是RX方拒绝切换自己的seqbit,并返回NAK给TX方(返回stall或者timeout等情形类似NAK)。 此时TX方由于收到的不是ACK,不能切换seqbit,只能仍然以DATA0的seqbit组装数据包重发,若如右图RX方接收了这个包了,这才是一次成功的收发,RX和TX各自的seqbit发生切换。 如下图是一次控制传输涉及到的三个packet,其中中间那个是datapacket,红框处就是其sequencebit。 继续分析代码。 对于PID_OUT,1框处就是将待发送的命令填进SL811内部RAM的data0缓冲处。 Cmd的预置同上面PID_IN,只是方向变成了OUT而已。 2框处正是上面提到的切换发送方的SeqBit,并反映在cmd中。 显然这跟协议规定的只有在收到ACK才允许切换seqbit相悖,但是Cypress公司的官方例程是这样处理的(又被Cypress耍了一道,尽信code不如无code),有空我会把这部分代码改过来。 对于SetupToken的处理。 当使用等时传输的时候置cmd的某位。 但对于玩U盘,这行其实可以删除。 对于控制传输阶段(endpoint=0),IN或者OUT的pid都使用DATA1的seqbit。 而对于setuppid,都采用DATA0的seqbit。 这是由协议规定的。 没什么好说的。 终于可以依次填写SL811的寄存器,启动一次传输。 下面开始判断发送是否成功。 开始进入while循环,循环读入SL811的状态寄存器,判断如果是总线复位或者设备中途拔出则
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 电脑 基础知识 单片机 读写 FAT32 FAT16 文件系统
![提示](https://static.bdocx.com/images/bang_tan.gif)