STM32 串行FLASH W25Q64 移植文件系统FatFs ——(1)

引言

在嵌入式系统中,外部存储器如串行FLASH在数据存储和管理方面起到了至关重要的作用。本文将介绍如何在STM32平台上通过软件模拟SPI(Soft SPI)完成对W25Q64串行FLASH的控制,并编写相应的驱动程序。这个过程是移植文件系统FatFs的第一步。

准备工作

硬件准备

  1. STM32开发板:本文使用野火霸道开发板,芯片是STM32F103ZET6。
  2. W25Q64串行FLASH:一个外部存储芯片。
  3. 连接:开发板上已经将W25Q64的引脚和STM32的GPIO引脚连接好。

软件准备

  1. Keil MDK: 用于编写和调试代码。

引脚连接

下面是对应野火霸道开发板的引脚连接方案,具体连接根据自己使用开发板而定。

W25Q64引脚STM32引脚
CS        PA4
DO(对应主机MISO)PA6
WP                3.3V
GND        GND
DI(对应主机MOSI)PA7
CLKPA5
HOLD3.3V
VCC3.3V

软件模拟SPI的实现

GPIO初始化

为了更好的移植性,这里将使用到的GPIO引脚通过宏进行了定义,这样在后续有移植需求的情况下,只需要更改宏即可。

//CS(NSS)引脚 片选选普通GPIO即可
#define      FLASH_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_CS_PORT                 GPIOA
#define      FLASH_SPI_CS_PIN                  GPIO_Pin_4

//SCK引脚
#define      FLASH_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define      FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA   
#define      FLASH_SPI_SCK_PORT                GPIOA   
#define      FLASH_SPI_SCK_PIN                 GPIO_Pin_5
//MISO引脚
#define      FLASH_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MISO_PORT               GPIOA 
#define      FLASH_SPI_MISO_PIN                GPIO_Pin_6
//MOSI引脚
#define      FLASH_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MOSI_PORT               GPIOA 
#define      FLASH_SPI_MOSI_PIN                GPIO_Pin_7 


/* 控制W25Q64的相关GPIO初始化函数 */
void W25QXX_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	
    FLASH_SPI_CS_APBxClock_FUN(FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
							FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE);
	
	
	/* 配置SPI的 CS引脚 */
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);

	/* 配置SPI的 SCK引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
	GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
	
	/* 配置SPI的 MOSI引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
	GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);


	/* 配置SPI的 MISO引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);

	
	/* 停止信号 FLASH: CS引脚高电平*/
	SPI_FLASH_CS_HIGH();
}

SPI基本操作

编写通过SPI来进行读写的函数,因为使用的四线SPI是全双工的协议,发送一个字节数据的同时也会接收到一个字节数据,所以这里将读写一个字节的函数写成一个函数。

//通过下面的宏来完成基本的SPI操作
#define  	 SPI_FLASH_CS_LOW()     		   GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define  	 SPI_FLASH_CS_HIGH()    		   GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )

#define  	 SPI_FLASH_SCLK_LOW()     		   GPIO_ResetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define  	 SPI_FLASH_SCLK_HIGH()    		   GPIO_SetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )

#define  	 SPI_FLASH_MOSI_LOW()     		   GPIO_ResetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
#define  	 SPI_FLASH_MOSI_HIGH()    		   GPIO_SetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )    


/* SPI读写一个字节数据的函数 */
uint8_t SPI1_ReadWriteByte(u8 dat)
{
	u8 i, read_data;
	
	SPI_FLASH_SCLK_HIGH();	//时钟线拉高
	
	for (i = 0; i< 8; i++)
	{
		read_data <<= 1;
		
		if (0x80 == ((dat << i)&0x80))
		{
			SPI_FLASH_MOSI_HIGH();
		}	
		else
		{
			 SPI_FLASH_MOSI_LOW();
		}
		SPI_FLASH_SCLK_LOW();	//拉低时钟线,向总线输出数据
		delay_us(1);
		SPI_FLASH_SCLK_HIGH();	//拉高时钟线,准备读取数据
		delay_us(1);
		
		if (GPIO_ReadInputDataBit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN))
		{
			read_data |= 0x01;    //读取到数据位为1
		}
	}
	
	SPI_FLASH_SCLK_HIGH();	
	
	return read_data;    
}

