目录
一 使用STM32CubeMX配置QSPI接口
Parameter Setting 与 GPIO Setting 配置如下:
1. 配置Clock Prescaler 为2,即QSPI时钟 = AHB / ( Prescaler +1) = 216 / 3 = 72 MHz
- 通过W25Q256数据手册P5页,可以得出该芯片最大时钟为104MHz。
- 通过stm32f7数据手册,可以知道QUADSPI挂载AHB总线,而AHB = 216MHz。
2. 配置Fifo Thread为4.
由于采用的是4线SPI,每一个IO传送1Byte,一次可传输4Byte。
3.配置Sample Shifting 为QSPI_SAMPLE_SHIFTING_NONE 或者为QSPI_SAMPLE_SHIFTING_HALFCYCLE 都可以。
Tips:只有在DDR模式下,必须设置为QSPI_SAMPLE_SHIFTING_NONE
4.配置Flash Size 为 24
W25Q256总大小为256M-Bit = 32 M-Byte = 2 ^ 25 Byte。所以Flash Size取值24。
5.配置Chip Select High Time 为4
该项表示在两次Commands之间CS必须保持高电平为多少个clock。
在W25Q256的数据手册第9.6 “AC Electrical characteristics”表格中可知 tCSH >= 50ns
QSPI的时钟为72MHz,可以算出1clock = 13.88 ns ,所以13.88 * 4 = 55.52。所以Chip Select High Time = 4
6.配置Clock Mode 为Low(QSPI_CLOCK_MODE_0) 或者 High(QSPI_CLOCK_MODE_3) 都是可以的。
从W25Q256手册中随便找一个时序图,在该图可以发现该芯片同时支持Mode 0 与Mode 3.
二 W25Q256初始化
1. 初始化流程
2. HAL库中发送指令的函数为HAL_QSPI_Command,为了方便使用,对该函数进行如下封装
void QSPI_SendCmd(uint32_t cmd,uint32_t cmdMode,uint32_t addr,uint32_t addrMode,uint32_t addrSize,uint32_t dataMode)
{
QSPI_CommandTypeDef s_command = {0};
s_command.Instruction = cmd; //指令
s_command.InstructionMode = cmdMode; //指令模式
s_command.Address = addr; //地址
s_command.AddressMode = addrMode; //地址模式
s_command.AddressSize = addrSize; //地址长度
s_command.DataMode = dataMode; //数据模式
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
3. 使用QSPI_SendCmd编写如下宏,方便使用
#define W25QXX_ENTER_QSPI() QSPI_SendCmd(W25X_EnterQPIMode,QSPI_INSTRUCTION_1_LINE,0,0,0,0)
#define W25QXX_ENTER_4BYTEADDR() QSPI_SendCmd(W25X_Enable4ByteAddr,QSPI_INSTRUCTION_4_LINES,0,0,0,0)
#define W25Q_WRITE_ENABLE() QSPI_SendCmd(W25X_WriteEnable,QSPI_INSTRUCTION_4_LINES,0,0,0,0)
4. QSPI发送与接收的HAL库函数为QSPI_Transmit与HAL_QSPI_Receive,为方便使用,对着两个函数进行如下封装
void QSPI_Receive(uint8_t* buf,uint32_t len)
{
hqspi.Instance->DLR = len - 1; //配置数据长度
if(HAL_QSPI_Receive(&hqspi, buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
uint8_t QSPI_Transmit(uint8_t* buf,uint32_t len)
{
hqspi.Instance->DLR = len - 1; //配置数据长度
if(HAL_QSPI_Transmit(&hqspi, buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
5. 在初始化流程中需要设置QE为1,编写如下函数
void W25QXX_SetQE(void)
{
uint8_t value = 0x02;
/* 1.写使能 */
QSPI_SendCmd(W25X_WriteEnable,QSPI_INSTRUCTION_1_LINE,0,0,0,0);
/* 2.发送写状态寄存器2命令 */
QSPI_SendCmd(W25X_WriteStatusReg2,QSPI_INSTRUCTION_1_LINE,0,0,0,QSPI_DATA_1_LINE);
/* 3.发送数据 */
QSPI_Transmit(&value, 1);
}
6.在初始化流程中需要设置读参数,编写如下函数,该函数设置Bit4与Bit5为1,使得读取时最大速率可达104MHz
void W25QXX_SetReadParam(void)
{
uint8_t para = 3 << 4;
/* 1.发送命令 */
QSPI_SendCmd(W25X_SetReadParam,QSPI_INSTRUCTION_4_LINES,0,0,0,QSPI_DATA_4_LINES);
/* 2.发送数据 */
QSPI_Transmit(¶, 1);
}
7.最后添加一个读取ID的函数,通过该函数可检测是否初始化成功。根据手册可知,W25Q256的ID为0xEF18
uint16_t W25QXX_ReadId(void)
{
QSPI_CommandTypeDef s_command = {0};
uint8_t pData[2];
QSPI_SendCmd(W25X_ManufactDeviceID,QSPI_INSTRUCTION_4_LINES,
0,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_4_LINES);
QSPI_Receive(pData,2);
return pData[1] | ( pData[0] << 8 );
}
8 根据流程编写如下初始化流程函数。
void W25QXX_Init(void)
{
W25QXX_SetQE();
delay_ms(20);
W25QXX_ENTER_QSPI();
W25QXX_ENTER_4BYTEADDR();
W25QXX_SetParam();//设置读参数
W25_ID = W25QXX_ReadId();
}
三 W25Q256读写驱动
关于驱动就不细讲了,直接贴代码了。
1. 实现读函数(Tips:这里DummyCycles设置为8,时因为W25QXX_SetReadParam函数设置的读参数)
/***************************************************************************************
* @brief 读取SPI FLASH,仅支持QPI模式
* @input pBuffer:数据存储区
ReadAddr:开始读取的地址(最大32bit)
NumByteToRead:要读取的字节数(最大65535)
* @return
***************************************************************************************/
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
QSPI_CommandTypeDef s_command = {0};
s_command.Instruction = W25X_FastReadData;
s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
s_command.AddressMode = QSPI_ADDRESS_4_LINES;
s_command.AddressSize = QSPI_ADDRESS_32_BITS;
s_command.DataMode = QSPI_DATA_4_LINES;
s_command.DummyCycles = 8;
s_command.NbData = NumByteToRead;
s_command.Address = ReadAddr;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
if (HAL_QSPI_Receive(&hqspi, pBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
2. 实现扇区擦除函数
/***************************************************************************************
* @brief 等待空闲
* @input
* @return
***************************************************************************************/
void W25QXX_WaitIdle(void)
{
uint8_t sta_reg1 = 0x00;
do{
QSPI_SendCmd(W25X_ReadStatusReg1,QSPI_INSTRUCTION_4_LINES,0,0,0,QSPI_DATA_4_LINES);
QSPI_Receive(&sta_reg1,1);
}while( (sta_reg1&0x01) == 0x01 );
}
/***************************************************************************************
* @brief 擦除QSPI某个扇区
* @input
* @return
***************************************************************************************/
void W25QXX_EraseSector(uint32_t sector_id)
{
uint32_t addr = sector_id * 4096;
W25Q_WRITE_ENABLE();
W25QXX_WaitIdle();
QSPI_SendCmd(W25X_SectorErase,QSPI_INSTRUCTION_4_LINES,addr,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,0);
W25QXX_WaitIdle();
}
3. 实现页写函数
/***************************************************************************************
* @brief SPI在一页(0~65535)内写入少于256个字节的数据
* @input pBuffer:数据存储区
WriteAddr:开始写入的地址(最大32bit)
NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
* @return
***************************************************************************************/
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
W25Q_WRITE_ENABLE(); //写使能
QSPI_SendCmd(W25X_PageProgram,QSPI_INSTRUCTION_4_LINES,
WriteAddr,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,QSPI_DATA_4_LINES);
QSPI_Transmit(pBuffer, NumByteToWrite);
W25QXX_WaitIdle(); //等待写入结束
}
4. 实现五检测的写buffer函数,该函数默认已经擦除
/***************************************************************************************
* @brief 无检验写SPI FLASH ,具有自动换页功能
* @input pBuffer:数据存储区
WriteAddr:开始写入的地址(最大32bit)
NumByteToWrite:要写入的字节数(最大65535)
* @return
***************************************************************************************/
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t page_remain = 256 - WriteAddr % 256; //单页剩余的字节数
if(NumByteToWrite <= page_remain){
page_remain = NumByteToWrite;//不大于256个字节
}
while(1)
{
W25QXX_Write_Page(pBuffer, WriteAddr, page_remain);
if(NumByteToWrite == page_remain){
break;//写入结束了
} else {
pBuffer += page_remain;
WriteAddr += page_remain;
NumByteToWrite -= page_remain;//减去已经写入了的字节数
if(NumByteToWrite > 256)
page_remain = 256; //一次可以写入256个字节
else
page_remain = NumByteToWrite; //不够256个字节了
}
}
}
5. 实现写buffer函数,应用函数调用该接口实现写数据操作
/***************************************************************************************
* @brief 写SPI FLASH
* @input pBuffer:数据存储区
WriteAddr:开始写入的地址(最大32bit)
NumByteToWrite:要写入的字节数(最大65535)
* @return
***************************************************************************************/
uint8_t W25QXX_Buffer[4096];
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t sec_id;
uint16_t sec_offset;
uint16_t sec_remain;
uint16_t i;
uint8_t * W25Q_Buf = W25QXX_Buffer;
sec_id = WriteAddr / 4096;//扇区地址
sec_offset = WriteAddr % 4096;//在扇区内的偏移
sec_remain = 4096 - sec_offset;//扇区剩余空间大小
if(NumByteToWrite <= sec_remain)//
sec_remain = NumByteToWrite;//不大于4096个字节
while(1)
{
W25QXX_Read(W25Q_Buf, sec_id*4096, 4096);//读出整个扇区的内容
for(i=0; i<sec_remain; i++)//校验数据
{
if(W25Q_Buf[sec_offset+i] != 0xFF)
break;//需要擦除
}
if(i < sec_remain){//需要擦除
W25QXX_EraseSector(sec_id);//擦除这个扇区
for(i=0;i<sec_remain;i++){ //复制
W25Q_Buf[i+sec_offset] = pBuffer[i];
}
W25QXX_Write_NoCheck(W25Q_Buf, sec_id*4096, 4096);//写入整个扇区
} else {
W25QXX_Write_NoCheck(pBuffer,WriteAddr,sec_remain);//写已经擦除了的,直接写入扇区剩余区间.
}
if(NumByteToWrite == sec_remain){
break; //写入结束了
} else { //写入未结束
sec_id++; //扇区地址增1
sec_offset = 0;//偏移位置为0
pBuffer += sec_remain; //指针偏移
WriteAddr += sec_remain; //写地址偏移
NumByteToWrite -= sec_remain;//字节数递减
if(NumByteToWrite > 4096)
sec_remain = 4096; //下一个扇区还是写不完
else
sec_remain = NumByteToWrite;//下一个扇区可以写完了
}
}
}