【STM32篇】SPI时序驱动W25Q64(硬件SPI和模拟SPI)

这篇具有很好参考价值的文章主要介绍了【STM32篇】SPI时序驱动W25Q64(硬件SPI和模拟SPI)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        由于MCU的FLASH空间有限,在特殊使用场所中会存在FLASH存储不够使用的情况。例如上篇中驱动LCD屏,需要将一个中文字库保存到MCU的FLASH中是不太现实的(STM32F103ZET6内部FLASH大小512KB),为此可使用外部FLASH作为拓展。

1. W25Q64简介

w25q64驱动,M3,stm32,嵌入式硬件,单片机

        W25Q64(64Mbit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行FLASH存储器。25Q系列比普通的串行FLASH存储器更灵活,性能更优越。基于双倍/四倍的SPI,它们能够可以立即完成提供数据给RAM,包括声音、文本和数据。

        W25Q64由每页256字节组成,每页的256字节用一次页编程指令即可完成。每次可以擦除16页(一个扇区),128页(32KB块),256页(64KB块)和全片擦除。

        W25Q64的内存空间结构:一页256字节,4k(4096字节)为一个扇区,16个扇区为一块,总容量为8M字节,共有128个块即2048个扇区。SPI最高支持80MHZ,当使用快读双倍/四倍指令时,相当于双倍输出时最高速率160MHZ,四倍输出时最高速率320MHZ。

特点:

  • 标准、双倍和四倍SPI​​​​​​
  • 高性能串行FLASH存储器
  • 灵活的4KB扇区结构

       统一的扇区擦除和块擦除, 一次编程256字节,至少100000写/擦除周期,数据保存20年。

  • 高级的安全特点
  • 低功耗、宽温度范围

        单电源2.7~3.6V,工作电流4mA,-40℃~85℃工作。

封装

w25q64驱动,M3,stm32,嵌入式硬件,单片机
8Pin SOIC 208-mil

 引脚描述

引脚编号 引脚名称 I/O 功能
1 /CS I 片选端输入
2 DO(IO1) I/O 数据输出
3 /WP(IO2) I/O 写保护输入
4 GND
5 DI(IO0) I/O 数据输入
6 CLK I 串行时钟输入
7 /HOLD(IO3) I/O 保持端输入
8 VCC 电源

1.1 片选端(/CS)

        SPI片选(/CS)引脚使能和禁止芯片操作。当/CS为高电平时,芯片未被选择,串行数据输出(IOx)引脚为高阻态,芯片处于待机状态下的低功耗,除非芯片内部在擦除。当/CS变成低电平,芯片功耗将增长到正常工作,能够从芯片上读写数据。上电后,在接收新的指令前,/CS必须由高电平变成低电平

1.2 串行数据输入输出(DI、DO)

       标准的 SPI 传输用单向的 DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。标准的SPI 用单向的 DO(输出)在 CLK 的下降沿从芯片内读出数据或状态

1.3 写保护(/WP)

        写保护引脚(/WP)用来保护状态寄存器。和状态寄存器的块保护位(SEC、TB、BP2、BP1 和BP0)和状态寄存器保护位(SRP)对存储器进行一部分或者全部的硬件保护。/WP 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/WP 引脚(硬件写保护)的功能不可用,被用作了 IO2。

1.4 保持端(/HOLD)

        当/HOLD 引脚是有效时,允许芯片暂停工作。在/CS 为低电平时,当/HOLD 变为低电平,DO 引脚将变为高阻态,在 DI 和 CLK 引脚上的信号将无效。当/HOLD 变为高电平,芯片恢复工作。/HOLD功能用在当有多个设备共享同一 SPI 总线时。/HOLD 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/ HOLD 引脚的功能不可用,被用作了 IO3。

1.5 串行时钟(CLK)

        串行时钟输入引脚为串行输入和输出操作提供时序。设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从上升沿开始。

1.6  结构框图

w25q64驱动,M3,stm32,嵌入式硬件,单片机

1.7 SPI操作

        W25Q64/16/32 兼容的 SPI 总线包含四个信号:串行时钟(CLK)、片选端(/CS)、串行数据输入(DI)和串行数据输出(DO)。标准的 SPI 用 DI 输入引脚在 CLK 的上升沿连续的写命令、地址或数据到芯片内。DO 输出在 CLK 的下降沿从芯片内读出数据或状态。
        支持 SPI 总线的工作模式 0(0,0)和 3(1,1)。模式 0 和模式 3 的主要区别在于常态时的 CLK信号,当 SPI 主机已准备好数据还没传输到串行 Flash 中,对于模式 0 CLK 信号常态为低。
        设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲
状态时,时钟线CLK 为高电平。

1.8 状态寄存器

w25q64驱动,M3,stm32,嵌入式硬件,单片机         W25Q64的状态寄存器支持读写操作,读状态寄存器(指令:05H),写状态寄存器(指令:01H)。

        S0:忙位(BUSY)。BUSY位是个只读位。当器件在执行“页编程”“扇区擦除”“块区擦除”“芯片擦除”“写状态寄存器”指令时,该位自动置一。这时,除了“读状态寄存器”指令,其他指令都忽略。当编程、擦除和写状态寄存器指令执行完毕之后,该位自动变为0,表示该芯片可以接收其他指令了。

        S1:写保护位(WEL)。WEL位是个只读位,位于状态寄存器中的S1。执行完“写使能”指令后,该位置一。当芯片处于“写保护状态”下,该位为0。下面两种情况下,会进入“写保护状态”:

  •         掉电后。
  •         执行完以下指令后:写禁能,页编程,扇区擦除,块区擦除,芯片擦除和写状态寄器。

其他状态位:略。

2. 引脚连接

w25q64驱动,M3,stm32,嵌入式硬件,单片机

 注: 该原理图为野火霸道开发板上的原理图。

PA4 /CS
PA5 CLK
PA6 DO/IO1
PA7 DI/IO0

3. SPI

3.1 硬件SPI

        本次使用的MCU为STM32F103ZET6,硬件SPI详细介绍请仔细查看STM32F10x用户手册。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
SPI1 复用功能重映射

         使用MCU硬件SPI时,需要选择合适的IO口,并使能AFIO外设。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
SPI 框图

 通常SPI通过4个引脚与外部器件相连:

  • MISO:主设备输入/从设备输出。在从模式下发送数据,主模式下接收数据。
  • MOSI:主设备输出/从设备输入。在从模式下接收收据,主模式下发送数据。
  • SCK:  串口时钟,作为主设备的输出,从设备的输入。
  • NSS:从设备选择(片选引脚)。

在本次使用过程中,应该将SPI1设为主设备,用于控制W25Q64。

3.1.1 时钟极性和时钟相位

        SPI_CR寄存器的CPOL和CPHA位,能够组合成4种可能的时序关系。CPOL(时钟极性)控制在没有数据传输时时钟的空闲状态电平:CPOL=0时,空闲时钟电平为低电平;CPOL=1时,空闲时钟电平为高电平。CPHA(时钟相位)用于控制数据在第几个边沿被锁存:CPHA=0,数据在第一个时钟边沿被锁存;CPHA=1,数据在第二个时钟边沿被锁存。时钟相位需要搭配时钟极性使用,才能决定数据在哪个时钟边沿被锁存。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
CPHA=1

 如上图所示,时钟相位为1,即第二个时钟边沿数据被锁存。 

当时钟极性(CPOL)为1时,CLK空闲电平为高电平,第①个边沿为下降沿,第②个边沿为上升沿。数据在CLK上升沿是被锁存。主设备在时钟为低电平时可给从设备发送数据,在时钟为高电平时读取数据(模拟SPI思路)。

当时钟极性(CPOL)为0时,CLK空闲电平为低电平,第①个边沿为上升沿,第②个边沿为下降沿。数据在CLK下降沿是被锁存。主设备在时钟为高电平时可给从设备发送数据,在时钟为低电平时读取数据(模拟SPI思路)。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
CPHA=0

 时钟相位为0时,数据在第一个时钟边沿被锁存。

当时钟极性(CPOL)为1时,CLK空闲电平为高电平,第①个边沿为下降沿。数据在CLK下降沿是被锁存。主设备在时钟为高电平时可给从设备发送数据,在时钟为低电平时读取数据(模拟SPI思路)。

当时钟极性(CPOL)为0时,CLK空闲电平为低电平,第①个边沿为上升沿。数据在CLK上升沿是被锁存。主设备在时钟为低电平时可给从设备发送数据,在时钟为高电平时读取数据(模拟SPI思路)。

3.1.2 数据帧格式

根据SPI_CR1寄存器中的LSBFIRST位,输出数据位时可以MSB(高)在先也可以LSB在先。
根据SPI_CR1寄存器的DFF位,每个数据帧可以是8位或是16位。所选择的数据帧格式对发送和
/或接收都有效。

3.1.3配置为SPI主模式

w25q64驱动,M3,stm32,嵌入式硬件,单片机
寄存器开发模式

 如上图所示,为官方寄存器配置方式,主要是配置波特率用于配置SPI数据传输速率;时钟极性和时钟相位;帧格式(8位或16位,先发高位或先发低位);片选引脚工作模式等。为了减少翻手册查看寄存器时间,本次使用固件库配置SPI。

API:void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)

