QSPI是基于SPI改进的,原理和SPI相似,只是数据线变成了4线,速度更快
也可以使用2线和1线模式,4线和2线模式是半双工模式,1线模式和普通SPI无区别
关于SPI的简介,可以看我的另一篇文章《SPI简介与实例分析》
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)
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;
}
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不能擦写,而是保护状态寄存器不被异常改写
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
检测
代码如下:
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)
擦除或全擦除
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;
}
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;
}
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;
}
输出
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);
}