STM32CubeMX学习笔记(48)——USB接口使用(MSC基于外部Flash模拟U盘)

news2024/11/20 13:28:07

一、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<NumByteToRead;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<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);//循环写数 
    /* 取消片选 */    
	HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
	W25QXX_Wait_Busy();					   //等待写入结束
} 

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;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除这个扇区
			for(i=0;i<secremain;i++)	   //复制
			{
				W25QXX_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++;//扇区地址增1
			secoff=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))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP —— HTTP 响应详解, 构造 HTTP 请求

JavaEE传送门JavaEE 【前端】JavaScript —— WebAPI HTTP —— HTTP 协议中的细节(超详细!!) 目录HTTP 响应详解认识 "状态码"状态码小结认识响应 "报头" headerContent-Type认识响应 "正文" body构造 HTTP 请求form 表单ajaxpostmanHTTP 响应…

API接口怎么使用(教你使用api接口获取数据)

API是应用程序的开发接口&#xff0c;在开发程序的时候&#xff0c;我们有些功能可能不需要从到到位去研发&#xff0c;我们可以拿现有的开发出来的功能模块来使用&#xff0c;而这个功能模块&#xff0c;就叫做库(libary)。比如说&#xff1a;要实现数据传输的安全&#xff0c…

【Linux】常见指令汇总

每个优秀的人&#xff0c;都有一段沉默的时光。 那段时光&#xff0c;是付出了很多努力&#xff0c;却得不到结果的日子&#xff0c;我们把它叫做扎根。 文章目录一、xshell的介绍操作系统1.1 xshell1.2 操作系统二、Linux下基本指令2.1 ls(显示当前目录下的文件列表&#xff…

【IVIF:引入注意力机制:GAN:双鉴别器】

AttentionFGAN: Infrared and Visible Image Fusion Using Attention-Based Generative Adversarial Networks &#xff08; AttentionFGAN&#xff1a;使用基于注意的生成对抗网络进行红外和可见光图像融合&#xff09; 在本论文中&#xff0c;我们将多尺度注意机制集成到GAN…

redis优化与一些问题

文章目录优化采用SSD硬盘-提升磁盘读写的速度控制 redis 的内存在10G以内&#xff0c;防止fork耗时太长fork 注意事项设置内存淘汰策略vm.overcommit_memory1尽可能地使用 hash 哈希存储参数调优swapinessulimitTCP backlog客户端缓冲优化碎片优化问题缓存与数据库数据不一致将…

换个地方写helloworld

1、Linux中I/O讲解 今天&#xff0c;我们重新学习C语言的基础&#xff0c;特别是I/O口的讲解。 所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作&#xff08;譬如读文件、写文件等&#xff09;相关的一系列库函数的集合&#xff0c;通常标准 I/O 库函数相关的函数定义都在头…

实时输出Java8 HashMap数据结构

看过 Java 8 HashMap 源码的知道底层数据结构是数组、链表和红黑树实现的&#xff0c;从Debug调试或者序列化输出的都是K,V值&#xff0c;没法直观的看到上述的数据结构&#xff0c;为了直观的看到数据结构存储方面的变化&#xff0c;本文通过动图演示HashMap的结构变化。 为了…

【6 - 完结】Sql Server - 郝斌(identity、视图、事务、索引、存储过程、触发器、游标、TL_SQL)

课程地址&#xff1a;数据库 SQLServer 视频教程全集&#xff08;99P&#xff09;| 22 小时从入门到精通_哔哩哔哩_bilibili ​ 目录 identity&#xff08;主键自动增长&#xff0c;用户不需要为identity修饰的主键赋值&#xff09; 用法 如何重新设置identity字段的值 如…

从React源码来学hooks是不是更香呢

本文将讲解 hooks 的执行过程以及常用的 hooks 的源码。 hooks 相关数据结构 要理解 hooks 的执行过程&#xff0c;首先想要大家对 hooks 相关的数据结构有所了解&#xff0c;便于后面大家顺畅地阅读代码。 Hook 每一个 hooks 方法都会生成一个类型为 Hook 的对象&#xff…

wpf布局学习二 wpf xaml 与android xml界面对比, C++图片旋转与缩放好复杂放弃

