自学内容网 自学内容网

STM32学习笔记---SPI与W25Q64

目录

一、什么是SPI

1、SPI总线概念

2、硬件连接图

3、数据帧传输

4、SPI配置方式

二、如何配置SPI

1、SPI通信IO口模拟

1.1 工作模式

1.2 数据传输规矩

1.3 程序设计

2、SPI控制器配置

2.1 SPI控制器框图

2.2 程序设计

三、具体使用SPI

1、W25Q64

2、基于SPI底层协议对W25Q24进行读写操作

2.1 W25Q64状态及控制寄存器介绍

2.2 W25Q64读写操作

3、具体使用


一、什么是SPI

1、SPI总线概念

        SPI 是 Motorola(摩托罗拉) 公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线, SPI 时钟频率相比 I2C 要高很多,I2C的速度上限是400Khz,而SPI的速度为40Mhz。

2、硬件连接图

说明:

SPI通信也可以挂载多个从器件,通过片选线选中具体从器件。SPI一般为4线也可以是3线。

常见的为四线模式,分别是下面四根线

CS/NSS

片选信号线,选择需要进行通信的从设备;拉低有效。

SCK

串行时钟信号线,为SPI提供时钟(主机发出)

MOSI/SDO

主出从入信号线,主机向从机发送数据

MISO/SDI

主入从出信号线,从机向主机发送数据

3、数据帧传输

片选低 +数据位(8/16) +片选高

4、SPI配置方式

①用IO口模拟SPI的时序

②直接配置SPI控制器的寄存器

二、如何配置SPI

1、SPI通信IO口模拟

1.1 工作模式

时钟极性:

        空闲时候时钟线电平状态   高  低

时钟相位:

        时钟线在第一个跳变沿时,数据线可以读

        时钟线在第二个跳变沿时,数据线可以读

具体模式如下:

①时钟极性为0(SCL_L),时钟相位为0(时钟线在第一个跳变沿数据线可以读)   

空闲:

        SCL_L

规则:

        先发高位

        时钟线上升沿,数据线稳定,可以读数据

        时钟线为下降沿,数据线可以改变

②时钟极性为0(SCL_L),时钟相位为1(时钟线在第二个跳变沿数据线可以读)

空闲:

        SCL_L

规则:

        先发高位

        时钟线为下降沿,数据线稳定,可以读数据

        时钟线为上升沿,数据线可以改变

③时钟极性为1(SCL_H),时钟相位为0(时钟线在第一个跳变沿数据线可以读)   

空闲:

        SCL_H

规则:

        先传输高位

        时钟线为下降沿,数据线稳定; (要想读数据,时钟线拉低)

        第一位数据时钟线为高电平,数据线可以改变;(要想写数据,时钟线要拉高)

        其他的数据时钟线为上升沿,数据线可以改变(要想写数据,时钟线要拉高)

④时钟极性为1(SCL_H),时钟相位为1(时钟线在第二个跳变沿数据线可以读)

空闲;

        SCL_H

规则:

        先传输高位

        时钟线为上升沿,数据线可读

        时钟线为下降沿,数据线可写

说明:

如果用IO口模拟SPI时序,IO口要用通用模式

本篇主要针对于0,0模式展开学习,其它模式同样操作

1.2 数据传输规矩

SPI 设备间的数据传输之所以又被称为数据交换,是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”。SPI是全双工通信,所以发送和接收是同时进行的。在每个 Clock 周期内,SPI 设备都会发送并接收一个 bit 大小的数据(不管主设备还是从设备),相当于该设备有一个 bit 大小的数据被交换了。一个 Slave 设备要想能够接收到 Master 发过来的控制信号,必须在此之前能够被 Master 设备进行访问。所以,Master 设备必须首先通过 NSS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。 在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致 SPI 物理模块最终失效。因此,在程序中一般都会在 SPI 传输完数据后,去读取 SPI 设备里的数据, 即使这些数据在我们的程序里是无用的(虽然发送后紧接着的读取是无意义的,但仍然需要从寄存器中读出来)。

