当前位置:   article > 正文

使用cubemx工具的STM32对外部flash(W25Q64)的简单编程_cubemx w25q

cubemx w25q

SPI

SPI简介

SPI通信原理

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如flash闪存芯片W25Q64等。

多NSS片选图如下(还有其他方式,如菊花链等)

在这里插入图片描述

模式编号

在这里插入图片描述

  • CPOL代表的是SCK时钟线闲置状态下的电平,如CPOL为0时,SCK时钟线,闲置时为0,反之为1。如下图所示。
  • CPHA代表的是SCK在奇数边沿采样还是在数边沿采样,如下图,当CPHA=0时,在奇数边沿采样,也就是绿色画线的地方;当CPHA=1时,在偶数边沿采样,就是上面标着2、4、6……的地方。

在这里插入图片描述

SPI线的类型

  • 一般的SPI为4-wire模式
    芯片的管脚上只占用四根线。
    MISO(Master Input Slave Output): 主器件数据输出,从器件数据输入。
    MOSI(Master Output Slave Input):主器件数据输入,从器件数据输出。
    SCK(Slava Clock): 时钟信号,由主设备控制发出。
    NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。
  • SPI还有3-wire模式
    芯片的管脚只占用3根
    MISO/MOSI: 主从双向通信。
    SCK(Slava Clock): 时钟信号,由主设备控制发出。
    NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。
    其实质就是把MISO和MIOSI合成一条了,如下图所示。
    在这里插入图片描述

W25Q64

W25Q64介绍

W25Q64。W25Q 系列为台湾华邦公司推出的是一种使用 SPI 通讯协议的 NOR FLASH 存储器,64代表的是64Mbit的大小,也就是8MB的大小。
> 8MB被分为128个块(一个块64KB),而一个块又被分为16个扇区(一个扇区4KB)。
W25Q64的最小擦除单位是一个扇区,而我们有时候不想擦除一个扇区里面的全部内容,于是我们需要开辟一个至少4KB的缓存,假如我们当前要擦掉一个字节,我们可以临时开辟一个4KB的缓冲区,把扇区中的内容读入到缓冲区中,我们只要对应删掉缓冲区里的那个字节,然后擦除扇区,再把缓存区里的内容拿出来,存入这个扇区,我们就可以保证不会丢失不必要的数据了。

  • 芯片的特性:
  • 1.芯片擦除后,不是把擦除的位置清0,而是全部置1。
  • 2.芯片只有在擦除后,才能往里面写入数据(原先写入过数据的情况),若是未擦除就写入,则会导致,写入数据的地方全部清零。
    在这里插入图片描述
    引脚介绍(前面带个斜杠的都是低电平有效):
  • CS:片选端,当被主机拉低时代表选中该器件。
  • DO:数据输出端,主机读取该引脚发出的数据。
  • DI:数据输入端,主机输出数据到该引脚。
  • WP:写保护,低电平有效,当拉低电平时,不允许单片机写入数据,一般给他拉高。
  • HOLD:低电平时,芯片停止工作,数据输出呈现高阻态,数据输入无效,一般给他拉高。

W25Q64命令及其介绍

总览W25Q64命令

指令介绍

一般用的到的指令也就下面被红色框住的这几个,加上一个蓝色的那个
在这里插入图片描述

这些指令分别是:

  • 1、写使能(0x06)和写失能(0x04),只有写使能了,才能写入数据和擦除数据;
  • 2、读状态寄存器1(0x05)和读状态寄存器2(0x35);
  • 3、页写(0x02),只有在擦除之后才能正确往里面写入数据;
  • 4、块擦除(0xD8),因为W25Q64有64Mbit,而分为了128个块,所以一个块是64KB,所以块擦除用这个0xD8这个指令而不是0x52;
  • 5、扇区擦除(0x20),扇区是W25Q64所能擦除的最小单位,而一个扇区有4KB,所以一次最小能清除4KB,要是我们不想一次性清除这么多内存,可以临时开辟一个4KB的缓冲区(可以是主控芯片内部的缓冲区,也可以是其他存储类芯片)和这个扇区一一对应,用于删改特定的不是很大的数据。
  • 6、全片擦除(0xC7/0x60):全片擦除有两个指令,俺只试过其中一个,所以俺不知道,两种擦除是否是一样的,是否都是全1擦。
  • 7、读ID(0x9F),这里可以读出三种ID,第一个是制造商ID,第二个是存储器类型ID,第三个是容量ID。
  • 8、读数据(0x03),读出芯片中的数据。