W25Q64基础驱动函数

写使能/写禁止函数

查看数据手册,对应指令如下:

#define W25X_WriteEnable		      	0x06 
#define W25X_WriteDisable		      	0x04 


//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteEnable);
	SPI_FLASH_CS_HIGH();
}

//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteDisable);
	SPI_FLASH_CS_HIGH();
}

读写状态寄存器

查看数据手册,对应指令如下:

#define W25X_ReadStatusReg		    	0x05 
#define W25X_WriteStatusReg		    	0x01 


//读取W25QXX的状态寄存器
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t W25QXX_ReadSR(void)
{
	u8 byte = 0;
	SPI_FLASH_CS_LOW();		/* 选择FLASH: CS低电平 */
	SPI1_ReadWriteByte(W25X_ReadStatusReg); //发送读状态寄存器指令
	byte = SPI1_ReadWriteByte(0xff);        //读取收到的状态寄存器的值
	SPI_FLASH_CS_HIGH();
	
	return byte;
}

//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteStatusReg); //发送写状态寄存器指令
	SPI1_ReadWriteByte(sr);                  //将值sr写入到状态寄存器
	SPI_FLASH_CS_HIGH();
}

读取设备ID

通过读取W25Q64的设备ID可以帮助确认通信是否正常。

 通过数据手册可以看出,当发送90h指令的时候,最后获取到的设备ID为0xef16。

#define W25X_ManufactDeviceID   		0x90 
#define W25Q64                          0xef16

u16 W25QXX_ReadID(void)
{
	u16 temp;
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ManufactDeviceID); //发送读设备ID指令
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	temp |= (SPI1_ReadWriteByte(0xff)<<8);    //数据存放在temp高8位
	temp |= SPI1_ReadWriteByte(0xff);         //数据存放在temp低8位
	SPI_FLASH_CS_HIGH();
	
	return temp;
}

读FLASH内部数据函数

#define W25X_ReadData			        0x03 


//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
	u16 i;
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ReadData);        //发送读数据指令
	SPI1_ReadWriteByte((u8)(ReadAddr>>16));   //发送要读的24bit地址
	SPI1_ReadWriteByte((u8)(ReadAddr>>8));
	SPI1_ReadWriteByte((u8)ReadAddr);
	
	for (i=0; i<NumByteToRead; i++)
	{
		pBuffer[i] = SPI1_ReadWriteByte(0xff); //读取数据
	}
	SPI_FLASH_CS_HIGH();
}

向FLASH内部写入数据函数

#define W25X_PageProgram		      	0x02 


//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
	u16 i;
	
	if (NumByteToWrite > 256)	/* 参数检查 */
		NumByteToWrite = 256;
	
	W25QXX_Write_Enable();		//写使能
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_PageProgram);
	SPI1_ReadWriteByte((u8)(WriteAddr>>16));
	SPI1_ReadWriteByte((u8)(WriteAddr>>8));
	SPI1_ReadWriteByte((u8)WriteAddr);
	
	for (i=0; i<NumByteToWrite; i++)
	{
		SPI1_ReadWriteByte(pBuffer[i]);
	}
	SPI_FLASH_CS_HIGH();
	W25QXX_Wait_Busy();	//等待写入结束
	
}

//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
	u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
	/* 取余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0 */
	Addr = WriteAddr % SPI_FLASH_PageSize;
	count  = SPI_FLASH_PageSize - Addr;	//差count个数据值,刚好可以对齐到页地址
	NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;	//计算出要写多少整数页/
	NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;	//计算出剩余不满一页的字节数
	
	if (Addr == 0)	//WriteAddr刚好按页对齐
	{
		if (NumOfPage == 0)	//不足一页
		{
			W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
		}
		else	//数据比一页多
		{
			while (NumOfPage--)	//先把整数页写完
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr += SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			/* 若有多余的不足一页的数据,将其写完 */
			W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
		}
	}
	else	//WriteAddr没有按页对齐
	{
		if (NumOfPage == 0)	//不足一页
		{
			/* 当前页可写的数据数不足以将数据写完 */
			if (NumOfSingle > count)
			{
				temp = NumOfSingle - count;
				//先将当前页写完
				W25QXX_Write_Page(pBuffer, WriteAddr, count);
				
				WriteAddr += count;
				pBuffer += count;
				//再将剩余的数据写完
				W25QXX_Write_Page(pBuffer, WriteAddr, temp);
			}
			else	/* 当前页可写的数据数可以将数据写完 */
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
			}
		}
		else	//要写的数据数大于一页
		{
			/*地址不对齐多出的count分开处理,不加入这个运算*/
			NumByteToWrite -= count;
			NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
			NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
			W25QXX_Write_Page(pBuffer, WriteAddr, count);
			WriteAddr += count;
			pBuffer += count;
			
			while (NumOfPage--)
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr += SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			
			if (NumOfSingle != 0)
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
			}
		}
	}
}	

