最近使用STM32F103+W25Q64+USB+FATFS做了一个U盘设备。程序已经调试完成了,现在重新梳理一下知识再做一个记录。
STM32F103+USB是根据官方demo修改的。USB大容量存储器的移植在下一篇文章介绍。本篇文章主要介绍W25Q64和驱动函数。
W25Q64容量是64Mbit的flash,64Mbit = 8MByte。
其内部组织关系如下:
W25Q64内部分为128个块(Block),每个块的大小是64K字节。 计算方式: (81024)K/128 = 64K字节。
每个块分为16个扇区(Sector),每个扇区的大小是4K字节。计算方式:64K/16 = 4K字节。
每个扇区分为16页(Page),每个页的大小是256个字节。计算方式:(4
1024)Byte/16 = 256Byte。
W25Q64和STM32的接线方式如下


PB12是SPI的片选接W25Q64的片选,程序中的片选是使用通用IO控制的。
PB13是SPI的时钟输出接W25Q64的时钟。
PB14是SPI的主机输入从机输出,接W25Q64的DO。
PB15是SPI的主机输出从机输入,接W25Q64的DI。
STM32的SPI初始化。

/*******SPI初始化函数******/
void spi_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;  //CLK管脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;    //CS管脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                   //SPI主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;         //8bit数据位SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                       //CPOL为高  SCK空闲为高SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                  //偶数边沿采集        SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          //CS软件控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//fpclk 2分频SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;       //高位先行SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI2, &SPI_InitStructure);SPI_Cmd(SPI2, ENABLE);//使能SPI2
}

上面关于SPI的配置就不细说了,本文的重点是介绍W25Q64。
通过SPI发送一个数据读取一个数据

/******通过SPI总线发送一个字节的数据******/
static uint8_t spi_flash_send_byte(uint8_t byte)
{while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);SPI_I2S_SendData(SPI2, byte);while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);return SPI_I2S_ReceiveData(SPI2);
}

此函数是后续一切函数的基础。我们先来看一下W25Q64的命令相关的函数。

#define W25X_WriteEnable           0x06
#define W25X_WriteDisable             0x04
#define W25X_ReadStatusReg          0x05
#define W25X_WriteStatusReg         0x01
#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 WIP_Flag                  0x01  /* 忙标志 */
#define Dummy_Byte                0xFF
/********SPI_FLASH进入掉电模式********/
void spi_flash_powerdown(void)
{ SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_PowerDown);//发送掉电命令SPI_FLASH_CS_HIGH();
}
/********SPI_FLASH从掉电模式唤醒********/
void spi_flash_wakeup(void)
{SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_ReleasePowerDown);//发送唤醒命令SPI_FLASH_CS_HIGH();
}
/********读取FLASH器件ID******/
uint32_t spi_flash_read_deviceid(void)
{uint32_t Temp = 0;SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_DeviceID);spi_flash_send_byte(Dummy_Byte);spi_flash_send_byte(Dummy_Byte);spi_flash_send_byte(Dummy_Byte);Temp = spi_flash_send_byte(Dummy_Byte);SPI_FLASH_CS_HIGH();return Temp;
}
/*******读取jedecid*******/
uint32_t spi_flash_read_jedecid(void)
{uint32_t temp = 0, temp0 = 0, temp1 = 0, temp2 = 0;SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_JedecDeviceID);temp0 = spi_flash_send_byte(Dummy_Byte);temp1 = spi_flash_send_byte(Dummy_Byte);temp2 = spi_flash_send_byte(Dummy_Byte);SPI_FLASH_CS_HIGH();temp = (temp0 << 16) | (temp1 << 8) | temp2;return temp;
}
/********写使能函数*********/
static void spi_flash_enable_write(void)
{SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_WriteEnable);//写使能命令SPI_FLASH_CS_HIGH();
}
/********flash判忙函数*******/
static void spi_flash_waitforwriteend(void)
{uint8_t flash_status = 0;SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_ReadStatusReg);do{flash_status = spi_flash_send_byte(Dummy_Byte);    }while ((flash_status & WIP_Flag) == SET); /* flash忙 */SPI_FLASH_CS_HIGH();
}

以上是一些最常用的指令函数,如果需要其它命令的函数,照葫芦画瓢就行。
然后我们介绍最重要的三个函数spi_flash_bufferreadspi_flash_bufferwritespi_flash_write_nocheck
函数spi_flash_bufferread功能:ReadAddr开始的地址读取NumByteToRead个数据到pBuffer指向的起始地址处。

