stm32基于HAL库驱动外部SPI flash制作虚拟U盘

stm32基于HAL库驱动外部SPI flash制作虚拟U盘


  • 📌参考文章:https://xiaozhuanlan.com/topic/6058234791
  • 🎞实现效果演示:
    在这里插入图片描述
  • 🔖上图中的读到的FLASH_ID所指的是针对不同容量,所对应的ID。
//W25X/Q不同容量对应不同ID关系
W25Q80  ID  0XEF13
W25Q16  ID  0XEF14
W25Q32  ID  0XEF15
W25Q64  ID  0XEF16
W25Q128 ID  0XEF17
W25Q256 ID  0XEF18

在这里插入图片描述

  • 🔖在电脑端,支持对虚拟出来的存储器进行读写操作。
    在这里插入图片描述

在这里插入图片描述

  • ✨如果设计成一块PCB,可以制作成一个微小容量的移动U盘。
  • 🌿基于STM32F103,HAL库生成的代码,可以移植到任意支持USB接口的STM32单片机上使用。
  • 🌿程序烧录后,通过PA11/PA12 USB初次连接电脑,会弹出提示格式化窗口。之后就可以使用,包括拷贝和创建文件到盘符内,保存数据后,拔插设备,数据不丢失。

stm32cumx配置

  • 🌿stm32cumx使能对应的SPI接口
    在这里插入图片描述

  • ✨串口非必须,只是方便调试时查看读取是否支持。

  • 🌿使能SPI接口:(尽量将SPI速度配置低一点,防止访问和写入出错)
    在这里插入图片描述

  • 🌿配置SPI CS(片选)引脚:
    在这里插入图片描述

  • 🌿使能USB外设:
    在这里插入图片描述

  • 🌿时钟配置为48MHz:
    在这里插入图片描述

在这里插入图片描述

  • 🌿使能调试接口
    在这里插入图片描述
  • 🌿勾选中间件:
    在这里插入图片描述
  • 🌿调整堆栈区大小:
    在这里插入图片描述

🛠Keil工程修改

  • 🌿导入驱动文件:bsp_spi_flash.cbsp_spi_flash.h到工程对应文件夹下:
    在这里插入图片描述
  • 🌿修改usbd_storage_if.c文件内容:
/* USER CODE BEGIN INCLUDE */
#include "bsp_spi_flash.h"	//将驱动头文件包含进来
/* USER CODE END INCLUDE */
//注释掉自动生成的以下3个宏定义
//#define STORAGE_LUN_NBR                  1
//#define STORAGE_BLK_NBR                  0x10000
//#define STORAGE_BLK_SIZ                  0x200

/* USER CODE BEGIN PRIVATE_DEFINES */
//重新定义以下3个宏
#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  2048 //块数量:256*8扇区=8MByte
#define STORAGE_BLK_SIZ                  4096  //每个扇区4096Byte
/* USER CODE END PRIVATE_DEFINES */

  • ✨这里的STORAGE_BLK_NBR宏代表,外部SPI FLASH 容量大小,2048 代表为8MB,如果spi flash容量为16MB,那么这里就是4096.以此类推,4MB spi flash就是1024
  • 📑补充以下函数内容:(usbd_storage_if.c文件内)
int8_t STORAGE_Init_FS(uint8_t lun)
{
  /* USER CODE BEGIN 2 */
    W25QXX_Init();
    return (USBD_OK);
  /* USER CODE END 2 */
}

int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
    *block_num  = STORAGE_BLK_NBR;
    *block_size = STORAGE_BLK_SIZ;
    return (USBD_OK);
  /* USER CODE END 3 */
}

int8_t STORAGE_IsReady_FS(uint8_t lun)
{
  /* USER CODE BEGIN 4 */
	u16 flash_ID;
	flash_ID =W25QXX_ReadID();
	printf("flash_ID:%d \r\n",flash_ID);//非必须,如需要调试,包含stdio.h头文件,启用串口
    return (USBD_OK);
  /* USER CODE END 4 */
}

