STM32F103(二十七)超长篇解读STM32访问外部flash

Posted 独独白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103(二十七)超长篇解读STM32访问外部flash相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

往期博客:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区

STM32F103五分钟入门系列(四)蜂鸣器实验(库函数+寄存器)

STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)

STM32F103五分钟入门系列(六)时钟框图+相关寄存器总结+系统时钟来源代码(寄存器)

STM32F103五分钟入门系列(七)SystemInit()函数、SetSysClock()函数

STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯

STM32F103五分钟入门系列(九)延时函数(自己重写的底层)(上限:477218ms和477218588us)

STM32F103五分钟入门系列(十)NVIC中断优先级管理

STM32F103五分钟入门系列(十一)外部中断大汇总

STM32F103五分钟入门系列(十二)定时器中断

STM32F103五分钟入门系列(十三)独立看门狗IWDG

STM32F103五分钟入门系列(十四)窗口看门狗WWDG

STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试

STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)

STM32F103你学不会系列(十七)电容触摸按键实现

STM32F103(十八)ADC总结(贼详细)

STM32F103(十九)ADC相关的几个实验—内部温度传感器、内部参照电压、光敏传感器

STM32F103(二十)DAC(贼详细)

STM32F103(二十一)DMA(超详细的~)

STM32F103(二十二)一篇文章精通《IIC通信》

STM32F103(二十三)通用同步异步收发器(USART)

STM32F103(二十四)一篇博客精通《485通信》

STM32F103(二十五)【完美解决】USART发送、接收float、u16、u32数据

STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)

目录

前言

前一篇博客总结了SPI的原理、STM32F1和F4的SPI的配置、库函数以及两块STM32之间的SPI通信。本博面向高级外设,通过SPI通信完成STM32F1访问外部高速Flash,板载的外部flash芯片为W25Q128,博客总结风格类似前面IIC通信总结的风格,会对W25Q128参考文档仔细分析,然后根据参考文档的时序一步步去编写代码。

相信通过本博的总结,会对STM32的SPI通信、面向高级外设编程有深刻的认识。

对W25Q128参考文档总结时,会对部分重要内容截屏,博主会尽量将文档页码截入图片,察看博客时方便定位内容所在位置。

本博中的代码是完整的,可以直接拷贝使用,同时也会以资源的形式上传…

W25Q128中文和引文版

本博编写的函数

一、W25Q128简述

1、外形及引脚说明

pcb模型如下:

原理图模型如下:

引脚:

1引脚:CS 片选信号引脚,用于MCU选中外设。

2引脚:DO 有时候也叫SO,为外设的数据输出引脚,MCU的数据输入引脚,即SPI通信的MISO线连接点。

3引脚:WP 写保护

4引脚:GND

5引脚:DI 有时候也叫SI,为外设的数据输入引脚,MCU的数据输出引脚,即SPI通信的MOSI线连接点。

6引脚:CLK 接MCU SPI的CLK引脚,通信的时钟由该引脚提供。

7引脚:HOLD

8引脚:VCC

SPI通信访问外设,一般情况下MCU为主设备,外设为从设备

2、容量

W25Q128 容量为128M-bit,即128/8=16MB。

W25Q128 由65536个可编程页面(programmable page)组成,每页(page)大小:16MB * 1024 * 1024/65536=256B。

W25Q128 由256个块(block)组成,每一个块的容量:16MB*1024/256=64KB。

W25Q128 由4096个可擦除扇区(sector)组成,故一个块中扇区数量:4096/256=16(个),每个扇区的容量:64KB/16=4KB

扇区是W25Q128 的最小擦除单位,即如果想要擦除flash的某个字节就必须把它所在的扇区的4KB全部擦除。如果本扇区有其他数据,则需要将数据备份,擦除扇区后,再将备份的数据重新写入该扇区。

3、时钟

W25Q128 支持标准串行外围接口(SPI)、双/快速IO SPI 和2锁指令周期外围接口(QPI),当使用标准串行外围接口(SPI)时时钟最大可支持104MHZ;使用双/快速IO SPI 时等效时钟速率为104 *2=208MHZ;使用2锁指令周期外围接口(QPI)时等效时钟速率为104 *4=416MHZ

4、板载原理图

