STM32CubeMX学习笔记(48)——USB接口使用(MSC基于外部Flash模拟U盘)
创始人
2024-02-09 23:51:57
0

一、USB简介

USB(Universal Serial BUS)通用串行总线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、Microsoft 等多家公司联合提出的。

USB 发展到现在已经有 USB1.0/1.1/2.0/3.0 等多个版本。目前用的最多的就是 USB1.1 和 USB2.0,USB3.0 目前已经开始普及。STM32F103 自带的 USB 符合 USB2.0 规范,不过 STM32F103 的 USB 都只能用来做设备,而不能用作主机。

标准 USB 共四根线组成,除 VCC/GND 外,另外为 D+,D-; 这两根数据线采用的是差分电压的方式进行数据传输的。在 USB 主机上,D-和 D+都是接了 15K 的电阻到低的,所以在没有设备接入的时候,D+、D-均是低电平。而在 USB 设备中,如果是高速设备,则会在 D+上接一个 1.5K 的电阻到 VCC,而如果是低速设备,则会在 D-上接一个 1.5K 的电阻到 VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。

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

1.1 USB MSC简介

USB大容量存储设备类(The USB mass storage device class)是一种计算机和移动设备之间的传输协议,它允许一个通用串行总线(USB)设备来访问主机的计算设备,使两者之间进行文件传输。通过这个标准的计算机连接到的设备包括:移动硬盘、移动光驱、U盘、SD、TF等储存卡读卡器、数码相机、各种数字音频播放器和便携式媒体播放器、智能卡阅读器、掌上电脑和手机。
MSC的通用性和操作简单使他成为移动设备上最常见的文件系统,USB MSC并不需要任何特定的文件系统, 相反,它提供了一个简单的界面来读写接口用于访问任何硬盘驱动器。操作系统可以把MSC像本地硬盘一样格式化,并可以与他们喜欢的任何文件系统格式它,当然也可以创建多个分区。

1.2 外部Flash芯片

开发板中的 FLASH 芯片型号:W25Q64。W25Q 系列为台湾华邦公司推出的是一种使用 SPI 通讯协议的 NOR FLASH 存储器。芯片型号后两位表示芯片容量,例如 W25Q64 的 64 就是指 64Mbit 也就是 8M 的容量。它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SPI 引脚 NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚虽然是其片上 SPI 外设的硬件引脚,但实际上后面的程序只是把它当成一个普通的 GPIO,使用软件的方式控制 NSS 信号,所以在 SPI 的硬件设计中,NSS 可以随便选择普通的 GPIO,不必纠结于选择硬件 NSS 信号。

FLASH 芯片中还有 WP 和 HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。
image

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、USB

3.1 参数配置

Connectivity 中选择 USB 设置,并勾选 Device(FS) 激活 USB 设备。

Parameter Settings 进行具体参数配置。

  • Speed: Full Speed 12MBit/s(固定为全速)
  • Low Power: 默认 Disabled(在任何不需要使用usb模块的时候,通过写控制寄存器总可以使usb模块置于低功耗模式(low power mode ,suspend模式)。在这种模式下,不产生任何静态电流消耗,同时usb时钟也会减慢或停止。通过对usb线上数据传输的检测,可以在低功耗模式下唤醒usb模块。也可以将一特定的中断输入源直接连接到唤醒引脚上,以使系统能立即恢复正常的时钟系统,并支持直接启动或停止时钟系统。)

3.2 引脚配置

USB 的 DP 引脚必须上拉 1.5K 欧的电阻,电脑才能检测到 USB,否则检测不到。

查看野火指南者开发板原理图可知,需要将 PD6 配置为低电平使能 USB。

在右边图中找到 PD6 引脚,选择 GPIO_Output

GPIO output level 中选择 Low 输出低电平。

3.3 配置时钟

选择 Clock Configuration,USB 时钟配置为 48MHz,且来源最好是外部晶振分频得到。

3.4 USB Device

USB有主机(Host)和设备(Device)之分。一般电脑的USB接口为主机接口,而键盘、鼠标、U盘等则为设备。