int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
    blk_addr += SPI_FLASH_START_SECTOR;

    SPI_FLASH_BufferRead(buf, blk_addr * SPI_FLASH_SECTOR_SIZE, blk_len * SPI_FLASH_SECTOR_SIZE);
    return (USBD_OK);
  /* USER CODE END 6 */
}
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
    uint32_t write_addr;
    blk_addr +=SPI_FLASH_START_SECTOR;
    write_addr = blk_addr * SPI_FLASH_SECTOR_SIZE;
    SPI_FLASH_SectorErase(write_addr);
    SPI_FLASH_BufferWrite((uint8_t*)buf, write_addr, blk_len * SPI_FLASH_SECTOR_SIZE);
    return (USBD_OK);
  /* USER CODE END 7 */
}

int8_t STORAGE_GetMaxLun_FS(void)
{
  /* USER CODE BEGIN 8 */
    return (STORAGE_LUN_NBR - 1);
  /* USER CODE END 8 */
}
  • main.c文件
/* USER CODE BEGIN Includes */
#include "bsp_spi_flash.h"
#include "stdio.h"//用于调试串口输出
/* USER CODE END Includes */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USB_DEVICE_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	W25QXX_Init();//初始化
	/* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


📝驱动文件

  • 📗bsp_spi_flash.h内容
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H

#include "stm32f1xx.h"
#include "stm32f1xx_hal_spi.h"

/* ???????? ----------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

typedef  uint32_t  u32;
typedef  uint8_t  u8;// 
typedef  uint16_t  u16;

#define SPI_FLASH_REBUILD           0    //1:???????????Flash??0??????????????Flash
#define SPI_FLASH_SECTOR_SIZE    4096    // ????Flash????????
#define SPI_FLASH_START_SECTOR   256*4    // ????Flash?????FatFS?????
#define SPI_FLASH_SECTOR_COUNT   256   // ????Flash?????FatFS???????????
//W25X/Q不同容量对应不同ID关系
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16
//W25Q128 ID  0XEF17
//W25Q256 ID  0XEF18
//#define W25Q80 	0XEF13
//#define W25Q16 	0XEF14
//#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
//#define W25Q128	0XEF17
//#define W25Q256 0XEF18
//#define  sFLASH_ID     0XEF4017     //W25Q64
#define  sFLASH_ID 0XEF16
extern SPI_HandleTypeDef hspi1;
extern u16 W25QXX_TYPE;					//????W25QXX??????

//#define	W25QXX_CS 		PBout(12)  		//W25QXX片选引脚
#define W25QXX_CS_1		  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET)
#define W25QXX_CS_0	      HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET)

#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

void W25QXX_Init(void);
u16  W25QXX_ReadID(void);  	    		//???FLASH ID
void W25QXX_WAKEUP(void);				//????
void SPI_FLASH_SectorErase(u32 SectorAddr);
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
#endif /* __SPI_FLASH_H */


  • 📓bsp_spi_flash.c内容
#include "bsp_spi_flash.h"
//需要重新添加代码
#include "stm32f1xx_hal_gpio.h"
#include "stdio.h"

u16 W25QXX_TYPE = W25Q64;	//设置SPI存储器型号

extern SPI_HandleTypeDef hspi1;


u8 W25QXX_ReadSR(u8 regno);             //??????????
void W25QXX_4ByteAddr_Enable(void);     //???4???????
void W25QXX_Write_SR(u8 regno,u8 sr);   //?????????
void W25QXX_Write_Enable(void);  		//?????
void W25QXX_Write_Disable(void);		//??????
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //???flash
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//????flash
void W25QXX_Erase_Chip(void);    	  	//???????
void W25QXX_Erase_Sector(u32 Dst_Addr);	//????????
void W25QXX_Wait_Busy(void);           	//???????
void W25QXX_PowerDown(void);        	//?????????


//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
    u8 Rxdata;
    HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);
    return Rxdata;          		    //返回收到的数据
}

void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
    assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
    __HAL_SPI_DISABLE(&hspi1);            //关闭SPI
    hspi1.Instance->CR1&=0XFFC7;          //位3-5清零,用来设置波特率
    hspi1.Instance->CR1|=SPI_BaudRatePrescaler;//设置SPI速度
    __HAL_SPI_ENABLE(&hspi1);             //使能SPI

}

