> 文章列表 > STM32 W25QXX芯片

STM32 W25QXX芯片

STM32 W25QXX芯片

 W25QXX芯片介绍
W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。
 

引脚介绍

下面只介绍W25Q64标准SPI接口,因为目前开发板上的封装使用的就是标准SPI接口。

 

下表是W25QXX的常用命令表

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

  • D1指示灯
  • K_UP和K_DOWN按键
  • USART1
  • SPI
  • W25Q64

在这里插入图片描述

 软件设计
STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式
  • PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚)名称为W25Q_CS
  • 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制

在这里插入图片描述

 MDK-ARM编程

  • 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
void MX_SPI2_Init(void){hspi2.Instance = SPI2;hspi2.Init.Mode = SPI_MODE_MASTER;	//设置为主模式hspi2.Init.Direction = SPI_DIRECTION_2LINES;	//双线模式hspi2.Init.DataSize = SPI_DATASIZE_8BIT;	//	8位数据长度hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;	//串行同步时钟空闲状态为高电平hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;	//第二个跳变沿采样hspi2.Init.NSS = SPI_NSS_SOFT;	//NSS软件控制hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;	//分配因子256hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;	//MSB先行hspi2.Init.TIMode = SPI_TIMODE_DISABLE;	//关闭TI模式hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;	//关闭硬件CRC校验hspi2.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi2) != HAL_OK){Error_Handler();}
}void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){GPIO_InitTypeDef GPIO_InitStruct = {0};if(spiHandle->Instance==SPI2){__HAL_RCC_SPI2_CLK_ENABLE();  __HAL_RCC_GPIOB_CLK_ENABLE();/SPI2 GPIO Configuration    PB13     ------> SPI2_SCKPB14     ------> SPI2_MISOPB15     ------> SPI2_MOSI */GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_14;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}
}
  • 创建按键驱动文件key.c 和相关头文件key.h。
  • 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h
#include "w25q64.h"
#include "redirect.h"/*内部函数声明区*/
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);/*内部函数定义区*//*
函数参数: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;
}/*
读状态寄存器参数:Select1、为0时是寄存器12、为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_Addr1、读取数据的地址参数:R_Data(数组指针)1、获取读取的数据参数:R_Size1、读取的数据的大小
*/
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个字节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 字节,地址将自动回到页的起始地址,覆盖掉之前的数据。参数:WriteAddr1、地址,三个字节地址参数:PW_Data(数组指针)1、要写入的数据,长度根据PW_size来定2、高位先传参数:PW_Size2、要写入的数据长度流程:先开写使能、判忙,再写命令,再写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个字节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_Addr1、扇区地址,以4KB为单位寻址2、高位先发
*/
HAL_StatusTypeDef Sector_Erase(uint32_t Sector_Addr)
{uint8_t cmd = W25Q_Sector_Erase;HAL_StatusTypeDef STD = HAL_ERROR;//一个扇区有4KB的大小,//为了使找到对应的扇区地址,所以要乘以4KBSector_Addr *= (1<<12);Sector_Addr <<= 8;	//只需要24位表示地址,并且高位先传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_Addr1、块地址,共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的大小,//为了使找到对应的块地址,所以要乘以64KBBlock_Addr *= (1<<16);Block_Addr <<= 8;	//只需要24位表示地址,并且高位先传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;
}
#ifndef W25Q64__H__
#define W25Q64__H__#include "spi.h"/*句柄重命名*/
#define W25Q_SPI					hspi2/*片选引脚定义与函数调用*/
#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__*/

main.c

头文件

#include "w25q64.h"

变量

uint8_t device_id[3];
uint8_t read_buf[10] = {0};
uint8_t write_buf[10] = {0};
int i;

代码主体

	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));}