初始化SPI外设。

SPI_InitTypeDef        初始化结构体

typedef struct
{
  uint16_t SPI_Direction;          //设置SPI单向或者双向数据模式
  uint16_t SPI_Mode;               //SPI工作模式
  uint16_t SPI_DataSize;           //SPI数据大小
  uint16_t SPI_CPOL;               //时钟极性
  uint16_t SPI_CPHA;               //时钟相位
  uint16_t SPI_NSS;                //N片选引脚管理方式
  uint16_t SPI_BaudRatePrescaler;  //波特率预分频值
  uint16_t SPI_FirstBit;           //高位先发或者低位先发
  uint16_t SPI_CRCPolynomial;      //CRC计算
}SPI_InitTypeDef;

(1)SPI_Direction设置了 SPI 单向或者双向的数据模式。

SPI_Direction 描述
SPI_Direction_2Lines_FullDuplex SPI 设置为双线双向全双工
SPI_Direction_2Lines_RxOnly SPI 设置为双线单向接收
SPI_Direction_1Line_Rx SPI 设置为单线双向接收
SPI_Direction_1Line_Tx SPI 设置为单线双向发送

(2)SPI_Mode 设置了 SPI 工作模式。主SPI(SPI_Mode_Master ),从SPI(SPI_Mode_Slave)。

