单片机/MCU论坛
直播中

胖子的逆袭

12年用户 1130经验值
私信 关注
[文章]

STM32 USB Mass Storage 例程调试笔记

一、问题起因
     近来有几个客户反映STM3210E的开发板的USB Mass Storage 例程有点问题,组长安排我来调试。Mass Storage例程在PC上实现两个U盘,一个是SD盘,一个是NAND Flash盘,把程序下载到开发板后,PC上能够检测到NAND Flash盘和SD盘,却提示磁盘要格式化,按照提示操作,格式化不成功,可用磁盘空间和已用磁盘空间都为0。
调试前的准备
二、调试前准备
  调试之前花了三天的时间,大致的看了一下USB的框架,后来才发现,没什么必要,不过多学点知识总是好的。
      1.USB只提供一个数据通道,USB总线并不知道设备具体是怎么操作,有什么状态,USB设备的状态是设备自己来决定的,这就是USB描述符的功能了。描述符中记录了设备的类型、厂商、产品ID、端点信息等。描述符主要要:设备描述符、接口描述符、端点描述符、字符串描述符等。设备描述符记录了该设备使用的USB版本号、厂商ID、产品ID、可能的配置数等与设备相关的信息;配置描述符记录了配置包含的接口数、供电方式、是否支持远程唤醒等;接口描述符记录了接口的端点数、接口使用的类、协议等;端点描述符记录了端点号、数据传输的方向、传输类型、最大包长度等;字符串描述符记录了一些文字信息,方便客户理解。
      USB协议中规定了四种传输类型:批量传输、同步传输、中断传输和控制传输
      MassStorage例程中只用到了批量传输和控制传输,所以这里只介绍这两种方式
      (1)批量传输使用bulk transaction传输数据,主要应用在数据大量数据传输和接受数据上同时又没有带宽和间隔时间要求的情况下。特点:要求保证传输。打印机、大容量存储设备和扫描仪属于这种类型这种类型的设备。适合于传输非常慢和大量被延迟的传输,可以等到所有其它类型的数据的传输完成之后再传输和接收数据。
       一次批量事物有三个阶段:令牌包阶段、数据包阶段和握手包阶段。
  第一阶段 令牌包阶段
Host端发出一个Bulk的令牌请求、令牌包中包含了设备地址、端点号。
如果令牌是IN请求 ,则是从Device到Host的请求;
如果令牌是OUT请求,则是从Host到Device端的请求。
  第二阶段 数据包阶段
根据先前请求的令牌的类型,数据传输有可能是IN方向,也有可能是OUT方向。传输数据的时候用DATA0和DATA1令牌携带着数据交替传送。
数据传输格式DATA1和DATA0,这两个是重复数据,确保在1数据丢失时0可以补上,不至于数据丢失。
第三阶段 握手包阶段
如果数据是IN 方向,握手信号应该是Host端发出;
如果数据是OUT方向,握手信号应该是Device端发出。
握手信号可以为ACK, 表示正常响应,
NAK, 表示没有正确传送。
STALL,表示出现主机不可预知的错误。
       (2)控制传输
  作用:USB系统用来主要进行查询配置和给USB设备发送通用的命令,它要保证数据传输过程的数据完整性。设备枚举过程中的各种设备描述符的获取以及设置地址、设置配置都是通过控制传输来实现的。特点:控制传输是双向传输,数据量通常较小;数据传送是无损性的。
控制传输也分为三个阶段,即令牌阶段、数据传送阶段、握手阶段
     2.USB协议中规定了一类大容量存储设备 mass storage device,包括u盘、移动硬盘等。大容量存储设备接口类代码(bInterfaceClass)为0x08,U盘接口子类代码(bInterfaceSubClass)使用0x06,表示使用SCSI透明指令集.协议代码(bInterfaceProtocol)有三种:0x01、0x00和0x50,前面两种需要使用中断传输,这里使用的是最后一种协议,即协议使用仅批量传输(bulk only transport)。
  仅批量传输协议中规定了两个特殊的类请求:bulk-only Mass Storage Reset 和Get Max LUN,前者是复位到命令状态的请求,后者是获取最大逻辑单元的请求。
      1.Get Max LUN的格式如下图
