打印

STM32+FLASH实现U盘

[复制链接]
6238|34
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
采用STM32内部自带USB控制器外加大页NAND FLASH K9F1G08U0A实现一个128M的U盘。

  1、STM32的USB控制器

  STM32F103的MCU自带USB从控制器,符合USB规范的通信连接;PC主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被USB外设直接访问。这块专用数据缓冲区的大小,由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用512字节缓冲区,最多可用于16个单向或8个双向端点。USB模块同PC主机通信,根据USB规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。

  当USB模块识别出一个有效的功能/端点的令牌分组时(如果需要传输数据并且端点已配置),随之发生相关的数据传输。USB模块通过一个内部的16位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。

  在数据传输结束时,USB模块将触发与端点相关的中断,通过读状态寄存器和/或者利用不同的中断来处理。USB的中断映射单元:将可能产生中断的USB事件映射到三个不同的NVIC请求线上:

(1)USB低优先级中断(通道20):可由所有USB事件触发(正确传输,USB复位等)。固件在处理中断前应当首先确定中断源。

(2)USB高优先级中断(通道19):仅能由同步和双缓冲批量传输的正确传输事件触发,目的是保证最大的传输速率。

(3)USB唤醒中断(通道42):由USB挂起模式的唤醒事件触发。


沙发
haolaishi|  楼主 | 2015-4-27 11:02 | 只看该作者
  2、大页NAND K9F1G08

  Nand flash 以页为单位读写数据,而以块为单位擦除数据。根据NAND的容量等级又将NAND FLASH分为大页NAND和小页NAND;K9F1G08就是大页NAND,它的页大小为(2K+64)Byte,块大小为(128K+4K)Byte。K9F1208U0M为小页NAND,它的页大小为(512+16)Byte,块大小为(16K+512)Byte。由于写数据至FLASH时,只能将指定的位变为0,而不能将指定的位变位1。因此在写一个页的数据前,必须先擦除(将所有的位全部置1),否则写数据会失败。在编制FLASH的读写程序时,需要传递三个参数,要操作的地址,要操作的数据缓存,要操作的数据长度;在写操作时,还要有擦写和坏块管理。

使用特权

评论回复
板凳
haolaishi|  楼主 | 2015-4-27 11:03 | 只看该作者
3、Mass Storage Class Bulk-Only Transport

  Mass Storage类支持两个传输协议: Bulk-Only 传输(BOT)与 Control/Bulk/Interrupt传输(CBI)。Mass Storage Class Bulk-Only Transport类规范定义了两个类规定的请求:Get_Max_LUN和Mass Storage Reset,所有的Mass Storage类设备都必须支持这两个请求。

Bulk-Only Mass Storage Reset(bmRequestType=00100001b and bRequest= 11111111b)用来复位Mass Storage设备及其相关接口。

使用特权

评论回复
地板
haolaishi|  楼主 | 2015-4-27 11:04 | 只看该作者
Get_Max_LUN(bmRequestType= 10100001b and bRequest= 11111110b)用来确认设备支持的逻辑单元数。Max LUN的值必须是0~15。注意:LUN是从0开始的。主机不能向不存在的LUN发送CBW。

使用特权

评论回复
5
haolaishi|  楼主 | 2015-4-27 11:14 | 只看该作者
支持BOT传输的Mass Storage设备接口描述符要求如下:

接口类代码bInterfaceClass=08h,表示为Mass Storage设备;

接口类子代码bInterfaceSubClass=06h,表示设备支持SCSI Primary Command-2(SPC-2);

协议代码bInterfaceProtocol有3种:0x00、0x01、0x50,前两种需要使用中断传输,最后一种仅使用批量传输(BOT)。

支持BOT的设备必须支持最少3个endpoint:Control, Bulk-In和Bulk-Out。USB2.0的规范定义了控制端点0。Bulk-In端点用来从设备向主机传送数据。Bulk-Out端点用来从主机向设备传送数据。

使用特权