//初始化SPI FLASH的IO口
void W25QXX_Init(void)
{
    u8 temp;
    GPIO_InitTypeDef GPIO_Initure;

//    __HAL_RCC_GPIOB_CLK_ENABLE();           //使能GPIOB时钟
     __HAL_RCC_GPIOA_CLK_ENABLE();           //使能GPIOB时钟

    //PA4
    GPIO_Initure.Pin=GPIO_PIN_4;          	//PA4
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速       
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);     //初始化

    W25QXX_CS_1;			                //SPI FLASH不选中
//    SPI2_Init();		   			        //初始化SPI
    SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_2); //设置为42M时钟,高速模式
    W25QXX_TYPE = W25QXX_ReadID();	        //读取FLASH ID.
		printf("flash_ID:%d \r\n",W25QXX_TYPE);
//    if(W25QXX_TYPE==W25Q64)                //SPI FLASH为W25Q32
//    {
        temp=W25QXX_ReadSR(3);              //读取状态寄存器3,判断地址模式
        if((temp&0X01)==0)			        //如果不是4字节地址模式,则进入4字节地址模式
        {
            W25QXX_CS_0; 			        //选中
            SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令   
            W25QXX_CS_1;       		        //取消片选   
        }
//    }
}
//读取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
//返回值:状态寄存器值
u8 W25QXX_ReadSR(u8 regno)
{
    u8 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;
    }
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(command);            //发送读取状态寄存器命令
    byte=SPI2_ReadWriteByte(0Xff);          //读取一个字节
    W25QXX_CS_1;                            //取消片选
    return byte;
}
//写W25QXX状态寄存器
void W25QXX_Write_SR(u8 regno,u8 sr)
{
    u8 command=0;
    switch(regno)
    {
        case 1:
            command=W25X_WriteStatusReg1;    //写状态寄存器1指令
            break;
        case 2:
            command=W25X_WriteStatusReg2;    //写状态寄存器2指令
            break;
        case 3:
            command=W25X_WriteStatusReg3;    //写状态寄存器3指令
            break;
        default:
            command=W25X_WriteStatusReg1;
            break;
    }
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(command);            //发送写取状态寄存器命令
    SPI2_ReadWriteByte(sr);                 //写入一个字节
    W25QXX_CS_1;                            //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_WriteEnable);   //发送写使能
    W25QXX_CS_1;                            //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令
    W25QXX_CS_1;                            //取消片选
}

//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//0XEF18,表示芯片型号为W25Q256
u16 W25QXX_ReadID(void)
{
    u16 Temp = 0;
    W25QXX_CS_0;
    SPI2_ReadWriteByte(0x90);//发送读取ID命令
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    Temp|=SPI2_ReadWriteByte(0xFF)<<8;
    Temp|=SPI2_ReadWriteByte(0xFF);
    W25QXX_CS_1;
    return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
    u16 i;
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_ReadData);      //发送读取命令
//    if(W25QXX_TYPE == W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
//    {
//        SPI2_ReadWriteByte((u8)((ReadAddr)>>24));
//    }
    SPI2_ReadWriteByte((u8)((ReadAddr)>>16));   //发送24bit地址
    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));
    SPI2_ReadWriteByte((u8)ReadAddr);
    for(i=0;i<NumByteToRead;i++)
    {
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);    //循环读数
    }
    W25QXX_CS_1;
}
/**
* @brief  读取FLASH数据
* @param 	pBuffer,存储读出数据的指针
* @param   ReadAddr,读取地址
* @param   NumByteToRead,读取数据长度
* @retval 无
*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
    /* 选择FLASH: CS低电平 */
    W25QXX_CS_0;

    /* 发送 读 指令 */
    SPI2_ReadWriteByte(W25X_ReadData);

    /* 发送 读 地址高位 */
    SPI2_ReadWriteByte((ReadAddr & 0xFF0000) >> 16);
    /* 发送 读 地址中位 */
    SPI2_ReadWriteByte((ReadAddr& 0xFF00) >> 8);
    /* 发送 读 地址低位 */
    SPI2_ReadWriteByte(ReadAddr & 0xFF);

    /* 读取数据 */
    while (NumByteToRead--) /* while there is data to be read */
    {
        /* 读取一个字节*/
        *pBuffer = SPI2_ReadWriteByte(0xFF);
        /* 指向下一个字节缓冲区 */
        pBuffer++;
    }

    /* 停止信号 FLASH: CS 高电平 */
    W25QXX_CS_1;
}
#define WIP_Flag                  0x01