指令与代码结合
例子1:写使能

我要写使能,我先把片选拉低选中芯片,然后我会发0x06(来之上面的表)指令,然后片选拉高,具体实现如下:

HAL_StatusTypeDef Write_Enable(uint8_t Type)
{
	uint8_t cmd = 0x06;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	W25Q_CS_Level(0);
	STD = w25q64_Transmit(&cmd, 1);
	W25Q_CS_Level(1);
	
	return STD;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
例子2:页写

先通过状态寄存器判断是否在忙,然后写使能,接着判忙,拉底片选选中,发命令字节0x02,发三个地址,高位优先,然后发你要写的数据,最后拉底片选,代码如下:

HAL_StatusTypeDef Page_Write(uint32_t WriteAddr, uint8_t * PW_Data, uint16_t PW_Size)
{
	uint8_t cmd = W25Q_Page_Program;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	WriteAddr <<= 8;	//只要24位,3个字节
	LargeToSmall((uint8_t *)&WriteAddr, sizeof(WriteAddr));
	
	Judge_Busy();	//判忙
	Write_Enable(1);	
	Judge_Busy();
	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&WriteAddr, 3) == HAL_OK)
		{
			if(w25q64_Transmit(PW_Data, PW_Size) == HAL_OK)
			{
				STD = HAL_OK;
			}
		}
	}

	W25Q_CS_Level(1);	
	return STD;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
例子3:块擦除

总共有128个块,选择块的范围在0~127,加入我们要找第2块,要找到芯片对应的块地址,得先把2*64KB(块对应的大小),得到第二给块区的位置。

先通过状态寄存器判断是否在忙,然后写使能,接着判忙,拉底片选选中,发命令字节0xD8,发三个地址,高位优先,最后拉底片选,代码如下:

HAL_StatusTypeDef	Block_Erase(uint32_t Block_Addr)
{
	uint8_t cmd = W25Q_Block_Erase;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	//总共有128个块,而一个块有64KB的大小,
	//为了使找到对应的块地址,所以要乘以64KB
	Block_Addr *= (1<<16);
	Block_Addr <<= 8;	//只需要24位表示地址,并且高位先传
	LargeToSmall((uint8_t *)&Block_Addr, sizeof(Block_Addr));
	
	Judge_Busy();
	Write_En_De(1);	
	Judge_Busy();
	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&Block_Addr, 3) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}
	
	W25Q_CS_Level(1);
	Judge_Busy();
	return STD;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

CubeMX

主要配置如下:

SPI配置:

  • 在 Connectivity 中选择 SPI1 设置
  • 并选择 Full-Duplex Master 全双工主模式
  • 不开启 NSS 即不使用硬件片选信号(NSS直接选择外部引出的任意引脚为输出模式即可)
  • 配置成时钟4分频
    在这里插入图片描述

这些配置从上到下分别是:

  • Frame Format:主从模式选择
  • Data Size:一次传输数据大小
  • First Bit:数据是高位在先还是低位在先
  • prescaler(for Baud Rate):分频值
  • Clock Polarity(CPOL):时钟空闲时的电平
  • Clock Phase(CPHA):数据取样是在奇数取样还是在偶数取样(1Edge是奇数取样,2Edge是偶数取样)

片选配置:

在这里插入图片描述

代码

w25q64.c

#include "w25q64.h"
#include "redirect.h"

