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;
}