/**
 * @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕
 * @param  none
 * @retval none
 */
void SPI_FLASH_WaitForWriteEnd(void)
{
    u8 FLASH_Status = 0;

    /* 选择 FLASH: CS 低 */
    W25QXX_CS_0;

    /* 发送 读状态寄存器 命令 */
    SPI2_ReadWriteByte(W25X_ReadStatusReg1);

    /* 若FLASH忙碌,则等待 */
    do
    {
        /* 读取FLASH芯片的状态寄存器 */
        FLASH_Status = SPI2_ReadWriteByte(0xFF);
    }
    while ((FLASH_Status & WIP_Flag) == SET);  /* 正在写入标志 */

    /* 停止信号  FLASH: CS 高 */
    W25QXX_CS_1;
}


/**
 * @brief  擦除FLASH扇区
 * @param  SectorAddr:要擦除的扇区地址
 * @retval 无
 */
void SPI_FLASH_SectorErase(u32 SectorAddr)
{

    /* 发送FLASH写使能命令 */
    W25QXX_Write_Enable();
    SPI_FLASH_WaitForWriteEnd();
    /* 擦除扇区 */
    /* 选择FLASH: CS低电平 */
    W25QXX_CS_0;
    /* 发送扇区擦除指令*/
    SPI2_ReadWriteByte(W25X_SectorErase);
    /*发送擦除扇区地址的高位*/
    SPI2_ReadWriteByte((u8)((SectorAddr)>>16));   //发送24bit地址
    SPI2_ReadWriteByte((u8)((SectorAddr)>>8));
    /* 发送擦除扇区地址的低位 */
    SPI2_ReadWriteByte(SectorAddr & 0xFF);
    /* 停止信号 FLASH: CS 高电平 */
    W25QXX_CS_1;
    /* 等待擦除完毕*/
    SPI_FLASH_WaitForWriteEnd();
}
/* WIP(busy)标志,FLASH内部正在写入 */


#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
/**
 * @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
 * @param	pBuffer,要写入数据的指针
 * @param WriteAddr,写入地址
 * @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
 * @retval 无
 */
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
    /* 发送FLASH写使能命令 */
    W25QXX_Write_Enable();

    /* 选择FLASH: CS低电平 */
    W25QXX_CS_0;
    /* 写页写指令*/
    SPI2_ReadWriteByte(W25X_PageProgram);
    /*发送写地址的高位*/
    SPI2_ReadWriteByte((WriteAddr & 0xFF0000) >> 16);
    /*发送写地址的中位*/
    SPI2_ReadWriteByte((WriteAddr & 0xFF00) >> 8);
    /*发送写地址的低位*/
    SPI2_ReadWriteByte(WriteAddr & 0xFF);

    if(NumByteToWrite > SPI_FLASH_PageSize)
    {
        NumByteToWrite = SPI_FLASH_PerWritePageSize;
        //FLASH_ERROR("SPI_FLASH_PageWrite too large!");
    }

    /* 写入数据*/
    while (NumByteToWrite--)
    {
        /* 发送当前要写入的字节数据 */
        SPI2_ReadWriteByte(*pBuffer);
        /* 指向下一字节数据 */
        pBuffer++;
    }

    /* 停止信号 FLASH: CS 高电平 */
    W25QXX_CS_1;

    /* 等待写入完毕*/
    SPI_FLASH_WaitForWriteEnd();
}

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
    u16 i;
    W25QXX_Write_Enable();                  //SET WEL
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_PageProgram);   //发送写页命令
//    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
//    {
//        SPI2_ReadWriteByte((u8)((WriteAddr)>>24));
//    }
    SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址
    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
    SPI2_ReadWriteByte((u8)WriteAddr);
    for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数
    W25QXX_CS_1;                            //取消片选
    SPI_FLASH_WaitForWriteEnd();					   //等待写入结束
}


