STM32驱动QSPI Flash

SPI简介

QSPI是基于SPI改进的,原理和SPI相似,只是数据线变成了4线,速度更快

也可以使用2线和1线模式,4线和2线模式是半双工模式,1线模式和普通SPI无区别

关于SPI的简介,可以看我的另一篇文章《SPI简介与实例分析》



Flash W25Q64简介

W25Q64存储容量共64M-bit/ 8M-byte,32768页(pages)、每页256-bytes,最大一次可编程256-bytes

一次擦除大小可以为16页(4KB)、128页(32KB)、256页(64KB)或者全擦除

W25Q64JV有2048个可擦除扇区(sectors),或者可以说有128个可擦除块(blocks)。关系 1 Block = 16 sectors;1 sector = 4KB,所以算起来能达到8M-byte

编程即写数据,由于Flash的特性,只能从1编程0,所以写数据之前Flash里面的数据不是0xFF就必须先擦除,然后才能写数据。擦除即将Flash里面的数据恢复为0xFF的过程

上电后设备自动处于写禁用状态(Write Enable Latch, WEL为0,WEL是只读位)。在Page Program, Sector Erase, Block Erase, Chip Erase or Write Status Register instruction(页编程、区擦除、块擦除、芯片擦除或者写状态寄存器指令)之前必须先进行写使能指令。在编程、擦除、写状态寄存器指令完成后,WEL自动变成0

BUSY位是状态寄存器0的第0位,并且是只读位。当执行页编程、区擦除、块擦除、芯片擦除、写状态寄存器、擦除/编程安全寄存器指令时,其值为1,并且在此期间不再接收新的指令,但是可以接收读状态寄存器指令和擦除编程挂起指令


环境

MCU: STM32H750VB76
Flash:W25Q64
IDE:Keil V5.34.0.0 + STM32CubeMX V6.8.1 (Package V1.11.1)
STM32驱动QSPI Flash_第1张图片

原理框图

本文不使用用双闪存模式
STM32驱动QSPI Flash_第2张图片

STM32驱动QSPI Flash_第3张图片
STM32驱动QSPI Flash_第4张图片

创建STM32CubeMX工程

创建工程不是本文重点,这里仅贴出QSPI的配置
STM32驱动QSPI Flash_第5张图片



开始编程

复位Flash

STM32驱动QSPI Flash_第6张图片
代码如下:

int QSPI_Flash_Reset()
{
    QSPI_CommandTypeDef s_command;

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
	s_command.AddressMode = QSPI_ADDRESS_NONE;				    // 无地址模式
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.DataMode = QSPI_DATA_NONE;					    // 无数据模式
	s_command.DummyCycles = 0;								    // 空周期个数
	s_command.Instruction = W25Qxx_ENABLE_RESET;			    // 执行复位使能命令

	// 发送复位使能命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_INIT;
	}
	
    HAL_Delay(5);
	s_command.Instruction = W25Qxx_RESET;                       // 执行复位命令

	// 发送复位器件命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_INIT;
	}
	
    HAL_Delay(10);

	return QSPI_W25Qxx_OK;
}



读ID

STM32驱动QSPI Flash_第7张图片
代码如下:

uint32_t QSPI_Flash_ReadID()
{
	QSPI_CommandTypeDef s_command;                              
	uint8_t QSPI_ReceiveBuff[3];                                // 存储QSPI读到的数据
	uint32_t W25Qxx_ID;			                                // 器件的ID

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;			    // 24位地址
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.AddressMode = QSPI_ADDRESS_NONE;				    // 无地址模式
	s_command.DataMode = QSPI_DATA_1_LINE;					    // 1线数据模式
	s_command.DummyCycles = 0;								    // 空周期个数
	s_command.NbData = 3;									    // 传输数据的长度
	s_command.Instruction = W25Q64_JedecID;				        // 执行读器件ID命令

	// 发送指令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		//		return W25Qxx_ERROR_INIT;
	}

	// 接收数据
	if (HAL_QSPI_Receive(&hqspi, QSPI_ReceiveBuff, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		//		return W25Qxx_ERROR_TRANSMIT; 
	}

	// 将得到的数据组合成ID
	W25Qxx_ID = (QSPI_ReceiveBuff[0] << 16) | (QSPI_ReceiveBuff[1] << 8) | QSPI_ReceiveBuff[2];

	return W25Qxx_ID;
}



等待擦写完成

擦写操作有处理时间,需要等待,可以读取状态寄存器1的bit0 WIP检测