上图为我所用板子的原理图,可以看到写保护(WP)和HOLD都接了高电平,这个地方后面总结到两者的时候就知道它是什么模式了…SO引脚接MISO线、SI接MOSI线、CLK接SPI2的CLK引脚,原理图比较简单,应该不难理解…

再总结一下引脚细节:

(1)片选(CS)

当CS引脚为高电平时,设备不能选中,数据输出口DO(即SO、MISO)处于高阻态。

当CS引脚为低电平时,设备被选中,此时可以对设备进行读、写。

注意
上电后,CS引脚必须从高到低过渡,才能接收新的指令,所以在程序中初始化W25Q128时,要先将CS引脚拉高再拉低。

(2)串行输入输出(DI、DO)

DI引脚为MCU发送数据到W25Q128的接收引脚,在时钟CLK的上升沿写入数据。

DO引脚是W25Q128的数据输出引脚,在时钟CLK的下降沿被读出。

(3)写保护(WP)

写保护低电平有效,用来防止状态寄存器被写入。前面原理图中WP引脚接了一个高电平,这样使写保护失效,在任何有效时间内都可以对状态寄存器写操作。

(4)HOLD
hold可以暂停操作,低电平有效。当HOLD引脚被拉低时,如果CS处于低电平(即当片选外设期间拉低HOLD引脚),此时DO、DI、CLK引脚的信号都被忽略,HOLD引脚拉高后,可以恢复操作。

前面原理图中HOLD引脚接了一个高电平,表示在任何有效时间内,DI、DO、CLK引脚的信号不被忽略。

具体内容请参考W25Q128的手册:

4、W25Q128的状态寄存器1

位S0 - BUSY:
忙标志位,该位只能进行读操作,当执行读写数据、擦除扇区、擦除块、擦除芯片、写入状态寄存器、擦除安全寄存器时,该位被置1,当操作、指令完成后该位自动被清除为0。 可以通过该位标志判断扇区擦除是否完成、数据写入、读出是否完成等操作。

位S1 -WEL:

写使能标志位,该位只能进行读操作,当执行写使能指令后,该位被置1,当写操作被禁用后,该位被清零。

位S2、S3、S4- BP0、BP1、BP2:
块保护位、对内存块进行保护,默认为S4 S3 S2=000 。(具体情况与后面两位一起总结)

位S5-TB:
TB位,顶部、底部位保护,默认该位为0 。

位S6-SEC:
SEC位,扇区、块保护位,默认该位为0 。

接下来一起看一下S2、S3、S4、S5、S6这5五位:

从上表可以看到当BP2、BP1、BP0(S4位、S3位、S2位)都为0时(表格第一行),flash的所有块都不保护

之后的表格可以很容易看出对于SEC、TB、PB2、PB1、PB0的不同组合,实现保护不同的块。

位S7-SRP0:
状态寄存器保护0,与状态寄存器2的S8位一起总结。

5、W25Q128的状态寄存器2

位S8-SRP1:

状态寄存器保护1,与前面寄存器的S7位一起使用。它们可以控制保护读、写操作,有:软件保护、硬件保护、电源封锁、一次性可编程保护。如下表格:


翻译一下:

SRP1(S8位)SRP0(S7位)/WP(写保护)寄存器状态描述
00x软件保护/WP引脚没有控制。在写使能指令后,(状态寄存器1的S1位)WEL=1,此时状态寄存器可以被写(工厂默认)
010硬件保护当/WP引脚较低时,状态寄存器被锁定,无法写入
011无硬件保护当/WP引脚高电平时,状态寄存器被解锁,并且在写使能指令后,WEL=1,
10x电源封锁状态寄存器受到保护,在下一个断电、断电周期之前不能被写入
11x一次性编程状态寄存器是永久保护的,不能被写入

位S9-QE:
QPI模式,不总结

位S10-R:
保留位

位S11、S12、S13-LB1、LB2、LB3:

安全寄存器解锁位,默认都为0。如果都设置为1,则安全寄存器变为永久只读模式。

位S14-CMP:
互补保护位,与前面的SEC、TB、BP2、BP1、BP0一起使用,如果CMP为被设置为1,则这几位设置的保护部分不再被保护,而未保护的部分将被保护。默认为0 。