/**
 * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
 * @param	pBuffer,要写入数据的指针
 * @param  WriteAddr,写入地址
 * @param  NumByteToWrite,写入数据长度
 * @retval 无
 */
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
    u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

    /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
    Addr = WriteAddr % SPI_FLASH_PageSize;

    /*差count个数据值,刚好可以对齐到页地址*/
    count = SPI_FLASH_PageSize - Addr;
    /*计算出要写多少整数页*/
    NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    /*mod运算求余,计算出剩余不满一页的字节数*/
    NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

    /* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
    if (Addr == 0)
    {
        /* NumByteToWrite < SPI_FLASH_PageSize */
        if (NumOfPage == 0)
        {
            SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
        }
        else /* NumByteToWrite > SPI_FLASH_PageSize */
        {
            /*先把整数页都写了*/
            while (NumOfPage--)
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            /*若有多余的不满一页的数据,把它写完*/
            SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
        }
    }
        /* 若地址与 SPI_FLASH_PageSize 不对齐  */
    else
    {
        /* NumByteToWrite < SPI_FLASH_PageSize */
        if (NumOfPage == 0)
        {
            /*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
            if (NumOfSingle > count)
            {
                temp = NumOfSingle - count;
                /*先写满当前页*/
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

                WriteAddr +=  count;
                pBuffer += count;
                /*再写剩余的数据*/
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
            }
            else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
            }
        }
        else /* NumByteToWrite > SPI_FLASH_PageSize */
        {
            /*地址不对齐多出的count分开处理,不加入这个运算*/
            NumByteToWrite -= count;
            NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
            NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

            /* 先写完count个数据,为的是让下一次要写的地址对齐 */
            SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

            /* 接下来就重复地址对齐的情况 */
            WriteAddr +=  count;
            pBuffer += count;
            /*把整数页都写了*/
            while (NumOfPage--)
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            /*若有多余的不满一页的数据,把它写完*/
            if (NumOfSingle != 0)
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}




//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
    u16 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)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
    u32 secpos;
    u16 secoff;
    u16 secremain;
    u16 i;
    u8 * 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;			//下一个扇区可以写完了
        }
    };
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
    W25QXX_Write_Enable();                  //SET WEL
    W25QXX_Wait_Busy();
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_ChipErase);        //发送片擦除命令
    W25QXX_CS_1;                            //取消片选
    W25QXX_Wait_Busy();   				   //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
    //监视falsh擦除情况,测试用
    //printf("fe:%x\r\n",Dst_Addr);
    Dst_Addr*=4096;
    W25QXX_Write_Enable();                  //SET WEL
    W25QXX_Wait_Busy();
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令
    if(W25QXX_TYPE==W25Q64)                //如果是W25Q256的话地址为4字节的,要发送最高8位
    {
        SPI2_ReadWriteByte((u8)((Dst_Addr)>>24));
    }
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  //发送24bit地址
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));
    SPI2_ReadWriteByte((u8)Dst_Addr);
    W25QXX_CS_1;                            //取消片选
    W25QXX_Wait_Busy();   				    //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
    while((W25QXX_ReadSR(1)&0x01)==0x01);   // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
    W25QXX_CS_0;                            //使能器件
    SPI2_ReadWriteByte(W25X_PowerDown);     //发送掉电命令
    W25QXX_CS_1;                            //取消片选
    //delay_us(3);                            //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
    W25QXX_CS_0;                                //使能器件
    SPI2_ReadWriteByte(W25X_ReleasePowerDown);  //  send W25X_PowerDown command 0xAB
    W25QXX_CS_1;                                //取消片选
    //delay_us(3);                                //等待TRES1
}


📚工程源码

