USB 项目技术报告
2002 年12 月 北航 frank
导读
本文介绍了在基于ARM7 的嵌入式系统上开发USB 接口的详细内容。项目使用ARM7
(MCU 处理器)和PDIUSBD12(USB 芯片)完成了U 盘的开发。主要内容涉及USB 接口的U 盘开发过程中所涉及的技术内容;重点介绍项目调试方法与步骤;并附上了自己在开发此项目过程中的一些心得与体会。文中所涉及内容有的来自互联网上的资料、所用芯片技术资料、有关协议的文档以及他人总结的一些东西,在此不一一列举出处,希望本文能给后续做此类开发的技术人员提供一个捷径和指引,起到抛砖引玉的作用。不足与欠妥之处,肯请指正wxjbuaa@sohu.com。
一、项目背景
1.1 PC 接口简介
PC 中的接口有两类:串行接口和并行接口。计算机内部总线,如CPU 与存储器之间匀采用并行接口,这样速度快;但外设却以串行接口比较占优势。传统的打印机接口为并行接口,它实际上叫Cetronix 标准,这种接口现在已经没有发展了。SCSI 标准的全名是小型设备通用接口标准,其传输速率为10M,早期的扫描仪一般使用此接口,硬盘与主机的联接也使用这种接口。串行接口出现最早,使用最广的RS232 接口,但其速度太慢,现在已经逐渐淘汰。USB接口和IEEE1394 接口是两种速度比较高的串行接口,还有局域网中的以太网接口,它们具有较广阔的发展前景和应用潜力。USB 适用于低档外设与主机之间的高速数据传输,USB1.1 可以达到1.5Mbps 或12Mbps的传输率,而1394 更是可达100/200/400Mbps。USB2.0 将速度定位在480Mbps,而IEEE1394 也推出了1394b 1.3.1 版草案,速度从800Mbps 起步,最高可达3.2Gbps。但这两种版本目前都还没有成熟。局域中用得最多的是以太网接口,速度可达100Mbps,当使用光纤传输时,速度可达1000Mbps。
1.2 USB 接口分析
通用串行总线(Universal Serial Bus USB),是一种快速、灵活的总线接口。与其它通信接口比较,USB接口的最大特点是易于使用,这也是USB的主要设计目标。作为一种高速总线接口,USB适用于多种设备,比如数码相机、MP3播放机、高速数据采集设备等。易于使用还表现在USB接口支持热插拔,并且所有的配置过程都由系统自动完成,无需用户干预。USB 接口支持1.5Mb/s(低速)、12Mb/s(全速)和高达480Mb/s(USB 2.0 规范)的数据传输速率,扣除用于总线状态、控制和错误监测等的数据传输,USB 的最大理论传输速率仍达1.2Mb/s 或9.6Mb/s,远高于一般的串行总线接口。USB 接口芯片价格低廉,一个支持USB 1.1 规范的USB 接口芯片价格大多在人民币(2002 年)20~40 元之间,这也大大促进USB 设备的开发与应用。
1.3 USB 器件的选择
在进行一个USB 设备开发之前,首先要根据具体使用要求选择合适的USB 控制器。目前,市场上供应的USB 控制器主要有两种:带USB 接口的单片机(MCU)或纯粹的USB接口芯片。带USB 接口的单片机从应用上又可以分成两类,一类是从底层设计专用于USB 控制的单片机;另一类是增加了USB 接口的普通单片机,如Cypress 公司的EZ-USB(基于8051),选择这类USB 控制器的最大好处在于开发者对系统结构和指令集非常熟悉,开发工具简单,但对于简单或低成本系统。但价格因素也是在实际选择过程中需要考虑的因素。纯粹的USB 接口芯片仅处理USB 通信,必须有一个外部微处理器来进行协议处理和数据交换。典型产品有Philips 公司的PDIUSBD11(I2C 接口)、PDIUSBD12(并行接口),NS公司的USBN9603/9604(并行接口),NetChip 公司的NET2888 等。USB 接口芯片的主要特点是价格便宜、接口方便、可靠性高,尤其适合于产品的改型设计(硬件上仅需对并行总线和中断进行改动,软件则需要增加微处理器的USB 中断处理和数据交换程序、PC 机的USB接口通信程序,无需对原有产品系统结构作很大的改动)。
1.4 Mass Storage 协议与FAT16
USB 组织定义了海量存储设备类(Mass Storage Class)的规范,这个类规范包括四个独立的子类规范,即:1. USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport 2. USB Mass Storage Class Bulk-Only Transport 3. USB Mass Storage Class ATA Command Block 4.USB Mass Storage Class UFI Command Specification。前两个子规范定义了数据/命令/状态在USB 上的传输方法。Bulk- Only 传输规范仅仅使用Bulk 端点传送数据/命令/状态,CBI 传输规范则使用Control/Bulk/ Inter -rupt 三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。ATA命令规范用于硬盘,UFI命令规范是针对USB移动存储。Microsoft Windows中提供对Mass Storage 协议的支持,因此USB移动设备只需要遵循Mass Storage 协议来组织数据和处理命令,即可实现与PC 机交换数据。而Flash的存储单元组织形式采用FAT16文件系统,这样,就可以直接在Windows的浏览器中通过可移动磁盘来交换数据了,Windows负责对FAT16文件系统的管理,USB设备不需要干预FAT16文件系统操作的具体细节。
二、项目主要组成部分
2.1 USB的端点
端点是USB中一个独特的概念,它是一个可以与USB Host 交换数据的硬件单元。USB
Host 与USB 设备之间都是通过端点来传输数据的,端点是桥梁和纽带,不同的端点其传输数据的能力不同,适于不同的应用场合。PDIUSBD12 的端点适用于不同类型的设备,例如图像打印机、海量存储器和通信设备。端点可通过Set Mode 命令配置为4种不同的模式,分别为:
模式0 Non-ISO 模式:非同步传输
模式1 ISO-OUT 模式:同步输出传输
模式2 ISO-IN 模式:同步输入传输
模式3 ISO-IO 模式:同步输入输出传输
这几种模式下可得到的端点情况如下表:
模式0(非同步模式)
模式1(同步输出模式)
端点2 叫做主端点,它在有些方面是比较特别的,它是进行吞吐大数据的主要端点,
同样地它执行主机的特性以减轻传输大数据的任务:
1 双缓冲,允许USB与本地CPU之间的并行读写操作,这样就增加了数据的吞吐量。
缓冲区切换是自动处理的。这导致了透明的缓冲区操作。
2 支持DMA直接存储器访问操作,可以和对其它端点的正常I/O操作交叉进行。
3 缓冲区的最大信息包长度较其它端点大。
4 可配置为同步传输或非同步批量和中断传输
2.2 Mass Storage 协议
USB 协议能够在启动或是当设备插入系统时对设备进行备置,这就是USB设备为什么可以热插拨的原因。USB设备被分成以下几类:显示器(Monitors)、通讯设备(Communication devices)、音频设备(Audio)、人机输入(Human input)、海量存储(Mass storage)。特定类(class)的设备又可划分成子类(subclass),划分子类的后软件就可以搜索总线并选择所有它可以支持的设备。每个设备可以有一个或多个配置(Configuration),配置用于定义设备的功能。如果某个设备有几种不同的功能,则每个功能都需要一个配置。配置(configuration)是接口(interface)的集合。接口指定设备中的哪些硬件与USB交换数据。每一个与USB 交换数据的硬件就叫做一个端点(endpoint)。因此,接口是端点的集合。USB的设备类别定义(USB Device Class Definitions)定义特定类或子类中的设备需要提供
的缺省配置、接口和端点。描述符(descriptor)描述设备、配置、接口或端点的一般信息,下图为USB描述符的层次结构。
USB(Host)唯一通过描述符了解设备的有关信息,根据这些信息,建立起通信,在这些描述符中,规定了设备所使用的协议、端点情况等。因此,正确地提供描述符,是USB设备正常工作的先决条件。USB 海量存储设备(USB Mass Storage Class)包括General Mass Storage Subclass、CD-ROM、Tape、Solid State。Mass Storage Class只需要支持一个接口,即数据(Data)接口,选择缺省配置时此接口即被激活。数据接口允许与设备之间进行数据传输,它提供三个端点:Bulk Input 端点、Bulk Output 端点和中断端点。通用海量存储设备(General Mass Storage Device)是随机存取、基于块/扇区存储的设备。它只能存储和取回来自CPU 的数据。这种设备的接口遵循SCSI-2 标准的直接存取存储设备(Direct Access Storage Device)协议。USB 设置上的介质使用与SCSI-2 设备相同的逻辑块(logical blocks)方式寻址。下面介绍基于Bulk Only(批量传输)模式的Mass Storage 设备的描述符:
每个USB 设备都必须有一个设备描述符。Mass Stroage设备的设备类型和子类代码均在接口描述符中设置,这里置0。其中iSerialNumber可为零,即不指定Serial Number。
typedef struct _USB_DEVICE_DESCRIPTOR { UCHAR bLength ; 指定该描述符的长度 字节方式 bLength ; 指定该描述符的长度 字节方式 UCHAR bDescriptorType ; 必须为USB_DEVICE_DESCRIPTOR_TYPEbDescriptorType ; 必须为USB_DEVICE_DESCRIPTOR_TYPE USHORT bcdUSB ; 确定USB规范版本 为二进制码十进制数bcdUSB ; 确定USB规范版本 为二进制码十进制数 UCHAR bDeviceClass ; USB规范组织指定的设备类编码bDeviceClass ; USB规范组织指定的设备类编码 UCHAR bDeviceSubClass ; USB规范组织指定的设备子类编码bDeviceSubClass ; USB规范组织指定的设备子类编码 UCHAR bDeviceProtocol ; USB规范组织指定的设备协议编码bDeviceProtocol ; USB规范组织指定的设备协议编码 UCHAR bMaxPacketSize0 ; 为设备端点0指定最大的包大小 只能为8 16 32 64字节bMaxPacketSize0 ; 为设备端点0指定最大的包大小 只能为8 16 32 64字节 USHORT idVendor ; USB规范组织指定的设备生产商标识idVendor ; USB规范组织指定的设备生产商标识 USHORT idProduct ; 产品标识 由生产商指定 设备独立性idProduct ; 产品标识 由生产商指定 设备独立性 USHORT bcdDevice ; 确定设备版本 为二进制码十进制数bcdDevice ; 确定设备版本 为二进制码十进制数 UCHAR iManufacturer ; 指定字符串描述符设备定义索引 提供包含该设备制造商名称的字符串iManufacturer ; 指定字符串描述符设备定义索引 提供包含该设备制造商名称的字符串 UCHAR iProduct ; 指定字符串描述符设备定义索引包含设备描述符iProduct ; 指定字符串描述符设备定义索引包含设备描述符 UCHAR iSerialNumber ; 指定字符串描述符设备定义索引 包含制造商定义的设备序列号iSerialNumber ; 指定字符串描述符设备定义索引 包含制造商定义的设备序列号 UCHAR bNumConfigurations ; 指定设备可能配置的总的数量bNumConfigurations ; 指定设备可能配置的总的数量} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR ;
typedef struct _USB_CONFIGURATION_DESCRIPTOR { UCHAR bLength ; 指定该结构的长度 字节方式bLength ; 指定该结构的长度 字节方式 UCHAR bDescriptorType ; 必须设置为USB_CONFIGURATION_DESCRIPTOR_TYPEbDescriptorType ; 必须设置为USB_CONFIGURATION_DESCRIPTOR_TYPE USHORT wTotalLength ; 为配置的所有数据指定总的长度 包含所有的接口 wTotalLength ; 为配置的所有数据指定总的长度 包含所有的接口 端点类或与配置描述符一起返回的生产商描述符 UCHAR bNumInterfaces ; 指定该配置支持的接口总数bNumInterfaces ; 指定该配置支持的接口总数 . . UCHAR iConfiguration ; 为该配置指定字符串描述符设备定义索引iConfiguration ; 为该配置指定字符串描述符设备定义索引 UCHAR bmAttributes ; 指定一个位兔以描述该配置的行为bmAttributes ; 指定一个位兔以描述该配置的行为 UCHAR MaxPower ; 以两个ma单位指定该设备的电源要求 仅当bmAttributes设置时有效} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR ;
设置应至少支持一个接口,这里为Bulk-Only Data接口,此接口使用三个端点:控制端点(默认)、Bulk-In和Bulk-Out。其中bInterfaceSubClass指定所使用的工业标准命令块,bInterfaceProtocol为所使用的传输协议,其定义见后。
typedef struct _USB_INTERFACE_DESCRIPTOR { UCHAR bLength ; 指定该描述符的长度 字节方式bLength ; 指定该描述符的长度 字节方式 UCHAR bDescriptorType ; 指定描述符类型 bDescriptor必须设备为USB_INTERFACE_DESCRIPTOR_TYPEbDescriptorType ; 指定描述符类型 bDescriptor必须设备为USB_INTERFACE_DESCRIPTOR_TYPE UCHAR bInterfaceNumber ; 指定该接口的索引数量bInterfaceNumber ; 指定该接口的索引数量 UCHAR bAlternateSetting ; 指定该接口交替设置的索引数量bAlternateSetting ; 指定该接口交替设置的索引数量 UCHAR bNumEndpoints ; 指定该接口使用的端点数量 不包含默认状态端点bNumEndpoints ; 指定该接口使用的端点数量 不包含默认状态端点 UCHAR bInterfaceClass ; USB规范组织分配的设备类编码bInterfaceClass ; USB规范组织分配的设备类编码 UCHAR bInterfaceSubClass ; USB规范组织分配的设备子类编码bInterfaceSubClass ; USB规范组织分配的设备子类编码 UCHAR bInterfaceProtocol ; USB规范组织分配的设备协议编码bInterfaceProtocol ; USB规范组织分配的设备协议编码 UCHAR iInterface ; 指定描述接口的一个字符串描述符 iInterface必须设置为0x1} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR ;
由于控制端点为每个设备都使用的缺省端点,因此不需要定义,只需定义Bulk-In 和Bulk-Out 两个端点,其端点描述符如下:
typedef struct _USB_ENDPOINT_DESCRIPTOR { UCHAR bLength ; 指定该描述符的长度 字节方式 bLength ; 指定该描述符的长度 字节方式 UCHAR bDescriptorType ; 必须设置为USB_bDescriptorType ; 必须设置为USB_ENDPOINT_DESCRIPTOR_TYPE UCHAR bEndpointAddress ; 指定USB定义的端点地址bEndpointAddress ; 指定USB定义的端点地址 UCHAR bmAttributes ; 两个低顺序位指定端点的类型 为USB_bmAttributes ; 两个低顺序位指定端点的类型 为USB_ENDPOINT_TYPE_CONTROL, USB_ENDPOINT_TYPE_ISOCHRONOUS, USB_ENDPOINT_TYPE_BULK, USB_ENDPOINT_TYPE_INTERRUPT之一 USHORT wMaxPacketSize ; 指定与该端点传输的最大的包大小wMaxPacketSize ; 指定与该端点传输的最大的包大小 UCHAR bInterval ; 为中断端点 以帧方式指定轮流见得的时间间隔bInterval ; 为中断端点 以帧方式指定轮流见得的时间间隔} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR ;
2.3 Bulk-Only 传输协议
设备插入到USB后,USB 即对设备进行搜索,并要求设备提供相应的描述符。在USB
Host得到上述描述符后即完成了设备的配置识别出为Bulk-Only 的Mass Storage 设备,然后即进入Bulk-Only 传输方式。在此方式下,USB与设备间的所有数据均通过Bulk-In和Bulk-Out 来进行传输,不再通过控制端点传输任何数据。在这种传输方式下,有三种类型的数据在USB 和设备之间传送,CBW、CSW 和普通数据。CBW(Command Block Wrapper,即命令块包)是从USB Host 发送到设备的命令,命令格式遵从接口中的bInterfaceSubClass 所指定的命令块,这里为SCSI 传输命令集。USB设备需要将SCSI 命令从CBW 中提取出来,执行相应的命令,完成以后,向Host 发出反映当前命令执行状态的CSW(Command Status Wrapper),Host 根据CSW 来决定是否继续发送下一个CBW 或是数据。Host 要求USB 设备执行的命令可能为发送数据,则此时需要将特定数据传送出去,完毕后发出CSW,以使Host 进行下一步的操作。USB 设备所执行的操作可用下图描述:
其中dCBWSignature 的值为43425355h(LSB),表示当前发送的是一个CBW;dCBWTag
的内容需要原样作为dCSWTag 再发送给Host;dCBWDataTransferLength 为本次CBW 需要传输的数据,bmCBWFlags 反映数据传输的方向,0 表示来自Host,1 表示发至Host;bCBWLUN 一般为零,但当设备有多个逻辑单元时,用此位指定本次命令是发给谁的;bCBWCBLength 为本次命令字的长度;CBWCB 即为真正的传输命令集的命令。
得到一个CBW 后,解析出CBWCD 中所代表的命令,然后按照SCSI 命令集中的定义
来执行相应的操作,或是需要接收下一个Bulk-Out 发来的数据,或是需要向Host 传送数据,完成以后需要向USB Host 发送CSW,反映命令执行的状态。USB 也是通过此来了解设备的工作情况的。下面是CSW 的格式和定义:
dCSWSignature 的内容为53425355h ,dCSWTag 即为dCBWTag 的内容,
dCSWDataResidue 还需要传送的数据,此数据根据dCBWDataTransferLength-本次已经传送的数据得到。Host 端根据此值决定下一次CBW 的内容,如果没有完成则继续;如果命令正确执行,bCSWStatus 返回0 即可。按这个规则组装好CSW 后,通过Bulk-In 端点将其发出即可。
2.4 SCSI 指令集
Bulk-Only 的CBW 中的CBWCB 中的内容即为如下格式的命令块描述符(Command
Block Descriptor)。SCSI-2 有三种字长的命令,6 位、10 位和12 位,Microsoft Windows 环境下支持12 位字长的命令。
12 位字长的SCSI 命令
Operation Code是操作代码,表示特定的命令。高3 位为Group Code,共有8种组合即8个组,低5五位为Command Code,可以有32种命令。Logicol unit Number是为了兼容SCSI-1而设的,此处可以不必关心。Logical block address 为高位在前,低位在后的逻辑块地址,即扇区地址。第2 位为高位,第3、4、5 依次为低位。
Transfer length为需要从逻辑块地址处开始传输的扇区数(比如在Write命令中);Parameter list length为需要传输的数据长度(比如在Mode Sense 命令中);Allocation length为初始程序为返回数据所分配的最大字节数,此值可以为零,表示不需要传送数据。SCSI指令集的Direct Accesss 类型存储介质的传输命令有许多,所幸运的是Mass Storage协议只用到了其中的一些。下面黑体部分即为需要USB 设备作出响应的请求,一般是要求向Host 发送一些有关设备的数据:
对于不同的命令,其命令块描述符略有不同,其要求的返回内容也有所不同,根据相应的文档,可以对每种请求作出适当的回应。比如,下面是INQUIRY 请求的命令块描述符和其返回内容的数据格式:
1)INQUIRY
命令块描述符:
Host 会依次发出INQUIRY、Read Capacity、UFI Mode Sense 请求,如果上述请求的返回结果都正确,则Host 会发出READ 命令,读取文件系统0 簇0 扇区的MBR 数据,进入文件系统识别阶段。对于PREVENT-ALLOW MEDIUM REMOVAL 和TEST UNIT READY 命令,只需直接返回CSW 即可,对于后者,由于Flash 盘总是处于READY 状态,故可直接返回CSW。
2.4 FAT16 文件系统
2.4.1 FAT 文件系统结构
一个FAT(FAT12/FAT16/FAT32)文件系统卷(卷可以理解为是一张软盘、一个硬
盘或是一个Flash 电子盘)由四个部分组成:
1)保留区(Reserved Region)
分区的保留区(Reserved Region)中的第一个扇区必须是BPB(BIOS Parameter Block),此扇区有时也称作“引导扇区”、“保留扇区”或是“零扇区”,因为它含有对文件系统进行识别的关键信息,因此十分重要。下表是此扇区的结构:
2)FAT 区
FAT 即File Allocation Table,文件分配表。操作系统分配磁盘空间按簇来分配的。因此,文件占用磁盘空间时,基本单位不是字节而是簇,即使某个文件只有一个字节,操作系统也会给他分配一个最小单元——即一个簇。为了可以将磁盘空间有序地分配给相应的文件,而读取文件的时候又可以从相应的地址读出文件,我们把整个磁盘空间分成32K 字节长的簇来管理,每个簇在FAT 表中占据着一个16位的位置,称为一个表项。对于大文件,需要分配多个簇。同一个文件的数据并不一定完整地存放在磁盘的一个连续的区域内,而往往会分成若干段,像一条链子一样存放。这种存储方式称为文件的链式存储。为了实现文件的链式存储,硬盘上必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存储后继内容的下一个簇的簇号,对一个文件的最后一簇,则要指明本簇无后继簇。这些都是由FAT 表来保存的,FAT 表的对应表项中记录着它所代表的簇的有关信息:诸如是否空,是否是坏簇,是否已经是某个文件的尾簇等。FAT 区的结构如下:
FAT 的项数与硬盘上的总簇数相关(因为每一个项要代表一个簇,簇越多当然需要的FAT 表项越多),每一项占用的字节数也与总簇数有关(因为其中需要存放簇号,簇号越大当然每项占用的字节数就大)。FAT 的格式有多种,最为常见是FAT16 和FAT32,其中FAT16是指文件分配表使用16 位,由于16 位分配表最多能管理65536(即2 的16 次方)个簇,又由于每个簇的存储空间最大只有32KB,所以在使用FAT16 管理硬盘时,每个分区的最大存储容量只有(65536×32 KB)即2048MB,也就是我们常说的2G。现在的硬盘容量是越来越大,由于FAT16 对硬盘分区的容量限制,所以当硬盘容量超过2G 之后,用户只能将硬盘划分成多个2G 的分区后才能正常使用。
由于FAT 对于文件管理的重要性,所以FAT 有一个备份,即在原FAT 的后面再建一个同样的FAT。
3)根目录区(Root Directory Region)
紧接着第二个FAT 表的后面一个扇区,就是根目录区了。根目录区中存放目录项,每个目录项为32 个字节,记录一个文件或目录的信息 (长文件名例外) 。以下是目录项的结构:
目录项所占的扇区数与有多少个目录项有关,它将占用(目录项*32/512)个扇区。
4)文件和目录数据区
目录项的所占的最后一个扇区之后,便是真正存放文件数据或是目录的位置了。
2.4.2 硬盘结构
FAT 结构是所有按照FAT 文件系统来组织存储单元的介质都必须遵守的一种文件系统格式,而对于不同的介质,其结构又有些差异,下面介绍一下文件系统格式为FAT 的硬盘的结构。硬盘上的数据按照其不同的特点和作用大致可分为5 部分:MBR 区、DBR 区、FAT 区、DIR 区和DATA 区。下列分别介绍:
(1)MBR(Main Boot Record)区
按其字面上的理解即为主引导记录区,位于整个硬盘的0 磁道0 柱面1 扇区。不过,
在总共512 字节的主引导扇区中,MBR 只占用了其中的446 个字节(偏移0-偏移1BDH),另外的64 个字节(偏移1BEH-偏移1FDH)交给了DPT(Disk Partition Table 硬盘分区表)(见下表),最后两个字节"55,AA"(偏移1FEH-偏移1FFH)是分区的结束标志。这个整体构成了硬盘的主引导扇区。大致的结构如下:
主引导记录中包含了硬盘的一系列参数和一段引导程序。其中的硬盘引导程序的主要作用是检查分区表是否正确并且在系统硬件完成自检以后引导具有激活标志的分区上的操作系统,并将控制权交给启动程序。MBR 是由分区程序(如Fdisk.com)所产生的,它不依赖任何操作系统,而且硬盘引导程序也是可以改变的,从而实现多系统共存。MBR 中可以定义四个分区信息表,每个分区信息表的16 个字节定义如下:
比如,以一个实例来直观地介绍分区信息表中的内容:
例:
最前面的"80"是一个分区的激活标志,表示系统可引导;"01 01 00"表示分区开始的磁头号为01,开始的扇区号为01,开始的柱面号为00;"0B"表示分区的系统类型是FAT32,其他比较常用的有04(FAT16)、07(NTFS);"FE BF FC"表示分区结束的磁头号为254,分区结束的扇区号为63、分区结束的柱面号为764;"3F 00 00 00"表示首扇区的相对扇区号为63;"7E 86 BB 00"表示总扇区数为12289622。
(2)DBR 区
DBR(Dos Boot Record)是操作系统引导记录区的意思。它通常位于硬盘的0 磁道1
柱面1 扇区,是操作系统可以直接访问的第一个扇区,它包括一个引导程序和一个被称为BPB(Bios Parameter Block)的本分区的参数记录表。引导程序的主要任务是当MBR 将系统控制权交给它时,判断本分区根目录前两个文件是不是操作系统的引导文件(以DOS 为例,即是Io.sys 和Msdos.sys)。如果确定存在,就把其读入内存,并把控制权交给该文件。BPB 参数块记录着本分区的起始扇区、结束扇区、文件存储格式、硬盘介质描述符、根目录大小、FAT 个数,分配单元的大小等重要参数,如在FAT 结构中所介绍的那样。
(3)FAT 区
在DBR 之后的即是FAT(File Allocation Table 文件分配表)区。文件分配表负责给文件分配空间,故称之为文件分配表。以簇为单位的存储方法存在着必然的缺陷,即总是无法占满整簇的空间,存在着空闲的空间。簇的大小与磁盘的规格有关,一般情况下,软盘每簇是1 个扇区,硬盘每簇的扇区数与硬盘的总容量大小有关,可能是4、8、16、32、64……
(4)DIR 区
DIR(Directory)是根目录区,紧接着第二FAT 表(即备份的FAT 表)之后,记录着根目录下每个文件(目录)的起始单元,文件的属性等。定位文件位置时,操作系统根据DIR中的起始单元,结合FAT 表就可以知道文件在硬盘中的具体位置和大小了。
(5)数据(DATA)区
数据区是真正意义上的数据存储的地方,位于DIR 区之后,占据硬盘上的大部分数据空间。
2.4.3 Flash 盘的FAT 结构
Flash 硬盘与普通的磁头、柱面式介质不一样。在开发U 盘的过程中,使用Flash 作为存储介质。它有其特定的结构特点。以16M 的三星K9F2808U0A-YCB0 Flash 为例,它有1024 个Block,每个Block 有32个Page,每个Page 有512+16=528 个字节。
Flash 的读写有其自身特点:1)必须以Page 为单位进行读写;2)写之前必须先擦除原有内容;3)擦除操作必须对Block 进行,即一次至少擦除一个Block 的内容。
针对这种情况,将Flash 的一个Page 定为1 个扇区,将其2 个Block,64 个扇区定为一个簇,这样,簇的容量刚好为512*64=32K,满足FAT16 对簇大小的要求。
FAT 分配空间的时候,是按簇来分配的,但是其给出的地址却是LBA(Logical Block
Address),即它只给出一个扇区号,比如对此Flash 而言,若给出LBA 为0x40,实代表簇1的扇区1。因此需要将Logical Block Address 转换为物理地址,这样,才可以对数据进行存取操作。根据我们定义的结构,转换公式为:
Flash 的Block = Logical Block Address/0x20
Flash 的Page = Logical Block Address %0x20
实际上,如果定义每个簇为32 个扇区是最好的,因为这样物理结构和逻辑结构刚好一致。但是这也无防,因为不管Logical Block Address 给出什么值,只要按上述公式,总可以得到物理上正确的Block 和Page,再使用Flash 的读写命令读取对应的Block 和Page 就可以了,读的问题复杂一些,在后面介绍。因此簇和扇区的概念只是在BPB 中给出存储介质信息的时候告之系统就可以了,我们只要做好LBA 与物理地址间的转换就可以了。由于做为U 盘的Flash 不要求启动,因此可以没有MBR 区,只包含DBR、FAT、DIR和DATA 四个区。
因此,Flash 的前两个Block 的内容如下:
当Host 发出READ 命令后,Flash 读写操作即告开始,Host 首先读取MBR,得到有关存储介质的有关信息,诸如扇区长度、每簇扇区数以及总扇区数等内容,以便知道此盘有多大。如果读取正确,会接着读取文件分配表,借以在PC 机上的可移动盘符中显示文件目录,并可以复制、删除或是创建文件。系统自动将这些命令都转换成Read 或Write 两种命令,通过USB 的READ 或WRITE 命令块描述符来从Flash 中相应扇区读取数据,或是将特定长度的数据写入Flash 相应簇中。
2.5 Flash 的读写
针对Flash 读写的特点,特别是其可随机读,但无法随机写的问题,需要通过设置缓冲区来解决。在与USB Host 进行数据交换的过程中,最小的单位是扇区:512 字节。由于Flash在写之前必须先擦除,而一擦又必须擦一个Block,因此在擦除某Block 之前必须保存同一个Block 中有关扇区的数据。因此,如果每收到一个扇区的内容就进行一次擦、保存、写的操作,系统任务将十分繁重,无法及时响应USB Host 端的请求。因此,在系统中设置32K 的缓冲区(ARM7 系统具有2M SDRAM,因此内存足够,如果在8051 平台上,则需要另外想办法),每完成一次数据传输后,记下本次要写的开始扇区和总扇区数,将本次要写的数据所涉及的扇区以外的数据从Flash 中读出来,存放在缓冲区中对应位置,然后擦除一个Block,再将缓冲区中内容一次全部重新写入Flash。
三、项目开发调试过程
在进行USB 开发过程中,比较难的是对设备端的程序(称为固件,Firmware)进行调试。由于USB 协议有严格的时间要求,这就使得程序必须在有效时间内对某些请求或状态进行处理,否则,USB 将无法正常工作。因此,在调试过程中,较多借助串口输出程序输出的一些信息来辅助调试,定位问题所在。比如,在某个函数中加入输出语句,程序运行时看有否特定的输出内容,借此来判断此函数是否得到了执行,并通过输出一些量来查看状态。调试工作基本分三步进行:首先对外部设备(单片机部分)借助PC 调试软件(芯片生产商提供或从网上下载Bus Hound,WINRT-USB 等调试软件)将设备端的USB 协议(主要有描述符请求、端口配置、地址设置以及基本数据交换)调通。然后,用调试好的USB设备接口来开发、调试PC 软件,这一步相对比较容易。最后,加上USB 设备端的其它用户程序,对整个完整的系统进行系统调试。
3.1 硬件电路和基本程序结构
下图为PDIUSBD12 的引脚定义,其中,下列几个方面在制作电路板时应该注意。
__
1)GND 接地,VDD 接正(3.3V 或5V),如果芯片工作在3.3V,则Vout3.3 与VDD 都
接3.3V;如果芯片工作在5V,则这时Vout3.3 会输出3.3V 的电压,用于提供给D+作参考电压,因为此参考电压必须为3.3V。对于U 盘来说,由于USB 接供的是5V 电压,因此应该按后一种接法接。即GND 接USB 接口中的GND,VDD 接USB 接口中的VDD,D+D-分别接USB 接口中的对应位。
2)DATA<0>~ DATA<7>、WR_N、RD_N、INT_N、XTAL1 和XTAL2 按传统接法接。
3)由GND 接一个1M 电阻,再从USB 电源VCC(USB 四根线,分别为VCC,D+、
D+和GND)接一个10K 的电阻,然后将1M 和10K 接在一起,引至EOT_N 引脚,借此
检测USB 设备是否已经连接到USB 口,SoftConnect 功能只有在检测到此信号时才会进行连接,换言之,如果不接此信号,则D12 总认为还没有插入到USB 接口中,SoftConnect永远不会连接。
4)CS_N 和DMACK_N 信号任一为低时,均可选中此芯片,因此,如果不使用DMA 方
式,则应将DMACK_N 接高电平,使用CS_N 作为片选。
5)ALE 和A0 的接法必须组合在一起,根据USB 芯片与MCU 之间数据地址总线情况
的不同,有两种接法:a)如果总线和地址复用,则可以将ALE 接至MCU 的ALE,A0 接高电平,这种情况下D12 会在ALE 的下降沿锁存地址信号,直接将数据写入对应的USB 地址码中。比如D0 是D12 的地址使能命令字,则直接将要使能的USB 地址写入D0 中,在MCU 的ALE 下降沿,D12 先将D0 保存下来,然后再将端口地址作为数据送至D12;b)如果MCU 总线和地址是分开的,则ALE 总接低,A0 为高时表示DATA<0>~DTAT<7>上收到的是命令字,A0 为低时表明收到的是数据,通过将地址线的高位接至A0,D12 就可以有独立的命令和数据端口,同样,端口使用时,先将D0 写入命令端口,再将端口号写入数据端口,不同的端口其实只有A0 的变化,从而告之D12 当前发送的是什么内容。
6)CLOCKOUT 可以作为MCU 的时钟源;SUSPEND 接至一个双向IO 口;GL_N 接
发光二极管后加电阻接高电平,它的亮暗将反映USB 芯片的工作状态。完成上述硬件电路的连接后,即可开始准备固件程序,主要包括三部分:a)初始化单片机和所有的外围电路(包括PDIUSBD12);b)主循环部分,主要完成对来自USB Host 端的
Setup 包,其任务是可以中断的;c)中断服务程序,其任务是对时间敏感的,必须马上执行。根据USB 协议,任何传输都是由主机(host)开始的,这样,单片机作它的前台工作(可能就是空循环,什么也不做),等待中断。当USB 设备插入主机时,主机将使用缺省的端口0 向USB 发送信息,会发送一系列令牌包给USB 设备(这里是PDIUSBD12),PDIUSBD12 接收到令牌包后就给单片机发中断,单片机进入中断服务程序,首先读PDIUSBD12 的中断寄存器,判断 USB 令牌包的类型,然后执行相应的操作。因此,USB单片机程序主要就是中断服务程序的编写。下面用形象通俗的语言描述一下USB 设备端是如何与主机建立起通信的。当一个USB设备在插入主机之前,主机对这个USB 设备的情况一无所知,这时当然无法建立起通信。但USB 协议规定了一些最基本的准则,比如说每个设备的端点0 都是可用的,属于控制端点。有了这个基本的沟通途径,主机就开始通过端点0 向设备提出一些问题,这些问题是有关设备的基本情况的。这些基本情况可以反映USB 设备所属的类别及子类,反映配置情况、接口情况和端点情况,一旦得知了这些信息,那么主机就大体了解这个设备是个什么样的设备了,按照USB 协议中的相应规定,就逐步建立起了一条介于设备之间的高速数据通道,用于数据的传输。主机向设备提出的这些问题实际上就是USB 协议中规定的各种标准请求,设备必须对这些问题进行回答,而回答的方式就是向主机传送相应的描述符:设备描述符、配置描述符、接口描述符、端点描述符。这里还要明白一个概念,USB 设备中与主机直接打交道的是USB 芯片,这里为D12,因此,D12 负责数据的接收和发送,并且在接收数据或是完成数据发送后,会产生相应的中断,不同的中断以中断寄存器中不同的状态来表示。当主机首先通过端点0 向D12 发出最初的配置包的时候,D12 会产生端点0 接收中断,单片机的中断服务程序要做两件事:a)读取中断及相应的数据,处理寄存器,为下一次中断创建条件;b)对中断进行处理,根据接收到的请求内容,或向主机发送相应数据,或在接收到数据后将数据从缓冲区中读走并进行分析处理,或是在上一帧数据发送成功后继续后续发送⋯⋯Philips 提供的D12 手册中,介绍了下图所示的一种程序结构,即使用相同的数据缓冲区,中断服务程序专注了读取中断寄存中的数据并对寄存器相应的设置和处理,主程序专注于对数据的处理。这种思想,其实只是结构上的一种表现形式,即形式上主程序专门处理一些事情,中断服务程序专门处理一些事情。但实质上,还是以中断服务程序为中心的,因此采用这种结构,只是将一部分程序挪到了主程序,但其仍是由中断服务程序中的相应内容来驱动的。而且,一般来说,当USB 主机发送来一个请求,D12 产生一次中断后,就需要设备对此中断进行响应,比如回传一些数据,而这些数据在回传之前,一般是不会再来中断的,
困此,没有必要一定要将数据放到某个缓冲区里面,然后退出中断服务程序,再跑到主程序中去处理。完全可以直接在中断服务程序中处理完毕后,再退出中断,因为主机在退出中断之间一般不会再来中断。这样使程序结构上思路更加清晰、以请求类型为驱动的中断服务程序也较好构建,这时其实可以认为主程序其实是在轮询,什么也不干,就是等着中断,来什么中断就进相应的中断服务程序内容进行处理,执行完毕后再到主程序里去等着新中断的到来。
__
3.2 U 盘调试的主要步骤和内容
USB 设备端的固件分以下几个层次:
在调试的时候,从现象上来看,分成以下几个阶段性的步骤:
1、 USB 芯片正常工作,可以实现软连接,此时PC 机上会出现“未知设备类型”的USB 设备;
2、 使用他人已经高度成功的USB 通用接口,按普通USB 设备提供描述符,提供正确的VID 和PID 后,PC 能够识别设备,但要求提供设备的驱动程序;
3、 安装驱动程序后,调试几个端点,使其均可传输数据,用PC 端的测试程序对其进行测试,验证硬件及固件的正确性;
4、 按Mass StorageBulk-Only 模式提供描述符,PC 机上设备类型变成Mass Storage Device;
5、 响应了Bulk-Only 的Inquiry 命令,可以出现盘符了,但尚无法访问磁盘;
6、 提供了其他所有的UFI命令(SCSI 子集),开始读取磁盘0 扇区(BPB 区)的内容,按照FAT16 的格式格式化Flash,可以正确读取信息,可以访问盘符,列目录为空;
7、 创建文件时,向设备发出Write 命令,调整Flash 的读写问题,解决写某几个扇区要先保存整个簇的内容,然后擦除整簇,再回写,可以正常创建文件;
8、 完成最后的调试,U 盘高度完毕。在此基础上,还需要提供支持FAT16 的文件系统接口函数,比如,可以从FAT16 中读取文件,可以创建文件并将其保存到FAT16 中去。
3.3 检查USB 器件工作与否
确定USB 芯片是否已经正常工作,是所有调试的基础,得到电路板之后,这是一个首先要解决的问题。判断USB 芯片是否已经工作,可以从两个方面来判断,一是将CLKOUT 设为12M(缺省为4M),然后用示波器量CLKOUT 引脚,若确为12M 而不是4M,则说明USB 的地址和数据都可以正确传送,各种信号线接法正确,USB 芯片可以正常工作,硬件基本没有问题。另一方面,如果使用SoftConnect,则在主程序中进行软连接后,如果GL_N 灯闪烁几下,则PC 机上出现未知设备,则说明USB 进行软连接正常,也说明芯片已经工作了。除了这两点之外,在整个调试标准设备请求阶段,都应该将中断寄存器的内容通过串口发送出来,以便进行查看。另外就是还可以使用Bus Hound,观察USB 总线上的数据,从而判断主机与设备之间的通信已经进行到哪一步了。
在主程序中,对USB 初始化过程为:
① 初始化MCU 的各种端口;
② 进行中断初始化,设置中断服务程序入口地址,将MCU 的中断方式设置为低电平
触发,因为D12 只要进入中断后INT_N 就一直为低。
③ 断开SoftConnect,延时1 秒后再次连接
④ 进入主程序循环,等待中断的到来,端点0 的中断处理程序会将数据放至缓冲区中,
主程序在前台判断是否有 Setup 包(通过一个变量,当中断服务程序检测到有Setup 包时,设置该变量),然后执行相应的控制端点的传输。主程序的这些工作完成以后,便可以根据前面提供的两种方法测试D12 芯片是否已经工作了。 以下两点还应该引起注意:PDIUSBD12 的中断输出引脚INT_N 只要中断寄存器不为0 就保持低电平,所以单片机的对应中断应设置成电平触发;中断处理后要用读上次传输状态寄存器清除中断寄存器中对应位(D0-D5)。PDIUSBD12 对内部寄存器的读写没有边界限制,程序设计中一定不要读写超过端点深度的数据。特别对于描述符请求,由于其长度大于Control IN 深度(64Bytes),要分几个数据周期传输。
3.4 提供描述符
USB 设备的调试过程其实就是根据主机的请求,不断地向主机提供各种信息的过程。
因此,了解主机按照什么样的顺序向设备发出请求,即Windows 对USB 设备的枚举顺序是非常有必要的:
① GetDeviceDescriptor。主机主要对Length 域感兴趣,发送内容一定要正确,特别是第 2 字节type 一定为0x01,即Device;否则,主机将不响应,或者再重复2 次后放弃。这时由于主机对Device 的描述符将有多长实际上都不知道,所以这个步骤只是试探性的,目的是得到真正长度,第三步中才正而巴经地读取DeviceDescriptor。查看DeviceDescriptor
② SetAddress。一般为02 或03。
③ 连续3 次GetDeviceDescriptor,读取全部设备描述符,一般为 18 B,分为多次传输。如果不正确,主机将不响应或重复2 次后放弃。
④ GetConfigDescriptor。注意第2 字节一定为0x02,即config 。这部分内容包括
Configuration Descriptor、Interface Descriptor 和所有要用到的端点的EndPooint Descriptor。
⑤ GetStringDescriptor(可能没有),根据在设备描述符中是否有 String 索引而定。一般先读取LanguageID,再读取product string。
⑥ 读取全部ConfigDescriptor,次数根据描述符的大小决定(端点个数不同,描述符大小不同),如果不正确,主机将不响应或再重复2 次后放弃。
⑦ 如果以上步骤都正确,主机将找到新设备,提示安装驱动程序;否则找到未知设备,不可用。安装驱动程序后,以后的每次PlugIn,枚举次序与以上步骤略有不同,之后会有SetConfiguration、GetConfiguration 和GetInterface 等调用。需要说明的是,USB 协议中所有字数据均定义为低字节在前传输(LSB),因此如果所使用MCU 对字的处理方式与之不同时,就需要进行转换。比如,ARM 平台上也是LSB,因此不必转换,而在8051平台上是MSB,因此Phlips的VID为0471H,但在Device Descriptor中的iDVendor 应定义成71H、04H。正确安装驱动程序以后,还可以通过一些工具来查看USB 设备的描述符情况,以确定自己所提供的描述符信息是否都正确。__在调试过程中,指望得到一个绝对正确的提供描述符的顺序是没有什么用处的,最好的办法就是掌握调试的方法,因为一旦掌握这个方法,那么在后续的调试过程中就更加得心应手了。下面就介绍如何使用Bus Hound 软件来辅助调试。
下图所示即为Bus Hound 获取USB 总线上的数据的情形,有三个部分的数据对调试来讲比较有用,Phase 可以让调试者知道当前处于什么阶段,是控制命令发出,数据发出还是接收到数据。Data 则可以让我们清楚地知道主机向Device 发了些什么,或是Device 向主机发了些什么,将此数据与Device 从主机收到的数据或是发向主机的数据进行对比,可以确保数据是否传输正确。Info 阶段则可以让我们知道当前处于USB 协议的什么阶段,它可以清晰地表示出是在GET DESCRIPTOR 阶段还是SET CONFIG 阶段⋯⋯在此工具的最上面一行中,还可以通过Save 将所得到的数据作为文件保存下来再进行分析;从Devices 中可以选择要对哪些设备的数据进行捕获;Settings 中可以设置缓冲区的总长度和每个Phase 数据长度的限制。总之,Bus Hound 是进行USB 开发的利器,是调试过程中的必备工具。
3.5 Mass Storage 协议
如果是按照Mass Storage Bulk-Only 传输协议提供的描述符,则当Bus Hound 中DI 阶段中的数据出现55 53 42 53 字样,说明已经开始开始发送Bulk-Only 协议的CBW 了。这时可以将前面的工作告一段落,即不用太多考虑USB 中断啦、端点啦之类的问题,到此阶段后只有两个端点工作,即Bulk-In 和Bulk-Out,In 或Out 都是从主机的角度来讲的,前者用于Device 向Host 发送数据,后者用于Host 向Device 发送数据。Bulk-In 端点的处理比较简单,在需要的时候,Device 将要发往Host 的数据通过此端点送出即可,如果数据一次不能发完,则设置标志位,通过发送中断的产生可以实现连续发送。Bulk-Out 端点的情况比较复杂一些,要判断收到的是协议内容还是数据,如果是协议内容,要对协议包进行解析,根据协议中的内容得到UFI(SCSI)命令,然后再根据这些命令处理处理相应的请求。对Bulk-In 端点的处理见下列流程图:
3.6 SCSI 命令集
在SCSI 命令集的处理中,比较难处理的是WRITE 和READ 比较难处理。因为Host传送数据至少一次为512 个字节,不管是读还是写,都需要一次传送512 个字节,但是批量传输端点一次只能传输64个字节,即要传8次才可以传完。因此,在向Host传送数据时,设置传输标志,当Device 的发送中断出现后判断此传输标志,如果正处于传输状态,则继续传输,直至需要的数据传输完毕,再传输CSW。在从Host接收数据时,也设置接收标志,当收到WRITE命令后,即进入此状态,直至从Host端接收数据完毕,再将数据存入Flash 中。
3.7 Flash 的读写
Flash 的读写直接调用两个函数ReadPage()和WritePage 即可,他们可以一次写入528个字节(512+16)的内容。但是注意必须在写数据之前首先将相应的簇擦除,擦除之前又需要先将不覆盖的数据保存。
四、总结
4.1 一点体会☺
在此项目的开发过程中,感觉C 语言的熟练程度是项目开发的关键。许多问题其实原理上都是非常成孰的东西,从一些资料上都可以找到。在自己的项目中,要做的也就是将原理表述的东西在特定的平台上实现就可以了。因此,开发工作本身没有多少创造性,没有多少高深的知识,要具备的仅是对C 语言的熟练,以及学会解决遇到的问题。一个协议或是一个方法,其原理和实现的不同在于,原理比较抽象,比较粗略,不需要精确;而要真正用代码实现时,则要求具体、详细、精确,不点差错都不能有,因此,对于语言的熟练程度的确可以事半功倍,提高效率。在众多的C 语言的内容中间,在项目开发过程中值得引起注意的内容有:循环控制、数组的使用、指针的使用。灵活运用数组名作为地址指针,可以使用程序编制过程中许多问题变得容易解决。另外就是动手实际开发,是学习的好机会,只有真正经历了开发过程以后,才会有更深的体会。项目开发过程中,经验与能力的增长是相辅相成的。有些内容是要凭经验,理论上的东西,是别人总结出来的东西,因此非常抽象,跟实践相比往往省略了许多细节,而项目开发有时候就是根据抽象的理论,来重新实现细节。如果某个问题在做第一遍不清楚,那么做第二遍时就一定知道了,就可以直接得到正确的结论,这就叫做经验。一次的经验并不能成为能力,凭一次的经验快速解决问题称不上能力的高低,但是,当进行长期的积累,对某一类问题有了丰富的经验之后,那么能力的高低就有所体现了。多次的经验积累,可以在能力上得到提高,以后遇到问题,就能够找到问题的原因,并分析问题所在,从而解决实际问题。在此项目的开发过程中,最重要的就是掌握调试手段,不断解决调试过程中发现的问题。比如,在响应Host 端的WRITE 指令时,由于一次至少写512 个字节,而USB 的批量传输端点一次只可以发送64 个字节,因此共需要传8 次,传完8 次后Device 端需要返回CSW,但在实际调试过程中,却发现这样的更象:传完7 次数据后主机就不继续传了,然后报告超时,而如果在第7 次传输后发送CSW,Host 端又会再传过来一帧数据。这就和常理显得比较不一致。但是即使在高度过程中发现这个问题,就在所有传数过程中,传到还剩下一帧时,向Host 发送CSW,然后再接收一帧数据,结果发现这种处理方式可靠。这种现象在已有的资料中并没有描述,是在调试过程中发现的,因此,如果完全凭借资料,是很难解决这一问题的。
4.2 后记
本文中所涉及的许多技术资料,可以到www.t10.org 下载,BusHound 软件,可以到
www.DriverDevelop.com 下载。
本文来自CSDN博客,转载请标明出处:file:///C:/Documents%20and%20Settings/Administrator/桌面/usb通信/NBUSB%20项目技术报告.mht