状态寄存器还可以设置保护区域,Flash还有硬件保护引脚WP,其并不是直接保护Flash不能擦写,而是保护状态寄存器不被异常改写

(此图中命令码有误,应该是05H/35H/15H)
STM32驱动QSPI Flash_第8张图片

STM32驱动QSPI Flash_第9张图片
代码如下:

int QSPI_Flash_AutoPolling_Busy()
{
	QSPI_CommandTypeDef s_command;
	QSPI_AutoPollingTypeDef s_config;                           // 轮询比较相关配置参数

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
	s_command.AddressMode = QSPI_ADDRESS_NONE;				    // 无地址模式
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.DataMode = QSPI_DATA_1_LINE;					    // 1线数据模式
	s_command.DummyCycles = 0;								    // 空周期个数
	s_command.Instruction = W25Q64_READ_STATUS_REG1;		    // 读状态信息寄存器

	// 不停的查询 W25Q64_READ_STATUS_REG1 寄存器,将读取到的状态字节中的 bit0 不停的与0作比较
	// 读状态寄存器1的第0位(只读),Busy标志位,当正在擦除/写入数据/写命令时会被置1,空闲或通信结束为0

	s_config.Match = 0;									        // 匹配值 运算结果与匹配值一致返回成功
	s_config.MatchMode = QSPI_MATCH_MODE_AND;			        // 与运算
	s_config.Interval = 0x10;							        // 轮询间隔
	s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;        // 自动停止模式
	s_config.StatusBytesSize = 1;						        // 状态字节数
	s_config.Mask = 0x01;			                            // 与状态字节进行运算的掩码

	// 发送轮询等待命令
	if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_AUTOPOLLING;
	}
	return QSPI_W25Qxx_OK;
}



擦写使能

每次擦写前都需要写使能,写成功与否可以读取状态寄存器1的bit1 WEL检测
STM32驱动QSPI Flash_第10张图片

代码如下:

int QSPI_Flash_WriteEnable(void)
{
	QSPI_CommandTypeDef s_command;	  // QSPI传输配置
	QSPI_AutoPollingTypeDef s_config; // 轮询比较相关配置参数

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	 // 1线指令模式
	s_command.AddressMode = QSPI_ADDRESS_NONE;				 // 无地址模式
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				 // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	 // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			 // 每次传输数据都发送指令
	s_command.DataMode = QSPI_DATA_NONE;					 // 无数据模式
	s_command.DummyCycles = 0;								 // 空周期个数
	s_command.Instruction = W25Q64_WRITE_ENABLE;			 // 发送写使能命令

	// 发送写使能命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_WriteEnable; 
	}

    s_command.Instruction = W25Q64_READ_STATUS_REG1;          // 读状态信息寄存器
	s_command.DataMode = QSPI_DATA_1_LINE;				      // 1线数据模式
	s_command.NbData = 1;								      // 数据长度

	// 不停的查询 W25Q64_READ_STATUS_REG1 寄存器,将读取到的状态字节中的 bit1 不停的与0作比较
	// 读状态寄存器1的第1位(只读),WEL写使能标志位,该标志位为1时,代表可以进行写操作

	s_config.Match = 0x02;								      // 匹配值 运算结果与匹配值一致返回成功
	s_config.MatchMode = QSPI_MATCH_MODE_AND;			      // 与运算
	s_config.Interval = 0x10;							      // 轮询间隔
	s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;      // 自动停止模式
    s_config.StatusBytesSize = 1;						      // 状态字节数
    s_config.Mask = 0x02;				                      // 与状态字节进行运算的掩码

	// 发送轮询等待命令
	if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_AUTOPOLLING;
	}
    
	return QSPI_W25Qxx_OK;
}



擦除

写Flash时只能从1变成0,不能从0变成1

故写之前需要将Flash进行擦除为全0xFF

擦除的最小单位为扇区(4KB)

还可以按块(32KB or 64KB)擦除或全擦除

这里介绍的是按扇区擦除
STM32驱动QSPI Flash_第11张图片
代码如下:

int QSPI_Flash_SectorErase(uint32_t SectorAddress)
{
	QSPI_CommandTypeDef s_command;

    s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
    s_command.AddressSize = QSPI_ADDRESS_24_BITS;			    // 24位地址模式
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;			    // 1线地址模式
	s_command.DataMode = QSPI_DATA_NONE;					    // 无数据
	s_command.DummyCycles = 0;								    // 空周期个数
	s_command.Address = SectorAddress;						    // 要擦除的地址
	s_command.Instruction = W25Q64_SECTORERASE;			        // 扇区擦除命令

	// 发送写使能
	if (QSPI_Flash_WriteEnable() != QSPI_W25Qxx_OK)
	{
		return W25Qxx_ERROR_WriteEnable;
	}

	// 发出擦除命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_Erase;
	}

	// 使用自动轮询标志位,等待擦除的结束
	if (QSPI_Flash_AutoPolling_Busy() != QSPI_W25Qxx_OK)
	{
		return W25Qxx_ERROR_AUTOPOLLING;
	}

	return QSPI_W25Qxx_OK;
}



读数据

STM32驱动QSPI Flash_第12张图片
代码如下:

int QSPI_Flash_ReadBuffer(uint8_t *pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{
	QSPI_CommandTypeDef s_command;

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;			    // 24位地址
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.AddressMode = QSPI_ADDRESS_4_LINES;			    // 4线地址模式
	s_command.DataMode = QSPI_DATA_4_LINES;					    // 4线数据模式
	s_command.DummyCycles = 6;								    // 空周期个数
	s_command.NbData = NumByteToRead;						    // 数据长度,最大不能超过flash芯片的大小
	s_command.Address = ReadAddr;							    // 要读取 W25Qxx 的地址
	s_command.Instruction = W25QXX_FASTREADQUAD_IO;		        // 1-4-4模式下(1线指令4线地址4线数据),快速读取指令

	// 发送读取命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_TRANSMIT;
	}

	//	接收数据
	if (HAL_QSPI_Receive(&hqspi, pBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_TRANSMIT;
	}
    
	return QSPI_W25Qxx_OK;
}



写数据

一次最多写一页256字节
STM32驱动QSPI Flash_第13张图片
代码如下:

int QSPI_Flash_WritePage(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
	QSPI_CommandTypeDef s_command;

	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;	    // 1线指令模式
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;			    // 24位地址
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    // 无交替字节
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;				    // 禁止DDR模式
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;	    // DDR模式中数据延迟,这里用不到
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;			    // 每次传输数据都发送指令
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;			    // 1线地址模式
	s_command.DataMode = QSPI_DATA_4_LINES;					    // 4线数据模式
	s_command.DummyCycles = 0;								    // 空周期个数
	s_command.NbData = NumByteToWrite;						    // 数据长度,最大只能256字节
	s_command.Address = WriteAddr;							    // 要写入 W25Qxx 的地址
	s_command.Instruction = W25QXX_QUADINPUTPAGEPROGRAM;        // 1-1-4模式下(1线指令1线地址4线数据),页编程指令

	// 写使能
	if (QSPI_Flash_WriteEnable() != QSPI_W25Qxx_OK)
	{
		return W25Qxx_ERROR_WriteEnable;
	}

	// 写命令
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_TRANSMIT;
	}

	// 开始传输数据
	if (HAL_QSPI_Transmit(&hqspi, pBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return W25Qxx_ERROR_TRANSMIT;
	}

	// 使用自动轮询标志位,等待写入的结束
	if (QSPI_Flash_AutoPolling_Busy() != QSPI_W25Qxx_OK)
	{
		return W25Qxx_ERROR_AUTOPOLLING;
	}

	return QSPI_W25Qxx_OK;
}



Demo

输出

Flash ID: EF4017
30 31 32 33 34 35 36 37
ff ff ff ff ff ff ff ff
30 31 32 33 34 35 36 37
void user_hex_dump(uint8_t *buf, uint16_t len)
{
	for (size_t i = 0; i < len; i++)
	{
		printf("%02x ", buf[i]);
	}
	printf("\r\n");
}

void qspi_flash_test()
{
	uint32_t flash_id;
	uint8_t buf[64] = {};

	QSPI_Flash_Reset();
	flash_id = QSPI_Flash_ReadID();

	printf("Flash ID: %06X\r\n", flash_id);

	QSPI_Flash_ReadBuffer(buf, 0, 8);
	user_hex_dump(buf, 8);

	QSPI_Flash_SectorErase(0);
	QSPI_Flash_ReadBuffer(buf, 0, 8);
	user_hex_dump(buf, 8);

	for (size_t i = 0; i < 8; i++)
	{
		buf[i] = i+0X30;
	}
	
	QSPI_Flash_WritePage(buf, 0, 8);
	QSPI_Flash_ReadBuffer(buf, 0, 8);
	user_hex_dump(buf, 8);
}

你可能感兴趣的:(stm32,嵌入式硬件,单片机)