本文程序运行于STM32L452CEUX系列单片机(适用于L4),使用MDK5.33、STM32CubeMX,实现QSPI通过DMA四线读写W25Q64或128JV。
用到的命令如下:
/* For W25Q64JV or W25Q128JV */
#define WRITE_STATUS_CFG_REG_CMD (0x01)
#define WRITE_ENABLE_CMD (0x06)
#define READ_ID_CMD2 (0x9F)
#define READ_STATUS_REG_CMD (0x05)
#define QUAD_INOUT_FAST_READ_CMD (0xEB)
#define QUAD_IN_FAST_PROG_CMD (0x32)
#define SUBSECTOR_ERASE_CMD (0x20)
W25Q64JV数据手册中的描述如下:
左上角的(1-4-4)表示指令阶段使用 1 个 IO,地址阶段使用 4 个 IO,数据阶段也是使用 4 个 IO
void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
uint8_t cnt = 0;
QSPI_CommandTypeDef sCommand = {0};
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1 线方式发送指令 */
sCommand.AddressSize = QSPI_ADDRESS_24_BITS; /* 24 位地址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q64JV 不支持 DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR 模式,数据输出延迟 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输要发指令 */
/* 读取数据 */
sCommand.Instruction = QUAD_INOUT_FAST_READ_CMD; /* 24bit 地址的 4 线快速读取命令 */
sCommand.DummyCycles = 6; /* 空周期 */
sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4 线地址 */
sCommand.DataMode = QSPI_DATA_4_LINES; /* 4 线数据 */
sCommand.NbData = _uiSize; /* 读取的数据大小 */
sCommand.Address = _uiReadAddr; /* 读取数据的起始地址 */
if (HAL_QSPI_Command(&hqspi, &sCommand, 10000) != HAL_OK)
{
Error_Handler();
}
/* 读取 */
qspi_RxCplt = 0;
if (HAL_QSPI_Receive_DMA(&hqspi, _pBuf) != HAL_OK)
{
Error_Handler();
}
while (!qspi_RxCplt)
{
cnt++;
if (cnt == 200)
{
cnt = 0;
Debug("Read Timeout");
break;
}
HAL_Delay(50);
}
}
qspi_RxCplt在接收完成中断置位:
__IO uint8_t qspi_RxCplt = 1;
void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
qspi_RxCplt++;
}
写数据之前需要擦除
W25Q64JV数据手册中关于Sector擦除的描述如下:
左上角的(1-1-1)表示指令阶段使用 1 个 IO,地址阶段使用 1个 IO,数据阶段也是使用 1 个 IO
/* *********************************************************************************************************
* 函 数 名: QSPI_EraseSector
* 功能说明: 页擦除
* 形 参: _uiSectorAddr : 擦除该地址所在的页
* 返 回 值: None
********************************************************************************************************* */
void QSPI_EraseSector(uint32_t _uiSectorAddr)
{
QSPI_CommandTypeDef sCommand = {0};
/* 写使能 */
QSPI_WriteEnable(&hqspi);
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1 线方式发送指令 */
sCommand.AddressSize = QSPI_ADDRESS_24_BITS; /* 32 位地址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q64JV 不支持 DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR 模式,数据输出延迟 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 仅发送一次命令 */
/* 写序列配置 */
sCommand.Instruction = SUBSECTOR_ERASE_CMD; /* 32bit 地址的扇区擦除命令 */
sCommand.DummyCycles = 0; /* 不需要空周期 */
sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 1 线地址 */
sCommand.NbData = QSPI_DATA_NONE; /* 无需发送数据 */
sCommand.Address = _uiSectorAddr; /* 扇区首地址,保证是4KB整数倍 */
if (HAL_QSPI_Command(&hqspi, &sCommand, 10000) != HAL_OK)
{
//return 0;
Error_Handler();
}
QSPI_AutoPollingMemReady(&hqspi);
}
写之前需要执行写使能
/* *********************************************************************************************************
* 函 数 名: QSPI_WriteEnable
* 功能说明: 写使能
* 形 参: None
* 返 回 值: None
********************************************************************************************************* */
static void QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi)
{
QSPI_CommandTypeDef sCommand = {0};
QSPI_AutoPollingTypeDef sConfig;
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1 线方式发送指令 */
sCommand.AddressSize = QSPI_ADDRESS_24_BITS; /* 24 位地址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q64JV 不支持 DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR 模式,数据输出延迟 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输要发指令 */
/* 读取数据 */
sCommand.Instruction = WRITE_ENABLE_CMD; /* 写使能命令 */
sCommand.AddressMode = QSPI_ADDRESS_NONE; /* 无需地址 */
sCommand.DataMode = QSPI_DATA_NONE; /* 无需数据 */
sCommand.DummyCycles = 0; /* 空周期 */
if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
/* Configure automatic polling mode to wait for write enabling ---- */
sConfig.Match = 0x02;
sConfig.Mask = 0x02;
sConfig.MatchMode = QSPI_MATCH_MODE_AND;
sConfig.StatusBytesSize = 1;
sConfig.Interval = 0x10;
sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
sCommand.Instruction = READ_STATUS_REG_CMD;
sCommand.DataMode = QSPI_DATA_1_LINE;
if (HAL_QSPI_AutoPolling(hqspi, &sCommand, &sConfig, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
/* *********************************************************************************************************
* 函 数 名: QSPI_WriteBuffer
* 功能说明: 页编程,页大小 256 字节,任意页都可以写入
* 形 参: _pBuf : 数据源缓冲区;
* _uiWriteAddr :目标区域首地址,即页首地址,比如 0, 256, 512 等。
* _usWriteSize :数据个数,不能超过页面大小,范围 1 - 256。
* 返 回 值: 1:成功, 0:失败
********************************************************************************************************* */
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
{
uint8_t cnt = 0;
QSPI_CommandTypeDef sCommand = {0};
/* 写使能 */
QSPI_WriteEnable(&hqspi);
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1 线方式发送指令 */
sCommand.AddressSize = QSPI_ADDRESS_24_BITS; /* 24 位地址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q64JV 不支持 DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR 模式,数据输出延迟 */
sCommand.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; /* 仅发送一次命令 */
/* 写序列配置 */
sCommand.Instruction = QUAD_IN_FAST_PROG_CMD; /* 24bit 地址的 4 线快速写入命令 */
sCommand.DummyCycles = 0; /* 不需要空周期 */
sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 1 线地址 */
sCommand.DataMode = QSPI_DATA_4_LINES; /* 4 线数据 */
sCommand.NbData = _usWriteSize; /* 写数据大小 */
sCommand.Address = _uiWriteAddr; /* 写入地址 */
if (HAL_QSPI_Command(&hqspi, &sCommand, 10000) != HAL_OK)
{
//return 0;
Error_Handler();
}
/* 启动传输 */
qspi_TxCplt = 0;
if (HAL_QSPI_Transmit_DMA(&hqspi, _pBuf) != HAL_OK)
{
//return 0;
Error_Handler();
}
while (!qspi_TxCplt)
{
cnt++;
if (cnt == 200)
{
cnt = 0;
Debug("Write Timeout");
break;
}
HAL_Delay(50);
}
QSPI_AutoPollingMemReady(&hqspi);
return 1;
}
qspi_TxCplt在发送完成中断中置位:
__IO uint8_t qspi_TxCplt = 1;
void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
qspi_TxCplt++;
}
static void QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi)
{
QSPI_CommandTypeDef sCommand = {0};
QSPI_AutoPollingTypeDef sConfig = {0};
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1 线方式发送指令 */
sCommand.AddressSize = QSPI_ADDRESS_24_BITS; /* 24 位地址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q64JV 不支持 DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR 模式,数据输出延迟 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输要发指令 */
/* 读取数据 */
sCommand.Instruction = READ_STATUS_REG_CMD; /* 读取状态命令 */
sCommand.AddressMode = QSPI_ADDRESS_NONE; /* 无需地址 */
sCommand.DataMode = QSPI_DATA_1_LINE; /* 1线数据 */
sCommand.DummyCycles = 0; /* 空周期 */
sConfig.Mask = 0x01;
sConfig.Match = 0x00;
sConfig.MatchMode = QSPI_MATCH_MODE_AND;
sConfig.StatusBytesSize = 1;
sConfig.Interval = 0x10;
sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(hqspi, &sCommand, &sConfig, 10000) != HAL_OK)
{
Error_Handler();
}
}
uint8_t read_buf[1024];
void Normal_Test(void)
{
memset(read_buf, 0x79, 128);
memset(read_buf + 128, 0x34, 128);
QSPI_EraseSector(1);
QSPI_WriteBuffer(read_buf, 0, 256);
memset(read_buf, 0x77, 256);
QSPI_ReadBuffer((uint8_t *)read_buf, 0, 256);
for (size_t i = 0; i < 256; i++)
{
Debug("0x%2X ", read_buf[i]);
}
}