STM32物联网项目--SPI读写Flash(W25Q64)

W25Q64中文数据手册:

链接:https://pan.baidu.com/s/1iBurtgwJyE1N7j8UuuHL2g 
提取码:9q7n 
--来自百度网盘超级会员V2的分享


程序功能:

        上电后,读出Flash的ID,然后间隔1s进行读写测试。


SPI通信

  • 同步、全双工
  • 一主多从(主机固定)
  • 4条线(一个从机时):MOSI(主机输出从机输入)、MISO(主机输入从机输出)、SCLK(时钟)、SS(片选)
  • 同一时间,主机只能选择一个从机
  • 输出配置为推挽输出(高驱动,上升沿/下降沿快,使得通信速度快),输入配置为浮空或上拉输入,当某从机未被主机选择时从机的MISO引脚改为高阻态
  • 当ss线为高电平,即未选中从机,则此从机的MISO线为高阻态,防止一条线有多个输出而导致的电平冲突问题
  • SPI通信基础:交换字节

硬件电路如图:

 交换字节示意图(高位先行,8位数据帧时):

 我们这里假设两个移位寄存器上升沿发送数据,下降沿接收数据

当经过第一次上升沿时:

两个移位寄存器的高位移到输出线上,即MOSI为高电平,MISO为低电平 

时钟继续运行,当到了下一个边沿,也就是下降延时:

两个移位寄存器分别接收对方输出的电平

就这样,随着时钟运行,经历这个步骤8次,则交换一个字节的数据:

当然,若是我们只想发送数据,不想接收数据呢?很简单,我们还是执行交换字节的时序,只不过当我接收时,不用去管接收到的数据。若是我们只想接收,不想发送数据时,我们依然执行交换字节的时序,只不过我们发送字节时,会随便发送一个字节(一般发送0x00或0x0f),去得到数据,起到抛砖引玉的作用 

时序单元

  • 起始条件:SS片选线:高电平到低电平
  • 终止条件:SS片选线:低电平到高电平
  • 交换字节:有4种通信模式,由CPOL与CPHA控制

   

 数据时钟时序图:

 

  其中,模式0用的最多。

W25Q64简介

引脚功能图:

!硬件控制片选引脚时,ss一直为低电平,不满足Flash规则,在最后一字节交换完成后,必须拉高cs,所以要使用GPIO口模拟SS管脚。


W25Q64,一个芯片存储空间分了128块,每块分了16个扇区 ,每扇区又分了16页,具体框图查看芯片中文手册第10页框图,手册分享已放在了文章开头。

具体写入流程(对照框图):

 Flash操作时又很多的注意事项,它不像RAM一样“指哪打哪”,想在哪里写就在哪里写:

实例(硬件外设读写Flash)

初始化:

选择spi3,模式为全双工(双线双向)模式:

spi参数配置:

 

 GPIO配置:

片选线配置:空闲为高电平(未选中):


 代码:

 由于代码较多,只给出重要部分代码:

运行函数:

static void Run()
{	
	uint8_t i;
	uint8_t CMP_Flag = TRUE;
	//定义读写缓存
	uint8_t Tx_Buffer[] = "168168168";
	const uint8_t BufferSize  = sizeof(Tx_Buffer)/sizeof(Tx_Buffer[0]);
	uint8_t Rx_Buffer[BufferSize];
	
	
	/*读写测试*/
	//ÉÈÇø²Á³ý
	SPI_Flash.EraseSector(0x00000000);
	//写入不定长度数据
	SPI_Flash.WriteUnfixed(Tx_Buffer,0x00000088,BufferSize);
	printf("写入数据为%s\r\n", Tx_Buffer);
	//读出不定长数据
	SPI_Flash.ReadUnfixed(Rx_Buffer,0x000000088,BufferSize);
	printf("读出数据为%s\r\n", Rx_Buffer);
	//比较缓存数据
	for(i=0;i<BufferSize;i++)
	{
		if(Tx_Buffer[i] != Rx_Buffer[i])
		{
			CMP_Flag = FALSE;
			break;
		}
	}
	//打印比较结果
	if(CMP_Flag == TRUE)
		printf("¹§Ï²£¬FalshоƬ¶Áд²âÊԳɹ¦£¡\r\n\r\n\r\n");
	else
		printf("What£¿FalshоƬ¶Áд²âÊÔʧ°Ü£¡\r\n\r\n\r\n");
	
	//延时1s
	HAL_Delay(1000);
}