链接:https://pan.baidu.com/s/164MW7mOR8fnaHZUC0dt7SQ 
提取码:0nei

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spring小记

Spring是轻量级的开源的javaEE框架目的&#xff1a;解决企业应用开发的复杂性 Spring有两个核心部分&#xff1a;IOC和AOP <1>IOC&#xff1a;控制反转&#xff0c;把创建的对象过程交给Spring进行管理 <2>AOP&#xff1a;面向切面&#xff0c;不修改源代码进行…

软件测试及数据分析处理实训室建设方案

一 、系统概述 软件测试及数据分析处理是软件开发过程中的一项重要测试活动&#xff0c;旨在验证不同软件模块或组件之间的集成与交互是否正常。综合测试确保各个模块按照设计要求正确地协同工作&#xff0c;以实现整个软件系统的功能和性能。以下是软件测试及数据分析处理的一…

无涯教程-Android - 系统架构

Android操作系统是一堆软件组件&#xff0c;大致分为五个部分和四个主要层&#xff0c;如体系结构图中所示。 Linux内核 底层是Linux-Linux 3.6&#xff0c;带有大约115个补丁&#xff0c;这在设备硬件之间提供了一定程度的抽象&#xff0c;并且包含所有必需的硬件驱动程序&am…

Linux重置ROOT密码(CentOS)

解释说明 在CentOS中重置root密码通常需要进入单用户模式&#xff0c;这是一个没有密码限制的特殊模式&#xff0c;允许您以root权限登录系统并更改密码。 重启系统 如果您无法登录到系统&#xff0c;可以通过重启系统来开始这个过程。您可以使用虚拟机控制台、物理服务器控制台…

Viobot基本功能使用及介绍

设备拿到手当然是要先试一下效果的&#xff0c;这部分可以参考本专栏的第一篇 Viobot开机指南。 接下来我们就从UI开始熟悉这个产品吧&#xff01; 1.状态 设备上电会自动运行它的程序&#xff0c;开启了一个服务器&#xff0c;上位机通过连接这个服务器连接到设备&#xff0c…

【java】LinkedList 和 ArrayList的简介与对比

Java LinkedList和 ArrayList 在使用上&#xff0c;几乎是一样的。由于LinkedList是基于双向链表的&#xff0c;会多出list.getFirst();获取头部元素等方法 链表&#xff08;Linked list&#xff09;是一种常见的基础数据结构&#xff0c;是一种线性表&#xff0c;但是并不会按…

Mysql--技术文档--MVCC(Multi-Version Concurrency Control | 多版本并发控制)

MVCC到底是什么 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;是一种并发控制机制&#xff0c;用于解决并发访问数据库时的数据一致性和隔离性问题。MVCC允许多个事务同时读取数据库的同一数据&#xff0c;而不会相互干扰或导致冲突。 在传统的并发控制机制中…

【深度解析】朗逸与宝来汽车:哪款更适合你?

在汽车市场中&#xff0c;朗逸和宝来都是非常受欢迎的车型。它们各自都有独特的优点和缺点&#xff0c;那么&#xff0c;究竟哪款车更适合你呢&#xff1f;让我们一起来深度解析一下。 朗逸&#xff0c;作为大众的入门级车型&#xff0c;以其稳定的性能和较高的性价比赢得了消费…

爬虫逆向实战(二十四)--某鸟记录中心

一、数据接口分析 主页地址&#xff1a;某鸟记录中心 1、抓包 通过抓包可以发现数据接口是front/record/search/page 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现&#xff0c;请求参数是加密的 请求头是否加密&#xff1f; 通过查…

Android Studio开发之路 (五)导入OpenCV以及报错解决

一、步骤 官网下载opencv包&#xff08;我下的是4.7.0&#xff09;并解压&#xff0c;openvc官网 先创建一个空项目&#xff0c;简单跑一下能正常输出helloworld 点击file->new->Import Module选择解压之后的opencv-android-sdk文件夹中的SDk文件夹&#xff0c; modu…