位S15-SUS:
挂起状态标志位,该位只读。在执行擦除、挂起指令(75h)后该位置1,执行擦除、挂起恢复指令(7Ah)指令以及断电、上电周期后,该位被清零。

6、W25Q128的状态寄存器3

位S16、S17、S19、S20为保留位

位S18-WPS:

写保护选择位。

当WPS=0时,写保护采用CMP、SEC、TB、BP2、BP1、BP0设置的区域保护。

当WPS=1时,采用单独的块锁来保护任意扇区或块。默认为1,即使用块锁来写保护。

位S21、S22-DRV0、DRV1:

输出驱动强度设置位:

DRV1DVR0驱动强度
00100%
0175%
1050%
1125%(默认)

位S23-HOLD/RST:

HOLD/RST只在QPI模式下有效,默认为0

7、块地址、扇区地址、页地址计算(重要)

推断各个块、各个扇区、页的地址范围在清除指定扇区时非常重要。W25Q128清除的最小单位为扇区,并且W25Q128由256个块组成,每块64KB;每个块由16个扇区组成,每个扇区为4KB。因为擦除、写数据、读数据都需要传递地址,要精准找到操作的地址,需要会计算地址。

地址计算:

一个地址存储8位的数,即一个地址空间存储1B的数据,则一个块的地址增量为64*1024=65536
第1块:000000h~00FFFFh
第2块:010000h~01FFFFh
第3块:020000h~02FFFFh

第11块:0A0000h~0AFFFFh

第100块:630000h~63FFFFFFh

第256块:FF0000h~FFFFFFh

规律已经看出来了,后四位数不变,都是0000~FFFF,如果是第n块,则前两位是(n-1)的16进制数

即块地址规律:

第n块地址:(0x)(n-1)0000h~(0x)(n-1)FFFF

其中(0x)(n-1)含义:n-1的两位16进制数;

总共256个块,刚好(0x)(n-1)为00~FF
-----------------------------------------------
-----------------------------------------------
一个地址存储8位的数,即一个地址空间存储1B的数据,则一个扇区的地址增量为4*1024=4096
第1块第1扇区地址:000000h~000FFFh
第1块第2扇区地址:001000h~001FFFh
第1块第3扇区地址:002000h~002FFFh

第1块第11扇区地址:00A000h~00AFFFh

第1块第16扇区地址:00F000h~00FFFFh

规律已经看出来了,第一块地址范围:000000h~00FFFFh
则第n个扇区: 000000h+(0x)(n-1)000h~000000h+(0x)(n-1)FFFh

再如:

第11块地址范围:0A0000h~0AFFFF…
第11块第1扇区地址:0A0000h+0000h~0A0000h+0FFFh,即0A0000h ~0A0FFF;
第11块第2扇区地址:0A0000h+1000h~0A0000h+1FFFh,即0A1000h ~0A1FFF;

第11块第12扇区地址:0A0000h+B000h~0A0000h+B000h,即0AB000h ~0ABFFF;

第11块第16扇区地址:0A0000h+F000h~0A0000h+F000h,即0AF000h ~0AFFFF;

-----------------------------------------------
-----------------------------------------------
W25Q128由65536个可编程页面(programmable page)组成,每页(page)大小:16MB10241024/65536=256B。则一页的地址增量为256

第一页地址范围:000000h ~ 0000FF
第二页地址范围:000100h ~0001FF

第11页地址范围:000A00h~000AFF

第K页地址范围:(0x)(K-1)00h~(0x)(K-1)FFh

将公式写在一起:【(0x)运算为:将后面的数变为16进制数】

第n块的地址范围:【24位地址、(0x)(n-1)为00~FF】

(0x)(n-1)0000h~(0x)(n-1)FFFF

可以变为:

(0x)(n-1)×65536 ~ (0x)(n-1)×65536 +65535

第n块第m扇区的地址范围:【24位地址、(0x)(n-1)为00~FF、(0x)(m-1)为0 ~F】

(0x)(n-1)0000h+(0x)(m-1)000h~(0x)(n-1)0000h+(0x)(m-1)FFFh

可以合并为:

(0x)(n-1) (0x)(m-1) 000h~(0x)(n-1) (0x)(m-1) FFFh