/*内部函数声明区*/
static void LargeToSmall(uint8_t* type, uint8_t typeSize);
static HAL_StatusTypeDef w25q64_Transmit(uint8_t * T_pData, uint16_t T_Size);
static HAL_StatusTypeDef w25q64_Receive(uint8_t * R_pData, uint16_t R_Size);


/*内部函数定义区*/

//大端转小端
static void LargeToSmall(uint8_t* type, uint8_t typeSize)
{
	// 当大小小于1或者不是2的幂次位时退出
	if( typeSize <= 1 )
	{
		return;
	}
	for(uint8_t i=0; i<typeSize/2; i++)
	{
		*(type+i) = *(type+i) ^ *(type+typeSize-i-1);
		*(type+typeSize-i-1) = *(type+i) ^ *(type+typeSize-i-1);
		*(type+i) = *(type+i) ^ *(type+typeSize-i-1);
	}
}

/*
函数参数:
		1、T_pData:发送数据缓冲区中取出数据发送出去
		2、T_Size :需要发送的数据的长度
*/
static HAL_StatusTypeDef w25q64_Transmit(uint8_t * T_pData, uint16_t T_Size)
{
	return HAL_SPI_Transmit(&W25Q_SPI, T_pData, T_Size, 0xff);
}

/*
函数参数:
		1、R_pData:接收数据并放置到接收数据缓冲区中
		2、R_Size :需要接收的数据的长度
*/
static HAL_StatusTypeDef w25q64_Receive(uint8_t * R_pData, uint16_t R_Size)
{
	return HAL_SPI_Receive(&W25Q_SPI, R_pData, R_Size, 0xff);
}

/*
写使能或失能
	参数:
		Type:
			1、为1时使能
			2、为0时失能
*/
HAL_StatusTypeDef Write_En_De(uint8_t Type)
{
	uint8_t cmd;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	W25Q_CS_Level(0);

	switch(Type)
	{
		case 1:
			cmd = W25Q_W_ENA;
			break;
		case 0:
			cmd = W25Q_W_DIS;
			break;
		default:
			cmd = W25Q_W_DIS;
			break;
	}

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		STD = HAL_OK;
	}
	
	W25Q_CS_Level(1);
	return STD;
}

/*
读状态寄存器
	参数:Select
		1、为0时是寄存器1
		2、为1时是寄存器2
	参数:State(指针)
		1、返回的状态标志

	流程:先写入命令,然后读取状态
*/
HAL_StatusTypeDef Read_State_Reg(uint8_t Select, uint8_t* State)
{
	uint8_t cmd[4] = {0,0,0,0};
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	W25Q_CS_Level(0);
	
	switch(Select)
	{
		case 0:
			cmd[0] = W25Q_R_STA_REG1;
			break;
		case 1:
			cmd[0] = W25Q_R_STA_REG2;
			break;
		default:
			cmd[0] = W25Q_R_STA_REG1;
			break;
	}
		
	if(w25q64_Transmit(cmd, 4) == HAL_OK)
	{
		if(w25q64_Receive(State,1) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}

	W25Q_CS_Level(1);
	
	return STD;
}

/*
判忙
		用处:判断当前flash是否在忙碌状态
*/
void Judge_Busy(void)
{
	uint8_t State;	
	do{
		Read_State_Reg(0, &State);	//不要用指针类型局部变量传进去,必被卡死
		State &= 0x01;
	}while(State == 0x01);
}


/*
写状态寄存器
	参数:State(数组指针)
	参数解释:长度为两个字节的数组指针,
						第一个字节写入状态寄存器1;
						第二个字节写入状态寄存器2。

	流程:先写命令,再写状态
*/
HAL_StatusTypeDef Write_State_Reg(uint8_t * State)
{
	uint8_t cmd = W25Q_W_STA_REG_;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	Judge_Busy();
	Write_En_De(1);	
	W25Q_CS_Level(0);
//	Judge_Busy();
	
	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit(State, 2) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}
	
	W25Q_CS_Level(1);
	Write_En_De(0);	
	return STD;
}