所以一般在SPI通信中,写数据的时候从机一般会返回一个无效数据。读数据的时候,主机也需要向从机发送一个无效数据

1.3 程序设计

SPI初始化函数

{

        时钟线所用到的IO通用推挽输出

        MOSI线所用到的IO通用推挽输出

        MISO线所用到的IO通用输入

}

具体程序:

/***************************************
*函数名:spi_io_init
*函数功能:spi所用IO口初始化配置函数
*函数参数:无
*函数返回值    :无
*函数描述:SCK------PA5  通用推挽输出
                  MOSI-----PA7  通用推挽输出
  MISO-----PA6  通用输入
****************************************/
void spi_io_init(void)
{
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式
GPIOA->MODER &= ~((3 << 10) | (3 << 14) | (3 << 12));
GPIOA->MODER |= ((1 << 10) | (1 << 14));
//输出类型
GPIOA->OTYPER &= ~((1 << 5) | (1 << 7));
//输出速率  50M
GPIOA->OSPEEDR &= ~((3 << 10) | (3 << 14));
GPIOA->OSPEEDR |= ((2 << 10) | (2 << 14));
//上下拉
GPIOA->PUPDR &= ~((3 << 10) | (3 << 14) | (3 << 12));

//空闲
GPIOA->ODR &= ~(1 << 5);

}

SPI发送数据和接收数据的规则

发送一位数据一定会接收到一位数据

接收一位数据之前一定要发送一位数据

所以,发送函数和接收函数要写成一个函数.

收发一体函数:

u8 SPI收发一字节函数(u8)

{

        循环8次

        {

                发送一位数据:(主发从收)

                时钟线拉低,①主机帮从机拉低时钟线,从机可以改变数据线,②主机发送一位数据

                主机根据要发送的数据的对应位改变数据线 (MOSI)

                下一位数据

                接收一位数据:(从发主收)

                时钟线拉高,可以读取数据线

               主机根据数据线的电平状态决定变量的对应位

                返回数据

        }

}

过程:

发送一位数据,主机通过MOSI引脚向从机发送数据,主机根据要发送的数据的对应位改变数据线,主机帮从机拉低时钟线,从机可以改变数据线,从机自动接收;接收一位数据,从机通过MISO引脚向主机发送数据,主机根据数据线(MISO)的电平状态决定变量的对应位

如何使用:

发送数据:

调用此函数,只需要关注参数,传递要发送的数据,不需要关注返回值,可以不接收

接收数据:

调用此函数,只需要关注返回值,定义一个变量接受返回值,参数随便传入

例子:

u8 spi_byte(u8 data)

先发送命令0x10;    spi_byte(0x10);

在发送数据0x20;    spi_byte(0x20);

接收测量值数据;      u8 data = spi_byte(0xff)

u8 data = spi_byte(0x10)接收发的数据0x01--> 错误的,因为收和发不在同一时间段内,需要等到完全把地址完全发送出去后才能接收

具体程序:

/***************************************
*函数名:spi_byte
*函数功能:spi收发一字节函数
*函数参数:u8 data
*函数返回值    :u8
*函数描述:0,0模式
****************************************/
u8 spi_byte(u8 data)
{
u8 i;
/*发送一位数据*/
SPI_SCL_L;//时钟线拉低,①主机帮从机拉低时钟线,从机可以改变数据线②主机发送一位数据
for(i=0;i<8;i++)
{
//根据所要发送的数据的对应位改变数据线
if(data & 0x80)
{
SPI_MOSI_H;
}
else
{
SPI_MOSI_L;
}
data = data << 1;

/*接收一位数据*/
SPI_SCL_H;//时钟线拉高,可以读取数据线
if(SPI_MISO)
{
data |= 0x01;
}
}

return data;
}

2、SPI控制器配置

2.1 SPI控制器框图

通过框图可知:

        发送数据:

                发送数据检测之前的数据是否发送完成,等待之前的数据发送完成再发送下一个数据

        接收数据:

                内核接收数据之前检测是否接收完成数据,等待接收数据完成才去读DR中的数据

MOSI:发送接口

MISO:接收接口

SCK : 时钟线接口

