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;            // 
            }     
        };     
    }
  • 相关阅读:
    通过Spring @PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
    Java注释@interface的用法
    java的一段对象数据类型映射的代码
    Google Protocol Buffer 的使用和原理
    MyISAM 和 InnoDB 讲解
    一个PHP写的简单webservice服务端+客户端
    提高php运行效率的50个技巧
    剑指Offer:二叉搜索树的后序遍历序列【33】
    剑指Offer:链表中环的入口节点【23】
    剑指Offer:删除链表的节点【18】
  • 原文地址:https://www.cnblogs.com/nasduc/p/4920162.html
Copyright © 2011-2022 走看看