w25q128代码改进

之前, 一直觉得SPI和w25q128都是很复杂的操作.

看过野火的示例代码, .....哗, c代码+注释几百行, h文件也过百, 涉及函数记不清有多少, 反正很高大上.

原子哥的, 翻查参考数次, 寄存器版本的很精简, 但新手想理解其中的分扇区和分页逻辑还有点吃力. 

之前在LTDC的屏显上, 用来存取字库, 蒙查查地东拼西凑, 反正能正常工作, 没出问题, 嘻~

这两天反复参查,  更深入学习后, 精简了代码, 主要是write分页部分, 使其脉络更清晰. 

函数也只留三个全局函数,  使用时, 修改头文件就可以使用.

三个主函数: Init, Read, Write

五个基本功能函数: sendByte, writeSector, writeEnable, earseSector, waitReady

/*==================================================================================================================
 * 文件名称        W25Qxx.c
 * 功能描述        
 * 创建信息        L  2019.5.11    
 *
 =================================================================================================================*/    
#include "w25qx.h" 
#include "led.h"
  


u16 W25QxxType = 0 ; 
void  vW25qx_ReadID(void);
// 5个基本功能函数
u8     cW25qx_SendByte(u8 d);                          // 5_1
void  vW25qx_writeEnable(void) ;                      // 5_2 
void  vW25qx_waitReady(void) ;                        // 5_3
void  vW25qx_eraseSector(u32 addr);                   // 5_4
void  vW25qx_writeSector(char* p,u32 addr,u16 num);  // 5_5
      
                        

/***************************************************************************** 
  * @Fun    W25Qxx_Init
  * @brief  字模存储设备       
  *         @arg     
  *     
  *         @return 
  *    
  */  
void vW25qx_Init()
{
    // NSS  W25Qx设备片选线  
    vSys_SetGPIO (W25Qx_NSS_GPIOx , W25Qx_NSS_PIN , 1,0,2,1,0);            
    W25QX_NSS_HIGH ;              // 片选拉高        
    // SPI_GPIO
    vSys_SetGPIO (W25Qx_CLK_GPIOx , W25Qx_CLK_PIN  ,2,0,2,1,W25Qx_SPIx_AFx );  // SPI_CLK  
    vSys_SetGPIO (W25Qx_MISO_GPIOx ,W25Qx_MISO_PIN ,2,0,2,1,W25Qx_SPIx_AFx );  // SPI_MISO
    vSys_SetGPIO (W25Qx_MOSI_GPIOx ,W25Qx_MOSI_PIN, 2,0,2,1,W25Qx_SPIx_AFx );  // SPI_MOSI
      
    // SPI_通信配置
    W25Qx_RCC_EN ;                   // 使能SPI
    RCC->APB2RSTR |=  0x1<<20;       // 复位SPI5
    RCC->APB2RSTR &=~(0x1<<20);      // 停止复位SPI5     
    
    W25Qx_SPIx -> CR1  = 0x1<<0;         // CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样
    W25Qx_SPIx -> CR1 |= 0x1<<1;         // CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
    W25Qx_SPIx -> CR1 |= 0x1<<2;         // 主从模式:         1 = 主配置
    W25Qx_SPIx -> CR1 |= 0x0<<3;         // 波特率控制[5:3]:  0 = fPCLK /2
    W25Qx_SPIx -> CR1 |= 0x0<<7;         // 帧格式:           0 = 先发送MSB
    W25Qx_SPIx -> CR1 |= 0x1<<9;         // 软件从器件管理 :  1 = 使能软件从器件管理(软件NSS)
    W25Qx_SPIx -> CR1 |= 0x1<<8;         // 内部从器件选择,根据9位设置(失能内部NSS)
    W25Qx_SPIx -> CR1 |= 0x0<<11;        // 数据帧格式,       0 = 8位
    W25Qx_SPIx -> CR1 |= 0x1<<6;         // SPI使能           1 = 使能外设
    
    print("Flash存储     配置完成");       
    vW25qx_ReadID();  // 读取芯片型号,以判断通讯是否正常   
}



/***************************************************************************** 
  * @Fun    W25Qxx_ReadID
  * @brief  读取芯片型号,用于判断通讯状况      
  *         @arg      
  *         
  *         @return  芯片型号值
  *
  */        
