【STM32】SPI存儲器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 ;              // 寫地址增加一頁字節數
    }      
}



 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章