/*
读数据
			参数:R_Addr
						1、读取数据的地址
			参数:R_Data(数组指针)
						1、获取读取的数据
			参数:R_Size
						1、读取的数据的大小
*/
HAL_StatusTypeDef Read_Data(uint32_t R_Addr, uint8_t * R_Data, uint16_t R_Size)
{
	uint8_t cmd = W25Q_R_Dat;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	R_Addr <<= 8;	//只要24位,3个字节
	LargeToSmall((uint8_t *)&R_Addr, sizeof(R_Addr));
	
	Judge_Busy();	//判忙
	W25Q_CS_Level(0);
//	Judge_Busy();
	
	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&R_Addr, 3) == HAL_OK)
		{
			if(w25q64_Receive(R_Data,R_Size) == HAL_OK)
			{
				STD = HAL_OK;
			}
		}
	}
	
	W25Q_CS_Level(1);
	
	return STD;
}


/*
页编程
	页的描述:1字节到 256 字节(一页)
	页写的前提条件:编程之前必须保证额你存空间是0xff,
									所以得先进行擦除(擦除后模式全为1)
	页写的注意事项:进行页编程时,如果数据字节数超过了 256 字节,
									地址将自动回到页的起始地址,覆盖掉之前的数据。

	参数:WriteAddr
				1、地址,三个字节地址
	参数:PW_Data(数组指针)
				1、要写入的数据,长度根据PW_size来定
				2、高位先传
	参数:PW_Size
				2、要写入的数据长度

	流程:先开写使能、判忙,再写命令,
				再写3个字节的地址,后写入数据,最后写失能
*/

HAL_StatusTypeDef Page_Write(uint32_t WriteAddr, uint8_t * PW_Data, uint16_t PW_Size)
{
	uint8_t cmd = W25Q_Page_Program;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	WriteAddr <<= 8;	//只要24位,3个字节
	LargeToSmall((uint8_t *)&WriteAddr, sizeof(WriteAddr));
	
	Judge_Busy();	//判忙
	Write_En_De(1);	
	Judge_Busy();

	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&WriteAddr, 3) == HAL_OK)
		{
			if(w25q64_Transmit(PW_Data, PW_Size) == HAL_OK)
			{
				STD = HAL_OK;
			}
		}
	}

	W25Q_CS_Level(1);
	Judge_Busy();
	
	return STD;
}



/*
扇区擦除
	扇区的描述:W25Q64总共8MB,分为128块(每块64KB),
							每块16个扇区,每个扇区4K个字节。
	扇区的备注:W25Q64的最小擦除单位就是一个扇区
							所以至少给芯片开辟一个4KB的缓存区,
							以防止一次性删除太多,而丢失数据。(显然单片机不会给他开辟这么大的空间)
	
	参数:Sector_Addr
				1、扇区地址,以4KB为单位寻址
				2、高位先发
*/
HAL_StatusTypeDef Sector_Erase(uint32_t Sector_Addr)
{
	uint8_t cmd = W25Q_Sector_Erase;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	//一个扇区有4KB的大小,
	//为了使找到对应的扇区地址,所以要乘以4KB
	Sector_Addr *= (1<<12);
	Sector_Addr <<= 8;	//只需要24位表示地址,并且高位先传
	LargeToSmall((uint8_t *)&Sector_Addr, sizeof(Sector_Addr));
	
	Judge_Busy();
	Write_En_De(1);	
	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&Sector_Addr, 3) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}

	W25Q_CS_Level(1);
	
	Judge_Busy();
	
	return STD;
}