NSS: SPI控制器允许传输数据条件接口

软件模式:

        SPI配置为主器件:

                在NSS 软件模式下,将 SPI_CR1 寄存器中的SSM SSI 1

        SPI配置为从器件:

                在NSS 软件模式下,将 SPI_CR1 寄存器中的SSM 位置 1,将 SSI 位清零

思考:为什么IIC、SPI不用中断?

使用中断的必要性:被动,不确定CPU运行到哪个位置的时候,要执行紧急事件

而IIC与AT24C02之间的通信更倾向于主动的过程,无论是主机发送还是从机发送,都是主机发送起始信号+器件地址+内部地址之后才可以进行数据收发,整个过程主机都是占据着主动的位置,从机只能是处于被动的位置;SPI也是如此。所以这就是IIC、SPI不用中断的原因。而USART用中断的原因,无论是串口发送还是PC发送,双方都可以充当主动的一方,那就造成另一方充当了被动方;也就是串口/PC接收数据的位置是不固定的。

2.2 程序设计

SPI初始化配置函数

{

        /*IO口控制器配置*/

        //端口时钟是使能

        //端口模式配置-------------复用模式

        //输出类型配置

        //输出速度配置-------------50M

        //上下拉配置

        //复用功能配置

        /*SPI控制器配置*/

        //SPI时钟使能

        //CR1

        //CR2

}

具体程序:
 

/***************************************
*函数名:spi1_init
*函数功能:spi所用IO口初始化配置函数
*函数参数:无
*函数返回值    :无
*函数描述:SCK------PA5  复用输出
                  MOSI-----PA7  复用输出
  MISO-----PA6  复用输入
****************************************/
void spi1_init(void)
{
/*IO口配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式
GPIOA->MODER &= ~((3 << 10) | (3 << 14) | (3 << 12));
GPIOA->MODER |=  ((2 << 10) | (2 << 14) | (2 << 12));
//端口类型
GPIOA->OTYPER &= ~((1 << 5) | (1 << 7));
//输出速率  50M
GPIOA->OSPEEDR &= ~((3 << 10) | (3 << 14));
GPIOA->OSPEEDR |= ((2 << 10) | (2 << 14));
//上下拉
GPIOA->PUPDR &= ~((3 << 10) | (3 << 14) | (3 << 12));
//复用功能配置  AF5
GPIOA->AFR[0] &= ~((0xf << 20) | (0xf << 28) | (0xf << 24));
GPIOA->AFR[0] |= ((5 << 20) | (5 << 28) | (5 << 24));

/*SPI控制器配置*/
//SPI时钟使能
RCC->APB2ENR |= (1 << 12);

//CR1
SPI1->CR1 &= ~(1 << 15);
SPI1->CR1 &= ~(1 << 11);//8位数据帧
SPI1->CR1 &= ~(1 << 10);//全双工
SPI1->CR1 |=  (1 << 9);//软件从器件管理使能
SPI1->CR1 |=  (1 << 8);//主机模式
SPI1->CR1 &= ~(1 << 7);//先发高位
SPI1->CR1 &= ~(7 << 3);//波特率控制
SPI1->CR1 |=  (1 << 2);//主配置
SPI1->CR1 &= ~(1 << 1);//时钟极性
SPI1->CR1 &= ~(1 << 0);//时钟相位
//CR2
SPI1->CR2 &= ~(1 << 4);
//SPI使能
SPI1->CR1 |= (1 << 6);
}

u8 SPI传输一个字节数据函数(要发送的数据)

{

        //等待发送缓冲区为空 不为空就等待

        //将要发送的数据给DR

        //等待接收缓冲区有数据  没有数据就等待

        //将DR赋值给一个变量

        //变量返回

}

/***************************************
*函数名:spi1_byte
*函数功能:spi收发一字节函数
*函数参数:u8 data
*函数返回值    :u8
*函数描述:0,0模式
****************************************/
u8 spi1_byte(u8 data)
{
u8 val;
//等待发送缓冲区为空 不为空就等待
while(!(SPI1->SR & (1 << 1)));
//将要发送的数据给DR
SPI1->DR = data;
//等待接收缓冲区有数据  没有数据就等待
while(!(SPI1->SR & (1 << 0)));
//将DR赋值给一个变量
val = SPI1->DR;
//变量返回
return val;

}