(3)SPI_DataSize 设置了 SPI 的数据大小。16位数据帧结构(SPI_DataSize_16b),8位数据帧结构(SPI 发送接收 8 位帧结构)。

(4)SPI_CPOL设置了空闲时钟电平。空闲时钟高电平(SPI_CPOL_High),空闲时钟低电平(SPI_CPOL_Low)。

(5)SPI_CPHA设置了捕获的时钟边沿。数据在第一个边沿被捕获(SPI_CPHA_1Edge),数据在第二个边沿被捕获(SPI_CPHA_2Edge)。

(6)SPI_NSS指定了 NSS 信号由硬件(NSS 管脚)还是软件(使用 SSI 位)管理。NSS由硬件管理(SPI_NSS_Hard),内部 NSS 信号由 软件控制(SPI_NSS_Soft)。

(7)SPI_BaudRatePrescaler 用来定义波特率预分频的值,这个值用以设置发送和接收的 SCK 时钟。

(8)SPI_FirstBit 指定了数据传输从 MSB 位还是 LSB 位开始。数据传输从 MSB 位开始(SPI_FisrtBit_MSB),数据传输从 LSB 位开始(SPI_FisrtBit_LSB)。

(9)SPI_CRCPolynomial 定义了用于 CRC 值计算的多项式。

3.1.4 SPI初始化配置

/*
	引脚连接:
	PA4	- #CS
	PA5 - SCK
	PA6 - MISO
	PA7 - MOSI
*/
void W25Q64_InitConfig(void)
{
	//1.配置GPIO
	GPIO_InitTypeDef w25q64_GPIO;
	w25q64_GPIO.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_7;
	w25q64_GPIO.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	w25q64_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&w25q64_GPIO);
	
	w25q64_GPIO.GPIO_Pin = GPIO_Pin_4;
	w25q64_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽输出
	w25q64_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&w25q64_GPIO);
	
	W25Q64_CS_H();//取消选择状态
	
	//2.SPI配置
	SPI_I2S_DeInit(SPI1);
	SPI_InitTypeDef w25q64_SPI;
	w25q64_SPI.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线双向全双工
	w25q64_SPI.SPI_Mode = SPI_Mode_Master;		//主机模式
	w25q64_SPI.SPI_DataSize = SPI_DataSize_8b;	//8位数据模式
	w25q64_SPI.SPI_CPOL = SPI_CPOL_High;		//时钟极性高
	w25q64_SPI.SPI_CPHA = SPI_CPHA_2Edge;		//第二个边沿
	w25q64_SPI.SPI_NSS = SPI_NSS_Soft;			//由软件控制
	w25q64_SPI.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
	w25q64_SPI.SPI_FirstBit = SPI_FirstBit_MSB;//高位先发
	w25q64_SPI.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1,&w25q64_SPI);
	//3.配置更新中断
	//SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
	//4.使能SPI1
	SPI_Cmd(SPI1,ENABLE);
}

(1)配置GPIO工作模式。CS由IO管脚自行控制,配置为通用推挽输出模式;其他3个管脚由SPI1控制,配置为复用推挽输出模式。

(2)配置SPI。在配置SPI前,可将片选引脚拉高。

        由于需要读写数据,所以配置为主机模式,双线双向全双工数据模式。

        数据帧格式配置为8为数据高位先发。

        时钟极性和时钟相位可配置为模式(0,3),这里配置为模式3,时钟极性为高,时钟相位为第二边沿捕获。

        片选引脚由软件控制。(硬件控制:SPI控制)

        时钟分频这里选择4分频。其他分频值也可。

(3)使能SPI1。

注:外设时钟在main.c中配置。

3.15 读写数据函数