整片擦除和擦除扇区函数

#define W25X_SectorErase		      	0x20 
#define W25X_ChipErase			      	0xC7 

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
	W25QXX_Write_Enable();
	W25QXX_Wait_Busy();
	
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_SectorErase);	
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 16));
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 8));
	SPI1_ReadWriteByte((u8)Dst_Addr);
	SPI_FLASH_CS_HIGH();
	W25QXX_Wait_Busy();
}


//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
	/* 发送FLASH写使能命令 */
	W25QXX_Write_Enable();
	W25QXX_Wait_Busy();

	/* 整块 Erase */
	SPI_FLASH_CS_LOW();		//选择FLASH: CS低电平
	SPI1_ReadWriteByte(W25X_ChipErase);	// 发送整块擦除指令
	SPI_FLASH_CS_HIGH();	//停止信号 FLASH: CS 高电平

	/* 等待擦除完毕*/
	W25QXX_Wait_Busy();
}

等待空闲函数

该函数一般用于写操作之后,确保数据写入完成。

//等待空闲
void W25QXX_Wait_Busy(void)
{
	while ((W25QXX_ReadSR() & 0x01) == 0x01);	// 等待BUSY位清空
}

进入掉电模式/唤醒函数

#define W25X_PowerDown			      	0xB9 
#define W25X_ReleasePowerDown	    	0xAB 


//进入掉电模式
void W25QXX_PowerDown(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_PowerDown);	
	SPI_FLASH_CS_HIGH();
	delay_us(3);
}

//唤醒
void W25QXX_WAKEUP(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ReleasePowerDown);	
	SPI_FLASH_CS_HIGH();
	delay_us(3);
}

通过这些函数就可以实现对W25Q64的基本读写和擦除操作,在main函数中对这些函数进行测试,看能否正确读写数据。

函数测试

typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;

/* 获取缓冲区的长度 */
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define  BufferSize (countof(Tx_Buffer)-1)

#define  WriteAddress     0x00000
#define  ReadAddress      FLASH_WriteAddress
#define  SectorToErase    FLASH_WriteAddress

#define  W25Q64            0xEF16
     
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = "软件模拟SPI对FLASH进行测试\r\n";
uint8_t Rx_Buffer[BufferSize];

u16 W25QXX_TYPE;    

// 函数原型声明
void Delay(__IO uint32_t nCount);
TestStatus Buffercmp(uint8_t* pBuffer1,uint8_t* pBuffer2, uint16_t BufferLength);

/*
 * 函数名:main
 * 描述  :主函数
 * 输入  :无
 * 输出  :无
 */
int main(void)
{ 	
	
	/* 配置串口为:115200 8-N-1 */
	USART_Config();
	printf("\r\n 串行flash(W25Q64)实验 \r\n");
	
	/* W25Q64初始化 */
	W25QXX_Init();
	
	/* 获取 Flash Device ID */
	W25QXX_TYPE= W25QXX_ReadID();	
	Delay( 200 );
	
    /* 检验 SPI Flash ID */
	if (W25QXX_TYPE == W25Q64)
	{	
		printf("\r\n 检测到 W25Q64 !\r\n");
		
		/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */
		// 这里擦除4K,即一个扇区,擦除的最小单位是扇区
		W25QXX_Erase_Sector(FLASH_SectorToErase);	 	 
		
		/* 将发送缓冲区的数据写到flash中 */
		W25QXX_Write(Tx_Buffer, FLASH_WriteAddress, BufferSize);		
		printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);
		
		/* 将刚刚写入的数据读出来放到接收缓冲区中 */
		W25QXX_Read(Rx_Buffer, FLASH_ReadAddress, BufferSize);
		printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);
		
		/* 检查写入的数据与读出的数据是否相等 */
		TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
		
		if( PASSED == TransferStatus1 )
		{ 
			printf("\r\n W25Q64 测试成功!\n\r");
		}
		else
		{        
			printf("\r\n W25Q64 测试失败!\n\r");
		}
	}
	else
	{ 
		printf("\r\n 获取不到 W25Q64 ID!\n\r");
	}
	
	while(1);  
}