评论回复
6
haolaishi|  楼主 | 2015-4-27 11:17 | 只看该作者
Bulk-Only传输(BOT)像控制传输一样,BOT也是由Command阶段,可选的数据阶段和状态阶段组成。所有的command请求都可能有或没有Data阶段。下图说明了BOT的Command传输,Data-In,Data-Out传输及Status传输。

  CBW是由31个字节组成的短包。CBW和后续的数据以及CSW都是从新封包开始的。要注意的是所有CBW传输都是little-endian模式。 在CBW中,dCBWSignature必须是“43425355h”,表示是CBW封包。dCBWTag是CBW标签,会通过对应的CSW的标签反馈回来。 在CSW中,dCSWSignature必须是“53425355h”,表示是CSW包。  

使用特权

评论回复
7
haolaishi|  楼主 | 2015-4-27 11:18 | 只看该作者
二、系统的初始化
1、初始化系统时钟,设置USB时钟为48MHz;

2、USB中断配制,选择通道、设置优先级、使能中断。

3、USB初始化:连接USB、USB硬件复位、配制CNTR寄存器使能和屏蔽中断、清零中断状态寄存器。

4、FLASH初始化。 

使用特权

评论回复
8
haolaishi|  楼主 | 2015-4-27 11:18 | 只看该作者
三、USB的枚举
  当USB连接时,进入USB低优先级中断。首先获取中断状态(读ISTR寄存器),在MASS STORAGE中有USB复位中断、USB挂起中断和正确的数据传输中断。

注:在usb_istr.c 的void USB_Istr(void)函数中。

使用特权

评论回复
9
haolaishi|  楼主 | 2015-4-27 11:18 | 只看该作者
1、USB总线复位:

  设置分组缓冲区描述表起始地址;

  初始化端点:端点0为控制端点、端点1、2为批量端点;设置发送和接收状态,设置发送和接收缓冲区地址。

  设置CBW签名, CBW.dSignature=0x43425355;

  初始化BOT状态机。

  注:在usb_prop.c的void MASS_Reset()函数中。

使用特权

评论回复
10
haolaishi|  楼主 | 2015-4-27 11:19 | 只看该作者
2、USB总线挂起:Xms总线上无数据传输,USB总线挂起,进入低功耗模式。

  注:在usb_pwr.c的void Suspend()函数中。

3、正确的数据传输中断(usb_int.c   CTR_LP();)

  清除中断标志;

  获取端点标识符;

  控制端点处理:读端点寄存器,用来判断是数据输入、输出还是建立包。

  非控制端点处理:下一节介绍。

使用特权

评论回复
11
haolaishi|  楼主 | 2015-4-27 11:21 | 只看该作者
USB枚举软件流程图

使用特权

评论回复
12
haolaishi|  楼主 | 2015-4-27 11:22 | 只看该作者
四、非控制端点处理(usb_endp.c -> usb.bot.c)
  端点2输出中断:usb主机传数据或命令包至mcu。 端点1输入中断:mcu传数据或描述符至usb主机。

  1、端点2输出中断

  (1)将主机传过来的数据从USB端点缓存区copy至MCU内存;

  (2)判断BOT状态,根据BOT状态做出相应的处理:当BOT状态位为0时,CBW包解析,并处理SCSI命令;当BOT状态为1时,表示数据输出,执行WRITE10命令处理。

  2、端点1输入中断

  判断BOT状态,如果BOT状态为2,表示数据输入,执行READ10命令处理;如果BOT状态为4,则表示数据输入完成,则返回CSW,进行到命令状态。

使用特权

评论回复
13
haolaishi|  楼主 | 2015-4-27 11:22 | 只看该作者
  3、BOT状态机软件流程图

  (1)端点2输出中断流程图(usb_bot.c -> Mass_Storage_Out())

使用特权

评论回复
14
haolaishi|  楼主 | 2015-4-27 11:23 | 只看该作者
(2)CBW包解析软件流程图(usb_bot.c -> CBW_Decode())

使用特权

评论回复
15
haolaishi|  楼主 | 2015-4-27 11:24 | 只看该作者
(3)READ10命令软件流程图(usb_scsi.c -> SCSI_Read10_Cmd(lun , LBA , BlockNbr))

使用特权

评论回复
16
haolaishi|  楼主 | 2015-4-27 11:24 | 只看该作者
本帖最后由 haolaishi 于 2015-4-27 11:26 编辑

(4)WRITE10命令软件流程图(usb_scsi.c -> SCSI_Write10_Cmd(lun , LBA , BlockNbr))