uint8_t W25Q64_ReadWriteByte(uint8_t bety)
{
	//uint16_t count=4096;
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);//等待发送缓存区空
	SPI_I2S_SendData(SPI1,bety);
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);//等待接收缓存区非空
	return SPI_I2S_ReceiveData(SPI1);
}

 (1)等待发送存储器为空,这里可加延时等待,避免出现程序卡死现象。只有发送寄存器中没有数据了,才发送数据。

(2)等待接收缓存器中有数据,再接收数据。

3.2 模拟SPI

        模拟SPI相较于硬件SPI,模拟SPI不需要MCU由SPI这个外设,使用普通IO口就可以模拟SPI时序完成通信。

/*
	引脚连接:
	PA4	- #CS
	PA5 - SCK
	PA6 - MISO
	PA7 - MOSI
*/
#define W25Q64_SCK_H() GPIO_SetBits(GPIOA,GPIO_Pin_5)
#define W25Q64_SCK_L() GPIO_ResetBits(GPIOA,GPIO_Pin_5)
#define W25Q64_OUT_H() GPIO_SetBits(GPIOA,GPIO_Pin_7)
#define W25Q64_OUT_L() GPIO_ResetBits(GPIOA,GPIO_Pin_7)
#define W25Q64_IN()	   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)

uint8_t W25Q64_ReadWriteByte(uint8_t bety)
{
	uint8_t i;
	uint8_t data=0;
	W25Q64_SCK_L();
	for(i=0;i<8;i++)
	{
		bety & 0x80? W25Q64_OUT_H():W25Q64_OUT_L();
		bety <<=1;
		W25Q64_SCK_H();
		if(W25Q64_IN())
		{
			data |= (1<<(7-i));
		}
		W25Q64_SCK_L();
	}
	return data;
}

void W25Q64_InitConfig(void)
{
	//1.配置GPIO
	GPIO_InitTypeDef w25q64_GPIO;
	w25q64_GPIO.GPIO_Pin = GPIO_Pin_4| GPIO_Pin_5 | GPIO_Pin_7;
	w25q64_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;//复用推挽输出
	w25q64_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&w25q64_GPIO);
	
	w25q64_GPIO.GPIO_Pin = GPIO_Pin_6;
	w25q64_GPIO.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
	w25q64_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&w25q64_GPIO);
	
	W25Q64_CS_H();//取消选择状态
}

(1)配置GPIO工作模式,/CS,CLK,MOSI引脚配置为输出模式,MISO配置为输入模式。

(2)编写模拟SPI函数。在使用模拟SPI时,选择了模式0,时钟极性为0,时钟相位为0。时钟空闲电平为低电平,上升沿捕获数据。

  • 定义一个无符号字符类型的变量data,用于保存接收到的数据。
  • 拉低时钟管脚。
  • for循环中循环8次,读取或发送一个字节。
  • 上升沿时捕获数据,所以在时钟管脚低电平时输出高电平或低电平,使其在上升沿到来之前保持稳定。这里使用三目运算符获取要发送字节的最高位。并将低以为左移至最高位,便于下一位传输。
  • 拉高时钟管脚。
  • 在时钟管脚为高电平时读取数据。并移位至对应位。
  • 拉低时钟为下一次上升沿到来做准备,也可以在最后以为数据传输完成后保持时钟为高电平。

4. W25Q64

4.1 读取W25Q64制造/器件号(90H)

        在读写FLASH前,可用读取W25Q64的“制造/器件号”检验SPI读写函数是否可行。

        先把/CS引脚拉低,然后把指令90H通过函数W25Q64_ReadWriteByte发送到芯片,接着把24位地址000000H发送到芯片,然后芯片会把“制造ID”和“器件ID”在DO引脚上升沿发送出去,使用W25Q64_ReadWriteByte函数读取即可。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
读取制造/器件号时序
uint16_t W25Q64_GetID(void)
{
	uint16_t id=0;
	//发送读制造/器件号指令 0x90
	W25Q64_CS_L();
	W25Q64_ReadWriteByte(0x90);
	//发送24位地址
	W25Q64_ReadWriteByte(0x00);
	W25Q64_ReadWriteByte(0x00);
	W25Q64_ReadWriteByte(0x00);
	id = W25Q64_ReadWriteByte(0xFF)<<8;	//生产ID
	id |= W25Q64_ReadWriteByte(0xFF);		//器件ID
	W25Q64_CS_H();
	return id;
}

(1)拉低片选线。

(2)发送读取 “制造/器件号”指令90H,发送24位地址000000H。

(3)读取制造ID,读取器件ID。

(4)读取完毕,拉高片选。

制造ID=0xEF,器件ID=0x16。

w25q64驱动,M3,stm32,嵌入式硬件,单片机

 错误:在使用硬件SPI时,模式(0,3)读取ID以及收发数据和模拟SPI模式0相同。在使用模拟SPI模式3时,收发数据正常,读取ID就会存在ID号不同的问题。观看本篇文章学习的友友们可以试试模拟SPI模式3看看会不会出现该问题。