三、具体使用SPI

1、W25Q64

使用SPI通信是基于Flash的存储器上的,故需要了解什么是W25Q64

特点:

W25Q64是一款Flash类型存储芯片.

内存大小64Mbit ==8Mbyte

通信接口是标准SPI, 支持 (0,0) 和 (1,1) 模式    MSB

手动擦除数据(写数据前要擦除空间)

不允许跨页写

内存单位补充:

1Byte=8bit

1KB = 1024Byte

1MB=1024Kb

1GB=1024MB

1TB=1024GB

存储结构:

存储空间是8M字节

内存区域划分:  块   扇区   页

块  :一共有128块,每块有16扇区

扇区:一个扇区有16页             

页  :一页有256byte

绝对地址:

十六进制::0~0x7F F F FF

某块 的 某扇区 的 某页 的 某个字节

 XX           X             X           XX

存储原理: 

此芯片只能写入0,不能写入1,擦除后的芯片空间每个字节都是0xff,1只能靠擦除后的1代替。所以,写数据之前擦除空间(擦除要以扇区擦除/块擦除/芯片擦除)

如果确保需要写入的空间是0xff就可以不用擦除

注意:①擦除最小空间至少是扇区  ②芯片擦除时间需要15~20s左右

为什么W25Q64写入0擦除后变成了0xff

在Flash存储器中,擦除操作通常是将存储单元中的数据位全部设置为1(即0xFF)。这是因为Flash存储器的物理结构使得它更容易将所有位设置为1,而不是设置为0。因此,在擦除过程中,无论存储单元中原本存储的是什么数据,都会被重置为全1的状态写入操作则是将特定的数据位从1更改为0(或从0更改为1,但这在Flash中通常是不允许的,因为Flash只能由1编程到0)。所以擦除操作将存储单元中的数据位全部重置为1(0xFF),是为了后续的写入操作做准备。因此,在擦除之后,无论之前存储的是什么数据,都会变成全1的状态。

引脚介绍:

CS:低电平选中芯片开始通信---PB14

WP:写保护--->低电平开启写保护

HOLD:低电平时其他引脚断开;高电平时,正常工作

SCLK :SCK---PA5

DO  :MISO---PA6                               

DI   :MOSI--PA7

注意:

1、带斜杠的CS(SS)—代表低电平有效

2、WP(Write Protect)—配合内部的寄存器配置,可以实现硬件的写保护(保护时不可写)低电平有效

3、 HOLD—数据保持—低电平有效

2、基于SPI底层协议对W25Q64进行读写操作

2.1 W25Q64状态及控制寄存器介绍
 

S0位:在执行擦除操作和写操作时,此位会自动置1,执行完后自动变为0

            所以在写完或擦除完后要等待此位变为0再继续执行程序

S1: 执行完写使能指令后置1,也就是在执行写操作和擦除操作之前需要执行写使能指令

S2-S5:写保护模块  写入0x00则可以解除写保护

S0位BUSY总结:

作用是什么 ?   

        配置位作用  :块区保护

        读状态位作用:写指令是否执行完成

如何配置(读)寄存器?

        配置:发送指令和发送配置值

        读  :发送指令和读寄存器值

什么时候使用?

        初始化配置模块的时候,解除所有块保护

        对W25Q64写操作后,要读寄存器,从而等待0号位变0

2.2 W25Q64读写操作

W25Q64的初始化函数:

/***************************************
*函数名:w25q64_init
*函数功能:w25q64初始化
*函数参数:无
*函数返回值    :无
*函数描述:CS---PB14
****************************************/
void w25q64_init(void)
{
 spi1_init();
/*片选所用IO初始化*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 1);
//端口模式
GPIOB->MODER &= ~(3 << 28);
GPIOB->MODER |=  (1 << 28);
//端口类型
GPIOB->OTYPER &= ~(1 << 14);
//输出速率  50M
GPIOB->OSPEEDR &= ~(3 << 28);
GPIOB->OSPEEDR |=  (2 << 28);
//上下拉
GPIOB->PUPDR &= ~(3 << 28);

//片选拉高
GPIOB->ODR |=  (1 << 14);
//配置寄存器解除所有块写保护
write_status(0x00);
}

写使能;指令:0x06

在执行写操作和擦除操作时,需要写使能

①为什么要将WEL位置1(因为要解除写保护以便执行对W25Q64写操作)

②如何将WEL置1(要执行写使能指令)

③如何执行写使能指令

配置:

1.先拉低片选

2.发送写使能指令(0x06); //spi发送接收函数

3.拉高片选

程序设计:

封装写使能函数(写操作时候的前提)

参数:无

/***************************************
*函数名:write_enable
*函数功能:写使能
*函数参数:无
*函数返回值    :无
*函数描述:对W25Q64进行写操作之前要执行写使能--指令0x06
  写操作时候的前提
****************************************/
void write_enable(void)
{
//片选拉低
W25Q64_CS_L;
//发送写使能指令(0x06)
spi1_byte(0x06);
//片选拉高
W25Q64_CS_H;
}

读状态寄存器:指令0x05

状态寄存器的0位为忙位,通过判断此位来确定芯片是否可以接受下一次操作。

页写,擦除,写状态寄存器的时候,要等待执行完毕

1:忙状态

0:不忙可以接受下一条指令

配置:

1.先拉低片选

2.发送指令0x05        //返回值不需要接收

3.接收状态寄存器的值  //发送参数随便

4.片选拉高

5.返回状态值

程序设计:

封装读状态寄存器函数(写操作后等待完成)

参数:无

有返回值


/***************************************
*函数名:read_status
*函数功能:读状态寄存器
*函数参数:无
*函数返回值    :u8
*函数描述:写操作后等待完成--指令0x05
****************************************/
u8 read_status(void)
{
u8 status_val;
//片选拉低
W25Q64_CS_L;
//发送指令(0x05)
spi1_byte(0x05);
//接收状态寄存器的值
status_val = spi1_byte(0xff);
//片选拉高
W25Q64_CS_H;
//返回状态值
return status_val;
}

写控制及状态寄存器:指令0x01

通过对控制及状态寄存器的配置,实现控制块区写保护.

配置:

0.写使能

1.片选拉低

2.发送写寄存器指令

3.发送配置数据

4.拉高片选

5.等待BUSY位为0

程序设计:

封装写状态寄存器函数(初始化时候解除所有块写保护)

参数:u8 cmd_data 需要发的数据

/***************************************
*函数名:write_status
*函数功能:配置寄存器
*函数参数:无
*函数返回值    :无
*函数描述:初始化时候解除所有块写保护--指令0x01
****************************************/
void write_status(u8 cmd_data)
{
//写使能
write_enable();
//片选拉低
W25Q64_CS_L;
//发送写寄存器指令(0x01)
spi1_byte(0x01);
//发送配置数据
spi1_byte(cmd_data);
//片选拉高
W25Q64_CS_H;
//等待配置完成
while(read_status() & 0x01);
}

页写操作:指令0x02

在某一页(256byte)写数据可以连续写,但是不能跨页写

配置:

0.写使能

1.片选拉低

2.发送写数据指令

3.发送24位地址

4.循环发送数据

5.片选拉高

6.等待写入完成

程序:

封装页写函数

函数参数: u32 inner_addr        要写入的起始地址

                u8 *data               要写入数据的首地址

                u16 len               写入数据的长度

/***************************************
函数名  :w25q64_page_write
函数功能:  页写函数
函数参数: u32 inner_addr  要写入的起始地址
u8 *data        要写入数据的首地址
u16  len        写入数据的长度
返回值  :  无
****************************************/
void w25q64_page_write(u32 inner_addr,u16 len,u8 *data)
{
//写使能
write_enable();
//片选拉低
W25Q64_CS_L;
//发送写数据指令
spi1_byte(0x02);
//发送24位地址
spi1_byte(inner_addr>>16);
spi1_byte(inner_addr>>8);
spi1_byte(inner_addr);
//循环发送数据
while(len)
{
spi1_byte(*data);
len--;
data++;
}
//片选拉高
W25Q64_CS_H;
//等待写入完成
//等待配置完成
while(read_status() & 0x01);

}

⑤连续页写(可跨页)

程序:

封装连续页写函数

函数参数: u32 inner_addr        要写入的起始地址

                u8 *data          要写入数据的首地址

                u32  len               写入数据的长度

返回值  :  无

思路:

//计算本页还剩多少空间可写 less_byte = 8 - inner_addr % 8   

//如果要写的内容不需要跨页 less_byte >= num_byte

//调用发送页写函数(inner_addr,len,str)

//如果要写的内容需要跨页 less_byte < num_byte

//调用发送页写函数把本页剩下的空间写完(inner_addr,less_byte,data)

//计算出剩下多少个字节:num_byte = num_byte - less_byte

//下一页的首地址: inner_addr = inner_addr + less_byte

//剩余要写的内容数据的地址:data = data + less_byte

/***************************************
函数名  :w25q64_page_write
函数功能:连续页写
函数参数: u32 inner_addr  要写入的起始地址
u8 *data        要写入数据的首地址
u32  len         写入数据的长度
返回值  :  无
****************************************/
void w25q64_pages_write(u32 inner_addr,u32 len,u8 *data)
{
u16 less_byte;

while(1)
{
//计算本页还剩多少空间可写 less_byte = 8 - inner_addr % 8   
less_byte = 256 - inner_addr % 256;
//如果要写的内容不需要跨页 less_byte >= num_byte
if(less_byte >= len)
{
//调用发送页写函数(inner_addr,len,str)
w25q64_page_write(inner_addr,len,data);
break;
}
//如果要写的内容需要跨页 less_byte < num_byte
else 
{
//调用发送页写函数把本页剩下的空间写完(inner_addr,less_byte,data)
w25q64_page_write(inner_addr,less_byte,data);
//计算出剩下多少个字节:num_byte = num_byte - less_byte
len = len - less_byte;
//下一页的首地址: inner_addr = inner_addr + less_byte
inner_addr = inner_addr + less_byte;
//剩余要写的内容数据的地址:data = data + less_byte
data = data + less_byte;
}
}

}

⑥连续读数据操作:指令0x03

可以连续读,不考虑跨页.

配置:

1.先拉低片选 (CS拉低)

2.发送读数据指令(0x03)

3.发送24位(3字节)地址

4.可以连续读(数据线随便发)

5.拉高片选

程序:

封装连续读函数

参数  : u32 innner_addr

           u8 *data

           u32 len

/***************************************
函数名  : w25q64_read_bytes
函数功能:在W25q64中进行连续读
函数参数: u32 inner_addr    从哪个地址开始读
u8 *data        读到数据存放的地址
    u8  len         读取数据的长度
返回值  :  u8 
****************************************/
void w25q64_read_bytes(u32 inner_addr,u16 len,u8 *data)
{

//1.先拉低片选 (CS拉低)
W25Q64_CS_L;
//2.发送读数据指令(0x03)
spi1_byte(0x03);
//3.发送24位(3字节)地址
spi1_byte(inner_addr>>16);
spi1_byte(inner_addr>>8);
spi1_byte(inner_addr);
//4.可以连续读(数据线随便发)
while(len)
{
*data = spi1_byte(0xff);
len--;
data++;
}
//5.拉高片选
W25Q64_CS_H;

}

扇区擦除:0x20

写入扇区指令之前一定要执行写使能

配置:

0.写使能

1.先拉低片选 (CS拉低);

2.发送指令(0x20)

3.发送24位(3字节)地址

4.拉高片选

5.等待擦除完成

程序设计:

封装扇区擦除函数

参数: u32 inner_addr  所在扇区地址

/***************************************
函数名  : w25q64_sector_erase
函数功能:W25q64扇区擦除
函数参数: u32 inner_addr  所在扇区地址
返回值  :  无
****************************************/
void w25q64_sector_erase(u32 inner_addr)
{
//写使能
write_enable();
//先拉低片选 (CS拉低);
W25Q64_CS_L;
//发送指令(0x20)
spi1_byte(0x20);
//发送24位(3字节)地址
spi1_byte(inner_addr>>16);
spi1_byte(inner_addr>>8);
spi1_byte(inner_addr);
//拉高片选
W25Q64_CS_H;
//等待擦除完成
while(read_status() & 0x01);
}

⑧块区擦除:0xD8

写入区指令之前一定要执行写使能

配置:

0.写使能

1.先拉低片选

2.发送指令(0xD8)

3.发送24位(3字节)地址

4.拉高片选

5.等待擦除完成

程序设计:

封装块区擦除函数

参数:u32 inner_addr  所在块区地址

/***************************************
函数名  : w25q64_block_erase
函数功能:W25q64块擦除
函数参数: u32 inner_addr  所在块区地址
返回值  :  无
****************************************/
void w25q64_block_erase(u32 inner_addr)
{
//写使能
write_enable();
//先拉低片选 (CS拉低);
W25Q64_CS_L;
//发送指令(0xD8)
spi1_byte(0xD8);
//发送24位(3字节)地址
spi1_byte(inner_addr>>16);
spi1_byte(inner_addr>>8);
spi1_byte(inner_addr);
//拉高片选
W25Q64_CS_H;
//等待擦除完成
while(read_status() & 0x01);
}

⑨芯片擦除:0xC7

写入芯片指令之前一定要执行写使能

配置:

0.写使能

1.先拉低片选

2.发送指令(0xC7)

3.拉高片选

4.等待擦除完成

程序设计:

封装芯片擦除函数

参数:无

/***************************************
函数名  : w25q64_block_erase
函数功能:芯片擦除
函数参数: u32 inner_addr  所在块区地址
返回值  :  无
****************************************/
void w25q64_chip_erase(void)
{
//写使能
write_enable();
//先拉低片选 (CS拉低);
W25Q64_CS_L;
//发送指令(0xC7)
spi1_byte(0xC7);
//拉高片选
W25Q64_CS_H;
//等待配置完成
while(read_status() & 0x01);
}

⑩多块擦除 

/***************************************
函数名  : w25q64_blocks_erase
函数功能:多块擦除
函数参数: u32 inner_addr  所在块区地址  u8 blocks 要擦除的块数
返回值  :  无
****************************************/
void w25q64_blocks_erase(u32 inner_addr,u8 blocks)
{

while(blocks)
{
w25q64_block_erase(inner_addr);
inner_addr+=16*16*256;
blocks--;
}

}

3、具体使用

需求:往W25Q64存储结构体,并且读出

typedef struct book
{

u8 name[15];
u8 writer[15];
u8 number[15];
u32 hot;
  u32 sc;
float price;
}BK;
 

int main(void)
{
BK send_book = {"西游记","吴承恩","W201955",0,30,66.5};
BK rec_book;
NVIC_SetPriorityGrouping(5); //设置优先级分组
Usart1_init(115200);//串口初始化
Key_init();
LED_init();
Beep_init();
w25q64_init();
//擦除扇区
w25q64_sector_erase(0x025a02);

//擦除块区
//w25q64_block_erase(0x025a02);
//擦除芯片
//printf("开始擦除\r\n");
//w25q64_chip_erase();
//printf("擦除结束\r\n");
////2号块的5号扇区的10号页2号字节开始写
w25q64_pages_write(0x025a02,sizeof(send_book),(u8 *)&send_book);
w25q64_read_bytes(0x025a02,sizeof(rec_book),(u8 *)&rec_book);


while(1)
{
printf("name:%s writer:%s number:%s hot:%d sc:%d price:%.1f\r\n",rec_book.name,rec_book.writer,rec_book.number,rec_book.hot,rec_book.sc,rec_book.price);
printf("OK\r\n");
timer11_delay_ms(100);
}

}


原文地址:https://blog.csdn.net/weixin_56459724/article/details/142760035

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!