使用特权

评论回复
17
haolaishi|  楼主 | 2015-4-27 11:26 | 只看该作者
(5)端点1输入中断流程图(usb_bot.c -> Mass_Storage_In())

使用特权

评论回复
18
haolaishi|  楼主 | 2015-4-27 11:27 | 只看该作者
  4、USB对Memory的操作

  在MASS STORAGE中,USB对MEMORY的操作是以扇区(在FAT中,一个扇区为512字节)为单位的,而USB端点的最大包长为64字节,因此要发送或接收的数据会先放到内存,假设是CPU向端点1读数据,则首先从FLASH或SD卡中读取一扇区数据,再按最大包长分8次向USB端点发送。如果是端点2输出数据,则CPU将收到的数据先放至内存,并累加,当是512字节的整数倍时,再将数据写入FLASH或SD,软件操作流程如下:

使用特权

评论回复
19
haolaishi|  楼主 | 2015-4-27 11:27 | 只看该作者
五、FLASH读写
  USB对FLASH的操用是任意的,以扇区为单位,由于不是按连续地址写数据,所以FLASH的写函数中要有擦写管理。这里我是直接移植圈圈的FLASH读写函数,所以这里引用他的说明(以下文字出自圈圈的BLOG):

  由于NAND FLASH擦除时,只能按按块擦除,因此在写扇区时,首先要擦除一个块。在擦除块前,必须将块内其他数据复制出来,由于一个块比较大(128KB),无法在MCU内开辟如此大的缓冲区。只好借助该NAND FLASH内的页复制命令,将原来的块暂时复制到一个交换用的交换块中。但是如果仅用一个块作为交换的话,它就会被频繁擦写,因而寿命会大大降低。所以在该系统中,保留了10个块用来作为交换区,轮流使用。另外对扇区的连续写进行了优化,当连续写扇区时,就不必每次重复上面的操作,只有当地址跨块时,才需要重新擦除。连续写扇区的实现原理如下:当检测到扇区地址跨块时,就把原来的整块数据复制到交换块中,然后将该块内当前所写地址的前面部分页面复制到原来的块中,接着就从交换块中取出当前扇区地址所在页(使用页复制-随机写入命令),再把一个扇区的数据写入,并接着写入其他扇区,当扇区地址跨页时,就把该页写入到原来的块中,直到扇区被写完(当然如果写的过程中跨块了,还需要重复前面的块擦除以及复制过程)。接着把交换块中剩余的页再复制回原来的块中,这样一个连续写过程就完成了。

使用特权

评论回复
20
haolaishi|  楼主 | 2015-4-27 11:28 | 只看该作者
    因为NAND FLASH在生产和使用过程中,会产生坏块,所以必须增加坏块管理。在该系统中,保留了50个块用做坏块管理。当上述的擦、写过程中,如果发现坏块,那么就把该块的地址重新影射到一个保留的块中。以后每次对该坏块地址操作时,都被重新定位到新的块地址。使用一个二维数组来保存该影射关系。二维数组的前半部分为坏块地址,后半部分为重新影射过后的块地址。每当发现坏块时,就需要重建这张表(主要是增加新的影射并排序,方便地址重新影射的二分查表法),并将其三份一样的写入到专门为此而保留的三个块中。之所以使用三个备份保存,是考虑到这些数据的重要性,因为一旦这个影射关系被破坏,后果将会是灾难性的。在保存这个表格时,做了特殊处理,首先标志他们准备擦除,然后才依次擦除并写入数据,这样即使在操作过程中突然断电,也至少有两个块的备份数据是可以用的,在系统开机初始化时,可以将这些数据恢复。数据使用了特殊标志(0x0055AAFF)以及累加和校验来判断是否有效。对于新出厂的FALSH,在加载坏块表时,就会校验失败,程序就会假设没有坏块并初始化该数组后保存。每个备用块还由另外一个数组用来标志其状态(未用、已损坏、已用等)。当需要使用新的备用块时,就从该数组中查找未用的,并标志为已用。如果备用块本身是坏的,那么就标志它已损坏。该数组与坏块重影射表一并保存。此外还保存了当前坏块的数量。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:不忘初心,积极乐观,勤且道义!

242

主题

1995

帖子

12

粉丝