void vW25qx_ReadID()
{    
    // 1: 读取芯片型号, 判断联接状况    
    W25QX_NSS_LOW; 
    cW25qx_SendByte(0x90);  // 发送读取ID命令,命令分两分,第一字节是命令,第四字节是0
    cW25qx_SendByte(0x00);
    cW25qx_SendByte(0x00);
    cW25qx_SendByte(0x00);  // 第四字节必节须是 0h      
    W25QxxType  = cW25qx_SendByte(0x00)<<8;   // u16 W25QxxType  在本文件定义,全局
    W25QxxType |= cW25qx_SendByte(0x00);    
    W25QX_NSS_HIGH;  
    
    switch (W25QxxType)
    {                        
        case W25Q16:            
            sprintf(Data.w25qx_Type ,"%s","W25Q16");               
            break;        
        case W25Q32:
            sprintf(Data.w25qx_Type ,"%s","W25Q32");              
            break;
        case W25Q64:
            sprintf(Data.w25qx_Type ,"%s","W25Q64");              
            break;
    	case W25Q128:
            sprintf(Data.w25qx_Type ,"%s","W25Q128");              
    		break;
    	case W25Q256:
            sprintf(Data.w25qx_Type ,"%s","W25Q256");           // 注意:W25Q256的地址是4字节               
    		break;
    	default:             
            sprintf(Data.w25qx_Type ,"%s","Flash设备失败 !!!");  
            Flag.w25qx_Fail =1;                                 // 设备失败
    		break;
    } 
    
    // 2:读取存储数据, 增加启动次数记录      
    if(Flag.w25qx_Fail !=1 )
    {   
        u32 numAddr=0x00000000;      // 数据地址, 0x0000:标志0xEE, 0x0001:标志0X00, 0x0002:数据高位, 0x0003:数据低位
        char d[4];                   // 
        u16 f   =0;                  // 标志
        u16 num =0;                  // 启动次数
        
        vW25qx_Read( d, numAddr, 4); // 读取4个字节数据
        f   = (d[0]<<8) | d[1];      // 标志
        num = (d[2]<<8) | d[3];      // 启动次数
        //printf("f= 0X%X,  num= %d\n", f, num);
        
        if(f!=0xEE00) {             
            num=1;            
            d[2]=0;
            d[3]=1;                
        }
        else{
            num++;                         
            d[2]=(u8)(num>>8);
            d[3]=(u8)num;                         
        }
        d[0]=0xEE;
        d[1]=0x00;
        vW25qx_Write(d, numAddr ,4);  
        Data.w25qx_Num =num; 
    }
    
    if( Flag.w25qx_Fail ==1) vLed_RedOn ();
    
    char c[45]=" ";
    sprintf (c,"              存储设备:%s ,第%d次使用!",  Data.w25qx_Type, Data.w25qx_Num); 
    print(c);        
}



/******************************************************************************
 * @Function        W25Qxx_Read  全局 4_3
 * @Description     读取数据
 *                                 
 * @Input           u8   *p    读出的数值存放位置    
 *                  u32  addr  读取地址
 *                  u16  num   连续读取的字节数
 *
 * @Return          
 *   
**/
void vW25qx_Read(char *p,u32 addr, u16 num)
{
    W25QX_NSS_LOW ;
    cW25qx_SendByte ( 0x03);             // 发送读取命令 03h
    cW25qx_SendByte ((u8)(addr>>16));
    cW25qx_SendByte ((u8)(addr>>8));
    cW25qx_SendByte ((u8)addr);
    
    for(int i=0;i<num;i++)
    {
        p[i]=cW25qx_SendByte(0);
    }
    W25QX_NSS_HIGH ;    
}



/******************************************************************************
 * @Function        W25Qxx_Write  全局 4_4
 * @Description     读取数据
 *                                 
 * @Input           u8   *p    要写入的数据存储区    
 *                  u32  addr  写入地址         (W25Q128 只用3字节, W25Q256用4字节)
 *                  u16  num   连续写入的字节数 (
 *
 * @Return          
 *   
**/
char W25Qx_buffer[4096];     // 开辟一段内存空间