/*
块擦除
	块的描述:W25Q64有8MB的容量,而8MB有128个块,所以1块有64kB的大小,
						所以这个函数一次能擦除64KB的大小。
	
	参数:Block_Addr
				1、块地址,共128个块,对应128个地址,以64K为单位寻址
				2、高位先传

	流程:先开写使能、判忙,再写命令,
				再写3个字节的地址,最后写失能
*/
HAL_StatusTypeDef	Block_Erase(uint32_t Block_Addr)
{
	uint8_t cmd = W25Q_Block_Erase;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	//总共有128个块,而一个块有64KB的大小,
	//为了使找到对应的块地址,所以要乘以64KB
	Block_Addr *= (1<<16);
	Block_Addr <<= 8;	//只需要24位表示地址,并且高位先传
	LargeToSmall((uint8_t *)&Block_Addr, sizeof(Block_Addr));
	
	Judge_Busy();
	Write_En_De(1);	
	Judge_Busy();
	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Transmit((uint8_t *)&Block_Addr, 3) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}
	
	W25Q_CS_Level(1);
	Judge_Busy();
	return STD;
}


/*
全片擦除
	描述:直接把芯片全部擦除
*/
HAL_StatusTypeDef Full_Erase(void)
{
	uint8_t cmd = W25Q_Full_Erase;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	Judge_Busy();
	Write_En_De(1);	
	W25Q_CS_Level(0);

	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		STD = HAL_OK;
	}

	W25Q_CS_Level(1);
	Judge_Busy();
	
	return STD;
}