docker搭建owncloud,Harbor,构建镜像

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 拉取镜像 docker pull owncloud docker pull mysql:5.6 2、安装搭建私有仓库 Harbor 1.下载docker-compose 2.安装harbor 3.编辑 harbor.yml文件 使用./intall.sh安装 4.登录 3、编写Dockerfile制作Web应用系…

Java 程序打印 OpenCV 的版本

我们可以使用 Java 程序来使用 OpenCV。 OpenCV 的使用需要动态库的加载才可以。 加载动态库 到 OpenCV 的官方网站上下载最新的发布版本。 Windows 下载的是一个可执行文件&#xff0c;没关系&#xff0c;这个可执行文件是一个自解压程序。 当你运行以后会提示你进行解压。…

解决抖音semi-ui的Input无法获取到onChange事件

最近在使用semi-ui框架的Input实现一个上传文件功能时遇到了坑&#xff0c;就是无法获取到onChange事件&#xff0c;通过console查看只是拿到了一个文件名。但若是把<Input>换成原生的<input>&#xff0c;就可以正常获取到事件。仔细看了下官方文档&#xff0c;发现…

Windows 转 mac 记录

初次从Windows转mac可能会不适应&#xff0c;建议先看看 【6分钟搞定MacBook】不懂时无所适从&#xff0c;学会后越用越爽&#xff01;_哔哩哔哩_bilibili 我主要是做一些补充记录 1、Windows的右键等于mac的双击触控板、control单击触控板 2、运行中的应用下方会有一个点&…

时序预测 | MATLAB实现基于PSO-BiLSTM、BiLSTM时间序列预测对比

时序预测 | MATLAB实现基于PSO-BiLSTM、BiLSTM时间序列预测对比 目录 时序预测 | MATLAB实现基于PSO-BiLSTM、BiLSTM时间序列预测对比效果一览基本描述程序设计参考资料 效果一览 基本描述 MATLAB实现基于PSO-BiLSTM、BiLSTM时间序列预测对比。 1.Matlab实现PSO-BiLSTM和BiLSTM…

微信小程序分享后真机参数获取不到和部分参数不能获取问题问题解决

微信小程序的很多API&#xff0c;都是BUG&#xff0c;近期开发小程序就遇到了分享后开发工具可以获取参数&#xff0c;但是真机怎么都拿不到参数的问题 一、真机参数获取不到问题解决 解决方式&#xff1a; 在onLoad(options) 中。 onLoad方法中一定要有options 这个参数。…

Python爬虫框架之快速抓取互联网数据详解

概要 Python爬虫框架是一个能够帮助我们快速抓取互联网数据的工具。在互联网时代&#xff0c;信息爆炸式增长&#xff0c;人们越来越需要一种快速获取信息的方式。而Python爬虫框架就能够帮助我们完成这个任务&#xff0c;它可以帮助我们快速地从互联网上抓取各种数据&#xf…

Java并发编程第6讲——线程池(万字详解)

Java中的线程池是运用场景最多的并发框架&#xff0c;几乎所有需要异步或并发执行任务的程序都可以使用线程池&#xff0c;本篇文章就详细介绍一下。 一、什么是线程池 定义&#xff1a;线程池是一种用于管理和重用线程的技术&#xff08;池化技术&#xff09;&#xff0c;它主…

Python爬虫追踪新闻事件发展进程及舆论反映

目录 实现方案 1. 确定目标新闻源&#xff1a; 2. 确定关键词&#xff1a; 3. 使用网络爬虫获取新闻内容&#xff1a; 4. 提取和分析新闻文章&#xff1a; 5. 追踪新闻事件的发展进程&#xff1a; 6. 监测舆论反映&#xff1a; 7. 数据可视化&#xff1a; 完整代码示例…

【DEVOPS】Jenkins使用问题 - 控制台输出乱码

0. 目录 1. 问题描述2. 解决方案3. 最终效果4. 总结 1. 问题描述 部门内部对于Jenkins的使用采取的是Master Slave Work Node的方式&#xff0c;即作为Master节点的Jenkins只负责任务调度&#xff0c;具体的操作由对应的Slave Work Node去执行。 最近团队成员反馈一个问题&a…