部分型号的STM32芯片有1~2个USB接口。像STM32F103系列的有一个USB Device接口,STM32F407系列的有2个USB接口,既可以作为HOST,又可以作为Device,还可以作为OTG接口。

Middleware 中选择 USB_DEVICE 设置,在 Class For FS IP 设备类别选择 Mass Storage Class(HID) 大容量存储设备类。

参数配置保持默认(或根据存储介质的最小存储单元修改缓冲区大小)。

  • MSC_MEDIA_PACKET (Media I/O buffer Size)(读写缓冲区大小): 4096(默认为512,这个的大小对于USB读写速度会有一些影响,最好和存储介质的最小存储单元一致)

本实验板使用的W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。

设备描述符保持默认。

四、SPI1

4.1 参数配置

Connectivity 中选择 SPI1 设置,并选择 Full-Duplex Master 全双工主模式,不开启 NSS 即不使用硬件片选信号

image

原理图中虽然将 CS 片选接到了硬件 SPI1 的 NSS 引脚,因为硬件 NSS 使用比较麻烦,所以后面直接把 PA4 配置为普通 GPIO,手动控制片选信号。

在右边图中找到 SPI1 NSS 对应引脚,选择 GPIO_Output纠正:野火STM32F103指南者开发板SPI1 NSS须配置为PC0

image

修改输出高电平 High,标签为 W25Q64_CHIP_SELECT
image

SPI 为默认设置不作修改。只需注意一下,Prescaler 分频系数最低为 4,波特率 (Baud Rate) 为 18.0 MBits/s。这里被限制了,SPI1 最高通信速率可达 36Mbtis/s。
image

  • Clock Polarity(CPOL):SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
  • Clock Phase(CPHA):指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
    image
    根据 FLASH 芯片的说明,它支持 SPI 模式0模式 3,支持双线全双工,使用 MSB 先行模式,数据帧长度为 8 位。
    image
    所以这里配置 CPOL 为 Low,CPHA 为 1 Edge 即 SPI 模式0
    image

五、生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

六、编写W25Q64的驱动程序

驱动程序有问题后面Windows无法格式化硬盘

6.1 添加宏定义

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17
#define W25Q256 0XEF18#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg1		0x05 
#define W25X_ReadStatusReg2		0x35 
#define W25X_ReadStatusReg3		0x15 
#define W25X_WriteStatusReg1    0x01 
#define W25X_WriteStatusReg2    0x31 
#define W25X_WriteStatusReg3    0x11 
#define W25X_ReadData			0x03 
#define W25X_FastReadData		0x0B 
#define W25X_FastReadDual		0x3B 
#define W25X_PageProgram		0x02 
#define W25X_BlockErase			0xD8 
#define W25X_SectorErase		0x20 
#define W25X_ChipErase			0xC7 
#define W25X_PowerDown			0xB9 
#define W25X_ReleasePowerDown	0xAB 
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 
#define W25X_Enable4ByteAddr    0xB7
#define W25X_Exit4ByteAddr      0xE9
/* USER CODE END PD */

6.2 封装SPI Flash(W25Q64)的命令和底层函数

  • 发送数据的同时读取数据的函数
//SPI 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{uint8_t Rxdata;HAL_SPI_TransmitReceive(&hspi1, &TxData, &Rxdata, 1, 1000);       return Rxdata;          		    //返回收到的数据		
}

6.3 读取 Manufacture ID 和 Device ID

读取 Flash 内部这两个 ID 有两个作用:

  • 检测 SPI Flash 是否存在
  • 可以根据 ID 判断 Flash 具体型号
    image
