STM32学习笔记---SPI与W25Q64
目录
一、什么是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)!