/*
 * 函数名:Buffercmp
 * 描述  :比较两个缓冲区中的数据是否相等
 * 输入  :-pBuffer1     src缓冲区指针
 *         -pBuffer2     dst缓冲区指针
 *         -BufferLength 缓冲区长度
 * 输出  :无
 * 返回  :-PASSED pBuffer1 等于   pBuffer2
 *         -FAILED pBuffer1 不同于 pBuffer2
 */
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }

    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}

完整的W25Q64驱动文件如下

spi_flash.h

#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H

#include "stm32f10x.h"
#include <stdio.h>


#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17

#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256

/*命令定义-开头*******************************/
#define W25X_WriteEnable		      	0x06 
#define W25X_WriteDisable		      	0x04 
#define W25X_ReadStatusReg		    	0x05 
#define W25X_WriteStatusReg		    	0x01 
#define W25X_ReadData			        0x03 
#define W25X_FastReadData		      	0x0B 
#define W25X_FastReadDual		      	0x3B 
#define W25X_PageProgram		      	0x02 
#define W25X_BlockErase			      	0xD8 
#define W25X_SectorErase		      	0x20 
#define W25X_ChipErase			      	0xC7 
#define W25X_PowerDown			      	0xB9 
#define W25X_ReleasePowerDown	    	0xAB 
#define W25X_DeviceID			        0xAB 
#define W25X_ManufactDeviceID   		0x90 
#define W25X_JedecDeviceID		    	0x9F

/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag                  0x01
#define Dummy_Byte                0xFF
/*命令定义-结尾*******************************/


/*SPI接口定义-开头****************************/
#define      FLASH_SPIx                        SPI1
#define      FLASH_SPI_APBxClock_FUN           RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CLK                     RCC_APB2Periph_SPI1

//CS(NSS)引脚 片选选普通GPIO即可
#define      FLASH_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_CS_PORT                 GPIOA
#define      FLASH_SPI_CS_PIN                  GPIO_Pin_4

//SCK引脚
#define      FLASH_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define      FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA   
#define      FLASH_SPI_SCK_PORT                GPIOA   
#define      FLASH_SPI_SCK_PIN                 GPIO_Pin_5
//MISO引脚
#define      FLASH_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MISO_PORT               GPIOA 
#define      FLASH_SPI_MISO_PIN                GPIO_Pin_6
//MOSI引脚
#define      FLASH_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA    
#define      FLASH_SPI_MOSI_PORT               GPIOA 
#define      FLASH_SPI_MOSI_PIN                GPIO_Pin_7

#define  	 SPI_FLASH_CS_LOW()     		   GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define  	 SPI_FLASH_CS_HIGH()    		   GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )

#define  	 SPI_FLASH_SCLK_LOW()     		   GPIO_ResetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define  	 SPI_FLASH_SCLK_HIGH()    		   GPIO_SetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )

#define  	 SPI_FLASH_MOSI_LOW()     		   GPIO_ResetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
#define  	 SPI_FLASH_MOSI_HIGH()    		   GPIO_SetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )


/*SPI接口定义-结尾****************************/


/*函数声明-开头****************************/
void W25QXX_Init(void);										  
uint8_t SPI1_ReadWriteByte(u8 dat);
uint8_t W25QXX_ReadSR(void);										  
void W25QXX_Write_SR(u8 sr);										  
void W25QXX_Write_Enable(void);										  
void W25QXX_Write_Disable(void);
u16 W25QXX_ReadID(void);										  
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead);
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);										  
void W25QXX_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);										  
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);										  
void W25QXX_Erase_Chip(void);										  
void W25QXX_Erase_Sector(u32 Dst_Addr);										  
void W25QXX_Wait_Busy(void);	
void W25QXX_PowerDown(void);										  
void W25QXX_WAKEUP(void);										  
			