4.2写使能(06H)

        写使能指令将会使状态寄存器的WEL位置位。在执行每个“页编程”“扇区擦除”“块区擦除”“芯片擦除”和“写状态寄存器”指令之前,都要先置位WEL。/CS先拉低,向芯片发送06H指令,然后再拉高/CS引脚。

//写使能
void W25Q64_WriteENABLE(void)
{
	W25Q64_CS_L();
	W25Q64_ReadWriteByte(0x06);//写使能
	W25Q64_CS_H();
}

4.2 擦除指令

        在向FLASH中写入数据前必须保证内存空H,而擦除后的扇区位都为1,扇区字节都为FFH。

4.2.1 扇区擦除(20H)

        扇区擦除指令将一个扇区(16页4096字节)擦除,在执行扇区擦除指令前,需要先执行“写使能”指令,保证WEL位为1。

        先拉低/CS引脚,然后向芯片发送20H指令,接着把24位扇区地址发送到芯片,然后再拉高/CS。如果没有及时拉高/CS,指令将不起作用。在执行指令期间,BUSY位为1。在执行完指令后,BUSY为将复位,WEL位也会复位。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
扇区擦除时序
/*
	\brief:	扇区擦除
	\param:	addr 24位扇区地址
	\retval:	none
*/
void W25Q64_SectorErase(uint32_t addr)
{
	while(W25Q64_ReadState() & W25Q64_BUSY);//等待忙结束
	W25Q64_WriteENABLE();//写使能
	W25Q64_CS_L();
	W25Q64_ReadWriteByte(0x20);//扇区擦除
	W25Q64_ReadWriteByte((addr&0xFF0000)>>16);//发送24位地址
	W25Q64_ReadWriteByte((addr&0xFF00)>>8);
	W25Q64_ReadWriteByte((addr&0xFF));
	W25Q64_CS_H();
}

(1)在连续擦除扇区时,会存在上一个扇区代码执行结束了但擦除没有结束,所以要等待擦除结束。这里我用来死等,可加入超时时间。

(2)写使能,确保在擦除扇区前WEL位为1。

(3)拉低片选。

(4)发送扇区擦除指令,发送24为扇区地址。

(5)拉高片选。

4.2.2 块区擦除(DBH)

略。(详情请查看W25Q64数据手册,代码请查看附件)

4.3 读数据(03H)

        “读数据”指令允许读出一个字节或一个以上的字节被读出。先把/CS 引脚拉低,然后把 03h 通过 DIO 引脚送到芯片,之后再送入 24位的地址,这些数据在 CLK 的上升沿被芯片采集。芯片接收完 24 位地址之后,就会把相应地址的数据在 CLK 引脚的下降沿从 DO 引脚送出去,高位在前当读完这个地址的数据之后,地址自动增加,然后通过 DO 引脚把下一个地址的数据送出去,形成一个数据流。也就是说,只要时钟在工作,通过一条读指令,就可以把整个芯片存储区的数据读出来。把/CS 引脚拉高,“读数据”指令结束。当芯片在执行编程、擦除和读状态寄存器指令的周期内,“读数提”指令不起作用。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
读数据时序
/*
	\brief:	读数据
	\param:	addr:要读取的地址
				data:保存读取的数据
				size:读取的字节数(数据长度)
	\retval:	none
*/
void W25Q64_ReadData(uint32_t addr,uint8_t *data,uint16_t size)
{
	if(addr+size > W25Q64_END_ADDR) 
	{
		return ;
	}
	uint8_t *pData=data;
	while(W25Q64_ReadState() & W25Q64_BUSY);//等待忙结束
	W25Q64_CS_L();
	W25Q64_ReadWriteByte(0x03);//读数据指令
	W25Q64_ReadWriteByte((addr&0xFF0000)>>16);//发送24位地址
	W25Q64_ReadWriteByte((addr&0xFF00)>>8);
	W25Q64_ReadWriteByte((addr&0xFF));
	while(size--)
	{
		*pData=W25Q64_ReadWriteByte(0xFF);//保存数据
		pData++;
	}
	W25Q64_CS_H();
}

(1)判断地址是否操作FLASH最大地址范围。

(2)等待忙结束。

(3)拉低片选。

(4)发送读数据指令03H,发送要读取数据的起始地址。

(5)连续读取数据。这里设置data的大小为一个扇区,只能读取一个扇区数据。

(6)拉高片选。