u***协议中采用的是小端格式,这一点要格外注意,比如ASCII 0x55、0x53,用小端格式表示就是0x5355
bmRequestType为0xa1,表示它是发送到接口的类输入请求,bRequest为0xfe,wIndex为请求的接口号,传输的数据长度为1字节,设备将在数据过程返回1字节的数据,表示设备有多少个逻辑单元,0表示1个逻辑单元,1表示有两个。
  2.bulk-only Mass Storage Reset 的格式如下图

bulk-only Mass Storage Reset请求是通知设备接下来的批量端点输出数据为命令快封装包CBW(Command Block Wrapper),在这个请求中,仅需要设置一下状态,说明接下来的数据是CBW,然后返回一个长度为0的状态数据包。
  3.仅批量传输的的数据流
  类请求完成后,就进入了数据传输过程,在仅批量数据传输协议中规定,数据传输分为三个阶段:命令阶段、数据阶段和状态阶段。命令阶段是由主机通过批量端点发送一个CBW(命令封装包)的结构,在CBW中定义了要操作的命令以及传输数据的方向和数量,数据阶段的传输方向由命令阶段决定,而状态阶段则总是由设备返回该命令完成的状态。

CBW的结构如下图

官方文档对这些字段的介绍:
dCBWSignature:
Signature that helps identify this data packet as a CBW. The signature field shall contain the value
43425355h (little endian), indicating a CBW.
dCBWTag:
A Command Block Tag sent by the host. The device shall echo the contents of this field back to the
host in the dCSWTag field of the associated CSW. The dCSWTag positively associates a CSW with the corresponding CBW.
dCBWDataTransferLength:
The number of bytes of data that the host expects to transfer on the Bulk-In or Bulk-Out endpoint (as indicated by the Direction bit) during the execution of this command. If this field is zero, the device and the host shall transfer no data between the CBW and the associated CSW, and the device shall ignore
bmCBWFlags:
The bits of this field are defined as follows:
Bit 7 Direction - the device shall ignore this bit if the dCBWDataTransferLength field is
zero, otherwise:
0 = Data-Out from host to the device,
1 = Data-In from the device to the host.
Bit 6 Obsolete. The host shall set this bit to zero.
Bits 5..0 Reserved - the host shall set these bits to zero.
bCBWLUN:
The device Logical Unit Number (LUN) to which the command block is being sent. For devices that
support multiple LUNs, the host shall place into this field the LUN to which this command block is
addressed. Otherwise, the host shall set this field to zero.
bCBWCBLength:
The valid length of the CBWCB in bytes. This defines the valid length of the command block. The
only legal values are 1 through 16 (01h through 10h). All other values are reserved.
CBWCB:
The command block to be executed by the device. The device shall interpret the first bCBWCBLength
bytes in this field as a command block as defined by the command set identified by bInterfaceSubClass .
If the command set supported by the device uses command blocks of fewer than 16 (10h) bytes in
length, the significant bytes shall be transferred first, beginning with the byte at offset 15 (Fh). The
device shall ignore the content of the CBWCB field past the byte at offset (15 + bCBWCBLength - 1).
  命令封装包CSW

dCSWSignature:
Signature that helps identify this data packet as a CSW. The signature field shall contain the value
53425355h (little endian), indicating CSW.
dCSWTag:
The device shall set this field to the value received in the dCBWTag of the associated CBW.
bCSWStatus:
bCSWStatus indicates the success or failure of the command. The device shall set this byte to zero if
the command completed successfully. A non-zero value shall indicate a failure during command
execution according to the following table:
Value Description
00h Command Passed ("good status")
01h Command Failed
02h Phase Error
03h and 04h Reserved (Obsolete)
05h to FFh Reserved
  定义一个缓冲区用来接收命令块封装包CBW,然后进入到数据处理阶段,在数据处理中,对CBW进行解码,返回或者接收响应的数据。数据发送或者接收完毕后,进入到状态阶段,返回命令执行的情况,然后再次进入命令阶段,等待主机发送CBW包。
