时间:2017/06/14 SPI---读写串行FLASH HAL库版本
G15电装.王维鋆
一、SPI基础知识
1.SPI(Serial Peripheral interface),即串行外围设备接口。主要应用于EEPROM、FLASH、实时时钟、AD转换器以及数字信号处理器和数字信号解码器之间。是一种高速的、全双工、同步的通信总线,在芯片管脚上只占用四根线。
2.SPI接口一般使用4条线进行通信:
MISO--主设备数据输入,从设备数据输出
MOSI--主设备数据输出,从设备数据输入
SCLK--时钟信号,由主设备产生
NSS/CS --从设备片选信号,因为SPI协议不像IC2设备有设备地址可以寻址,SPI采
11111
用NSS信号线寻找,
本信号线独占主机的一个引脚,即有多少个从设备,就有多少
条片选信号线。
3.SPI主要特点:可以同时发出和接收串行数据,可以当做主机或从机工作
4.时钟极性(CPOL),=1串行同步时钟的空闲状态为高电平;=0串行同步时钟的空闲状态为低电平
5.时钟相位(CPHA),=1串行同步时钟的第一个跳变沿(偶数边沿)采样数据;=0串行同步时钟的第二个跳变沿(奇数边沿)采样数据。
6.STM32F429的SCK时钟频率可为
fpclk1
为 90MHz, fpclk2 为 45MHz.
二.串行FLASH
W25Q128容量为16M字节,共有256个块(
block
),而一个块(Block)包含 16 个扇区,一个扇区4k(4096字节),即每个Block 64KB.
FLASH_SIZE=16*1024*1024即16MByte寻址空间.
FLASH 芯片的最小擦除单位为扇区(Sector),也就是说每次必须擦除4K字节,所有,我们必须给W25QXX一个至少4K的缓存区.
SPI初始化步骤:
1:配置相关引脚复用为SPI,使能SPI时钟;
2:设置SPI工作模式,包括主机或者从机、数据格式(高位在前还是低位在前)、设置串行时钟的极性和相位(采样方式)、SPI时钟频率(SPI的传输速度);
3:使能SPI;
SPI底层函数,重点就是写SPI FLASH函数,这里简要说明哈自己对写函数思路 我使用的是原子的HAL库,所有,这里 我参考的写数据源码就不是火哥的。
在上面已经说过,每个扇区是4k,也就是4096个地址,所以,我们在写地址之前,如果该地址的值不是0XFF,必须先擦除对应扇区,然后在写入..
1.根据要写的起始地址,确定要写的起始区域的扇区号(
Sector
)以及在起始扇区中的偏移量.
2.根据要写入数据的起始地址和字节数,是否已经超过了扇区容量.确定操作扇区的地址范围.
3.对没一个扇区,先遍历即将写入数据的地址区域保存的数据是否为0xff,如果都是,则不用擦除,如果检测到有不是0xff的地址区域,则先读出里面的数据,保存在一个已经事前写好的缓存区里面,再擦除扇区内容。然后把需要写入的数据,先写入缓存区,最后一起把缓存的数据写入扇区.
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u32 secpos;
u16 secoff;
u16 secremain;
u16 i;
u8 * W25QXX_BUF;
W25QXX_BUF=W25QXX_BUFFER;
secpos=WriteAddr/4096;//扇区地址
secoff=WriteAddr%4096;//在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
if(NumByteToWrite<=secremain)
{
secremain=NumByteToWrite;//所要写入区域有不为0xff字节,停止检查,直接对扇区进行擦除,即写入0xff,此时i < secremain
}
//以下对写入数据进行处理,如果数据在一个扇区写完则结束,如果超过则将其进行分割,每份不超过一个扇区容纳量,依次写入,直到写完为止
while(1)
{
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出所在扇区所有数据放在缓冲池
for(i=0;i
{
if(W25QXX_BUF[secoff+i]!=0XFF)//所要写入区域有不为0xff字节,停止检查,直接对扇区进行擦除,即写入0xff
break;
}
if(i
{
W25QXX_Erase_Sector(secpos);//将需要写入扇区且要写入区域存在非0XFF字节的扇区全部擦除
for(i=0;i
{
W25QXX_BUF[i+secoff]=pBuffer[i];
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);////将缓冲池中的数据一一对应写入扇区,写入区域前的数据进行了恢复
}else
W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入区域数据均为0xff,无需对sector进行擦除,直接写即可
if(NumByteToWrite==secremain)//如果写入数据正好在同一sector写完则处理完毕
break;//写入结束了
else//写入未结束
{
secpos++;//扇区地址增1 进入下一sector继续存放剩余数据
secoff=0;//偏移位置为0 从下一扇区起始位置开始写入
pBuffer+=secremain; ////更新数据,要写入的数据地址偏移已写入数据量作为继续写入新地址
WriteAddr+=secremain;//更新数据,存储芯片写入地址更新
NumByteToWrite-=secremain; //更新数据,要写入数据减去已写入字节数量作为继续写入新数量
if(NumByteToWrite>4096) //剩余数据超过一个sector,按照一个sector进行写,重走以上过程
secremain=4096; //下一个扇区还是写不完
else //下一个扇区可以写完了
secremain=NumByteToWrite;
}
};
}
然后我们看
读取SPI FLASH函数
,W25Q128容量为16M字节,共有256个块(
block
),而一个块(Block)包含 16 个扇区,所以扇区的个数为 256*16=4096,
那么上面函数的参数
Dst_Addr
的范围就是
0-4096
,假如要擦除第
1000
个的扇区,那么这个扇区的字节起始就是
1000*4096=4096000
,因此把
4096000
先发送最高
8
位,次高
8
位,再到最低
8
位,然后
W25Q64
就从
4096000
开始往下擦除
4K
大小的数据空间,计算地址的时候是使用字节来计算的。(这里我是参考了网上的资料总结的)
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
W25QXX_CS=0; //使能器件
SPI5_ReadWriteByte(W25X_ReadData); //发送读取命令
SPI5_ReadWriteByte((u8)((ReadAddr)>>16)); //发送24bit地址
SPI5_ReadWriteByte((u8)((ReadAddr)>>8));
SPI5_ReadWriteByte((u8)ReadAddr);
for(i=0;i
{
pBuffer[i]=SPI5_ReadWriteByte(0XFF); //循环读数
}
W25QXX_CS=1;
}
有需要参考的小伙伴,我分享了百度云0 F429挑战者SPI通信实验HAL库版本,对写读SPI函数有详细的注释。