/*********flash读取*******/
void spi_flash_bufferread(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{SPI_FLASH_CS_LOW();spi_flash_send_byte(W25X_ReadData);spi_flash_send_byte((ReadAddr & 0xFF0000) >> 16);spi_flash_send_byte((ReadAddr& 0xFF00) >> 8);spi_flash_send_byte(ReadAddr & 0xFF);while (NumByteToRead--) {*pBuffer = spi_flash_send_byte(Dummy_Byte);//读出pBuffer++;}SPI_FLASH_CS_HIGH();
}

我们看一下读取指令的特点

意思就是说发送完读取指令以后,只要不断的读可以把整个芯片的数据都读取出来,不受页,扇区,块等分区的限制。因此spi_flash_bufferread函数就很简洁。

函数spi_flash_write_nocheck的功能:把从pBuffer地址开始的数据写入到WriteAddr开始的地址处,写入NumByteToWrite,使用该函数一定要保证待写入的flash空间是擦除过的。

/****flash写入函数(要保证flash是被擦除过的)****/
void spi_flash_write_nocheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{                    uint16_t pageremain = 0;    pageremain = 256 - WriteAddr % 256; //单页剩余的字节数               if(NumByteToWrite <= pageremain){pageremain = NumByteToWrite;//写入的字节数小于一页  pageremain就等于要写入的字节数}while(1){     spi_flash_pagewrite(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个字节了}}}
}

写函数咋这么复杂呢?为啥不能用与读取数据相同的套路呢?

我记得EEPROM也有这个特点,是叫做“页卷”。写完一页必须重新发送地址,否则地址自动变为页的开始地址,后边写入的数据会覆盖前边写入的数据,EEPROM会覆盖,SPI FLASH不会,为什么呢?因为FLASH写入之前需要擦除。
由于上边的缘故我们的写函数逻辑上就稍微复杂了。一定要记住flash写入之前必须擦除(以保证flash待写区域是0XFF),写flash内部的数据只能由1变为0不能由0变为1,想由0变为1必须擦除,擦除以后flash内部的数据就是0xFF了。所以调用spi_flash_write_nocheck函数之前要保证flash的待写区域是0xFF。
flash一次最多写入256个字节。顺带说一下擦除一次最少擦除4K字节(最小的擦除单位是一个Sector)。W25Q64可没有“页擦除”

下面再介绍一个写入函数,该写入函数的好处是调用函数的时候不用考虑flash是否需要擦除,函数内部处理了这个擦操作。直接指定flash空间地址,缓存地址,写入个数就可以了,一句话总结就是省心。
函数spi_flash_bufferwrite的功能:把从pBuffer地址开始的数据写入到WriteAddr开始的地址处,写入NumByteToWrite

/*********flash写入*******/
void spi_flash_bufferwrite(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{ u32 secpos;u16 secoff;u16 secremain;     u16 i;    u8 * W25QXX_BUF = W25QXX_BUFFER;secpos=WriteAddr>>12;//扇区地址  secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小   if(NumByteToWrite<=secremain){secremain=NumByteToWrite;//不大于4096个字节}while(1) {   spi_flash_bufferread(W25QXX_BUF,secpos<<12,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF){break;//需要擦除 }    }if(i<secremain)//需要擦除{spi_flash_sectorerase(secpos);      //擦除这个扇区for(i=0;i<secremain;i++)          //复制{W25QXX_BUF[i+secoff]=pBuffer[i];   }spi_flash_write_nocheck(W25QXX_BUF,secpos<<12,4096);//写入整个扇区  }else {spi_flash_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;     //下一个扇区可以写完了}}   }
}

貌似这个函数用起来很省心,但是我们在享受省心的同时却带来了性能下降的问题(这就像世界上没有一份工作是钱多事少离家近的)。
假设如下情景:
当我们每次只写入一个字节数据,写4K字节空间的话,函数内部需要4096次读,擦,写。需要先把数据从flash搬运到内存,擦除flash,然后再写入。使用spi_flash_write_nocheck函数只要写就可以了,函数内部不会读取和擦除。擦除巨耗时,所以性能急剧下降。
spi_flash_bufferwrite函数用在性能要求不高的场合还是可以的。如果用在SPI FLASH模拟U盘这种场景下,性能不忍直视,速度感人。
到此STM32驱动SPI FLASH就介绍完了,下一篇我们介绍 SPI FLASH如何模拟U盘以及如何提升性能。

剖析STM32F103读写W25Q64相关推荐

  1. STM-32:SPI通信协议/W25Q64简介—软件SPI读写W25Q64

    目录 一.SPI简介 1.1电路模式 1.2通信原理 1.3SPI时序基本单元 1.3.1起始和终止 1.3.2交换字节 二.W25Q64 2.1W25Q64简介 2.2W25Q64硬件电路 2.3W ...

  2. 剖析SPDK读写NVMe盘过程--从hello_world开始

    1 概述 本文基于SPDK v22.09(点击蓝字,阅读往期文章),从hello_world程序来剖析SDPK NVMe用户态驱动,对NVMe盘的初始化.管理和读写操作. spdk/examples/ ...

  3. 问题贴 STM32 SPI 读写 W25Q64 接上逻辑分析仪后读取失败

    问题描述: 使用STM32野火指南者(F103VET6),对板载外设 W25Q64 读写时候,数据正常如下: 但是使用逻辑分析仪连接 GND CS MOSI MISO四个引脚后,STM32发送数据还正 ...

  4. STM32F103 读写内部FLASH—学习笔记(野火)

    STM32F103的Flash类型是NOR Flash 烧录过程:调试器→STM32的SRAM→运行→Flash 内部Flash比外部Flash更高效,原因是使用了内部总线,且工作频率是72MHz 2 ...

  5. SPI通讯介绍 以及读写W25Q64(块,扇区,页的区别)

    附工程百度网盘链接 链接:https://pan.baidu.com/s/1nCgNb5OyGpABAL657-gX0A?pwd=6666 提取码:6666 介绍:摩托罗拉开发的一种通用数据总线, 四 ...

  6. STM32系列(HAL库)——F103C8T6通过SPI方式读写W25Q64—(Flash存储模块)

    1.软件准备 (1)编程平台:Keil5 (2)CubeMX (3)XCOM(串口调试助手) 2.硬件准备 (1)W25Q64模块 (2)F1的板子,本例使用经典F103C8T6 (3)ST-link ...

  7. 学习STM32 Flash存储 W25Q64 SPI总线存储模块进行读写数据

    今天学习 Flash 存储芯片进行数据写入和读取方法.了解W25Q64 存储芯片的使用.能够用 STM32 单片机对 W25Q64 进行写入数据,擦除数据,读取数据. w25q64 是什么? W25Q ...

  8. Hadoop HDFS (3) JAVA訪问HDFS之二 文件分布式读写策略

    先把上节未完毕的部分补全,再剖析一下HDFS读写文件的内部原理 列举文件 FileSystem(org.apache.hadoop.fs.FileSystem)的listStatus()方法能够列出一 ...

  9. STM32H750VB读写FM24CL16铁电存储器

    STM32H750VB读写FM24CL16铁电存储器,原来一直用stm 32f103硬件I2C总线读写FM24CL16铁电存储器,效果还不错,读写也非常稳定,将原来的程序移植到Stm32h750 VB ...

  10. STM32单片机初学8-SPI flash(W25Q128)数据读写

    当使用单片机进行项目开发,涉及大量数据需要储存时(例如使用了屏幕作为显示设备,常常需要存储图片.动画等数据),单靠单片机内部的Flash往往是不够用的. 如STM32F103系列,内部Flash最多只 ...

最新文章

  1. android studio无线真机调试------Android
  2. 流媒体实质上是计算机在哪方面的应用,流媒体技术主要用于什么
  3. cisco 单词 词典
  4. CodeForces - 1295C Obtain The String(dp预处理+贪心)
  5. Kylin3.1.3连接Hbase报错找不到 hbase-common lib not found的解决办法
  6. 如何在linux上压缩文件夹,如何在Linux中使用命令压缩文件和文件夹
  7. 超级无敌的TcpDump
  8. 请问mysql优化相关
  9. json格式化工具有哪些?在项目中使用什么工具来格式化JSON数据?
  10. 程序员必备网页前端设计网站
  11. 软件架构设计说明书该怎么写?
  12. html图片从左到右慢慢出来,css 实现一个div的背景颜色从左到右慢慢出现 里面文字颜色也改...
  13. 5G 技术特点与应用
  14. 斗鱼实时计算平台的演进
  15. RobotStudio仿真—Smart组件创建动态夹具
  16. 设计一个长方体类Cuboid
  17. Jetson Nano控制SIM7020 开关机
  18. android(安卓系统)系统下优秀的笔记软件,小筑笔记app下载 小筑笔记(手机笔记本软件) for Android v1.31 安卓手机版 下载-脚本之家...
  19. PHP调用IMAP协议读取邮件类库
  20. 社会关系网络(SNA)如何应用于团伙欺诈识别

热门文章

  1. matlab单服务排队模型,MATLAB模拟银行单服务台排队模型
  2. 底盘域控制器(CDC)
  3. 记录几个视频处理软件
  4. U8来料报检保存出错
  5. FastStone Capture无法录制系统声音解决方法(win10)
  6. 管理者必须要精通的六项管理技能
  7. UE4 实时渲染原理优化策略笔记
  8. android矢量地图画法_Android 我们的矢量地图,放大不失真
  9. C语言在线词典项目—Linux
  10. 新流星搜剑录服务器维护,一梦江湖1月15日更新内容介绍