1.flash说明
1.W25Q64 是华邦公司推出的大容量SPIFLASH 产品,W25Q64 的容量为 64Mb,W25Q128的容量为128Mb。W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V。
2.操作时序。
根据手册说明,发送指令需要将CS拉低。指令发送完毕在将CS拉高。指令有可能是单字节有可能是多字节。
下图为写指令时序示意图。
2.SPI操作说明
1.SPI每发送一个数据的同时会接收到一个字节的数据
2.SPI有4条线,MISO,MOSI,SCLK三条数据线,还有片选线CS,片选线对于SPI接口的从设备是低电平有效,主机输出一个低电平从机就被选中。这样就方便一个主机可以连接多个从设备,只需要使用不同的片选线。
3.TM4C SPI初始化
根据W25Q时序要求有两种方式处理
1.配置为普通SPI。初始化时将FSS配置为普通IO口。根据需求拉高或者拉低。
2.官方的高级SPI模式,
源码:
SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);//使能SPI2时钟
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);//使能GPIOD时钟
GPIOPinConfigure(GPIO_PD0_SSI2XDAT1);//SPI2 IO 口定义
GPIOPinConfigure(GPIO_PD1_SSI2XDAT0);
GPIOPinConfigure(GPIO_PD2_SSI2FSS);
GPIOPinConfigure(GPIO_PD3_SSI2CLK);
GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1 |GPIO_PIN_2|
GPIO_PIN_3);
#if defined(TARGET_IS_TM4C129_RA0) || \
defined(TARGET_IS_TM4C129_RA1) || \
defined(TARGET_IS_TM4C129_RA2)
SSIConfigSetExpClk(SSI2_BASE, ui32SysClock, SSI_FRF_MOTO_MODE_0,
SSI_MODE_MASTER, 1000000, 8); //设置SSI 时钟 模式
SSIAdvModeSet(SSI2_BASE,SSI_ADV_MODE_READ_WRITE);
#else
SSIConfigSetExpClk(SSI2_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
SSI_MODE_MASTER, 1000000, 8);
#endif
SSIAdvFrameHoldEnable(SSI2_BASE);
SSIEnable(SSI2_BASE);//使能SPI2
4.相关操作函数源码
在往某个地址写之前必须确保这个地址上的值是0xFF,否则说明这个地址以前被写过数据,还没有被擦除。W25Q64擦除的最小单位是Sector也就是4k个字节,也就是说如果要想往某个地址写一个值,如果这个地址上的值不是0xFF,那么就要把整个扇区都擦除,然后在写。
给W25Q64开辟一个4k的缓存,比如定义一个4k的数组,然后在写数据之前先判断如果这个地址上的数据不是0xFF,就先把这个地址所在的Sector里的数据全部保存在4k缓存中,再擦除这个扇区,再把缓存中对应的地址上的数据更新,再把这个4k缓存区的所有数据一次性的写入到这个Sector中。
相关源码
void delayms(uint16_t ms) //延时函数
{
uint8_t i;
while(ms--)
for(i = 0; i < 120; i ++);
}
void Spi_WriteByte(uint8_t TxData)//写入单字节
{
SSIDataPut(SSI2_BASE,(uint32_t) TxData);
SSIDataGet(SSI2_BASE,&SpiReviceBuf[0]);
}
void Spi_Flash_Write_Enable(void)//写使能
{
SSIAdvDataPutFrameEnd(SSI2_BASE,W25X_WriteEnable);
while(SSIBusy(SSI2_BASE))
{
}
SSIDataGet(SSI2_BASE,&SpiReviceBuf[0]);
}
void Spi_Flash_Write_Disable(void)//写禁止
{
SSIAdvDataPutFrameEnd(SSI2_BASE,W25X_WriteDisable);
while(SSIBusy(SSI2_BASE))
{
}
SSIDataGet(SSI2_BASE,&SpiReviceBuf[0]);
}
void W25QXX_Erase_Sector(uint32_t Dst_Addr)//擦除扇区
{
Dst_Addr*=4096;
Spi_Flash_Write_Enable();
W25QXX_Wait_Busy();
Spi_WriteByte(W25X_SectorErase);
Spi_WriteByte((uint8_t)((Dst_Addr)>>16));
Spi_WriteByte((uint8_t)((Dst_Addr)>>8));
SSIAdvDataPutFrameEnd(SSI2_BASE,(uint8_t)((Dst_Addr)>>0));
while(SSIBusy(SSI2_BASE))
{
}
SSIDataGet(SSI2_BASE,&SpiReviceBuf[0]);
W25QXX_Wait_Busy();
}
uint8_t SPI_Flash_ReadSR(void)//读取flash状态
{
uint32_t data;
Spi_WriteByte(W25X_ReadStatusReg);
SSIAdvDataPutFrameEnd(SSI2_BASE,0xFF);
while(SSIBusy(SSI2_BASE))
{
}
SSIDataGet(SSI2_BASE,&data);
return (uint8_t)data;
}
void W25QXX_Wait_Busy()//等待flash
{
uint8_t Status;
do{
Status = SPI_Flash_ReadSR();
Status &= 0x01;
}while(Status != 0);
}
void W25QXX_Read(uint8_t *pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)//读取
{
uint16_t i;
uint32_t data;
Spi_WriteByte(W25X_ReadData);
Spi_WriteByte((uint8_t)((ReadAddr)>>16));
Spi_WriteByte((uint8_t)((ReadAddr)>>8));
Spi_WriteByte((uint8_t)((ReadAddr)>>0));
while(SSIDataGetNonBlocking(SSI2_BASE, &SpiReviceBuf[0])) //清除缓冲区
{
}
for(i=0;i < NumByteToRead;i++)
{
if(i == NumByteToRead - 1)
{
SSIAdvDataPutFrameEnd(SSI2_BASE,0xFF);
while(SSIBusy(SSI2_BASE))
{
}
}
else
SSIDataPut(SSI2_BASE,0XFF); //循环读数,只需要不停的发送0xff,就可以读出数据
SSIDataGet(SSI2_BASE,&data);
pBuffer[i] = (uint8_t)data;
}
}
void W25QXX_Write_Page(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)//写一页
{
uint16_t i;
Spi_Flash_Write_Enable();
Spi_WriteByte(W25X_PageProgram);
Spi_WriteByte((uint8_t)((WriteAddr)>>16));
Spi_WriteByte((uint8_t)((WriteAddr)>>8));
Spi_WriteByte((uint8_t)((WriteAddr)>>0));
for(i = 0;i < NumByteToWrite;i++)
{
if(i == NumByteToWrite - 1)
{
SSIAdvDataPutFrameEnd(SSI2_BASE,pBuffer[i]);
while(SSIBusy(SSI2_BASE))
{
}
SSIDataGet(SSI2_BASE,&SpiReviceBuf[0]);
}
else
Spi_WriteByte(pBuffer[i]);
}
W25QXX_Wait_Busy();
}
void W25QXX_Write_NoCheck(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)//写,但不校验是否已擦除
{
uint16_t pageremain;
pageremain=256-WriteAddr%256;
if(NumByteToWrite<=pageremain)
pageremain=NumByteToWrite;
while(1)
{
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);//要写入的字节数小或等于单页剩余的字节数直接写
if(NumByteToWrite==pageremain)
break;//写入结束了
else //NumByteToWrite>pageremain,如果要写入的数据大于单页剩余的字节数。
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //减去已经写入了的字节数
if(NumByteToWrite>256)
pageremain=256; //一次可以写入256个字节
else
pageremain=NumByteToWrite; //不够256个字节了
}
}
}
void W25QXX_Write(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)//写
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t * W25QXX_BUF;
W25QXX_BUF=W25QXX_BUFFER;
secpos=WriteAddr/4096;//算出来扇区地址
secoff=WriteAddr%4096;//取余数,算出在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
if(NumByteToWrite<=secremain) //没有跨扇区
secremain=NumByteToWrite;
while(1)
{
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容,保存在Buffer中。
for(i=0;i < secremain;i++) //校验数据
{
if(W25QXX_BUF[secoff+i]!=0XFF)
break;//如果有不等于0xFF的数据,就需要擦除
}
if(i < secremain)//需要擦除
{
W25QXX_Erase_Sector(secpos); //擦除这个扇区
for(i=0;i < secremain;i++) //更新缓存中的数据
{
W25QXX_BUF[ secoff + i ]=pBuffer[i]; //这里的pBuffer中是我们要写的数据,把这些数据更新到缓存中对应的 位置
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//重新写入整个扇区
}
else
W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.
if(NumByteToWrite==secremain)
break;//写入结束了
else//写入未结束
{
secpos++;//扇区地址增1
secoff=0;//偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumByteToWrite-=secremain; //字节数递减
if(NumByteToWrite>4096)
secremain=4096;//下一个扇区还是写不完
else
secremain=NumByteToWrite; //下一个扇区可以写完了
}
}
}