4.4 页编程(02H)

        执行“页编程”指令之前,需要先执行“写使能”指令,而且要求待写入的区域位都为1,也就是需要先把待写入的区域擦除。先把/CS 引脚拉低,然后把代码 2h 通过 DIO 引送到芯片,然后再把 24 位地址送到芯片,然后接着送要写的字节到芯片。在写完数据之后,把/CS 引脚拉高。
        写完一页 (256 个字节)之后,必须把地址改为 0,不然的话,如果时钟还在继续,地址将自动变为页的开始地址。在某些时候,需要写入的字节不足 256 个字节的话,其它写入的字节都是无意义的。如果写入的字节大于了 256 个字节,多余的字节将会加上无用的字节覆盖刚刚写入的 256 个字节。所以需要保证写入的字节小于等于 256 个字节
        在指令执行过程中,用“读状态寄存器”可以发现 BUSY 位为 1,当指令执行完毕,BUSY位自动变为 0。如果需要写入的地址处于“写保护”状态,“页编程”指令无效。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
页编程时序

         在编程时,一次最多只能写一页的数据,发送编程指令在发送要写入的地址后,直接发送要写入的数据,地址会自动偏移。在编写页编程函数时,不考虑写入的数据是否超过一页而发生数据覆盖,就默认写入的字节在一页以内。

//页编程
void W25Q64_PageWrite(uint32_t addr,uint8_t *data,uint16_t size)
{
	if(addr+size > W25Q64_END_ADDR) 
		return ;
	uint8_t *pData = data;
	while(W25Q64_ReadState() & W25Q64_BUSY);//等待忙结束
	W25Q64_WriteENABLE();//写使能
	W25Q64_CS_L();
	//while(!(W25Q64_ReadState() & W25Q64_WEL));//等待写使能完成
	W25Q64_ReadWriteByte(0x02);//页写指令
	W25Q64_ReadWriteByte((addr&0xFF0000)>>16);//发送24位地址
	W25Q64_ReadWriteByte((addr&0xFF00)>>8);
	W25Q64_ReadWriteByte((addr&0xFF));
	while(size--)
	{
		W25Q64_ReadWriteByte(*pData);
		pData++;
	}
	W25Q64_CS_H();
}

(1)判断要写入数据的地址是否超过FLASH地址范围。

(2)等待忙结束,这个函数没有“擦除”,但具体调用函数前可能调用了“擦除函数”,所以要等待擦除完成。否则以下命令皆无效。

(3)写使能。在执行页编程前先执行写使能。

(4)拉低片选。

(5)发送页编程指令,接着发送24位FLASH地址。

(6)依次写入要写入的数据。

(7)拉高片选。页编程结束。

4.4.1 跨页写函数(不考虑擦除)

        在向W25Q64写入数据时,只支持页写操作,如果要写入更多的数据只能分一页一页依次写到FLASH中,不方便操作。W25Q64没有跨页写功能,但是可以一页一页依次写入,根据这个思路,可封装跨页写函数。

/*
	\brief:	可跨页写数据(不考虑擦除,认为写入的地址都为0xFF)
	\param:	addr:要写入的地址
				data:写入的数据
				size:数据的数量(字节数)
	\retval:	none
*/
void W25Q64_StepOverPageWrite(uint32_t addr,uint8_t *data,uint32_t size)
{
	uint32_t addr_remain= 256 - addr%256;//当前页地址剩余
	uint8_t *pData=data;
	if(size <= addr_remain)
	{
		addr_remain = size;
	}
	while(1)
	{
		W25Q64_PageWrite(addr,pData,addr_remain);
		if(addr_remain == size) break;		//数据全部写入
		pData += addr_remain;	//数据地址偏移
		addr += addr_remain;	//地址偏移
		size -= addr_remain;	//计算剩余数据
		addr_remain = 256;//写入一页数据
		if(size <= addr_remain)	//计算当前页是否够写入剩余数据
		{
			addr_remain = size;
		}
	}
}

(1)定义一个变量基于记录当前页剩余地址,计算当前页剩余地址。例如:0x00000F为要写入数据的起始地址。0x000000~0x0000FF为第一页(256字节),地址从0x00000F开始到0x0000FF,还有241个字节地址空间。256-15%256=241。

(2)判断要写入的数据是否超过当前页剩余空间,没有超过就意味着当前页够写要写入的数据量,就另addr_remain等于要写入的数据量。addr_remain变量在当前函数中作用很大,如果要写书的数据等于addr_remain,就意味着数据全部写入完成。

(3)在当前页地址写入addr_remain个字节数据。addr_remain最大256字节,不会造成数据覆盖,所以在页编程函数中没有加判断。

(4)如果addr_remain等于要写入的数据表示数据已经写入完成。因为在第(2)步做了判断,只有要写入的数据小于当前页剩余量,才会相等。不相等的情况是要写入的数据当前页剩余地址空间写不满,需要跨页。

(5)数据未全部写入,已经写了addr_remain字节,数据指针偏移addr_remain,FLASH地址偏移addr_remain(到了下一页起始地址),要写入的数据量减少addr_remain。

(6)计算当前页剩余空间(其实就是一整页256字节)。0x00000F+241=0x000100(0x100=256),256%256=0,256-0=256。