写入数据函数:


/*
	* @name   SPI_Flash_WriteUnfixed
	* @brief  写入不定长数据
	* @param  pWriteBuffer£  待写入缓存数据指针
  *         WriteAddr   地址
  *         WriteLength 长度
	* @retval None
*/

static void SPI_Flash_WriteUnfixed(uint8_t* pWriteBuffer, uint32_t WriteAddr, uint32_t WriteLength)
{
	uint32_t PageNumofWirteLength     = WriteLength / SPI_FLASH_PageSize;            //待写入页数
	uint8_t  NotEnoughNumofPage       = WriteLength % SPI_FLASH_PageSize;            //不足一页的数量
	uint8_t  WriteAddrPageAlignment   = WriteAddr % SPI_FLASH_PageSize;              //若取余为0,则地址对齐,可以连续写入256字节
	uint8_t  NotAlignmentNumofPage    = SPI_FLASH_PageSize - WriteAddrPageAlignment; //地址不对齐部分,最多可以写入的字节数
	
	//写入地址页对齐
	if(WriteAddrPageAlignment == 0)
	{
		//待写入数据不足1页
		if(PageNumofWirteLength == 0)
		{
			SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
		}
		//待写入数据超过1页
		else
		{
			//先写入整页
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			//再写入不足一页的数据
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
	//地址不对齐
	else
	{
		//待写入数据不足1页
		if(PageNumofWirteLength == 0)
		{
			//²»×ãÒ»Ò³µÄÊý¾Ý <= µØÖ·²»¶ÔÆ벿·Ö
			if(NotEnoughNumofPage <= NotAlignmentNumofPage)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
			}
		
			else
			{
				
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
				pWriteBuffer += NotAlignmentNumofPage;
				WriteAddr    += NotAlignmentNumofPage;
				
				
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage-NotAlignmentNumofPage);
			}
		}
		
		else
		{
			
		  SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
			pWriteBuffer += NotAlignmentNumofPage;
			WriteAddr    += NotAlignmentNumofPage;
			
			
			WriteLength           -= NotAlignmentNumofPage;
			PageNumofWirteLength   = WriteLength / SPI_FLASH_PageSize;           
	    NotEnoughNumofPage     = WriteLength % SPI_FLASH_PageSize; 
			
			
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
}

读取数据函数:

/*
	* @name   SPI_Flash_ReadUnfixed
	* @brief  读取不固定长度数据
	* @param  pReadBuffer£  存放读取数据缓存的指针
  *         ReadAddr   地址
  *         ReadLength 长度
	* @retval None
*/
static void SPI_Flash_ReadUnfixed(uint8_t* pReadBuffer, uint32_t ReadAddr, uint32_t ReadLength)
{
	//检测Flash是否在Busy位
	SPI_Flash_WaitForWriteEnd();
	
	//拉低片选线,选择从机
	CLR_SPI_Flash_CS;
	
	//写入读命令
	SPI_Flash_WriteByte(W25X_ReadData);	
	//发送高字节
	SPI_Flash_WriteByte((ReadAddr & 0xFF0000) >> 16);
	//发送中字节
	SPI_Flash_WriteByte((ReadAddr & 0xFF00) >> 8);
	//发送低字节
	SPI_Flash_WriteByte(ReadAddr & 0xFF);
	//接收数据
	while (ReadLength--)
  {
     /* 读取一个字节
    *pReadBuffer = SPI_Flash_ReadByte();
    /* 指针指向下一个缓存区
    pReadBuffer++;
  }
	
	//拉高片选线,禁用从机
	SET_SPI_Flash_CS;
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西红柿鸡蛋超级美味

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值