void vW25qx_Write(char* p, u32 addr, u16 num)
{
    u32  secPos      = addr/4096;         // 扇区地址,第几个扇区
    u16  secOff      = addr%4096;         // 开始地始偏移字节数: 数据在扇区的第几字节存放
    u16  secRemain   = 4096-secOff;       // 扇区剩余空间字节数 ,用于判断够不够存放余下的数据
    char *buf = W25Qx_buffer;     // 原子哥代码,为什么不直接使用所声明的数组. (回看前面的疑问, 接触C有15年, 原来没下过工夫) 
   
    if(num<=secRemain) secRemain=num;  
    while(1)
    {
        vW25qx_Read (buf, secPos*4096, 4096);       // 读取扇区内容到缓存
        vW25qx_eraseSector(secPos );                        // 擦扇区
        
        for(u16 i=0;i<secRemain ;i++)                       // 原始数据写入缓存
            buf[secOff +i]=p[i];
        
        vW25qx_writeSector(buf, secPos*4096, 4096); // 缓存数据写入设备
        
        if(secRemain == num)                 // 已全部写入
            break;                                         
        else                                 // 未写完
        {
            p=p+secRemain ;                  // 原始数据指针偏移
            
            secPos ++;                       // 新扇区
            secOff =0;                       // 新偏移位,扇区内数据起始地址            
            num=num-secRemain ;              // 剩余未写字节数            
            secRemain = (num>4096)?4096:num; // 计算新扇区写入字节数                  
        }          
    }    
}

  
// 内部功能函数
// *******************************************************************************************************************************

// 5_1 发送1字节,返回1字节
// SPI通信,只一个动作:向DR写入从设命令值,同步读出数据!写读组合,按从设时序图来. 作为主设,因为收发同步,连接收发送中断也不用开,未验证其它中断对其工作的影响. 
u8  cW25qx_SendByte(u8 d)
{
    while((W25Qx_SPIx ->SR&(0x1<<1)) == 0);   // 等待发送区为空
    W25Qx_SPIx ->DR =d;
    
    while((W25Qx_SPIx->SR&(0x1<<0)) == 0);   // 等待接收完数据
    return W25Qx_SPIx->DR ;     
} 



// 5_2 写使能
void vW25qx_writeEnable()
{
    W25QX_NSS_LOW ;
    cW25qx_SendByte (0x6);          // 命令: Write Enable : 06h
    W25QX_NSS_HIGH ;              
}



// 5_3 等待空闲
void vW25qx_waitReady()
{
    u8 t=1;
    
    W25QX_NSS_LOW ;
    cW25qx_SendByte (0x5);          // 命令: Read Status Register : 05h
    while((t&0x1)==1)
        t=cW25qx_SendByte(0x00);    // 只要发送读状态寄存器指令,芯片就会持续向主机发送最新的状态寄存器内容 ,直到收到通信的停止信号。
    W25QX_NSS_HIGH ;    
} 
         


// 5_4 擦除一个扇区, 每扇区>150ms
void vW25qx_eraseSector(u32 addr)
{
    addr=addr*4096;         // 从第几扇区开始
    
    vW25qx_writeEnable();
    vW25qx_waitReady();
    // 命令
    W25QX_NSS_LOW ;
    cW25qx_SendByte (0x20);   // 命令: Sector Erase(4K) : 20h
    cW25qx_SendByte ((u8)(addr>>16));
    cW25qx_SendByte ((u8)(addr>>8));
    cW25qx_SendByte ((u8)addr);
    W25QX_NSS_HIGH ;
    
    vW25qx_waitReady();      
} 



// 5_5 写扇区. 要分页写入
void vW25qx_writeSector(char *p,u32 addr,u16 num)
{
    u16 pageRemain = 256;      // W25Qxx每个页命令最大写入字节数:256字节;    
  
    // 扇区:4096bytes, 缓存页:256bytes, 写扇区要分16次页命令写入     
    for(char i=0;i<16;i++)                       
    {            
        vW25qx_writeEnable ();                  // 写使能
        vW25qx_waitReady ();                    // 等待空闲
        
            W25QX_NSS_LOW ;                     // 低电平,开始
            cW25qx_SendByte(0x02);              // 命令: page program : 02h , 每个写页命令最大缓存256字节
            cW25qx_SendByte((u8)(addr>>16));    // 地址
            cW25qx_SendByte((u8)(addr>> 8));
            cW25qx_SendByte ((u8)addr); 
            for(u16 i=0;i<pageRemain; i++)      // 发送写入的数据 
                cW25qx_SendByte( p[i] );        // 高电平, 结束
            W25QX_NSS_HIGH ;     
        
        vW25qx_waitReady ();                    // 等待空闲    
      
        p = p + pageRemain;                     // 缓存指针增加一页字节数 
        addr = addr + pageRemain ;              // 写地址增加一页字节数
    }      
}



发布了44 篇原创文章 · 获赞 20 · 访问量 9536

猜你喜欢

转载自blog.csdn.net/zhouml_msn/article/details/103377350