(7)判断当前页是否够写剩下的数据,如果剩余数据量超过一页,那么就只能写入一页数据,addr_remain = 256。当前页够写,则写下剩余数据。

(8)以此反复直至数据写完。

4.4.2 跨页写(考虑擦除和原有数据)

        在跨页写时,都没有考虑地址是否可写入数据,接下来考虑擦除问题。用的擦除函数为扇区擦除(4096字节),流程和跨页写相似。扇区擦除会把当前扇区的存储位都置一,会把原有数据都给清掉,所以在擦除函数前应该做好数据备份。

w25q64驱动,M3,stm32,嵌入式硬件,单片机
第二个扇区

         如图所示为第二个扇区的地址,共有16页,在写数据时,如果当前扇区的前2页都含有有用数据,现要在当前扇区的第3页写入一页数据。在擦除扇区时,该页将被全部擦除(包括有效数据),所以需要将该页的数据保存起来,最后一并写入。当然,如果不需要擦除,就直接写到第二页中。

/*
	\brief:	可跨页写数据(考虑擦除和原有数据)
	\param:	addr:要写入的地址
				data:写入的数据
				size:数据的数量(字节数)
	\retval:	none
*/

uint8_t sector_data[W25Q64_SECTOR_SIZE];
void W25Q64_WriteData(uint32_t addr,uint8_t *data,uint32_t size)
{
	uint16_t sector_offset = addr%4096;	//计算当前扇区的地址偏移
	uint16_t sector_remain = 4096 - sector_offset;	//计算当前扇区剩余
	uint32_t sector_addr = addr - sector_offset;	//计算当前扇区的起始地址
	uint8_t *pData=data;
	uint32_t i;
	if(size <= sector_remain)
	{
		sector_remain=(uint16_t )size;
	}
	while(1)
	{
		W25Q64_ReadData(addr,sector_data,sector_remain);//读取要写入地址的数据
		for(i=0;i<sector_remain;i++)
		{
			if(sector_data[i]!=0xFF) break;
		}
		if(i!=sector_remain)//判断是否需要擦除扇区
		{
			//擦除前保存当前扇区前一段数据
			W25Q64_ReadData(sector_addr,sector_data,sector_offset);
			//擦除前保存当前扇区后一段数据
			W25Q64_ReadData(addr + sector_remain,sector_data+(sector_offset+sector_remain),W25Q64_SECTOR_SIZE - (sector_offset+sector_remain));
			W25Q64_SectorErase(sector_addr);//擦除扇区
			//将要写入的数据插入缓冲区
			for(i=0;i<sector_remain;i++)
			{
				sector_data[sector_offset+i]= pData[i];
			}
			W25Q64_StepOverPageWrite(sector_addr,sector_data,W25Q64_SECTOR_SIZE);
			sector_offset = 0;
		}
		else
		{
			W25Q64_StepOverPageWrite(addr,pData,sector_remain);//向当前扇区写入数据
		}
		if(sector_remain == size) break;//全部数据完全写入
		
		pData += sector_remain;	//数据地址偏移
		addr += sector_remain;	//flash地址偏移
		sector_addr = addr;		//当前扇区起始地址
		size -= sector_remain;	//数据量减少
		sector_remain = W25Q64_SECTOR_SIZE;//当前扇区剩余
		if(size <= W25Q64_SECTOR_SIZE)//计算当前扇区是否够写入剩余数据
		{
			sector_remain = size;
		}
	}
}

例:从地址0x001300写入4096字节数据。

w25q64驱动,M3,stm32,嵌入式硬件,单片机

(1)计算要写入的起始地址在当前上去中的偏移量(768),扇区剩余空间(3328)和当前扇区的起始地址(0x001000)。

(2)判断当前扇区是否够写入4096字节数据。显然不够的。

(3)读取0x001300~0x001FFF的(3328字节)数据并判断含有不为0xFF的。

(4)不需要擦除,直接在0x001300地址依次写入3328字节数据。

(5)需要擦除,读取前半段地址(0x001000~0x000FFF)数据并保持至缓存区,以及后半段数据(因为这里需要写满该扇区,所以后半段没有数据需要保存)。再将3328字节数保存至缓存区前半段数据后。

(6)数据没有写完,还有768字节(4096-3328)。

(7)偏移数据地址和flash地址,当前扇区地址(变为0x002000),数据量减少3328字节,当前页剩余4096字节空间。

(8)当前页够写剩下的数据,另rector_remain=size。再回到(2)。

        调用该函数,就可以直接在flash中跨页写入数据且自带擦除功能,可以在多次写数据时不会因为扇区擦除而导致数据丢失。

5. 附件

链接:https://pan.baidu.com/s/1GWecV_hZuDM6DFUpf77Yxg?pwd=1234 
提取码:1234