弄不明白的事&#xff0c;还是不要去做。 没懂清楚原理&#xff0c;不要尝试去修改。浪费时间。 wpf布局学习二 <Window x:Class"WpfM20UpdateFW.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://sch…

使用adb shell 命令接收串口发送过来的16进制数据 或者 发送16进制数据

首先执行 adb root Adb shell 找到你要发送或者接收串口数据的设备 如果我们再android设备上调试串口的时候&#xff0c;有时我们需要使用adb shell命令&#xff0c;假设我们使用串口发送的数据是16进制&#xff0c;那么使用cat 这个设备节点&#xff0c;有可能显示的是乱码例…

第6章 循环神经网络

系列文章目录 第1章 绪论 第2章 机器学习概述 第3章 线性模型 第4章 前馈神经网络 第5章 卷积神经网络 第6章 循环神经网络 第7章 网络优化与正则化 第8章 注意力机制与外部记忆 第9章 无监督学习 第10章 模型独立的学习方式 第11章 概率图模型 第12章 深度信念网络 第13章 深…

新相微在科创板过会:计划募资约15亿元,2022年业绩开始下滑

11月22日&#xff0c;上海证券交易所科创板披露的信息显示&#xff0c;上海新相微电子股份有限公司&#xff08;下称“新相微”&#xff09;获得上市委会议通过。据贝多财经了解&#xff0c;新相微于2022年6月28日在科创板递交招股书。 本次冲刺科创板上市&#xff0c;新相微计…

企业架构LB-服务器的负载均衡之LVS实现

01_学习目标和内容 02_LVS介绍和ipvsadm管理工具安装 03_LVS常见工作方式和调度算法介绍 04_需要知道的几个概念名词 05_LVS-NAT方式实现的流程原理 06_配置NAT模式准备工作 在centos中建立网卡配置信息 07_NAT模型RS真实服务器的网关配置 08_NAT模型DR调度服务器规则和转发配…

2010年数学二真题复盘

高数部分 选择题 第一题 思路分析:本要主要考察间断点的概念与极限运算. ☆首先无穷间断点属于第二类间断点,则至少有一个点不存在,要么没定义,要么就是在某个点的左极限或者右极限上趋于无穷,可以先化简,找一些分母趋于0的,那么就是无穷间断点了。 第二题 思路分…

设计模式之美——多组合少继承

组合优于继承&#xff0c;多用组合少用继承。 继承举例 假设我们要设计一个关于鸟的类。我们将“鸟类”这样一个抽象的事物概念&#xff0c;定义为一个抽象类 AbstractBird。所有更细分的鸟&#xff0c;比如麻雀、鸽子、乌鸦等&#xff0c;都继承这个抽象类。 我们知道&…

web前端期末大作业——贵州山地旅游介绍网页1页 HTML旅游网站设计与实现

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

Qt+Win10使用QAxWidget控件实现远程桌面控制

Windows开始菜单-运行-输入mstsc&#xff0c;可以打开自带的远程桌面连接工具。如果想使用Qt来实现这个工具&#xff0c;怎么弄&#xff1f; 一、Win10环境的配置 1、Win10-我的电脑-属性-远程桌面-开启 2、打开控制面板-管理工具&#xff08;Win11是【Windows工具】&#xff…

Ubuntu16.4安装搜狗拼音输入法

Ubuntu16.04安装搜狗输入法&#xff0c;总结可以分为5步&#xff1a; 1.下载搜狗输入法的安装包 2.安装fcitx输入法框架 3.安装搜狗输入法 4.重启Ubuntu 5.配置搜狗输入法 1.下载搜狗输入法的安装包 百度搜索“搜狗输入法 linux” https://pinyin.sogou.com/linux. ​​​​…

CY3/CY5/CY7标记牛血清白蛋白/人血清白蛋白,CY3/CY5/CY7-BSA/HSA

产品名称&#xff1a;CY3/CY5/CY7标记牛血清白蛋白/人血清白蛋白 英文名称&#xff1a;CY3/CY5/CY7-BSA/HSA 血清白蛋白一般指人血白蛋白&#xff0c;是由580个氨基酸残基单链组成的蛋白质&#xff0c;由肝脏分泌&#xff0c;在血浆中含量最高&#xff0c;约占52%-68%左右。血…