外設驅動庫開發筆記23:AT24Cxx外部存儲器驅動

在我們的應用開發過程中,經常會使用到外部的EEPROM外部存儲器來保存一些參數和配置數據等。而比較常用的就是AT24Cxx系列產品,這一節我們來開發用於操作AT24Cxx系列產品的驅動。

1、功能概述

AT24Cxx系列EEPROM包括從1Kbit2Mbit的各種容量。AT24Cxx系列產品採用I2C總線數據傳送協議。儘管容量跨度很大,但它們都擁有相同的封裝和引腳排布,具體的引腳分配如下:

由於A0A1A2可以組成000~111八種情況,即通過器件地址輸入端A0A1A2可以實現將最多8個器件連接到同一條總線上,通過不同的配置進行器件的選擇。

對於AT24Cxx系列EEPROM不同的容量對地址的分配有較大差異,這涉及到設備地址和寄存器地址。從1K容量到2M容量寄存器地址分別採用718位來表示。16K及以下容量的EEPROM採用一個字節的寄存器地址配合設備地址段實現711位的寄存器地址尋址。而32k及以上的EEPROM採用兩個字節的寄存器地址配合設備地址段實現1218位的寄存器地址尋址。具體的地址分配如下:

從上表我們很容易明白,設備地址的低3位的定義決定了在同一條I2C總線上,最多可以掛載多少個AT24Cxx設備。有3位用於設備地址則最多可掛載8個設備;有2位用於設備地址則最多可掛載4個設備;有1位用於設備地址則最多可掛載2個設備;有0位用於設備地址則最多可掛載1個設備。需要注意的是,不同定義的位的設備混用於同一總線時,相同的定義位必須一樣,否則用作寄存器地址的位可能讓總線上的總線無法識別。

在一些AT24Cxx系列EEPROM型號中,帶有序列號的專用存儲單元。這些存儲單元不佔用存儲器的存儲單元。序列號爲128位,讀取序列號的設備地址以0xB0開頭,以區別於EEPROM存儲區域的讀取。

在一些AT24Cxx系列EEPROM型號中,除了帶有序列號的專用存儲單元外,還有帶有48位或者64位的MAC地址,固定在專用的存儲單元。這些單元不佔用存儲器的存儲單元。讀取序列號和讀取MAC地址採用同樣的設備地址,均以0xB0開頭。有一些型號該區域並未用於MAC定製可用於用戶操作。

需要注意的是有些型號的AT24Cxx系列EEPROM存儲器的設備地址是固化的,需通過型號的後綴標識來識別。

2、驅動設計與實現

我們已經瞭解了AT24Cxx存儲器的基本功能及讀寫方式,接下來我們將開發操作AT24Cxx系列EEPROM存儲器的驅動程序。

2.1、對象定義

在使用一個對象之前我們需要獲得一個對象。同樣的我們想要AT24Cxx系列EEPROM存儲器就需要先定義AT24Cxx系列EEPROM存儲器的對象。

2.1.1、對象的抽象

我們要得到AT24Cxx系列EEPROM存儲器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下AT24Cxx系列EEPROM存儲器的對象。

先來考慮屬性,作爲屬性肯定是用於標識或記錄對象特徵的東西。我們來考慮AT24Cxx系列EEPROM存儲器對象屬性。首先AT24Cxx系列EEPROM存儲器採用的是I2C接口,對於每一個I2C接口元件都有一個設備地址用於區別總線上的設備,所以我們將I2C設備地址作爲對象的屬性用以區別總線設備。AT24Cxx系列EEPROM存儲器存在多個型號對應不同的容量和特性,所以我們將其型號設置爲對象的屬性以區別對象的類型。前面我們也說過,不同容量的AT24Cxx系列EEPROM存儲器由於尋址空間不同,所以寄存器地址長度也是不同的,所以我們將其地址長度作爲屬性以區分處理。

接着我們還需要考慮AT24Cxx系列EEPROM存儲器對象的操作問題。我們需要對AT24Cxx系列EEPROM存儲器進行數據讀寫操作,無論讀寫其實都依賴於對I2C接口的操作,而這些操作基本都會依賴於具體的硬件平臺,所以我們將讀寫操作作爲對象的操作。

根據上述我們對AT24Cxx系列EEPROM存儲器的分析,我們可以定義AT24Cxx系列EEPROM存儲器的對象類型如下:

typedef struct At24cObject {
       uint8_t devAddress;          //設備地址
       At24cModeType mode;           //設備類型
       At24cMemAddLengthType memAddLength;           //寄存器地址長度
       void (*Read)(struct At24cObject *at,uint16_t regAddress,uint8_t *rData,uint16_t rSize);       //讀數據操作指針
       void (*Write)(struct At24cObject *at,uint16_t regAddress,uint8_t *wData,uint16_t wSize);    //寫數據操作指針
       void (*Delayms)(volatile uint32_t nTime);       //毫秒延時操作指針
}At24cObjectType;

2.1.2、對象初始化

我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這裏我們來考慮AT24Cxx系列EEPROM存儲器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是爲對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計AT24Cxx系列EEPROM存儲器對象的初始化函數如下:

/* 初始化AT24CXX對象 */
void At24cxxInitialization(At24cObjectType *at,    //AT24CXX對象實體
                                   uint8_t devAddress,   //AT24CXX設備地址
                                   At24cModeType mode,    //AT24CXX對象類型
                                   At24cMemAddLengthType length,      //寄存器地址長度
                                   At24cRead read,        //讀AT24CXX對象操作指針
                                   At24cWrite write,      //寫AT24CXX對象操作指針
                                   At24cDelays delayms           //延時操作指針
                            )
{
       if((at==NULL)||(read==NULL)||(write==NULL)||(delayms==NULL))
       {
              return;
       }

       if((devAddress&0xF0)==0xA0)
       {
              at->devAddress=devAddress;
       }
       else
       {
              at->devAddress=0x00;
       }
      
       at->mode=mode;
       at->memAddLength=length;
      
       at->Read=read;
       at->Write=write;
       at->Delayms=delayms;
}

2.2、對象操作

我們已經完成了AT24Cxx系列EEPROM存儲器對象類型的定義和對象初始化函數的設計。但我們的主要目標是獲取對象的信息,接下來我們還要實現面向AT24Cxx系列EEPROM存儲器的各類操作。

2.2.1、寫單個字節

AT24Cxx系列EEPROM存儲器支持單字節寫數據,收到正確的設備地址和字地址字節後,EEPROM將發送一個確認。然後設備將準備接收8位數據字。在接收到8位數據字之後,EEPROM將返回一個ACK。然後,尋址設備(如總線主機)必須使用停止條件終止寫操作。此時,EEPROM將進入一個內部自動定時的寫週期,這個寫週期將在一定時間內完成,而數據字將被編程到非易失性EEPROM中。在這個寫週期中,所有的輸入都是禁用的,EEPROM在寫完成之前不會響應。

如果AT24Cxx對象是一個使用7位到11位地址表示寄存器地址的話,其數據格式如下圖所示:

如果AT24Cxx對象是一個使用12位到18位地址表示寄存器地址的話,其數據格式如下圖所示:

根據上述時序圖,我們可以編寫AT24Cxx系列EEPROM存儲器寫單個字節數據程序如下:

/*向AT24CXX寫入單個字節*/
void WriteByteToAT24CXX(At24cObjectType *at,uint32_t regAddress,uint8_t data)
{
       uint8_t temp;
       uint16_t regAdd;
      
       if(at->memAddLength==AT24C8BitMemAdd)
       {
              regAdd=(uint16_t)(regAddress&0xFF);
              temp=(uint8_t)(regAddress>>8);
       }
       else
       {
              regAdd=(uint16_t)regAddress;
              temp=(uint8_t)(regAddress>>16);
       }
       temp=(temp&(~(devAddMask[at->mode]>>1)))<<1;
       at->devAddress=(at->devAddress & devAddMask[at->mode])|temp;

       at->Write(at,regAdd,&data,1);
}

2.2.2、寫多個字節

AT24Cxx系列EEPROM存儲器支持多字節的寫操作,但對於AT24Cxx對象來說最多隻支持寫到發送地址所在的頁尾,所以資料中也稱其爲頁寫。而對於不同型號的AT24Cxx系列EEPROM存儲器每頁所包含的字節數是不一樣的,從8個字節到256個字節不等,我們需要注意寫入的字節數。