/*
读ID
	描述:读3个字节分别是生产厂家、存储器类型、容量
*/
HAL_StatusTypeDef Read_Jedec_ID(uint8_t * R_Jedec_ID)
{
	uint8_t cmd = W25Q_JEDEC_ID;
	HAL_StatusTypeDef STD = HAL_ERROR;
	
	Judge_Busy();
	W25Q_CS_Level(0);
//	Judge_Busy();
	
	if(w25q64_Transmit(&cmd, 1) == HAL_OK)
	{
		if(w25q64_Receive(R_Jedec_ID, 3) == HAL_OK)
		{
			STD = HAL_OK;
		}
	}
	
	W25Q_CS_Level(1);

	return STD;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400

w25q64.h

#ifndef W25Q64__H__
#define W25Q64__H__

#include "spi.h"

/*句柄重命名*/
#define W25Q_SPI					hspi1

/*片选引脚定义与函数调用*/
#define W25Q_CS_Pin 			GPIO_PIN_0
#define W25Q_CS_Port			GPIOC

#define W25Q_CS_Level(_CS_STATE__)		HAL_GPIO_WritePin(W25Q_CS_Port, W25Q_CS_Pin, (GPIO_PinState)_CS_STATE__)

//#define W25Q_CS_Level(_CS_STATE__) (*((volatile unsigned int *)(0x42000000+((uint32_t)&GPIOC->ODR-0x40000000)*32+0*4))) = _CS_STATE__

#define W25Q_W_ENA				0x06		//写使能
#define W25Q_W_DIS				0x04		//写禁止

#define W25Q_R_Dat				0x03		//读数据

#define W25Q_R_STA_REG1		0x05		//读状态寄存器1,紧跟着的字节就是当前状态
#define	W25Q_R_STA_REG2		0x35		//读状态寄存器2,紧跟着的字节就是当前状态

#define W25Q_W_STA_REG_		0x01		//写状态寄存器,写入两个字节,分别到寄存器1,和寄存器2

#define W25Q_Page_Program 0x02		//页编程,先跟3个地址字节,再跟一个数据字节

#define W25Q_Block_Erase	0xD8		//块擦除64k,三个地址字节

#define W25Q_Sector_Erase	0x20		//扇区擦除,跟三个地址

#define W25Q_Full_Erase		0xC7		//全片擦除
												//0x60
												
#define W25Q_Susp_Erase		0x75		//暂停擦除

#define W25Q_Rest_Erase		0x7A		//恢复擦除

#define W25Q_PowDow_Mode	0xB9		//掉电模式

#define W25Q_HPer_Mode		0xA3		//高性能模式

#define W25Q_JEDEC_ID			0x9F		//读3个字节分别是生产厂家、存储器类型、容量

/*写使能或失能*/
HAL_StatusTypeDef Write_En_De(uint8_t Type);

/*读状态寄存器*/
HAL_StatusTypeDef Read_State_Reg(uint8_t Select, uint8_t* State);

/*判忙*/
void Judge_Busy(void);

/*写状态寄存器*/
HAL_StatusTypeDef Write_State_Reg(uint8_t * State);

/*读数据*/
HAL_StatusTypeDef Read_Data(uint32_t R_Addr, uint8_t * R_Data, uint16_t R_Size);

/*页写*/
HAL_StatusTypeDef Page_Write(uint32_t WriteAddr, uint8_t * PW_Data, uint16_t PW_Size);

/*扇区擦除*/
HAL_StatusTypeDef Sector_Erase(uint32_t Sector_Addr);

/*块擦除*/
HAL_StatusTypeDef	Block_Erase(uint32_t Block_Addr);

/*全片擦除*/
HAL_StatusTypeDef Full_Erase(void);

/*读ID*/
HAL_StatusTypeDef Read_Jedec_ID(uint8_t * R_Jedec_ID);


#endif /*W25Q64__H__*/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

main.c

头文件

#include "w25q64.h"
  • 1

变量

uint8_t device_id[3];
uint8_t read_buf[10] = {0};
uint8_t write_buf[10] = {0};
int i;
  • 1
  • 2
  • 3
  • 4

代码主体

	
  Read_Jedec_ID((uint8_t *)device_id);
  printf("W25Q64 ID 0x%x, 0x%x, 0x%x\r\n", device_id[0], device_id[1], device_id[2]);

  /* 为了验证,首先读取要写入地址处的数据 */
  printf("-------- read data before write -----------\r\n");
  Read_Data(0, read_buf, 10);

  for(i = 0; i < 10; i++) 
  {
    printf("[0x%08x]:0x%02x\r\n", i, *(read_buf+i));
  }

  /* 擦除该扇区 */
  printf("\r\n-------- erase sector 0 -----------\r\n");
  Sector_Erase(0);

  /* 再次读数据 */
  printf("-------- read data after erase -----------\r\n");
  Read_Data(0, read_buf, 10);
  for(i = 0; i < 10; i++)
  {
    printf("[0x%08x]:0x%02x\r\n", i, *(read_buf+i));
  }

  /* 写数据1,擦除扇区后的写入 */
  printf("\r\n-------- write data 11111 -----------\r\n");
  for(i = 0; i < 10; i++)
  {
    write_buf[i] = i;
  }
  Page_Write(0, write_buf, 10);

  /* 再次读数据 */
  printf("-------- read data after write -----------\r\n");
  Read_Data(0, read_buf, 10);
  for(i = 0; i < 10; i++)
  {
    printf("[0x%08x]:0x%02x\r\n", i, *(read_buf+i));
  }
	
//	Sector_Erase(0);
  /* 写数据2,未擦除连续写入,观察未擦除能否写入 */
  printf("\r\n-------- write data 22222 -----------\r\n");
  for(i = 0; i < 10; i++)
  {
    write_buf[i] = i*16;
  }
  Page_Write(0, write_buf, 10);

  /* 再次读数据 */
  printf("-------- read data after write -----------\r\n");
  Read_Data(0, read_buf, 10);
  for(i = 0; i < 10; i++)
  {
    printf("[0x%08x]:0x%02x\r\n", i, *(read_buf+i));
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

效果

在这里插入图片描述
在这里插入图片描述

资料

基于3线spi通信的oled(cubemx图形化编程软件).

嵌入式硬件入门——Flash Memory(W25Q64+SPI协议).

STM32CubeMX学习笔记(10)——SPI接口使用(读写SPI Flash W25Q64).

数据手册1.

数据手册2.

数据手册3.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/219946
推荐阅读
相关标签
  

闽ICP备14008679号