可以变为:
(0x)65536×(n-1) +4096×(m-1) ~ (0x)65536×(n-1) +4096×(m-1)+4095

如上面推导:

第11块第12扇区地址:0A0000h+B000h~0A0000h+B000h,即0AB000h ~0ABFFF;

n=11,m=12,利用公式:

(0x)(65536×10 +4096×11) ~ (0x)(65536×10 +4096×11 +4095)

=(0x)700416 ~ (0x)704511

=0AB000~0ABFFF

第K页的地址范围:【24位地址、(0x)(K-1)为0000~FFFF】

(0x)(K-1)00h~(0x)(K-1)FFh
可以变为:
(0x)256×(K-1) ~ (0x)256×(K-1)+255

如果记不住,就这样记:

块地址:XX0000~XXFFFF

扇区地址:XXX000~XXXFFF

页地址:XXXX00~XXXXFF

XX…就编号,从0开始…

二、面向外设–根据时序编写函数

1、MCU端数据传输函数

还是沿用上一篇博客的代码:

u8 SPI2_Send_Data(u8 data)

	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)
	
		//等待发送缓存寄存器为空
		
	  SPI_I2S_SendData(SPI2,data);
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET)
	
		//等待接收缓存器非空
	
	return SPI_I2S_ReceiveData(SPI2);

2、写使能函数(指令:06h)

发送写使能指令后,状态寄存器器1的写使能标志位WEL被置1。通过驱动CS低电平,将0x06在时钟上升沿通过DI引脚输入到外设,然后驱动CS引脚为高。

因为用到片选,为了方便,来个宏定义并放入头文件:

#define CS 	PBout(12)	

代码:

//写使能函数 指令:0x06(宏定义:Write_Enable)
void W25Q128_Write_Enable(void)						//写使能函数

	CS=0;
	SPI2_Send_Data(Write_Enable);
	CS=1;

3、状态寄存器写使能函数(指令:50h)

根据时序可以看到,当CS拉低时,发送0x50指令,完成后拉高CS。

需要注意的是对状态寄存器写操作时,不需要使能写(上一个函数),即不对状态寄存器的WEL位操作。

代码:

//状态寄存器写使能函数 指令:0x50(宏定义:Status_Register_Write_Enable)
void W25Q128_Status_Register_Write_Enable(void)		//状态寄存器写使能

	CS=0;
	SPI2_Send_Data(Status_Register_Write_Enable);
	CS=1;


4、写失能函数(指令:04h)

根据时序,将CS拉低,再在接下来的8个时钟周期内发送0x04指令,完成后将CS拉高。

写失能后状态寄存器的WEL被硬件置0 。

代码:

//写失能函数 指令:0x04(宏定义:Write_Disable)
void W25Q128_Write_Disable(void)					//写失能函数

	CS=0;
	SPI2_Send_Data(Write_Disable);
	CS=1;

5、读状态寄存器函数(指令:05h、35h、15h)

读状态寄存器通过将CS拉低,发送指令,在8个时钟周期内传输指令。接下来的8个时钟周期DO引脚输出状态寄存器的值(8位)。状态寄存器可以连续读取,即如果CS一直处于低电平,则持续输出状态寄存器的值,当CS拉高后,停止状态值输出,此时DO引脚又变为高阻态。

由于SPI通信时传输和接收时同时进行的,如果不把CS拉高,会持续读取状态寄存器,DI、DO引脚会一直被占用,无法进行其他操作,为了避免忘记拉高CS结束读取状态寄存器,读取完一次后直接拉高CS就好,要想多次获取状态寄存器,就多次调用这个函数就好了。

需要返回状态寄存器的值,在返回数据的周期内,DI引脚也是有值的,因为SPI通信时,MCU需发送数据同时才能接收到外设数据。

传递8位的指令,返回8位的状态值,代码:

状态寄存器1:

//读状态寄存器1 指令:0x05(宏定义:Read_Status_Register1)
u8 W25Q128_Read_Status_Register1(void)			// 读状态寄存器1

	u8 temp;
	CS=0;
	SPI2_Send_Data(Read_Status_Register1);
	temp=SPI2_Send_Data(0xff);
	CS=1;
	return temp;

状态寄存器2:

//读状态寄存器2 指令:0x35(宏定义:Read_Status_Register2)
u8 W25Q128_Read_Status_Register2(void)			// 读状态寄存器2

	u8 temp;
	CS=0;
	SPI2_Send_Data(Read_Status_Register2);
	temp=SPI2_Send_Data(0xff);
	CS=1;
	return temp;

状态寄存器3:

//读状态寄存器3 指令:0x15(宏定义:Read_Status_Register3)
u8 W25Q128_Read_Status_Register3(void)			// 读状态寄存器1

	u8 temp;
	CS=0;
	SPI2_Send_Data(Read_Status_Register3);
	temp=SPI2_Send_Data(0xff);
	CS=1;
	return temp;

6、写状态寄存器函数(指令:01h、31h、11h)

状态寄存器不是全部能进行写操作的。能进行写操作的位:

状态寄存器1:BP0、BP1、TB、SEC、SRP0

状态寄存器2:SRP1、QE、LB1、LB2、LB3、CMP

状态寄存器3:WPS、DRV0、DRV1、HOLD

注意下面这段话:

对状态寄存器进行写操作前,必须写使能状态寄存器写操作;同时要保持WEL为0 。换句话说,在对状态寄存器进行写操作前,必须使能状态寄存器写、失能内存写操作

时序:

CS拉低后,传入指令,之后再传入寄存器的值。注意时序这里没有延时周期,所以传递完指令后,直接传送状态寄存器的值。

写状态寄存器1:

//写状态寄存器1  指令:0x01(宏定义:Write_Status_Register1)
void W25Q128_Write_Status_Register1(u8 data)		//写状态寄存器1
 
	W25Q128_Status_Register_Write_Enable();			//使能寄存器写
	W25Q128_Write_Disable();						//失能内存写操作
	CS=0;
	SPI2_Send_Data(Write_Status_Register1);
	SPI2_Send_Data(data);
	CS=1;

写状态寄存器2:

//写状态寄存器2  指令:0x31(宏定义:Write_Status_Register2)
void W25Q128_Write_Status_Register2(u8 data)		//写状态寄存器2

	W25Q128_Status_Register_Write_Enable();			//使能寄存器写
	W25Q128_Write_Disable();
	CS=0;
	SPI2_Send_Data(Write_Status_Register2);
	SPI2_Send_Data(data);
	CS=1;

写状态寄存器3:

//写状态寄存器3  指令:0x11(宏定义:Write_Status_Register3)
void W25Q128_Write_Status_Register3(u8 data)		//写状态寄存器3

	W25Q128_Status_Register_Write_Enable();			//使能寄存器写
	W25Q128_Write_Disable();
	CS=0;
	SPI2_Send_Data(Write_Status_Register3);
	SPI2_Send_Data(data);
	CS=1;

7、读数据函数(指令:03h)

读取数据指令可以在内存中顺序读取一个或多个数据。将CS拉低后,先发送8位的指令,再发送24位地址。之后就开始输出数据,数据可以连续输出,读取一个地址空间的数据后,地址会自增,然后读取下一个地址空间的数据。地址自增到最大后又会从0开始自增。CS拉高后结束读取,如果CS一直不拉高,可以对整个内存进行单指令访问。

为了连续读取数据,函数不仅需要传递24位的地址,还需要传递需要读取的字节数量,此外由于是SPI通信,所以要想返回数据,需要给外设发送数据才能返回数据,发送数据任意,但是时序中DO引脚一直为高电平,所以选用0xff为发送的数据。

代码:

extern u8 buff[65536];
//读数据 指令:0x03(宏定义:Read_Data)
void W25Q128_Read_Data(u32 address,u16 num)		//读数据

	u16 i;
	CS=0;
	SPI2_Send_Data(Read_Data);
	SPI2_Send_Data((u8)(address>>16));//发送位23:16地址
	SPI2_Send_Data((u8)(address>>8));//发送位15:8地址
	SPI2_Send_Data((u8)address);//发送位7:0地址
	
	for(i=0;i<num;i++)
	buff[i]=SPI2_Send_Data(0xff);
	CS=1;

8、忙标志等待结束函数

该函数直接对状态寄存器1访问,没有指令,代码:

void W25Q128_Wait_Busy(void)						//等待状态寄存器1的BUSY位清零

	while((W25Q128_Read_Status_Register1()&0x01));