3.SCSI命令集
小型计算机系统接口(英语:Small Computer System Interface; 简写:SCSI),一种用于计算机和智能设备之间(硬盘、软驱、光驱、打印机、扫描仪等)系统级接口的独立处理器标准。 SCSI是一种智能的通用接口标准。它是各种计算机与外部设备之间的接口标准。
在U盘中经常用到的命令有:INQUIRY、READ CAPACITY 、READ(10)、WRITE(10)命令等。
INQUIRY命令请求查询目标设备的一些基本信息,操作码为0x12,。
READ FORMAT CAPACITIES命令可以让主机读取设备各种可能的格式化容量的列表,如果设备中没有存储设备,则设备返回最大能够支持的格式化容量。
读容量命令READ CAPACITY可以让主机读取到当前存储媒介的容量,操作代码为0x25,READ CAPACITY读取的是实际的磁盘容量。
主机使用READ(10)命令来读取实际磁盘的数据,使用WRITE(10)来往设备中写入实际的磁盘数据。
4.STM32 相关知识
  STM32提供的有USB全速设备接口,支持USB全速总线、USB挂起/恢复操作,可以停止设备时钟实现低功耗。USB和CAN共用一个专用的512字节的SRAM存储器用于数据的发送和接收,不能同时使用USB和CAN总线。
PC主机和微控制器之间的数据传输是通过共享这一专用的数据缓冲区来完成的,数据缓冲区能被USB外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用512 字节缓冲区,最多可用于16个单向或8 个双向端点。USB模块同PC主机通信,根据USB规范实现令牌分组的检测,数据发送/ 接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。 每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。 当USB模块识别出一个有效的功能/ 端点的令牌分组时,( 如果需要传输数据并且端点已配置) 随之发生相关的数据传输。USB模块通过一个内部的16位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。 在数据传输结束时,USB模块将触发与端点相关的中断,通过读状态寄存器和/ 或者利用不同的中断处理程序,微控制器可以确定
● 哪个端点需要得到服务
● 产生如位填充、格式、CRC、协议、缺失ACK、缓冲区溢出/ 缓冲区未满等错误时,正在进行的是哪种类型的传输。
USB模块对同步传输和高吞吐量的批量传输提供了特殊的双缓冲区机制,在微控制器使用一个缓冲区的时候,该机制保证了USB外设总是可以使用另一个缓冲区。 在任何不需要使用USB模块的时候,通过写控制寄存器总可以使USB模块置于低功耗模式(SUSPEND模式) 。在这种模式下,不产生任何静态电流消耗,同时USB时钟也会减慢或停止。通过对USB线上数据传输的检测,可以在低功耗模式下唤醒USB模块。也可以将一特定的中断输入源直接连接到唤醒引脚上,以使系统能立即恢复正常的时钟系统,并支持直接启动或停止时钟系统。
三、例程分析
  1.首先进行系统配置,如时钟、USB、NAND FLASH、SD卡的初始化等
      2.配置中断,本例程中使用了三个中断通道
  USB低优先级中断(通道20):可由所有USB事件触发(正确传输,USB复位等)。固件在处理中断前应当首先确定中断源。中断处理函数为:USB_Istr ,进入中断处理函数后会进行u***复位操作或者u***数据传输(调用CTR_LP函数)。
  USB高优先级中断(通道19):仅能由同步和双缓冲批量传输的正确传输事件触发,目的是保证最大的传输速率。它的中断处理函数是CTR_HP。
  说明:函数CTR_HP和CTR_LP最终都会调用Mass_Storage_In(端点1)和Mass_Storage_Out(端点2)两个函数来和PC端的USB HOST 通信
     3.SD的I/O 中断(通道49)
  为了让SD I/O卡能够中断多媒体卡/SD 模块,在SD接口上有一个具有中断功能的引脚——第8脚,在4 位SD模式下这个脚是SDIO_D1,卡用它向多媒体卡/SD 模块提出中断申请。对于每一个卡或卡内的功能,中断功能是可选的。SD I/O的中断是电平有效,即在被识别并得到多媒体卡/SD 模块的响应之前,中断信号线必须保持有效电平( 低) ,在中断过程结束后保持无效电平(高)。在多媒体卡/SD 模块服务了中断请求后,通过一个I/O 写操作,写入适当的位到SD I/O卡的内部寄存器,即可清除中断状态位。所有SD I/O卡的中断输出是低电平有效,多媒体卡/SD 模块在所有数据线(SDIO/D[3:0])上提供上拉电阻。多媒体卡/SD 模块在中断阶段对第8 脚(SDIO_D/IRQ) 采样并进行中断检测,其它时间该信号线上的数值将被忽略。
  4.进入while循环,等待中断的发生
      当USB主机检测到USB设备插入后,进入中断,先对设备进行复位,USB在复位后其地址为0,主机通过地址0和刚插入的设备通信,建立控制传输,将设备描述符发送给主机。复位的操作在MASS_Reset()函数中。
      主机对设备又一次复位,这时就进入了设置地址阶段,这也是一个控制传输的过程,主机会分配一个唯一的地址给刚接入的设备。
      主机再次获取设备描述符。
      主机获取配置描述符,主机获取字符串描述符。