6. 实际测试

        这里选择在0x2000000地址两侧分别写入数据,因为0x200000是下一个扇区的起始地址(也可以选择其他的两个扇区),0x200000-10是上一个扇区的末端。在上一个扇区0x200000-10地址处连续写入5字节数据,再从0x200000-5地址处跨扇区写入25字节数据,共写入30字节数据。

w25q64驱动,M3,stm32,嵌入式硬件,单片机

        可看到读取的前五的字节数据“abcde”和第一次写入的数据相同,并没有因为第二次写数据而被清除掉,第二次写数据也能实现跨页写数据。 

运用:

使用W25Q64保存字库,并在LCD中调用。

【STM32篇】LCD显示汉字(从W25Q64中读取GBK字库)

2023/07/19文章来源地址https://www.toymoban.com/news/detail-719057.html

到了这里,关于【STM32篇】SPI时序驱动W25Q64(硬件SPI和模拟SPI)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32】软件SPI读写W25Q64芯片

    目录 W25Q64模块 W25Q64芯片简介 硬件电路 W25Q64框图 Flash操作注意事项 状态寄存器 ​编辑 指令集 INSTRUCTIONS​编辑 ​编辑 SPI读写W25Q64代码 硬件接线图 MySPI.c MySPI.h W25Q64 W25Q64.c W25Q64.h W25Q64_Ins.h main.c 测试 SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片)  SPI通信文章:【

    2024年02月19日
    浏览(48)
  • 【STM32 CubeMX】SPI W25Q64功能实现

    SPI Flash 存储器在嵌入式系统中扮演着重要角色,它可以为微控制器提供额外的存储空间,并且具有快速的读写速度和较大的存储容量。W25Q64 是一款常见的 SPI Flash 存储器,容量为64Mb,采用 SPI 接口进行通信。在 STM32 微控制器上实现对 W25Q64 的功能使用,可以通过 STM32 CubeMX 和

    2024年02月22日
    浏览(73)
  • 【STM32】SPI初步使用 读写FLASH W25Q64

    (1) SS( Slave Select):从设备选择信号线,常称为片选信号线,每个从设备都有独立的这一条 NSS 信号线,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以 SPI通讯以 NSS 线置低电

    2024年02月10日
    浏览(56)
  • 【STM32】STM32学习笔记-软件SPI读写W25Q64(38)

    在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I 2 S音频协议。SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式。 在小容量和中容量产品上,不支持I 2 S音频协议。 串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方

    2024年01月24日
    浏览(50)
  • STM32驱动W25Q64读写数据

    1.采用串行Nor flash外扩存储芯片 2.支持SPI接口 3.工作电压:2.7~3.6V 4.容量: 32Mbit(W25Q32) 64Mbit(W25Q64) 128Mbit(W25Q128) 此处使用硬件SPI 引脚 功能 CS(NSS) 片选,低电平有效 SCK 时钟信号引脚 MISO/DO 模块数据输出引脚 MOSI/DI 模块数据输入引脚 W25Q64模块 STM32F103C8T6 VCC 3.3V SPI_CS GPIO

    2023年04月08日
    浏览(52)
  • STM-32:SPI通信协议/W25Q64简介—软件SPI读写W25Q64

    SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种高速、全双工、同步通信总线,所以可以在同一时间发送和接收数据,SPI没有定义速度限制,通常能达到甚

    2024年02月16日
    浏览(46)
  • Clion开发Stm32之存储模块(W25Q64)驱动编写

    涵盖之前文章: Clion开发STM32之HAL库SPI封装(基础库) 头文件 源文件

    2024年02月10日
    浏览(51)
  • W25Q64模块以及STM32代码

    W25Qxx 系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景 ( 易失性存储器:SRAM、DRAM...              非易失性存储器:E2PROM、Flash... ) 存储介质: Nor Flash (闪存) 时钟频率: 80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI

    2024年04月10日
    浏览(75)
  • STM32F103单片机通过SPI全双工通信协议与W25Q64(FLASH)进行通信【串行同步通信(数据线与时钟线配合),(一主多从模式)】附相关驱动代码详解

    1.W25Qxx系列是一种低成本、小型化、使用简单的 非易失性存储器 ,常应用于数据存储、字库存储、固件程序存储等场景 2.存储介质: Nor Flash(闪存) 3.时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI) 4.存储容量(24位地址): W25Q40: 4Mbit / 512KByte W25Q80: 8Mbit / 1MByte W25Q16: 16

    2024年04月13日
    浏览(60)
  • 26、江科大stm32视频学习笔记——W25Q64简介

    一、W25Q64简介 1、W25Q64的内存空间结构:  一页256字节,4K(4096 字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048 个扇区。   2、W25Q64每页大小由256字节组成,每页的256字节用一次页编程指令即可完成。 3、擦除指令分别支持: 16页(1个扇区)、128页、256页、全片

    2024年01月22日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包