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五分钟入门系列(十五)输出比较(PWM输出)+各类测试
STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)
STM32F103(十九)ADC相关的几个实验—内部温度传感器、内部参照电压、光敏传感器
STM32F103(二十三)通用同步异步收发器(USART)
STM32F103(二十五)【完美解决】USART发送、接收float、u16、u32数据
STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)
目录
- 前言
- 一、W25Q128简述
- 二、面向外设--根据时序编写函数
- 1、MCU端数据传输函数
- 2、写使能函数(指令:06h)
- 3、状态寄存器写使能函数(指令:50h)
- 4、写失能函数(指令:04h)
- 5、读状态寄存器函数(指令:05h、35h、15h)
- 6、写状态寄存器函数(指令:01h、31h、11h)
- 7、读数据函数(指令:03h)
- 8、忙标志等待结束函数
- 9、页写入函数(指令:02h)
- 10、扇区擦除函数(指令:20h)
- 11、块擦除函数(指令:D8h)
- 12、全片擦除函数(指令:C7h或60h)
- 13、擦除暂停函数(指令:75h)
- 14、擦除恢复函数(指令:7Ah)
- 15、掉电函数(指令:B9h)
- 16、掉电恢复函数(指令:ABh)
- 17、读取制造商ID和设备ID函数(指令:90h)
- 18、顺序写函数
- 19、顺序读函数
- 19、顺序擦除函数
- 20、所有函数的合影
- 三、函数测试
前言
前一篇博客总结了SPI的原理、STM32F1和F4的SPI的配置、库函数以及两块STM32之间的SPI通信。本博面向高级外设,通过SPI通信完成STM32F1访问外部高速Flash,板载的外部flash芯片为W25Q128,博客总结风格类似前面IIC通信总结的风格,会对W25Q128参考文档仔细分析,然后根据参考文档的时序一步步去编写代码。
相信通过本博的总结,会对STM32的SPI通信、面向高级外设编程有深刻的认识。
对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(写保护) | 寄存器状态 | 描述 |
---|---|---|---|---|
0 | 0 | x | 软件保护 | /WP引脚没有控制。在写使能指令后,(状态寄存器1的S1位)WEL=1,此时状态寄存器可以被写(工厂默认) |
0 | 1 | 0 | 硬件保护 | 当/WP引脚较低时,状态寄存器被锁定,无法写入 |
0 | 1 | 1 | 无硬件保护 | 当/WP引脚高电平时,状态寄存器被解锁,并且在写使能指令后,WEL=1, |
1 | 0 | x | 电源封锁 | 状态寄存器受到保护,在下一个断电、断电周期之前不能被写入 |
1 | 1 | x | 一次性编程 | 状态寄存器是永久保护的,不能被写入 |
位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:
输出驱动强度设置位:
DRV1 | DVR0 | 驱动强度 |
---|---|---|
0 | 0 | 100% |
0 | 1 | 75% |
1 | 0 | 50% |
1 | 1 | 25%(默认) |
位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