uint16_t W25QXX_TYPE;					//定义W25QXX芯片型号
uint16_t W25QXX_ReadID(void)
{uint16_t Temp = 0;	  /* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);				    SPI1_ReadWriteByte(0x90);//发送读取ID命令	    SPI1_ReadWriteByte(0x00); 	    SPI1_ReadWriteByte(0x00); 	    SPI1_ReadWriteByte(0x00); 	 			   Temp|=SPI1_ReadWriteByte(0xFF)<<8;  Temp|=SPI1_ReadWriteByte(0xFF);	 W25QXX_TYPE=Temp;/* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);				    return Temp;
} 

6.4 读取状态寄存器数据并判断Flash是否忙碌

SPI Flash 的所有操作都是靠发送命令完成的,但是 Flash 接收到命令后,需要一段时间去执行该操作,这段时间内 Flash 处于“忙”状态,MCU 发送的命令无效,不能执行,在 Flash 内部有 2-3 个状态寄存器,指示出 Flash 当前的状态,有趣的一点是:

当 Flash 内部在执行命令时,不能再执行 MCU 发来的命令,但是 MCU 可以一直读取状态寄存器,这下就很好办了,MCU可以一直读取,然后判断 Flash 是否忙完。
image
image

//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
//状态寄存器2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
//状态寄存器3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
uint8_t W25QXX_ReadSR(uint8_t regno)   
{  uint8_t byte=0,command=0; switch(regno){case 1:command=W25X_ReadStatusReg1;    //读状态寄存器1指令break;case 2:command=W25X_ReadStatusReg2;    //读状态寄存器2指令break;case 3:command=W25X_ReadStatusReg3;    //读状态寄存器3指令break;default:command=W25X_ReadStatusReg1;    break;}    /* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET); SPI1_ReadWriteByte(command);            //发送读取状态寄存器命令    byte=SPI1_ReadWriteByte(0Xff);          //读取一个字节  /* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);   return byte;   
} 

然后编写阻塞判断 Flash 是否忙碌的函数:

//等待空闲
void W25QXX_Wait_Busy(void)   
{   while((W25QXX_ReadSR(1)&0x01)==0x01);   // 等待BUSY位清空
}

6.5 读取数据

SPI Flash 读取数据可以任意地址(地址长度32bit)读任意长度数据(最大 65535 Byte),没有任何限制。
image

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ uint16_t i;   							/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);  SPI1_ReadWriteByte(W25X_ReadData);      //发送读取命令  if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位{SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>24));    }SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16));   //发送24bit地址    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));   SPI1_ReadWriteByte((uint8_t)ReadAddr);   for(i=0;i pBuffer[i]=SPI1_ReadWriteByte(0XFF);    //循环读数  }/* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);  				    	      
}

6.6 写使能/禁止

Flash 芯片默认禁止写数据,所以在向 Flash 写数据之前,必须发送命令开启写使能。
image
image

//W25QXX写使能	
//将WEL置位   
void W25QXX_Write_Enable(void)   
{HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);                            //使能器件   SPI1_ReadWriteByte(W25X_WriteEnable);   //发送写使能  HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);                            //取消片选     	      
} //W25QXX写禁止	
//将WEL清零  
void W25QXX_Write_Disable(void)   
{  HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);                            //使能器件   SPI1_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令    HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);                            //取消片选     	      
} 

6.7 擦除扇区

SPI Flash有个特性:

数据位可以由1变为0,但是不能由0变为1。

所以在向 Flash 写数据之前,必须要先进行擦除操作,并且 Flash 最小只能擦除一个扇区,擦除之后该扇区所有的数据变为 0xFF(即全为1)。
image

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)   
{  //监视falsh擦除情况,测试用   //printf("fe:%x\r\n",Dst_Addr);	  Dst_Addr*=4096;W25QXX_Write_Enable();                  //SET WEL 	 W25QXX_Wait_Busy();   /* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);   SPI1_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令 if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位{SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>24)); }SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //发送24bit地址    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   SPI1_ReadWriteByte((uint8_t)Dst_Addr);  /* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);    	      W25QXX_Wait_Busy();   				    //等待擦除完成
} 

6.8 页写入操作

向 Flash 芯片写数据的时候,因为 Flash 内部的构造,可以按页写入。
image
image

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint16_t i;  W25QXX_Write_Enable();                  //SET WEL /* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);   SPI1_ReadWriteByte(W25X_PageProgram);   //发送写页命令   if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位{SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>24)); }SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址    SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));   SPI1_ReadWriteByte((uint8_t)WriteAddr);   for(i=0;i

6.9 写入数据

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 uint16_t pageremain;	   pageremain=256-WriteAddr%256; //单页剩余的字节数		 	    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){	   W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain;	NumByteToWrite-=pageremain;			  //减去已经写入了的字节数if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节else pageremain=NumByteToWrite; 	  //不够256个字节了}};	    
} 
//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
uint8_t W25QXX_BUFFER[4096];		 
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ uint32_t secpos;uint16_t secoff;uint16_t secremain;	   uint16_t i;    uint8_t * W25QXX_BUF;	  W25QXX_BUF=W25QXX_BUFFER;	     secpos=WriteAddr/4096;//扇区地址  secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小   //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节while(1) {	W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;iif(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  }if(iW25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;iW25QXX_BUF[i+secoff]=pBuffer[i];	  }W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff=0;//偏移位置为0 	 pBuffer+=secremain;  //指针偏移WriteAddr+=secremain;//写地址偏移	   NumByteToWrite-=secremain;				//字节数递减if(NumByteToWrite>4096)secremain=4096;	//下一个扇区还是写不完else secremain=NumByteToWrite;			//下一个扇区可以写完了}	 };	 
}

七、修改usbd_storage_if.c

打开工程文件夹Application/User/USB_DEVICE/Appusbd_storage_if.c文件

7.1 修改扇区个数和大小

/** @defgroup USBD_STORAGE_Private_Defines* @brief Private defines.* @{*///#define STORAGE_LUN_NBR                  1
//#define STORAGE_BLK_NBR                  0x10000
//#define STORAGE_BLK_SIZ                  0x200#define STORAGE_LUN_NBR                  1      // 1个盘
#define STORAGE_BLK_NBR                  0x2000 // 模拟16个扇区
#define STORAGE_BLK_SIZ                  0x1000  // W25Q64最小读写单位是4KB
  • STORAGE_BLK_NBR 是扇区个数,此处使用的是0x2000,大小完全根据自己需求定义。W25Q64最大容量为64Mb。

  • STORAGE_BLK_SIZ 扇区大小取决于所用芯片Flash页面的单位,实验所用SPI Flash W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节,所以
    STORAGE_BLK_SIZ = FLASH_PAGE_SIZE = 4096即0x1000

  • U盘实际容量 = 扇区个数 * 扇区大小,所以这个U盘最终大小应该是0x2000*4K = 32768K = 32M

7.2 修改存储函数

  • STORAGE_Init_FS 存储介质初始化
/*** @brief  Initializes over USB FS IP* @param  lun:* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_Init_FS(uint8_t lun)
{/* USER CODE BEGIN 2 */if(W25QXX_ReadID()){return (USBD_OK);}return (USBD_FAIL);/* USER CODE END 2 */
}
  • STORAGE_IsReady_FS 获取存储介质状态