/*函数声明-结尾****************************/							  
									


#endif /* __SPI_FLASH_H */

spi_flash.c

#include "spi_flash.h"


u16 W25QXX_TYPE = W25Q64;

void delay_us(u16 nus)
{
	u16 i;
	for (i=0; i<nus; i++)
	{
		
	}
}



void W25QXX_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	
    FLASH_SPI_CS_APBxClock_FUN(FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
							FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE);
	
	
	/* 配置SPI的 CS引脚,普通IO即可 */
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);

	/* 配置SPI的 SCK引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
	GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
	
	/* 配置SPI的 MOSI引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
	GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);


	/* 配置SPI的 MISO引脚*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);

	
	/* 停止信号 FLASH: CS引脚高电平*/
	SPI_FLASH_CS_HIGH();
	
	W25QXX_TYPE = W25QXX_ReadID();
	if (W25QXX_TYPE == W25Q64)
	{
		printf("FlashID: %#x\r\n", W25QXX_TYPE);
		printf("w25q64 ok\r\n");
	}
}

uint8_t SPI1_ReadWriteByte(u8 dat)
{
	u8 i, read_data;
	
	SPI_FLASH_SCLK_HIGH();	//时钟线拉高
	
	for (i = 0; i< 8; i++)
	{
		read_data <<= 1;
		
		if (0x80 == ((dat << i)&0x80))
		{
			SPI_FLASH_MOSI_HIGH();
		}	
		else
		{
			 SPI_FLASH_MOSI_LOW();
		}
		SPI_FLASH_SCLK_LOW();	//拉低时钟线,向总线输出数据
		delay_us(1);
		SPI_FLASH_SCLK_HIGH();	//拉高时钟线,准备读取数据
		delay_us(1);
		
		if (GPIO_ReadInputDataBit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN))
		{
			read_data |= 0x01;
		}
	}
	
	SPI_FLASH_SCLK_HIGH();	
	
	return read_data;
}

//读取W25QXX的状态寄存器
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t W25QXX_ReadSR(void)
{
	u8 byte = 0;
	SPI_FLASH_CS_LOW();		/* 选择FLASH: CS低电平 */
	SPI1_ReadWriteByte(W25X_ReadStatusReg);
	byte = SPI1_ReadWriteByte(0xff);
	SPI_FLASH_CS_HIGH();
	
	return byte;
}

//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteStatusReg);
	SPI1_ReadWriteByte(sr);
	SPI_FLASH_CS_HIGH();
}


//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteEnable);
	SPI_FLASH_CS_HIGH();
}

//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_WriteDisable);
	SPI_FLASH_CS_HIGH();
}

//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
u16 W25QXX_ReadID(void)
{
	u16 temp;
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ManufactDeviceID);
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	temp |= (SPI1_ReadWriteByte(0xff)<<8);
	temp |= SPI1_ReadWriteByte(0xff);
	SPI_FLASH_CS_HIGH();
	
	return temp;
}

//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
	u16 i;
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ReadData);
	SPI1_ReadWriteByte((u8)(ReadAddr>>16));
	SPI1_ReadWriteByte((u8)(ReadAddr>>8));
	SPI1_ReadWriteByte((u8)ReadAddr);
	
	for (i=0; i<NumByteToRead; i++)
	{
		pBuffer[i] = SPI1_ReadWriteByte(0xff);
	}
	SPI_FLASH_CS_HIGH();
}

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
	u16 i;
	
	if (NumByteToWrite > 256)	/* 参数检查 */
		NumByteToWrite = 256;
	
	W25QXX_Write_Enable();		//写使能
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_PageProgram);
	SPI1_ReadWriteByte((u8)(WriteAddr>>16));
	SPI1_ReadWriteByte((u8)(WriteAddr>>8));
	SPI1_ReadWriteByte((u8)WriteAddr);
	
	for (i=0; i<NumByteToWrite; i++)
	{
		SPI1_ReadWriteByte(pBuffer[i]);
	}
	SPI_FLASH_CS_HIGH();
	W25QXX_Wait_Busy();	//等待写入结束
	
}

