zoukankan      html  css  js  c++  java
  • SPI Flash的操作

    智能硬件设备的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<NumByteToRead;i++)
        { 
            pBuffer[i]=SPI1_ReadWriteByte(0XFF);   //  发送dummy,移出读取数据
        }
        W25QXX_CS=1;                                
    }  

    //这个函数是用来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<NumByteToWrite;i++)
    SPI1_ReadWriteByte(pBuffer[i]); // 循环操作 W25QXX_CS=1; // W25QXX_Wait_Busy(); // }

    下面这个函数,写入的数据要大于一个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
    ",WriteAddr,NumByteToWrite);//测试用
         if(NumByteToWrite<=secremain)secremain=NumByteToWrite;// 思路和上面的函数类似
        while(1) 
        {    
            W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//因为后面可能需要擦除,所以要把sector读出来
            for(i=0;i<secremain;i++)// 
            {
                if(W25QXX_BUF[secoff+i]!=0XFF)break;  //碰到非FF的,就需要擦除了 
            }
            if(i<secremain)//跳出了for循环,说明碰到非FF了
            {
                W25QXX_Erase_Sector(secpos);// 擦除这个sector
                for(i=0;i<secremain;i++)       // 
                {
                    W25QXX_BUF[i+secoff]=pBuffer[i];  //左边这个数据已经把整个sector读出来了,右边这个是需要写入的数据,右边把左边覆盖掉,这个是指针操作,所以可以这样    
                }
                W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096); //虽然真正写入的是sector后面一部分,但是由于整个都擦除了,所以需要都写
    
            }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain); //  发现剩余部分没有非FF,那就直接全部写入
            if(NumByteToWrite==secremain)break;//  要写入的都在一个sector里面,就一次写完了,可以跳出
            else// 
            {
                secpos++;// 转到下一个sector
                secoff=0;// 到了一个新的sector,就是从偏移地址0开始写
    
                   pBuffer+=secremain;  // 
                WriteAddr+=secremain;// 
                   NumByteToWrite-=secremain;                // 
                if(NumByteToWrite>4096)secremain=4096;    //  这个和page操作类似
                else secremain=NumByteToWrite;            // 
            }     
        };     
    }
  • 相关阅读:
    VOA 2009/11/02 DEVELOPMENT REPORT In Kenya, a Better Life Through Mobile Money
    2009.11.26教育报道在美留学生数量创历史新高
    Java中如何实现Tree的数据结构算法
    The Python Tutorial
    VOA HEALTH REPORT Debate Over New Guidelines for Breast Cancer Screening
    VOA ECONOMICS REPORT Nearly Half of US Jobs Now Held by Women
    VOA ECONOMICS REPORT Junior Achievement Marks 90 Years of Business Education
    VOA 2009/11/07 IN THE NEWS A Second Term for Karzai; US Jobless Rate at 10.2%
    Ant入门
    Python 与系统管理
  • 原文地址:https://www.cnblogs.com/nasduc/p/4920162.html
Copyright © 2011-2022 走看看