获取介质状态,我们要对存储介质的状态进行判断,这里我们要判断两点,一个是是否正在读写状态中,另外一个就是存储介质是否是不可工作状态。

/*** @brief  .* @param  lun: .* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_IsReady_FS(uint8_t lun)
{/* USER CODE BEGIN 4 */return W25QXX_ReadSR(1);/* USER CODE END 4 */
}
  • STORAGE_Read_FS 读取存储介质

读取一个扇区,我们将准备好的读取一个扇区的代码填充进来就好

/*** @brief  .* @param  lun: .* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 6 */W25QXX_Read(buf, blk_addr * STORAGE_BLK_SIZ, blk_len * STORAGE_BLK_SIZ);return (USBD_OK);/* USER CODE END 6 */
}
  • STORAGE_Write_FS 写入存储介质

写入一个扇区,我们将准备好的读取一个扇区的代码填充进来就好

/*** @brief  .* @param  lun: .* @retval USBD_OK if all operations are OK else USBD_FAIL*/
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 7 */W25QXX_Write(buf, blk_addr * STORAGE_BLK_SIZ, blk_len * STORAGE_BLK_SIZ);return (USBD_OK);/* USER CODE END 7 */
}

八、查看效果

编译工程,下载到板子上,插上USB线连接到电脑上,识别出为大容量存储设备

注意: 如果设备带有感叹号,则参考下面十、注意事项

弹出格式化对话框,直接格式化就行