主机发送Get Max LUN请求,获取最大逻辑单元。
主机通过批量端点发送CBW包,在CBW中定义了要操作的命令以及传输数据的方向和数量,返回该命令完成的状态,即由设备返回CSW包,端点2用于输出,端点1用于输入数据至主机。CBW的标志是0x55,0x53,0x42,0x43。
在USB主机和设备的通信过程中,数据会先被放到一个大小为512字节的专用SRAM缓冲区里面,然后再传输到主机或者USB设备。
      USB主机通过发送 SCSI Read10 Command和SCSI Write10 Command命令来对从机进行读写,分别调用的是SCSI_Read10_Cmd和SCSI_Write10_Cmd函数,最终调用MAL_Read和MAL_Write,在这两个函数中判断是对SD卡还是NANDFLASH进行读写。具体的过程可以参考代码。
四、例程问题现象和解决方法
1.NAND FLASH盘可以被识别,格式化失败。既然NAND FLASH盘可以被识别,那就表明USB的控制传输没出现问题,USB批量传输出问题了,而且最可能的是NAND FLASH的驱动有问题,仔细看完代码,发现在建立坏块表,对Spare 区域进行读操作中,对NAND FLASH 有一个NAND_CMD_AREA_TRUE1命令操作,查看代码,
#define NAND_CMD_AREA_TRUE1        ((u8)0x30)
而30H这个命令在数据手册中找不到,在程序中把这行注释掉,编译代码,下载,还是无法格式化,最后研究了一下手册,在注释代码的这一行加上5微秒的延迟,然后编译,下载,NAND FLASH可移动磁盘可以正常的读写了。
2.SD盘也能被识别,不能格式化。开始用的是2G的SD卡,后来尝试1G和512M的,它们都能用,也就是说,这个SD卡驱动只识别1G和512M的SD卡。
在代码中添加调试信息,结果是写入的块长度出错。通过对读SD卡信息了解到,2G的SD卡,块大小是1024字节,其它的卡都是512字节。我在SDIO例程中设置块大小为1024字节,读写出错,设置512字节,居然是正确的。
USB MassStorage例程中是首先从SD卡中读取SD卡块大小的值,按照这个值来读写SD卡,而实际上2G的SD卡只能按照512字节来读写。所以在读取块大小的值后,把块的大小除以2,块数目乘以2,卡的总容量不变,程序运行后,一切正常了。

回帖(8)

lee_st

2015-3-31 16:19:25
好东西,分享下!
举报

wnxyy

2015-8-14 15:17:36
写的太好了,mark
举报

h1654155136.7400

2015-8-25 17:15:15
不错,现在仔细看看
举报

鑫鑫同学

2016-4-25 11:52:32
666666                           
举报

Benj

2016-5-1 23:28:06
谢谢分享
举报

就爱吃泡芙

2016-5-5 10:07:37
嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿
举报

mrzhong

2016-6-4 11:09:15
谢谢分享︿( ̄︶ ̄)︽( ̄︶ ̄)︿飞.飞.飞.
举报

黄尚

2016-6-14 01:02:45
这篇文章还是很有技术含量的
举报

更多回帖

发帖
×
20
完善资料,
赚取积分