前言
本次我们学习一下STM32F103关于SPI对存储芯片的读写,针对flash芯片不能跨页写的限制,写一个扇区写入的函数,不需要自己手动跨页,使用更方便,为字库,图片烧入做铺垫。
本篇博客大部分是自己收集和整理,如有侵权请联系我删除。
本次实验板子使用的是正点原子精英版,芯片是STM32F103ZET6,需要资料可以@我拿取。
交流群:717237739
如果觉得有用点赞关注收藏三连,多谢支持
本博客内容原创,创作不易,转载请注明
没有初步认识SPI协议的,可以先看看我之前的博客:SPI协议介绍
没有初步认识W25QXX芯片的,可以先看看我之前的博客:w25QXX芯片读写函数
FLASH 驱动手册自取
一. FLASH 跨页读写,分区和注意事项
1. 内部区域写入划分等级
W25Q16,W25Q32 ,W25Q64 系列的FLASH存储器分别有8192,16384,32768可编程页,每页256个字节。芯片内部分为了块,扇区,页的编程指令和擦除指令,下面来看看他们之间的关系。
内部区分是从 块 -> 扇区 -> 页->页地址字节写入
2.FLASH 注意事项
写入操作解读:
1.写使能的操作:相当于FLASH芯片做的保护措施,防止误操作写入其他数据,意义和手机的解锁差不多,其实芯片内部的FLASH也是需要解锁关锁操作的,芯片类型都一样。
2.每个数据位只能由1改为0,是因为FLASH芯片自身的限制决定,它没有完全任意修改的能力,所以芯片内部无数据的时候默认为0XFF,表示为空。
3.针对第二个限制,所以我们在每次写入flash的时候,都是需要保证芯片内部是0XFF,就需要先擦除在写入,这样数据就不会出错了。
4.擦除不能针对指定地址擦除,在W25QXX芯片介绍中就有说明,最小只能扇区擦除,所以每次都是需要擦除最小4K的扇区。
5.每次写入最多写入255个字节,芯片规定不能跨页写,写入的数据超过之后就会重新从该页的首地址写入,数据就会出错,针对这一现象等会我们分解数据进行写入。
6.写入结束之后,芯片会进入busy状态,在这个状态下不能进行读写操作。每次写完之后我们都可以判断一下这个标志位,防止出错。
7.busy状态位下,不能进行读写,此处该注意。
SPI读写函数:(可以查看之前的文章)
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
二. FLASH 芯片ID 读取
读取芯片ID的意义是什么?
1.首先识别芯片的型号:不同型号的 Flash 存储器可能有不同的特性,包括容量、擦写速度、寿命等。这有助于系统在运行时适应不同型号的 Flash 存储器。
2.适应对应配置:针对不同的 Flash 存储器,可能需要特定的配置参数,如擦写和写入时的超时时间、擦写和写入的命令序列等。通过读取 Flash ID,系统可以在运行时动态配置这些参数,以确保与连接的 Flash 存储器的兼容性。
3.针对固件更新检测:在一些应用中,可能需要进行固件更新。读取 Flash ID 可以帮助系统确认连接的 Flash 存储器是否与新固件兼容。这有助于防止在错误的 Flash 存储器上执行固件更新,从而避免不可逆的系统问题。
4.错误检测和恢复:Flash 存储器可能会受到环境因素或硬件问题的影响,导致数据损坏或存储器出现错误。读取 Flash ID 可以用于检测存储器是否正常工作,并在可能的情况下采取适当的措施,如进行错误检测和修复。
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
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;
}
三 . FLASH 扇区读写实现
注意:因为一些写使能,写禁止的一些函数在其他文章已经封装了,这些函数不懂的可以看看主页的W25QXX芯片介绍,这些就不多介绍了,下面主要实现扇区跨页写的实现流程:
1.在指定页写入少于256个字节的数据函数,此处不做擦除,是因为在扇区函数做了擦除,这里如果想单独写入,也可以在写入之前加一个读取地址下的数据判断是否需要擦除。
//无检验写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;//写入结束了 直接退出while循环,写入完成
}
else //NumByteToWrite>pageremain
{
pBuffer+=pageremain; //传入的数据,往写入完成的数据作偏移
WriteAddr+=pageremain; //传入的地址,往写入完成的做地址偏移
NumByteToWrite-=pageremain; //减去已经写入了的字节数
if(NumByteToWrite>256)
{
pageremain=256; //一次可以写入256个字节
}
else
{
pageremain=NumByteToWrite; //不够256个字节了,就直接等于当前字节数
}
}
}
}
2.在扇区内连续跨页写入数据,先擦除后写入。
//写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) //不大于4096个字节 写不完一个扇区内的空间大小,即不跨扇区
{
secremain=NumByteToWrite; //假设一个扇区原来已经写了3K的数据,后来再写入0.5K,结果还有0.5K空间没写,
//这样就不用全部擦除剩余的1K空间,只要擦除要写入的0.5K的空间就可以了
}
while(1)
{
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容,保存到W25QXX_BUF
for(i=0;i<secremain;i++)//校验数据,对数组的数据进行遍历
{
if(W25QXX_BUF[secoff+i]!=0XFF)break; //需要擦除,从secoff的位置开始 读出来的内容如果不是0xff,需要擦除,手册上有说明P38
}
if(i<secremain)//需要擦除 假设写入10个数,而写到第8个的时候,上面的校验出错,那么就要擦除了
{
W25QXX_Erase_Sector(secpos); //擦除这个扇区,这时候这个扇区的内容已经被存放在W25QXX_BUF,故可以擦除
for(i=0;i<secremain;i++) //复制
{
W25QXX_BUF[i+secoff]=pBuffer[i]; //之前存放的数据在上面也已经存放到 //W25QXX_BUF中了,所以从 secoff 处开始存放数据(要写入的数据),
//这样就把之前存放的数据和后来写入的数据都存入W25QXX_BUF中了。
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
}else
W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入整个扇区 secpos*4096就是地址
if(NumByteToWrite==secremain)
{
break;//写入结束了
}
else//写入未结束 写满了一个扇区,开始往第二个扇区写数据
{
secpos++;//扇区地址增1 已经是下一个扇区了,所以++
secoff=0;//偏移位置为0 已经是一个新的扇区了,所以为0
pBuffer+=secremain; //源数据指针偏移 已经写了secremain个数据,所以需要偏移
WriteAddr+=secremain; //写地址偏移
NumByteToWrite-=secremain; //字节数递减
//已经写了secremain个数据 故减去secremain个数据
if(NumByteToWrite>4096) //下一个扇区还是写不完
{
secremain=4096; //下一下扇区的内容就是4096
}
else
{
secremain=NumByteToWrite; //下一个扇区可以写完了
}
}
}
}
总结:
以上就是关于SPI对FLASH芯片跨页写入的方法了,难度不是很大,其实就是一种代码逻辑的实现,主要就是注意对各种函数的调用和做一些芯片限制的判断,这样我们就能用一条函数实现我们需要的功能了。下一篇博客主要介绍字库和图片的写入存储,大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。
交流群:717237739
如果觉得有用点赞关注收藏三连,多谢支持
本博客内容原创,创作不易,转载请注明