整頁寫的初始化方式與單字節寫的初始化方式相同,但是總線主機在第一個數據字被鎖定後不會發送停止條件。相反,在EEPROM承認接收到第一個數據字之後,總線主機可以傳輸最多到所在頁結尾的數據字。EEPROM將在接收到每個數據字後返回一個ACK。一旦所有要寫的數據都被髮送到設備,總線主機必鬚髮出一個停止條件此時內部的自計時寫週期將開始。字地址的下四位在接收到每個數據字後進行內部遞增,高階位地址位元不會增加。整頁寫操作僅限於在單個物理頁中寫入字節,而不管實際寫入的字節數。當增加的字地址到達頁面邊界時,地址計數器將滾動到同一頁面的開頭。這是必須要注意的,一旦繼續寫數據可能會將頁面中先前加載的數據無意中更改。

如果AT24Cxx對象是一個使用7位到11位地址表示寄存器地址的話,其寫多個字節的數據格式如下圖所示:

如果AT24Cxx對象是一個使用12位到18位地址表示寄存器地址的話,其寫多個字節的數據格式如下圖所示:

根據上述時序圖,我們可以編寫AT24Cxx系列EEPROM存儲器寫多個字節數據程序如下:

/*向AT24CXX寫入多個字節,從指定地址最多到所在頁的結尾*/
void WriteBytesToAT24CXX(At24cObjectType *at,uint32_t regAddress,uint8_t *wData,uint16_t wSize)
{
       uint16_t regAdd;
       uint8_t size;
       uint8_t temp;
      
       if(at->memAddLength==AT24C8BitMemAdd)
       {
              regAdd=(uint16_t)(regAddress&0xFF);
              temp=(uint8_t)(regAddress>>8);
       }
       else
       {
              regAdd=(uint16_t)regAddress;
              temp=(uint8_t)(regAddress>>16);
       }
       temp=(temp&(~(devAddMask[at->mode]>>1)))<<1;
       at->devAddress=(at->devAddress & devAddMask[at->mode])|temp;
      
       if((wSize<=pageBytes[at->mode])&&(wSize<=(pageBytes[at->mode]-(regAddress&regAddMask[at->mode]))))
       {
              size=wSize;
       }
       else
       {
              size=pageBytes[at->mode]-(regAddress&regAddMask[at->mode]);
       }

       at->Write(at,regAdd,wData,size);
}

2.2.3、讀單個字節

AT24Cxx系列EEPROM存儲器支持單字節的讀操作。這一方式其實有兩種模式,讀當前位置和讀隨機位置。其實讀當前位置是讀隨機位置特例,我們考慮一般性則只考慮隨機讀取就可以了。隨機讀的開始方式與字節寫操作加載新數據字地址的方式相同。這就是所謂的僞寫序列;但是,必須省略數據字節和字節寫的停止條件,以防止該部分進入內部寫循環。一旦設備地址和字地址被鎖定並被EEPROM確認,總線主機必鬚生成另一個啓動條件。總線主機現在通過發送一個啓動條件來初始化一個讀取的當前地址,接着是一個有效的設備地址字節,其R/W位設置爲邏輯“1”。之後EEPROM將對設備地址進行ACK處理,並在SDA線路上連續地輸出數據字。如果總線主機在第9個時鐘週期內沒有響應ACK,則所有類型的讀操作都將終止。在NACK響應之後,主進程可以發送一個停止條件來完成協議。

如果AT24Cxx對象是一個使用7位到11位地址表示寄存器地址的話,其讀單個字節的數據格式如下圖所示:

如果AT24Cxx對象是一個使用12位到18位地址表示寄存器地址的話,其讀單個字節的數據格式如下圖所示:

根據上述時序圖,我們可以編寫AT24Cxx系列EEPROM存儲器隨機讀取一個字節數據程序如下:

/*從AT24CXX讀取單個字節,從隨機地址讀取*/
uint8_t ReadByteFromAT24CXX(At24cObjectType *at,uint32_t regAddress)
{
       uint8_t rData;
       uint16_t regAdd;
       uint8_t temp;

       if(at->memAddLength==AT24C8BitMemAdd)
       {
              regAdd=(uint16_t)(regAddress&0xFF);
              temp=(uint8_t)(regAddress>>8);
       }
       else
       {
              regAdd=(uint16_t)regAddress;
              temp=(uint8_t)(regAddress>>16);
       }
       temp=(temp&(~(devAddMask[at->mode]>>1)))<<1;
       at->devAddress=(at->devAddress & devAddMask[at->mode])|temp;

       at->Read(at,regAdd,&rData,1);
 
       return rData;
}

