基于STM32读取W25Q64(模拟SPI)
如有错误,希望能够提出
关于模拟SPI的介绍详见博主另外一篇博客
1、读取8位 Device ID
/*读取设备ID*/
void Read_Device_ID(void)
{
u8 ID;
SPI_CS_0();
Software_SPI_Write_Read(Device_ID);
Software_SPI_Write_Read(Dummy_Bytes);
Software_SPI_Write_Read(Dummy_Bytes);
Software_SPI_Write_Read(Dummy_Bytes);
ID = Software_SPI_Write_Read(Dummy_Bytes);
SPI_CS_1();
printf("Device_ID:%x\r\n",ID);
}
=SPI在读取数据时,为什么我们必须发送虚拟字节Dummy_Bytes才能接收结果?=
SPI必须生成时钟脉冲才能将数据移出。对于大多数(如果不是全部)SPI主机,产生时钟脉冲的唯一方式是发送字节。如果你仔细想想,这是有道理的。
总结:Dummy_Bytes无实际意义,只是为了产生时钟脉冲,这样才能读取数据。
2、读取MANUFACTURER ID和16位Device ID
/*读取制造商ID*/
void Read_JEDEC_ID(void)
{
u32 Temp;
u8 Temp0,Temp1,Temp2;
SPI_CS_0();
Software_SPI_Write_Read(JEDEC_ID);
Temp0 = Software_SPI_Write_Read(Dummy_Bytes);
Temp1 = Software_SPI_Write_Read(Dummy_Bytes);
Temp2 = Software_SPI_Write_Read(Dummy_Bytes);
SPI_CS_1();
/*把数据组合起来,作为函数的返回值*/
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
printf("JEDEC_ID:%x\r\n",Temp);
}
读取成功
3、扇区擦除
a、写使能
在扇区擦除之前要进行写使能
/*
*写使能
*/
void FLASH_WriteEnable(void)
{
SPI_CS_0();
Software_SPI_Write_Read(Write_Enable);
SPI_CS_1();
}
b、等待BUSY标志位置0
BUSY是状态寄存器(S0)中的只读位,当器件执行页面编程、扇区擦除、挡路擦除、芯片擦除或写入状态寄存器指令时,该位设置为1状态。在此期间,器件将忽略除读取状态寄存器和擦除挂起指令之外的其他指令(参见交流特性中的TW、TPP、Tse、TBE和Tce)。当编程、擦除或写入状态寄存器指令完成时,BUSY位将被清除为0状态,表示器件已准备好接受进一步的指令。
开始扇区擦除
为什么要进行数据擦除:因为Flash芯片内的数据只能由1变0,不能由0变1
扇区擦除指令将一个扇区(4K字节)擦除,擦除后扇区位都为1,在执行扇区擦除指令之前需要先执行写使能指令,保证WEL位为1。WEL位是一个只读位,在状态寄存器的S1位。在执行完“写使能”指令后,该位会被硬件自动置1。当芯片掉电后和执行“写禁能”、“页编程”、“扇区擦除”、“块区擦除”以及“芯片擦除”指令都会进入“写保护状态0”。
/**
* @brief 擦除FLASH扇区
* @param SectorAddr:要擦除的扇区地址
* @retval 无
*/
void FLASH_SectorErase(u32 SectorAddr)
{
FLASH_WriteEnable();//写使能
SPI_FLASH_WaitForWriteEnd();//等待其他操作完成
SPI_CS_0();
Software_SPI_Write_Read(Sector_Erase);//发送擦除指令
Software_SPI_Write_Read((SectorAddr&0XFF0000)>>16);
Software_SPI_Write_Read((SectorAddr&0X00FF00)>>8);
Software_SPI_Write_Read((SectorAddr&0X0000FF)>>0);
SPI_CS_1();//开始擦除
SPI_FLASH_WaitForWriteEnd();//等待擦除完毕
printf("擦除完毕\r\n");
}
擦除成功
4、写入数据
a、页写入数据
/*
* @brief 页写入数据,调用本函数写入数据前必须先擦除扇区
* @para pBuffer 待写入数据的指针
* @para WriteAddr 写入地址
* @para NumByteToWrite 数据长度,必须小于SPI_FLASH_PerWritePageSize
*/
void SPI_FLASH_PageWrite(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
FLASH_WriteEnable();//在写数据之前一定要进行写使能,不然会写入失败
SPI_CS_0();
Software_SPI_Write_Read(Page_Program);//发送页写指令
Software_SPI_Write_Read((WriteAddr&0XFF0000)>>16);
Software_SPI_Write_Read((WriteAddr&0x00FF00)>>8);
Software_SPI_Write_Read((WriteAddr&0x0000FF)>>0);
if(NumByteToWrite>SPI_FLASH_PerWritePageSize)
{
NumByteToWrite = SPI_FLASH_PerWritePageSize;
printf("超出页写最大范围\r\n");
}
while(NumByteToWrite--)
{
Software_SPI_Write_Read(*pBuffer);
pBuffer++;
}
SPI_CS_1();
SPI_FLASH_WaitForWriteEnd();//等待写入完毕
}
页面编程指令允许从1字节到256字节(一页)的数据进行编程
在写数据之前一定要进行写使能,不然会写入失败,博主之前研究半天写入失败结果发现是没有写使能
b、任意位置写入任意大小数据(不超过地址范围)
/**
* @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count个数据值,刚好可以对齐到页地址*/
count = SPI_FLASH_PageSize - Addr;
/*计算出要写多少整数页*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod运算求余,计算出剩余不满一页的字节数*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,则WriteAddr 刚好按页对齐 aligned */
if (Addr == 0)
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*先把整数页都写了*/
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
/* 若地址与 SPI_FLASH_PageSize 不对齐 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先写满当前页*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*再写剩余的数据*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先写完count个数据,为的是让下一次要写的地址对齐 */
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
/* 接下来就重复地址对齐的情况 */
WriteAddr += count;
pBuffer += count;
/*把整数页都写了*/
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
5、读取数据
/**
* @brief 读取FLASH数据
* @param pBuffer,存储读出数据的指针
* @param ReadAddr,读取地址
* @param NumByteToRead,读取数据长度
* @retval 无
*/
void Flash_Read(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
SPI_CS_0();
//读指令
Software_SPI_Write_Read(Read_Data);
//读地址
Software_SPI_Write_Read((ReadAddr&0XFF0000)>>16);
Software_SPI_Write_Read((ReadAddr&0x00FF00)>>8);
Software_SPI_Write_Read((ReadAddr&0x0000FF)>>0);
//读取数据
while(NumByteToRead--)
{
*pBuffer = Software_SPI_Write_Read(Dummy_Bytes);
pBuffer++;
}
SPI_CS_1();
}
6、主函数
float data[] = {0.11,1.12,2.23,3.34,4.45,5.55};
float readdata[6]={0};
int num[] = {1,2,3,4,5,6};
int rnum[6] = {0};
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
u8 flag=0xcc,i=0;
u8 rflag;
USART_Config();
Software_SPI_Init();
Read_Device_ID();
SysTick_Delay_Us(10);
Read_JEDEC_ID();
while(1)
{
Flash_Read(&rflag,SPI_FLASH_PageSize*0,1);
if(rflag==0xcc)//有数据
{
printf("\r\n有数据,进行读取\r\n");
Flash_Read((void*)readdata,SPI_FLASH_PageSize*1,sizeof(data));
Flash_Read((void*)rnum,SPI_FLASH_PageSize*2,sizeof(num));
for(i=0;i<6;i++)
{
printf("%f\r\n%d\r\n",readdata[i],rnum[i]);
}
printf("读取完成,进行擦除,请复位重新写入\r\n");
FLASH_SectorErase(0);
while(1);
}
else
{
printf("无数据 开始写入\r\n");
SPI_FLASH_BufferWrite(&flag,SPI_FLASH_PageSize*0,1);
SPI_FLASH_BufferWrite((void*)data,SPI_FLASH_PageSize*1,sizeof(data));//小数
SPI_FLASH_BufferWrite((void*)num,SPI_FLASH_PageSize*2,sizeof(num));//整数
printf("写入完成,请复位读取\r\n");
while(1);
}
}
}
效果演示
本文参考了一下两篇文章,感谢
关于flahs存取小数问题
W25Q64说明手册
工程下载