使用 STM32 USB 设备库的闪存作为大容量存储设备

Posted

技术标签:

【中文标题】使用 STM32 USB 设备库的闪存作为大容量存储设备【英文标题】:Flash memory as mass storage device using STM32 USB Device Library 【发布时间】:2017-11-13 21:50:23 【问题描述】:

我的板上有这个闪存 IC,它连接到我的 STM32F04 ARM 处理器。处理器的 USB 端口可供用户使用。我希望我的闪存通过 USB 连接到 PC 时被检测为存储设备。

作为第一步,我在我的程序中将我的 USB 类定义为 MSC,它运行良好。因为当我将我的主板连接到 PC 时,它检测到连接了一个大容量存储设备,并给出一个错误“你应该在使用之前格式化磁盘”。

现在的问题是,如何将闪存定义为处理器的“存储”。以下可能是您答案的一部分: -usbd_msc_storage_template.c -FAT文件系统

我正在使用 STM32F446 处理器。 FREERTOS 和 FATFS。 我的电脑上的 Windows 10。

提前致谢:)

【问题讨论】:

【参考方案1】:

首先 - 如果您只需要在 PC 上将闪存作为大容量存储设备可见,那么您不需要 FatFS,因为它用于从 MCU 以逐个文件的方式访问存储.当 PC 访问存储设备时,它会自行管理其上的文件系统,您可以选择在格式化驱动器时使用哪种文件系统。在与存储本身通信时处于低级别,它所做的只是告诉存储“从 Y 地址读取/写入 X 字节”。设备所需要做的就是写入或读取给定的数据并返回操作结果。

USB 大容量存储设备类

此 USB 类将您的设备作为存储设备公开给主机,允许它从/向指定地址读取或写入给定数量的字节。对于您提到的STM32F4,您需要实现的功能如下(基于STM32Cube库):

typedef struct _USBD_STORAGE

  int8_t (* Init) (uint8_t lun);
  int8_t (* GetCapacity) (uint8_t lun, uint32_t *block_num, uint16_t *block_size);
  int8_t (* IsReady) (uint8_t lun);
  int8_t (* IsWriteProtected) (uint8_t lun);
  int8_t (* Read) (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
  int8_t (* Write)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
  int8_t (* GetMaxLun)(void);
  int8_t *pInquiry;
USBD_StorageTypeDef;

正如您所提到的,有一个USBD_MSC_Template_fops.c / .h 文件提供了一个示例空模板供您实现,最重要的功能是ReadWrite,真正的“工作”在其中完成。要初始化您的设备以在连接到 PC 主机时显示为 USB 大容量存储设备,剩下的就是:初始化 USB 本身(USBD_Init),注册 MSC 设备类(USBD_RegisterClass),在驱动程序中注册所述结构(USBD_MSC_RegisterStorage) 并在检测到与主机的连接时启动驱动程序 (USBD_Start) 的 USB 设备进程。有很多例子可以做到这一点 - 请参阅 Discovery 或 Eval 板的参考实现。您似乎做得对,因为主机将您的设备检测为 USB MSC 设备并将其报告为未格式化。

您的系统说驱动器未格式化的原因是因为 usbd_msc_storage_template.c 文件中的空实现返回 STORAGE_Read 函数的成功执行(返回代码 0),但实际上并未执行任何读取 - 没有数据送回。虽然这可能因操作系统而异,但最可能的情况是您会看到有关存储未格式化或数据已损坏的消息。

将 USB 大容量存储设备回调与物理内存接口

如上所述,调用USBD_MSC_RegisterStorage 将在USB MSC 设备类驱动程序中注册您的结构。此时,驱动程序本身将在适当的时候调用您提供的函数 - 每当主机请求时。如果目标内存是 SD 卡,自然的步骤是首先实现访问 SD 卡的功能。一旦这些功能经过测试并证明可以工作,剩下的就是将它们放入 USB MSC 设备 ReadWrite 函数中,并且 - 假设正确的中断优先级 - 它通常应该“开箱即用”。系统应该能够格式化卡,然后通过您的 MCU 读取和写入文件。

它适用于您选择的任何类型的内存。唯一的要求是完全按原样实现USBD_StorageTypeDef 回调函数。这意味着主机可以选择在报告的地址空间内的任何地址写入任意数量的随机字节,您要么完全服从(按原样写入所有数据)并返回“成功执行”或返回错误,这很可能会意味着您的驱动器将被卸载,并且将提示用户一条错误消息。在读取的情况下,这意味着如果主机从 Y 地址请求 X 个字节,则设备需要返回准确的数据量。这意味着,如果您的内存类型不完全适合这种访问,则必须在访问物理内存的层中完成更多工作才能遵守 USB MSC 接口。所有这一切自然将我们引向下面的最后一点。

闪存作为文件系统存储

对于直接访问原始数据的闪存,存在某些缺点,使其不完全适合文件系统应用程序。那些来自这些记忆的构建方式。尽管可以实现,但为了隐藏这些缺陷,还需要执行额外的步骤:

    单独写入“1” - 直接访问闪存时仅允许您在给定地址下写入“0”位。一旦某个位被翻转为“0”,就不能再单独将其翻转回“1”。为此,需要首先擦除整个数据块。根据闪存部分,这通常是 512、4096 等字节的区域。这意味着如果您想将给定字节从 1(二进制 0000 0001)更改为 4(二进制 0000 0100),则必须对整个扇区进行读写。对您而言,这意味着即使主机请求写入的某个位需要从“0”翻转为“1”,您也需要先擦除该区域。

    随机访问 - 根据内存 (NOR/NAND) 的类型,您可能会也可能无法随机访问数据。特别是,对于 NOR 闪存,您可以单独读取或写入数据,而对于 NAND 存储器,由于单元的互连方式,仅允许页面访问。这意味着您可能需要读取或写入比必要更多的数据。

    写入耐久性 - 闪存对每个单元都有一定数量的写入周期。这意味着如果您不断地向同一个地址写入数据,您可能很快就会超过这个限制。这对于像 FAT 这样的文件系统尤其重要,因为 FAT 区域将不断被写入。这可以通过实现某种形式的磨损均衡来解决,其中物理扇区写入是均匀分布的。您当然可以选择通过从 IsWriteProtected 返回 true 来将其设为只读,如果这对您的应用程序可行的话。

现在至于当前的 SD 卡如何实现这一切 - 我所知道的所有 SD 卡现在都包含一个简单的微控制器(某种 8081、ARM7 或类似的),它实现了上述所有功能以及 SD 协议。与卡交谈时,您并没有真正与原始内存交谈,而是与位于您和您的数据之间的 MCU 进行通信。它的作用是向您呈现完美连续数据的错觉。

【讨论】:

非常感谢您的快速回答 Jacek。我会尝试在 usbd_msc_storage_template.c 中实现这些 API 函数,看看会发生什么。

以上是关于使用 STM32 USB 设备库的闪存作为大容量存储设备的主要内容,如果未能解决你的问题,请参考以下文章

STM32F4 HAL库开发 -- USB U盘

STM32F4 HAL库开发 -- USB U盘

STM32F4 HAL库开发 -- USB U盘

[转]使用STM32CubeMX:USB大容量存储设备

从 Docker 访问 USB 设备

如何为STM32L475板交换闪存中的两个区域?