2.2.4、讀多個字節

AT24Cxx系列EEPROM存儲器支持多字節的讀操作,類似於單字節讀取,可以從當前位置開始讀也可以從指定地址開始讀。多字節讀取也稱爲順序讀取由當前地址讀取或隨機讀取啓動。總線主接收到一個數據字後,它以ACK應答。只要EEPROM接收到ACK,它就會繼續增加字地址,並連續地計時輸出連續的數據字。當達到最大內存地址時,數據字地址將滾動,順序讀取將從內存數組的開頭開始。如果總線主機在第9個時鐘週期內沒有響應ACK,則所有類型的讀操作都將終止。在NACK響應之後,主進程可以發送一個停止條件來完成協議。

如果AT24Cxx對象是一個使用7位到11位地址表示寄存器地址的話,其讀多個字節的數據格式如下圖所示:

如果AT24Cxx對象是一個使用12位到18位地址表示寄存器地址的話,其讀多個字節的數據格式如下圖所示:

根據上述時序圖,我們可以編寫AT24Cxx系列EEPROM存儲器順序讀取多個字節數據程序如下:

/*從AT24CXX讀取多個字節,從指定地址最多到所在頁的結尾*/
void ReadBytesFromAT24CXX(At24cObjectType *at,uint32_t regAddress,uint8_t *rData,uint16_t rSize)
{
       uint16_t regAdd;
       uint16_t size;
       uint8_t temp;
      
       if(at->memAddLength==AT24C8BitMemAdd)
       {
              regAdd=(uint16_t)(regAddress&0xFF);
              temp=(uint8_t)(regAddress>>8);
       }
       else
       {
              regAdd=(uint16_t)regAddress;
              temp=(uint8_t)(regAddress>>16);
       }
       temp=(temp&(~(devAddMask[at->mode]>>1)))<<1;
       at->devAddress=(at->devAddress & devAddMask[at->mode])|temp;
      
       if((rSize<=pageBytes[at->mode])&&(rSize<=(pageBytes[at->mode]-(regAddress&regAddMask[at->mode]))))
       {
              size=rSize;
       }
       else
       {
              size=pageBytes[at->mode]-(regAddress&regAddMask[at->mode]);
       }

       at->Read(at,regAdd,rData,size);
}

3、驅動的使用

在上一節我們設計並實現了AT24Cxx系列EEPROM存儲器的驅動程序,而這一節我們將設計一個簡單的應用來驗證這一驅動程序。

3.1、聲明並初始化對象

使用基於對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的AT24Cxx系列EEPROM存儲器對象類型聲明一個AT24Cxx系列EEPROM存儲器對象變量,具體操作格式如下:

At24cObjectType at24c;

聲明瞭這個對象變量並不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:

At24cObjectType *atAT24CXX對象實體

uint8_t devAddressAT24CXX設備地址

At24cModeType modeAT24CXX對象類型

At24cMemAddLengthType length,寄存器地址長度

At24cRead read,讀AT24CXX對象操作指針

At24cWrite write,寫AT24CXX對象操作指針

At24cDelayms delayms,延時操作指針

對於這些參數,對象變量我們已經定義了。對象類型與寄存器地址長度爲枚舉,根據實際情況選擇就好了。設備地址根據我們的實際使用情況設置就可以了。主要的是我們需要定義幾個函數,並將函數指針作爲參數。這幾個函數的類型如下:

/* 定義讀數據操作函數指針類型 */
typedef void (*At24cRead)(struct At24cObject *at,uint16_t regAddress,uint8_t *rData,uint16_t rSize);      

/* 定義寫數據操作函數指針類型 */
typedef void (*At24cWrite)(struct At24cObject *at,uint16_t regAddress,uint8_t *wData,uint16_t wSize);   

/* 定義延時操作函數指針類型 */
typedef void (*At24cDelayms)(volatile uint32_t nTime);      

對於這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平臺有關係。片選操作函數用於多設備需要軟件操作時,如採用硬件片選可以傳入NULL即可。具體函數定義如下:

