博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SPI Flash的操作
阅读量:6542 次
发布时间:2019-06-24

本文共 4766 字,大约阅读时间需要 15 分钟。

智能硬件设备的MCU下面,常常会挂一个SPI Flash,用于存放字库等文件。容量不会太大,16MB左右。今天记录一下通过SPI接口对其进行操作。

    这个图是SPI的接口结构图。主机写数据寄存器,通过 MOSI 信号线 传送给从机,从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。 如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。最后这句要理解,如果要读从机,除了发读命令,还要写空数据到从机,把从机中的数据挤出来。

SPI的配置中,有两个比特要注意。CPOL用来配置空闲的时候,CLK电平的高低。

CPHA用来控制采样时刻。CPHA=1的时候,采样发生在CS变低后的第二个沿,无论是下降沿还是上升沿。CPHA=0的时候,采样发生在CS变低后的第一个沿。这个需要查看从机的时序来确定怎么配置。ST的MCU,NSS管脚可以选择用硬件控制,也可以用软件控制,软件控制就是写GPIO,输出高低。ST的SPI口的其余配置就很简单了。

 

接下来介绍一下这颗SPI Flash。W25Q128 将 16MB 的容量分为 256 个块( Block),每个块大小为 64K 字节,每个块又分为16 个扇区( Sector),每个扇区 4K 个字节。 W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区。每个扇区又分为16个页(page),每个page

256B, 可以对整个page进行写操作。

 

//数据读写函数,这个函数主要用来发送控制命令 u8 SPI1_ReadWriteByte(u8 TxData){                         while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待发送缓冲区为空,SR寄存器的TXE位        SPI_I2S_SendData(SPI1, TxData); //往DR寄存器写入要发送的值,即是发送数据          while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收缓冲区为空     return SPI_I2S_ReceiveData(SPI1); //缓冲区空了,数据已经到DR寄存器了,就可以读了。             }
//读状态寄存器 u8 W25QXX_ReadSR(void)   {      u8 byte=0;       W25QXX_CS=0;                                 SPI1_ReadWriteByte(W25X_ReadStatusReg);    //  W25X_ReadStatusReg是读状态寄存器指令,0x05;      byte=SPI1_ReadWriteByte(0Xff);             //  写个无效数据,把要读取的数据移出来    W25QXX_CS=1;                            //     return byte;   }

 

   这个是读ID的指令,代码如下:

u16 W25QXX_ReadID(void){    u16 Temp = 0;          W25QXX_CS=0;                        SPI1_ReadWriteByte(0x90);// 发指令    SPI1_ReadWriteByte(0x00);  //dummy           SPI1_ReadWriteByte(0x00);  //dummy           SPI1_ReadWriteByte(0x00);                         Temp|=SPI1_ReadWriteByte(0xFF)<<8;  //读MF7-MF0    Temp|=SPI1_ReadWriteByte(0xFF);     //读ID7-ID0    W25QXX_CS=1;                        return Temp;}

以上是读数据的时序,下面是代码

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   //要放入的数组;读地址;要读的数据个数{      u16 i;                                                   W25QXX_CS=0;                            //     SPI1_ReadWriteByte(W25X_ReadData);         //    03h    SPI1_ReadWriteByte((u8)((ReadAddr)>>16));  //    地址23~16    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));   //    地址15~8    SPI1_ReadWriteByte((u8)ReadAddr);         //     地址7~0    for(i=0;i
//这个函数是用来page写,page写需要满足下面的条件。page都已经被擦除了,而且写使能已经执行了 //The Page Program instruction allows from one byte to 256 bytes (a page) of data to be programmed at //previously erased (FFh) memory locations. A Write Enable instruction must be executed before the device //will accept the Page Program Instruction (Status Register bit WEL= 1). //这个函数使用的前提是,这个page被擦干净了,所以这个函数是不会被单独调用的,会在另外一个函数中被引用 void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) //NumByteToWrite不能超过一个page的大小{     u16 i;      W25QXX_Write_Enable();                  //写使能    W25QXX_CS=0;                            //     SPI1_ReadWriteByte(W25X_PageProgram);      // page编程指令    SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); // 地址23~16    SPI1_ReadWriteByte((u8)((WriteAddr)>>8));     //地址15~8    SPI1_ReadWriteByte((u8)WriteAddr);          //地址7~0    for(i=0;i

 

下面这个函数,写入的数据要大于一个page。然后控制写入地址的偏移,把数据分割成小块,然后再调用上面的Page写函数。

pageremain表示这个page中要写入的数据个数
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   {                           u16 pageremain;           pageremain=256-WriteAddr%256; //要写入的地址所在的page,还剩余多少空间                    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;// 如果要写入的数据,连第一个page也填不满    while(1)    {               W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);        if(NumByteToWrite==pageremain)break;//一个page都没满,这就写完了         else //还需要写到下一个page        {            pBuffer+=pageremain; //地址偏移            WriteAddr+=pageremain;                NumByteToWrite-=pageremain;              //已经写掉的去除            if(NumByteToWrite>256)pageremain=256; //             else pageremain=NumByteToWrite;       //         }    };        }

 以下是真正的写,会涉及到擦除,会调用上面的函数

u8 W25QXX_BUFFER[4096];     //先开辟一个4K的空间    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;//获得sector号    secoff=WriteAddr%4096;// sector中的偏移    secremain=4096-secoff;// sector中剩余空间//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用     if(NumByteToWrite<=secremain)secremain=NumByteToWrite;// 思路和上面的函数类似    while(1)     {            W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//因为后面可能需要擦除,所以要把sector读出来        for(i=0;i
4096)secremain=4096; // 这个和page操作类似 else secremain=NumByteToWrite; // } }; }

 

转载于:https://www.cnblogs.com/nasduc/p/4920162.html

你可能感兴趣的文章
LeetCode (11): Container With Most Water
查看>>
【技巧】easyUI的datagrid,如何在翻页以后仍能记录被选中的行
查看>>
经过强制类型转换以后,变量a, b的值分别为( )short a = 128; byte b = (byte) a;
查看>>
ubuntu下msmtp+mutt的安装和配置
查看>>
spring中注解说明
查看>>
QLabel显示图片,图片可以自适应label的大小
查看>>
阅读下面程序,请回答如下问题:
查看>>
BZOJ3994:[SDOI2015]约数个数和——题解
查看>>
3、EJB3.0开发第一个无会话Bean和客户端(jboss4.2.3)
查看>>
git fetch & pull详解
查看>>
优酷2013.3去广告 不黑屏
查看>>
web入门、tomcat、servlet、jsp
查看>>
boost_1.63.0编译VS2013
查看>>
mysql查看每个数据库所占磁盘大小
查看>>
Android深度探索第三章
查看>>
jQuery 插件-(初体验一)
查看>>
PHP语言 -- Ajax 登录处理
查看>>
基于js的CC攻击实现与防御
查看>>
Largest Rectangle in a Histogram
查看>>
树状数组模板
查看>>