文件系统选择FAT模式

这里的显示空间为31.9M

新建一个文档测试.txt然后在文档中输入一些内容:

重新上电断开后再次打开U盘看里面的内容和已用空间

九、工程代码

链接:https://pan.baidu.com/s/1zIK767dz28zbg_kCzHJuqA?pwd=domy 提取码:domy

十、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。

如果USB端口出现感叹号设备无法启动的问题,可适当将堆改大,如0x400


• 由 Leung 写于 2022 年 11 月 23 日

• 参考:STM32cubeide/STM32cubeMX USB链接W25QXX做U盘
    使用STM32制作U盘(device)
    4.3、CUBEMX USB之MSC(基于外部FLASH(W25Q128))

相关内容

热门资讯

利益关系经典语录 利益关系经典语录  在日常的学习、工作、生活中,大家都知道一些经典的语录吧,语录是指一个人的说话记录...
哆啦a梦语录 哆啦a梦语录  01、骗人有风险,说谎要谨慎。  02、日子就像偷跑的小孩,无声地溜走。  03、知...
感人的情书经典语录   在很多超感人的情书中,是有一些情书经典语录起到关键作用。小编今天收集了超感人情书经典语录,希望得...
高中生毕业寄语 高中生毕业寄语4篇  在日常的学习、工作、生活中,大家总免不了要接触或使用寄语吧,寄语是所传的、寄托...
优秀班干部获奖感言 优秀班干部获奖感言优秀班干部获奖感言尊敬的老师,亲爱的同学: 大家晚上好!我是S,今天很高兴作为获奖...
最新感人经典语录 最新感人经典语录  1其实,我不是一定要等你,只是等上了,就等不了别人了。《朝露若颜》  2如果世界...
小说我的美女大小姐经典语录   揭穿谎言背后的谎言多累啊!还不如站的离你远点儿,看着你怎么在谎言中尽情的表演  ——李兴禹《我的...
个性早安寄语 常用个性早安寄语汇总75句  我只是想写一封信给你,寄信人在河这头,收信人在河那头。隔开我们的这条河...
假期家长评语怎么写 假期家长评语怎么写  假期我们家长任务是比较大的,许多家长都不知道怎么去写这个假期表现评语,假期家长...
一年级开学典礼校长致辞 一年级开学典礼校长致辞(精选5篇)  在平平淡淡的学习、工作、生活中,大家都对致辞很是熟悉吧,致辞是...
初中学生综合素质评语 初中学生综合素质评语大全  在生活、工作和学习中,大家总少不了要接触或使用评语吧,评语可有效引导被评...
毕业班主任寄语 毕业班主任寄语通用15篇  在平日的学习、工作和生活里,大家总少不了要接触或使用寄语吧,寄语的种类很...
谈“死亡”话题 谈“死亡”话题  死亡指丧失生命,生命终止,停止生存,是生存的反面。哲学上说,死亡是生命(或者事物件...
情人节温馨寄语 情人节温馨寄语集合15篇  在学习、工作、生活中,说到寄语,大家肯定都不陌生吧,寄语是所传的话语,有...
初中毕业寄语 初中毕业寄语(合集15篇)  在学习、工作或生活中,大家都尝试过写寄语吧,寄语是人们所传的话语,有时...
生日的寄语 生日的寄语(精选15篇)  在我们平凡的日常里,要用到寄语的情况还是蛮多的,借助寄语人们可以表达心中...
每晚睡前心灵鸡汤语录 每晚睡前心灵鸡汤语录  导语:我说过我什么都不缺,自己可以过得很好。 不需要深夜晚安,不需要爱的抱抱...
中学生综合素质评语 中学生综合素质评语(通用350句)  在学习、工作乃至生活中,大家都用到过评语吧,篇幅、重点等均不受...
爱情语录戏说爱情 -资料 爱情语录戏说爱情 -资料 爱情有时很像醉酒的感觉,头脑明明是清醒的,但行为就是不受控制……有些事,明...
小学校长寄语 小学校长寄语小学校长寄语培根曾经说过:“习惯是一种顽强的巨大力量,可以主宰人生。”有了好习惯,失败不...