/*讀AT24C寄存器值*/
static void ReadDataFromAT24C(At24cObjectType *at24c,uint16_t regAddress,uint8_t *rData,uint16_t rSize)
{
       uint16_t cSize;
       uint8_t cmd[2];
      
       if(at24c->memAddLength==AT24C8BitMemAdd)
       {
              cSize=1;
              cmd[0]=(uint8_t)regAddress;
       }
       else
       {
              cSize=2;
              cmd[0]=(uint8_t)(regAddress>>8);
              cmd[1]=(uint8_t)regAddress;
       }
      
       HAL_I2C_Master_Transmit(&at24chi2c,at24c->devAddress,cmd,cSize,1000);
 
       HAL_I2C_Master_Receive(&at24chi2c,at24c->devAddress+1,rData, rSize, 1000);
}

/*寫AT24C寄存器值*/
static void WriteDataToAT24C(At24cObjectType *at24c,uint16_t regAddress,uint8_t *wData,uint16_t wSize)
{
       uint8_t tData[wSize+2];
       uint16_t tSize;
      
       if(at24c->memAddLength==AT24C8BitMemAdd)
       {
              tSize=wSize+1;
              tData[0]=(uint8_t)regAddress;
       }
       else
       {
              tSize=wSize+2;
              tData[0]=(uint8_t)(regAddress>>8);
              tData[1]=(uint8_t)regAddress;
       }
      
       for(int i=0;i<wSize;i++)
       {
              tData[i+2]=wData[i];
       }

       HAL_I2C_Master_Transmit(&at24chi2c,at24c->devAddress,wData,wSize,1000);
}

對於延時函數我們可以採用各種方法實現。我們採用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數。於是我們可以調用初始化函數如下:

At24cxxInitialization(&at24c,       //AT24CXX對象實體
                      0xAE,           //AT24CXX設備地址
                      AT24C01C,        //AT24CXX對象類型
                      AT24C8BitMemAdd,       //寄存器地址長度
                      ReadDataFromAT24C,    //讀AT24CXX對象操作指針
                      WriteDataToAT24C, //寫AT24CXX對象操作指針
                      HAL_Delay //延時操作指針
                      );

3.2、基於對象進行操作

我們定義了對象變量並使用初始化函數給其作了初始化。接着我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據並轉換爲轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。

/*AT24XXX數據操作*/
void AT24CReadWriteData(void)
{
       uint16_t regAddress=0x02;
       uint8_t readByte;
       uint8_t writeByte=0x0A;
       uint8_t rData[2];
       uint16_t rSize=2;
       uint8_t wData[]={0x5A,0xA5};
       uint16_t wSize=2;
      
       /*從AT24CXX讀取單個字節,從隨機地址讀取*/
       readByte=ReadByteFromAT24CXX(&at24c,regAddress);

       /*向AT24CXX寫入單個字節*/
       WriteByteToAT24CXX(&at24c,regAddress,writeByte);

       /*從AT24CXX讀取多個字節,從指定地址最多到所在頁的結尾*/
       ReadBytesFromAT24CXX(&at24c,regAddress,rData,rSize);

       /*向AT24CXX寫入多個字節,從指定地址最多到所在頁的結尾*/
       WriteBytesToAT24CXX(&at24c,regAddress,wData,wSize);
}

4、應用總結

這一篇中,我們設計了AT24Cxx系列EEPROM存儲器的讀寫驅動,而且設計了一個簡單的應用驗證了驅動程序,讀寫操作都能按預期要求完成,而且操作也很穩定。

在使用驅動時我們需要注意,因爲不同容量的AT24Cxx系列EEPROM存儲器的每一頁的字節數數不一樣的。在多字節讀寫時,最多支持到所在頁尾。到頁尾後,EEPROM存儲器的內部指針將回到頁首,此時執行讀則得到的是錯誤數據,若執行寫則會覆蓋原有數據造成錯誤。所以在程序中若讀寫的範圍超越了一頁的範圍將會被捨棄。

在使用驅動時還需注意,因爲不同容量的AT24Cxx系列EEPROM存儲器的尋址範圍是不一樣的,所以用於表示寄存器地址的寄存器地址位數有1個字節和2個字節的差別,爲了便於區分需要在對AT24Cxx系列EEPROM存儲器對象進行初始化時指定。

源碼下載:https://github.com/foxclever/ExPeriphDriver

歡迎關注:

 

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