STM32F7通过QSPI驱动W25Q256芯片

it2022-05-05  40

目录

一  使用STM32CubeMX配置QSPI接口

二 W25Q256初始化

三 W25Q256读写驱动


一  使用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;//下一个扇区可以写完了 } } }

 


最新回复(0)