9、页写入函数(指令:02h)

可以看到当CS拉低后,传输0x02指令,之后再传输24位地址,开始对页写入数据。

需要注意的是,一页的大小为256B,所以只能传入256个8位数据,如果超过256个数据,则超出的部分会从本页起始地址开始写入,覆盖原来的数据。传递的地址可以是某页的任意地址,然后从这个地址开始顺序存储,本页满后,从本页0地址(XXXX00)处开始存储。

还有就是在传输指令前需要使能写操作。

下面这句话:

当拉高CS后,页面程序才会开始执行,所以拉高CS后,需要等待忙标志结束。

代码:

//页写入数据 指令:0x02(宏定义:Write_Page)
void W25Q128_Write_page(u32 address,u16 num,u8 buff[])	//页写入数据函数

	u16 i;
	W25Q128_Write_Enable();
	CS=0;
	SPI2_Send_Data(Write_Page);
	SPI2_Send_Data((u8)(address>>16));			//发送位23:16地址
	SPI2_Send_Data((u8)(address>>8));			//发送位15:8地址
	SPI2_Send_Data((u8)address);				//发送位7:0地址	
	
	for(i=0;i<num;i++)
	SPI2_Send_Data(buff[i]);
	CS=1;
	W25Q128_Wait_Busy();

10、扇区擦除函数(指令:20h)

CS拉低后,首先输入8位的指令,然后接下来传递擦除扇区的地址,扇区地址计算前面已经总结过了…

传递的地址可以为扇区内的任意地址,会把该地址所在的扇区内数据全部擦除。并且地址传递完毕后,再将CS拉高才开始擦除,否则指令不会生效。擦除时状态寄存器的BUSY位为1,表示正在执行擦除指令;当擦除完成后BUSY位清0 。

扇区擦除完成后,写操作会自动失能,另外如果设置了块保护,而且保护区域与擦除区域重叠,则擦除指令失效。

传输擦除指令前,必须使能写操作

扇区擦除代码:

//扇区擦除函数 指令:0x20(宏定义:Sector_Erase)
void W25Q128_Sector_Erase(u32 address)				//扇区擦除函数

	W25Q128_Write_Enable();
	CS=0;
	SPI2_Send_Data(Sector_Erase);
	SPI2_Send_Data((u8)(address>>16));				//发送位23:16地址
	SPI2_Send_Data((u8)(address>>8));				//发送位15:8地址
	SPI2_Send_Data((u8)address);					//发送位7:0地址	
	CS=1;
	W25Q128_Wait_Busy();							//等待擦除成功

11、块擦除函数(指令:D8h)

在CS被拉低时,在接下来的8个时钟周期开始传输擦除指令,之后传送24位的地址,同样在擦除过程中,需要等待BUSY位清零。

当然如果设置了块保护,而且块保护的区域就在要擦除的这个区域,则擦除指令失效。

传输擦除指令前,必须使能写操作
代码:

//块擦除 指令:0xD8(宏定义:Block_Erase)
void W25Q128_Block_Erase(u32 address)				//块擦除

	W25Q128_Write_Enable();
	CS=0;
	SPI2_Send_Data(Block_Erase);
	SPI2_Send_Data((u8)(address>>16));				//发送位23:16地址
	SPI2_Send_Data((u8)(address>>8));				//发送位15:8地址
	SPI2_Send_Data((u8)address);					//发送位7:0地址	
	CS=1;
	W25Q128_Wait_Busy();							//等待擦除成功

12、全片擦除函数(指令:C7h或60h)

当CS被拉低时,开始传输指令,指令传输完成后,需要及时拉高CS,再等待擦除完成。

传输擦除指令前,必须使能写操作
代码:

//全片擦除 指令:0xC7(宏定义:Chip_Erase)
void W25Q128_Chip_Erase(void)						//全片擦除
STM32F103(二十七)超长篇解读STM32访问外部flash

STM32F103(二十七)超长篇解读STM32访问外部flash

STM32F103(二十一)DMA(超详细的~)

STM32F103(二十一)DMA(超详细的~)

STM32F103你学不会系列(十七)电容触摸按键实现

STM32F103(二十)DAC(贼详细)