STM32F7通过QSPI驱动W25Q256芯片

目录

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

 

  • 9
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值