STM32—SPI读写FLASH
FLASH简介
FLASH俗称闪存,和EEPROM一样,都是掉电数据不丢失的非易失行存储器,但FLASH的存储容量普遍大于EEPROM,现在像如U盘、SD卡、SSD固态硬盘以及STM32芯片内部存储程序的设备都是FLASH类型的存储器。由此可见FLASH对于我们学习和工作的重要性,EEPROM可以实现单字节的擦写,而FLASH都是一大片的擦写,就像是大规模杀伤性武器,其最小擦除单位:扇区的大小也是4KB。
我们此次通过SPI对FLASH存储芯片W25Q64进行读写擦除的操作。
对于FLASH内部结构的详细说明博主会专门整理一篇博客来说明,所以关于FLASH芯片的相关原理,本文中只做简单说明,侧重代码部分。
FLASH详细说明的博客链接:(没有链接就说明还没有整理出)
W25Q64
W25Q64简介
就长这么个样子

STM32内部原理图如下:

W25Q64是一种使用SPI通信协议的NOR FLASH存储器,它 的CS/CLK/DIO/DO 引 脚 分 别 连 接 到 了 STM32 对 应 的 SPI 引 脚NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚是一个普通的 GPIO,不是 SPI 的专用NSS 引脚,所以程序中我们要使用软件控制的方式。FLASH 芯片中还有 WP 和 HOLD 引脚。 WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。 HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。
W25Q64支持SPI通讯的模式0和模式3
FLASH控制指令
FLASH芯片中规定了许多指令,只要SPI向FLASH发送相应的指令,FLASH就会执行相应的操作,所以我们对FLASH的一切操作都是基于这个指令集的,接下来介绍一下FLASH的控制指令:

表中第一列为指令名,第二列为相应的指令代码,第三列及后面的内容根据指令的不同而意义不同,其中带括号的字节参数,方向为 FLASH 向主机传输,即命令响应,不带括号的则为主机向 FLASH 传输。表中“A0~A23” 指 FLASH 芯片内部存储器组织的地址; “M0~M7” 为厂商号( MANUFACTURER ID); “ID0-ID15”为 FLASH 芯片的ID;“dummy”指该处可为任意数据;“D0~D7” 为 FLASH 内部存储矩阵的内容。
看起来很复杂的样子,其实只要在需要执行相应操作时来查这个表,只要能够理解这些指令的使用方法,FLASH就算学会了。
例如:要知道FLASH的ID,那就在指令中找对应的取ID指令“JEDEC ID”,仔细解读这个指令
可以看出对应的指令代码为“9F”,后面的三个字节带括号,代表这三个字节就是FLASH向STM32发送的数据,即这三个字节就是FLASH的ID,然后使用SPI进行读取就可以了。
我们一般是将这些指令宏定义在头文件中,便于使用:
#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
FLASH内部存储结构
FLASH的存储矩阵如图:其内存分为128块,每一块都有16个扇区,每个扇区大小为4KB,擦除数据的时候是以扇区为基本单位的。
代码讲解
代码都是博主亲手写出来的,可以运行。
代码部分会用到SPI的代码,关于SPI的说明之前整理过:SPI详解
读取芯片ID
/************************读取芯片ID*************************************/
uint32_t FLASH_ReadID(void)
{
uint32_t temp,temp1,temp2,temp3;
SPI_NSS_Begin();
/* 发送取ID指令 */
SPI_SendData( W25X_JedecDeviceID);
/* FLASH会连续发送三个字节数据 */
temp1=SPI_SendData(Dummy_Byte);
temp2=SPI_SendData(Dummy_Byte);
temp3=SPI_SendData(Dummy_Byte);
SPI_NSS_Stop();
/* 高为先行,将三个字节整理在一起 */
temp=temp1<<16 | temp2<<8 | temp3;
return temp;
}
由于SPI是全双工通信,所以接收数据和发送数据用的是同一个函数
发送写使能信号
/****************发送写使能信号****************************/
void FLASH_WriteEnable(void)
{
SPI_NSS_Begin();
/* 发送相应的指令代码 */
SPI_SendData(W25X_WriteEnable);
SPI_NSS_Stop();
}
在向FLASH中执行写操作之前,都要进行写使能操作,通过发送写使能指令到FLASH来实现
等待FLASH不忙
/******************等待,直到不忙***********************************/
void FLASH_WaitBusy(void)
{
uint8_t StatusReg=0x01;
SPI_NSS_Begin();
/* 读取状态寄存器中的数据,判断忙标志0x01位 置位代表忙 */
SPI_SendData(W25X_ReadStatusReg);
/* 只读取状态寄存器的BUSY位,即第一位 */
while((StatusReg & 0x01) == 1)
StatusReg=SPI_SendData(Dummy_Byte);
SPI_NSS_Stop();
}
FLASH在通讯的过程中需要一定的时间来执行操作,在这期间,传输数据是无效的,因为FLASH忙着呢,所以我们就要有一个函数来专门等!等到FLASH不忙了,再进行通讯,那怎么等呢?FLASH不忙了会给出一个信号——将状态寄存器的BUSY位重置(也就是0),所以我们需要不断的来检测状态寄存器中的BUSY位是否置位,利用读取寄存器状态的指令来获取状态寄存器当下的状态,然后根据寄存器的BUSY位(第1位)来判断FLASH是否处于忙碌状态。
简单来说,这就是个延时函数,延时直到FLASH空闲,可以进行下一步传输。
擦除扇区
/******擦除扇区的内容,切记地址要对其到4kB,每个扇区的大小都是4KB********/
void FLASH_SectorErase(uint32_t addr)
{
/* 开始的时候要发送写使能信号*/
FLASH_WriteEnable();
SPI_NSS_Begin();
/* 发送扇区擦除命令 */
SPI_SendData(W25X_SectorErase);
/* 发送扇区的地址,高位先行 */
SPI_SendData((addr & 0xff0000) >> 16);
SPI_SendData((addr & 0xff00) >> 8);
SPI_SendData(addr & 0xff);
SPI_NSS_Stop();
/* 最后也要等待FLASH处理完这次的信号再退出 */
FLASH_WaitBusy();
}
扇区的擦除之前要发送一个写使能信号,先发送擦除指令,然后发送要擦除扇区的地址(分三个字节发出去),高位先行。
扇区上的内容不是1就是0,擦除的过程就是写1的过程(将一个扇区全部写1),因为在写入数据的时候,可以将1写为0,但不能将0写为1.
写入数据
/************按页写入数据,但写入之前要进行擦除***********/
void FLASH_PageWrite(uint32_t addr , uint8_t* pBuffer ,uint8_t size)
{
/* 开始的时候要发送写使能信号 */
FLASH_WriteEnable();
SPI_NSS_Begin();
/* 发送页写入命令 */
SPI_SendData(W25X_PageProgram);
/* 发送写入的地址,高位先行 */
SPI_SendData((addr & 0xff0000) >> 16);
SPI_SendData((addr & 0xff00) >> 8);
SPI_SendData(addr & 0xff);
/* 逐位发送数据 */
while(size--)
{
SPI_SendData(*pBuffer);
pBuffer++;
}
SPI_NSS_Stop();
/* 最后也要等待FLASH处理完这次的信号再退出 */
FLASH_WaitBusy();
}
在执行写入数据的时候函数的参数有三部分:
1.要写入的地址
2.要写入数据的首地址
3.要写入数据的大小
函数在执行的过程中,首先发送一个写使能信号,然后发送写数据指令,紧接着发送数据要写入的地址,然后就是逐位发送数据了,函数最后等FLASH处理完这次操作再退出。
读取数据
/**********************读取指定地址、指定长度的数据******************/
/* 因为读取在了指针中,所以不需要返回值 */
void FLASH_BufferRead(uint32_t addr , uint8_t* pBuffer ,uint16_t size)
{
SPI_NSS_Begin();
/* 发送读取命令 */
SPI_SendData(W25X_ReadData);
/* 发送读取数据的地址,高位先行 */
SPI_SendData((addr & 0xff0000) >> 16);
SPI_SendData((addr & 0xff00) >> 8);
SPI_SendData(addr & 0xff);
/* 逐位读取数据到指针上 */
while(size--)
{
*pBuffer=SPI_SendData(Dummy_Byte);
pBuffer++;
}
SPI_NSS_Stop();
}
在执行读出数据的时候函数的参数也有三部分:
1.要读出的地址
2.读出到指定地址
3.读出数据的大小
函数执行过程,首先发送读取指令(这时就不用发送写使能了),然后读取数据的地址,然后将数据逐位读取在固定地址中(地址最好是全局变量),使用时再从全局变量地址中获取数据。
这里涉及到函数的返回值问题,具体分析链接:返回多个变量怎么办
注
有一个问题当时困扰了博主一天,那就是发送和读取数据时,怎么把数据返回到主函数中,解决方法是,创建俩个全局变量数组,一个负责发送数据、另一个负责接收数据,这样就ok了
附上主函数
#include "stm32f10x.h"
#include "usart.h"
#include "flash.h"
uint8_t Rx[100];
uint8_t Tx[]="小全全的实验终于好了...",n;
int main(void)
{
DEBUG_USART_Config();
SPI_Config();
printf("欢迎来到小全全的FLASH实验\n");
printf("FLASH的ID为0x%X\n",FLASH_ReadID());
FLASH_SectorErase(0x00000);
n=sizeof(Tx);
n--;
FLASH_PageWrite(0x00000 ,Tx ,n);
FLASH_BufferRead(0x00000 ,Rx ,n);
printf("接收到数据为%s\n",Rx);
while(1)
{
;
}
}
STM32—SPI读写FLASH的更多相关文章
- 第24章 SPI—读写串行FLASH—零死角玩转STM32-F429系列
第24章 SPI—读写串行FLASH 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...
- STM32 对内部FLASH读写接口函数(转)
源:STM32 对内部FLASH读写接口函数 因为要用内部FLASH代替外部EEPROM,把参数放在STM32的0x08000000+320K处,其中20K是bootloader,300K是应用程序. ...
- STM32F10X SPI操作flash MX25L64读写数据(转)
源:STM32F10X SPI操作flash MX25L64读写数据 前一段时间在弄SPI,之前没接触过嵌入式外围应用,就是单片机也只接触过串口通信,且也是在学校的时候了.从离开手机硬件测试岗位后,自 ...
- SPI操作flash MX25L64读写数据
STM32F10X SPI操作flash MX25L64读写数据 简单的一种应用,ARM芯片作为master,flash为slaver,实现单对单通信.ARM主控芯片STM32F103,flash芯片 ...
- STM32F10x_SPI(硬件接口 + 软件模拟)读写Flash(25Q16)
推荐 分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来! http://www.captainbed.net/strongerhuang Ⅰ.写在前 ...
- STM32F0xx_SPI读写(Flash)配置详细过程
Ⅰ.概述 关于SPI(Serial Peripheral Interface)串行外设接口可以说是单片机或者嵌入式软件开发人员必须掌握的一项通信方式,就是你在面试相关工作的时候都可能会问及这个问题.在 ...
- spi nor flash使用汇总
Overview SPI flash, 分为spi flash, DUAL spi flash, QUAD spi flash, 3-wire spi, 4-wire spi, 6-wire spi. ...
- CYPEESS USB3.0程序解读之---SPI读写
前面已经解读了GPIO以及同步FIFO操作,下面我们看一个SPI读写的例子,它是主程序命令从SPI中读写一些数据. SPI传输子程序看一下: 页地址,字节计数,缓冲区,读写标志 因为只能一页一页的读或 ...
- STM32片上Flash内存映射、页面大小、寄存器映射
STM32片上Flash内存映射.页面大小.寄存器映射 STM32有4种Flash module organization,分别是:low-density devices(32KB,1KB/page) ...
随机推荐
- linux学习之路第八天(组管理和权限管理)
组管理和权限管理 1.Linux 组基本介绍 在linux中的每个用户必须属于一个组,不能独立于组外.在linux中每个文件有所有者,所在组,其他组的概念 1)所有者 2)所在组 3)其它组 4)改变 ...
- bugku本地包含
重点:eval()函数有执行漏洞 函数本身作用,把字符串当成php代码计算. 所以自然想到,可以把我们的写好的php代码写入进去. 题目又暗示在本地,想到flag.php了,所以想办法把文件里面的内容 ...
- konga的初步使用
目录 1. 设置连接 2. konga的重要功能 Dashboard Snapshots Settings 3. 通过konga 实现kong api配置 前言: 在上篇文章中,我们已经创建了一个到k ...
- Apache Flink目录遍历(CVE-2020-17519)
1.漏洞描述 2021年1月5日,Apache Flink官方发布安全更新,修复了由蚂蚁安全非攻实验室发现提交的2个高危漏洞,漏洞之一就是Apache Flink目录遍历漏洞(CVE-2020-175 ...
- Python基础之函数的闭包与装饰器的介绍
1.闭包的概念: 如果在一个函数中,定义了另外一个函数,并且那个函数使用了外面函数的变量,并且外面那个函数返回了里面这个函数的引用,那么称为里面的这个函数为闭包. 2.话不多说,以demo示例: de ...
- sync/fsync/fdatasync的简单比较
此文主要转载自 http://blog.csdn.net/zbszhangbosen/article/details/7956558 官网上有关于MySQL的flush method的设置参数说明,但 ...
- 看懂UML类图笔记
在学习设计模式的时候,经常会遇到UML类图,所以就找了一些资料,做一些笔记. 从一个示例开始 下面这个类图,类之间的关系是我们需要关注的: 车的类图结构为<<abstract>> ...
- 使用xampp在本地环境配置虚拟域名
最近在学习ThinkPHP5.1.手册里面提到"实际部署中,应该是绑定域名访问到public目录,确保其它目录不在WEB目录下面."所以把使用xampp在本地配置虚拟域名的过程记录 ...
- TCP协议的“三次握手”和“四次挥手”
TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接.在TCP/IP协议中,TCP 协议提供可靠的连接服务,连接是通过三次握手进行初始化的.三次握手的目的是同步连接双方的 ...
- JDK 和 CGLib 实现动态代理和区别
JDK 和 CGLib 实现动态代理和区别 在日常的开发中,Spring AOP 是一个非常常用的功能.谈到 AOP,自然离不开动态代理. 那么,基于 JDK 和 CGLib 如何实现动态代理,他们之 ...

