剖析STM32F103读写W25Q64
最近使用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个字节。计算方式:(41024)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_bufferread
、spi_flash_bufferwrite
和spi_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相关推荐
- 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 ...
- 剖析SPDK读写NVMe盘过程--从hello_world开始
1 概述 本文基于SPDK v22.09(点击蓝字,阅读往期文章),从hello_world程序来剖析SDPK NVMe用户态驱动,对NVMe盘的初始化.管理和读写操作. spdk/examples/ ...
- 问题贴 STM32 SPI 读写 W25Q64 接上逻辑分析仪后读取失败
问题描述: 使用STM32野火指南者(F103VET6),对板载外设 W25Q64 读写时候,数据正常如下: 但是使用逻辑分析仪连接 GND CS MOSI MISO四个引脚后,STM32发送数据还正 ...
- STM32F103 读写内部FLASH—学习笔记(野火)
STM32F103的Flash类型是NOR Flash 烧录过程:调试器→STM32的SRAM→运行→Flash 内部Flash比外部Flash更高效,原因是使用了内部总线,且工作频率是72MHz 2 ...
- SPI通讯介绍 以及读写W25Q64(块,扇区,页的区别)
附工程百度网盘链接 链接:https://pan.baidu.com/s/1nCgNb5OyGpABAL657-gX0A?pwd=6666 提取码:6666 介绍:摩托罗拉开发的一种通用数据总线, 四 ...
- STM32系列(HAL库)——F103C8T6通过SPI方式读写W25Q64—(Flash存储模块)
1.软件准备 (1)编程平台:Keil5 (2)CubeMX (3)XCOM(串口调试助手) 2.硬件准备 (1)W25Q64模块 (2)F1的板子,本例使用经典F103C8T6 (3)ST-link ...
- 学习STM32 Flash存储 W25Q64 SPI总线存储模块进行读写数据
今天学习 Flash 存储芯片进行数据写入和读取方法.了解W25Q64 存储芯片的使用.能够用 STM32 单片机对 W25Q64 进行写入数据,擦除数据,读取数据. w25q64 是什么? W25Q ...
- Hadoop HDFS (3) JAVA訪问HDFS之二 文件分布式读写策略
先把上节未完毕的部分补全,再剖析一下HDFS读写文件的内部原理 列举文件 FileSystem(org.apache.hadoop.fs.FileSystem)的listStatus()方法能够列出一 ...
- STM32H750VB读写FM24CL16铁电存储器
STM32H750VB读写FM24CL16铁电存储器,原来一直用stm 32f103硬件I2C总线读写FM24CL16铁电存储器,效果还不错,读写也非常稳定,将原来的程序移植到Stm32h750 VB ...
- STM32单片机初学8-SPI flash(W25Q128)数据读写
当使用单片机进行项目开发,涉及大量数据需要储存时(例如使用了屏幕作为显示设备,常常需要存储图片.动画等数据),单靠单片机内部的Flash往往是不够用的. 如STM32F103系列,内部Flash最多只 ...
最新文章
- android studio无线真机调试------Android
- 流媒体实质上是计算机在哪方面的应用,流媒体技术主要用于什么
- cisco 单词 词典
- CodeForces - 1295C Obtain The String(dp预处理+贪心)
- Kylin3.1.3连接Hbase报错找不到 hbase-common lib not found的解决办法
- 如何在linux上压缩文件夹,如何在Linux中使用命令压缩文件和文件夹
- 超级无敌的TcpDump
- 请问mysql优化相关
- json格式化工具有哪些?在项目中使用什么工具来格式化JSON数据?
- 程序员必备网页前端设计网站
- 软件架构设计说明书该怎么写?
- html图片从左到右慢慢出来,css 实现一个div的背景颜色从左到右慢慢出现 里面文字颜色也改...
- 5G 技术特点与应用
- 斗鱼实时计算平台的演进
- RobotStudio仿真—Smart组件创建动态夹具
- 设计一个长方体类Cuboid
- Jetson Nano控制SIM7020 开关机
- android(安卓系统)系统下优秀的笔记软件,小筑笔记app下载 小筑笔记(手机笔记本软件) for Android v1.31 安卓手机版 下载-脚本之家...
- PHP调用IMAP协议读取邮件类库
- 社会关系网络(SNA)如何应用于团伙欺诈识别