//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
	u16 pageremain;
	pageremain = 256 - WriteAddr%256;
	
	if (NumByteToWrite <= pageremain)
	{
		pageremain = NumByteToWrite;
	}
	
	while (1)
	{
		W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);
		
		if (pageremain == NumByteToWrite)
		{
			break;
		}
		else
		{
			pBuffer += pageremain;
			WriteAddr += pageremain;
			
			NumByteToWrite -= pageremain;
			
			if (NumByteToWrite > 256)
			{
				pageremain = 256;
			}
			else
			{
				pageremain = NumByteToWrite;
			}
		}
	}
}

//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
	u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
	/* 取余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0 */
	Addr = WriteAddr % SPI_FLASH_PageSize;
	count  = SPI_FLASH_PageSize - Addr;	//差count个数据值,刚好可以对齐到页地址
	NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;	//计算出要写多少整数页/
	NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;	//计算出剩余不满一页的字节数
	
	if (Addr == 0)	//WriteAddr刚好按页对齐
	{
		if (NumOfPage == 0)	//不足一页
		{
			W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
		}
		else	//数据比一页多
		{
			while (NumOfPage--)	//先把整数页写完
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr += SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			/* 若有多余的不足一页的数据,将其写完 */
			W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
		}
	}
	else	//WriteAddr没有按页对齐
	{
		if (NumOfPage == 0)	//不足一页
		{
			/* 当前页可写的数据数不足以将数据写完 */
			if (NumOfSingle > count)
			{
				temp = NumOfSingle - count;
				//先将当前页写完
				W25QXX_Write_Page(pBuffer, WriteAddr, count);
				
				WriteAddr += count;
				pBuffer += count;
				//再将剩余的数据写完
				W25QXX_Write_Page(pBuffer, WriteAddr, temp);
			}
			else	/* 当前页可写的数据数可以将数据写完 */
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
			}
		}
		else	//要写的数据数大于一页
		{
			/*地址不对齐多出的count分开处理,不加入这个运算*/
			NumByteToWrite -= count;
			NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
			NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
			W25QXX_Write_Page(pBuffer, WriteAddr, count);
			WriteAddr += count;
			pBuffer += count;
			
			while (NumOfPage--)
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr += SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			
			if (NumOfSingle != 0)
			{
				W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
			}
		}
	}
}	

//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
	/* 发送FLASH写使能命令 */
	W25QXX_Write_Enable();
	W25QXX_Wait_Busy();

	/* 整块 Erase */
	SPI_FLASH_CS_LOW();		//选择FLASH: CS低电平
	SPI1_ReadWriteByte(W25X_ChipErase);	// 发送整块擦除指令
	SPI_FLASH_CS_HIGH();	//停止信号 FLASH: CS 高电平

	/* 等待擦除完毕*/
	W25QXX_Wait_Busy();
}

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
	W25QXX_Write_Enable();
	W25QXX_Wait_Busy();
	
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_SectorErase);	
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 16));
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 8));
	SPI1_ReadWriteByte((u8)Dst_Addr);
	SPI_FLASH_CS_HIGH();
	W25QXX_Wait_Busy();
}

//等待空闲
void W25QXX_Wait_Busy(void)
{
	while ((W25QXX_ReadSR() & 0x01) == 0x01);	// 等待BUSY位清空
}

//进入掉电模式
void W25QXX_PowerDown(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_PowerDown);	
	SPI_FLASH_CS_HIGH();
	delay_us(3);
}

//唤醒
void W25QXX_WAKEUP(void)
{
	SPI_FLASH_CS_LOW();
	SPI1_ReadWriteByte(W25X_ReleasePowerDown);	
	SPI_FLASH_CS_HIGH();
	delay_us(3);
}

结语

本文介绍了如何在STM32平台上通过软件模拟SPI完成对W25Q64串行FLASH的基本控制。我们实现了SPI基本操作、读取设备ID和扇区擦除等功能。下一步将介绍如何将FatFs文件系统移植到W25Q64上,以便实现更复杂的数据管理功能。

在接下来的文章中,我们将继续深入探讨文件系统的移植和优化,敬请期待!

相关阅读

如果你有任何问题或建议,请在评论区留言。感谢阅读!

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值