雖然說使用EEPROM保存參數很有效,但操作及使用次數均有一下限制。當我們的一些參數需要不定時修改或存儲時,使用FRAM就更為方便一點。這一節我們就來設計并實現FM24xxx系列FRAM的驅動。
1 、功能概述
我們首先說一下鐵電隨機存取存儲器,F-RAM是非易失性的,其讀寫操作與RAM類似。它提供了151年的可靠數據保存,同時消除了由EEPROM和其他非易失性內存引起的復雜性、開銷和系統級可靠性問題。
1.1 、硬件描述
FM24xxx系列FRAM存儲器擁有從4K到1M的各種容量。雖然不同型號的FM24xxx系列FRAM存儲器內部存儲矩陣存在差異,但都采用了相同的封裝和引腳排布。具體的引腳分布如下圖:
FM24xxx系列FRAM存儲器與EEPROM不同,F-RAM以總線速度執行寫操作。沒有發生寫延遲。數據在每個字節成功傳輸到設備后立即寫入內存數組。下一個總線循環可在不需要進行數據輪詢的情況下開始。此外,與其他非易失性存儲器相比,該產品具有較強的寫持久性。此外,F-RAM在寫期間的功耗比EEPROM低得多,因為寫操作不需要在內部提高寫電路的電源電壓。F-RAM能夠支持10的14次方個讀/寫周期,或比EEPROM多1億倍的寫周期。
這些功能使得FM24xxx系列FRAM存儲器非常適合非易失性內存應用程序,因為它需要頻繁或快速的寫操作。例如,數據日志記錄(寫入周期的數量可能至關重要)和嚴格的工業控制(EEPROM的長寫入時間可能導致數據丟失)。這些特性的組合允許更頻繁地編寫數據,同時減少系統的開銷。
1.2 、通訊接口
FM24xxx系列FRAM存儲器采用I2C通訊接口。其設備地址的前4位固定為1010b,后3位則用于設備地址或頁面地址,所以若后3為均用于設備地址則在同一條I2C總線上最多可以帶8個同類設備。其與主機之間的連接是以圖如下所示:
FM24xxx系列FRAM存儲器采用8位或者16位內存地址,對于不同的存儲容量,尋址的最終范圍當然是不同的,其尋址范圍為從512到131072不等。關于設備地址與內存地址的分配不同型號和容量的FM24xxx系列FRAM存儲器是不一樣的。具體分配如下圖所示:
從上表我們很容易明白,設備地址的低3位的定義決定了在同一條I2C總線上,最多可以掛載多少個FM24xxx設備。有3位用于設備地址則最多可掛載8個設備;有2位用于設備地址則最多可掛載4個設備;有1位用于設備地址則最多可掛載2個設備;有0位用于設備地址則最多可掛載1個設備。需要注意的是,不同定義的位的設備混用于同一總線時,相同的定義位必須一樣,否則用作寄存器地址的位可能讓總線上的總線無法識別。
2 、驅動設計與實現
我們在前面一節已經簡要的描述了FM24xxx FRAM存儲器的讀寫操作方式,在這一節我們將設計并實現FM24xxx FRAM存儲器的驅動程序。
2.1 、對象的定義
在使用一個對象之前我們需要獲得一個對象。同樣的我們想要FM24xxx FRAM存儲器就需要先定義FM24xxx FRAM存儲器的對象。
2.1.1 、對象的抽象
我們要得到FM24xxx FRAM存儲器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下FM24xxx FRAM存儲器的對象。
先來考慮屬性,作為屬性肯定是用于標識或記錄對象特征的東西。我們來考慮FM24xxx FRAM存儲器對象屬性。首先作為I2C從設備都需要有一個設備地址用以區分總線上的不同設備,所以我們將其作為對象的一個屬性。FM24xxx FRAM存儲器不同的型號在容量、尋址等方面有一些差異,為了正確操作不同類型的設備,我們將其作為對象的一個屬性。不同容量的FM24xxx FRAM存儲器因尋址范圍不同所使用的地址寄存器位數也不相同,為了便于操作我們將地址位長度也作為對象的一個屬性。
接著我們還需要考慮FM24xxx FRAM存儲器對象的操作問題。我們要對FM24xxx FRAM存儲器進行讀寫,但讀寫都需要同過具體的I2C接口進行,這依賴于具體的硬件平臺,所以我們將針對端口的讀寫作為對象的操作。FM24xxx FRAM存儲器還有一個寫保護引腳用于設置內部存儲器的寫保護問題,這同樣依賴于硬件平臺來實現,所以我們也將它作為對象的操作。
根據上述我們對FM24xxx FRAM存儲器的分析,我們可以定義FM24xxx FRAM存儲器的對象類型如下:
/*定義FM24XXX對象類型*/
typedef struct FM24Object {
uint8_tdevAddress; //FM24xxx設備地址
FM24ModeTypemode; //FM24xxx設備類型
FM24MemAddLengthTypememAddLength; //存儲器地址長度
void(*WP)(FM24WPType wp); //寫保護操作
void(*Read)(struct FM24Object *fram,uint8_t *wData,uint16_t wSize,uint8_t*rData,uint16_t rSize); //讀數據操作指針
void(*Write)(struct FM24Object *fram,uint8_t *wData,uint16_t wSize); //寫數據操作指針
void(*Delayms)(volatile uint32_t nTime); //延時操作指針
}FM24ObjectType;
2.1.2 、對象初始化
我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮FM24xxx FRAM存儲器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計FM24xxx FRAM存儲器對象的初始化函數如下:
/*FM24XXX對象初始化*/
void Fm24cxxInitialization(FM24ObjectType *fram, //FM24xxx對象實體
uint8_t devAddress, //FM24xxx設備地址
FM24ModeType mode, //FM24xxx設備類型
Fm24WP wp, //FM24xxx寫保護
Fm24Read read, //讀FM24xxx對象操作指針
Fm24Writewrite, //寫FM24xxx對象操作指針
Fm24Delaymsdelayms //延時操作指針
)
{
if((fram==NULL)||(read==NULL)||(write==NULL)||(delayms==NULL))
{
return;
}
fram->Read=read;
fram->Write=write;
fram->Delayms=delayms;
if((0xA0<=devAddress)&&(devAddress<=0xAE))
{
fram->devAddress=devAddress;
}
elseif((0x50<=devAddress)&&(devAddress<=0x57))
{
fram->devAddress=(devAddress<<1);
}
else
{
fram->devAddress=0x00;
}
if(mode>=FM24Number)
{
return;
}
fram->mode=mode;
if((modememAddLength=FM248BitMemAdd;
}
else
{
fram->memAddLength=FM2416BitMemAdd;
}
fram->WP=wp;
}
2.2 、寫對象操作
所有寫操作都以從站地址開始,然后是內存地址。總線主機通過將從站地址(R/W位)的LSB設置為“0”來指示寫操作。尋址之后,總線主機將每個字節的數據發送到FM24xxx FRAM存儲器,存儲器生成一個確認條件。可以寫入任意數量的順序字節,如果在內部到達地址范圍的末尾,地址計數器將從最高地址回滾到最低地址。
2.2.1 、寫單個字節
FM24xxx FRAM存儲器允許在任意地址寫一個字節數據。在內部,實際的存儲器寫發生在第8位數據傳輸之后。它將在確認發送之前完成。因此,如果用戶希望在不改變存儲器內容的情況下中止寫操作,那么應該在第8個數據位之前使用START或STOP條件來完成。FM24xxx FRAM存儲器不使用頁面緩沖。具體的操作時序如下:
根據上述時序圖,我們可以設計FM24xxx FRAM存儲器寫單個字節數據程序如下:
/*向FM24XXX寫入單個字節*/
void WriteByteToFM24xxx(FM24ObjectType*fram,uint32_t regAddress,uint8_t data)
{
uint8_t temp;
uint8_t tData[3];
uint16_t tSize=0;
if(fram->memAddLength==FM248BitMemAdd)
{
tData[tSize++]=(uint8_t)(regAddress&0xFF);
temp=(uint8_t)(regAddress>>8);
}
else
{
tData[tSize++]=(uint8_t)(regAddress>>8);
tData[tSize++]=(uint8_t)(regAddress&0xFF);
temp=(uint8_t)(regAddress>>16);
}
temp=(temp&(~(devAddMask[fram->mode]>>1)))<<1;
fram->devAddress=(fram->devAddress& devAddMask[fram->mode])|temp;
tData[tSize++]=data;
fram->WP(FM24WP_Disable);
fram->Write(fram,tData,tSize);
fram->WP(FM24WP_Enable);
}
2.2.2 、寫多個字節
FM24xxx FRAM存儲器也允許從一個地址開始順序寫入多個字節。存儲器內部地址指針會跟隨寫操作自動增加,其他的過程這與寫單個字節相同。具體的操作時序如下:
根據上述時序圖,我們可以設計FM24xxx FRAM存儲器寫多個字節數據程序如下:
/*向FM24XXX寫入多個字節,從指定地址最多到所在頁的結尾*/
void WriteBytesToFM24xxx(FM24ObjectType*fram,uint32_t regAddress,uint8_t *wData,uint16_t wSize)
{
uint8_t temp;
uint8_t tData[wSize+2];
uint16_t tSize=0;
if(fram->memAddLength==FM248BitMemAdd)
{
tData[tSize++]=(uint8_t)(regAddress&0xFF);
temp=(uint8_t)(regAddress>>8);
}
else
{
tData[tSize++]=(uint8_t)(regAddress>>8);
tData[tSize++]=(uint8_t)(regAddress&0xFF);
temp=(uint8_t)(regAddress>>16);
}
temp=(temp&(~(devAddMask[fram->mode]>>1)))<<1;
fram->devAddress=(fram->devAddress& devAddMask[fram->mode])|temp;
for(inti=0;iWP(FM24WP_Disable);
fram->Write(fram,tData,tSize);
fram->WP(FM24WP_Enable);
}
2.3 、讀對象操作
與寫操作相對應,FM24xxx FRAM存儲器允許從當前地址指針位置或任意制定的地址指針位置讀取單個或多個字節。為了執行選擇性讀取,總線主機發送最低位(R/W)設置為0的從站地址。根據寫協議,總線主機隨后發送加載到內部地址鎖存器的地址字節。在FM24xxx FRAM存儲器確認地址后,總線主機發出一個START條件。這將同時中止寫操作,并允許發出讀命令,并將從站地址最低為設置為“1”。
2.3.1 、讀單個字節
FM24xxx FRAM存儲器允許從當前地址指針位置或任意制定的地址指針位置讀取單個字節。當然從當前地址指針讀可以不用設置存儲器地址,不過我們考慮一般性,同意為從任意制定地址讀取一個字節。具體的操作時序如下:
根據上述時序圖,我們可以設計FM24xxx FRAM存儲器讀單個字節數據程序如下:
/*從FM24XXX讀取單個字節,從隨機地址讀取*/
uint8_t ReadByteFromFM24xxx(FM24ObjectType *fram,uint32_t regAddress)
{
uint8_t wData[2];
uint16_t wSize=0;
uint8_t rData;
uint8_t temp;
if(fram->memAddLength==FM248BitMemAdd)
{
wData[wSize++]=(uint8_t)regAddress;
temp=(uint8_t)(regAddress>>8);
}
else
{
wData[wSize++]=(uint8_t)(regAddress>>8);
wData[wSize++]=(uint8_t)regAddress;
temp=(uint8_t)(regAddress>>16);
}
temp=(temp&(~(devAddMask[fram->mode]>>1)))<<1;
fram->devAddress=(fram->devAddress& devAddMask[fram->mode])|temp;
fram->Read(fram,wData,wSize,&rData,1);
return rData;
}
2.3.2 、讀多個字節
FM24xxx FRAM存儲器允許從當前地址指針位置或任意制定的地址指針位置讀取多個字節。如果從當前的地址指針開始讀可以不用設置存儲器地址。但更一般的是從任意地址開始讀多個字節,所以我們考慮從任意地址開始讀取。具體的操作時序如下:
根據上述時序圖,我們可以設計FM24xxx FRAM存儲器讀多個字節數據程序如下:
/*從FM24XXX讀取多個字節,從指定地址最多到所在頁的結尾*/
void ReadBytesFromFM24xxx(FM24ObjectType*fram,uint32_t regAddress,uint8_t *rData,uint16_t rSize)
{
uint8_t temp;
uint8_t wData[2];
uint16_t wSize=0;
if(fram->memAddLength==FM248BitMemAdd)
{
wData[wSize++]=(uint8_t)regAddress;
temp=(uint8_t)(regAddress>>8);
}
else
{
wData[wSize++]=(uint8_t)(regAddress>>8);
wData[wSize++]=(uint8_t)regAddress;
temp=(uint8_t)(regAddress>>16);
}
temp=(temp&(~(devAddMask[fram->mode]>>1)))<<1;
fram->devAddress=(fram->devAddress& devAddMask[fram->mode])|temp;
fram->Read(fram,wData,wSize,rData,rSize);
}
在傳輸每個數據字節之后,在返回確認之前,FM24xxx存儲器會遞增內部地址鎖存器。這允許不使用其它地址訪問下一個順序字節。到達最后一個地址后,地址鎖存器將滾到0000h。一個讀或寫操作可以訪問的字節數,理論上講是沒有限制。
2.4 、休眠模式操作
在FM24xxx FRAM存儲器的某些型號的設備上實現了一種稱為休眠模式的低功耗模式。當休眠命令0x86被鎖定時,設備將進入這種低功耗狀態。進入睡眠模式的操作時序如下:
根據上述時序圖,我們可以設計FM24xxx FRAM存儲器進入休眠模式的程序如下:
/*FM24XXX對象進入休眠模式*/
void FM24xxxEnterSleepMode(FM24ObjectType*fram)
{
uint8_t wData[2];
wData[0]=fram->devAddress;
fram->devAddress=0xF8;
wData[1]=0x86;
fram->Write(fram,wData,2);
fram->devAddress=wData[0];
}
一旦進入休眠模式,設備建議低功耗運行,但設備會繼續監控I2C引腳。一旦主站設備發送一個FM24xxx FRAM存儲器標識的從站設備地址,它將被“喚醒”并進入正常運行。
3 、驅動的使用
FM24xxx FRAM存儲器驅動程序我們已經實現了,但這一驅動程序是否能正確讀寫,是否能穩定工作,還需要驗證。所以接下來我們將設計一個簡單的應用驗證FM24xxx FRAM存儲器驅動程序。
3.1 、聲明并初始化對象
使用基于對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的FM24xxx FRAM存儲器對象類型聲明一個FM24xxx FRAM存儲器對象變量,具體操作格式如下:
FM24ObjectTypefm24;
聲明了這個對象變量并不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
FM24ObjectType*fram,FM24xxx對象實體
uint8_tdevAddress,FM24xxx設備地址
FM24ModeTypemode,FM24xxx設備類型
Fm24WP wp,FM24xxx寫保護
Fm24Read read,讀FM24xxx對象操作指針
Fm24Write write,寫FM24xxx對象操作指針
Fm24Delaymsdelayms,延時操作指針
對于這些參數,對象變量我們已經定義了。設備地址根據實際的硬件設置。設備類型為枚舉,根據實際使用的設備類型情況選擇就好了。主要的是我們需要定義幾個函數,并將函數指針作為參數。這幾個函數的類型如下:
//寫保護操作
typedef void (*Fm24WP)(FM24WPType wp);
/* 定義讀數據操作函數指針類型 */
typedef void (*Fm24Read)(structFM24Object *fram,uint8_t *wData,uint16_t wSize,uint8_t *rData,uint16_trSize);
/* 定義寫數據操作函數指針類型 */
typedef void (*Fm24Write)(structFM24Object *fram,uint8_t *wData,uint16_t wSize);
/* 定義延時操作函數指針類型 */
typedef void (*Fm24Delayms)(volatileuint32_t nTime);
對于這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平臺有關系。具體函數定義如下:
/*讀FM24XXX寄存器值*/
static voidReadDataFromFM24(FM24ObjectType *fram,uint8_t *wData,uint16_t wSize,uint8_t*rData,uint16_t rSize)
{
HAL_I2C_Master_Transmit(&fm24hi2c,fram->devAddress,wData,wSize,1000);
HAL_I2C_Master_Receive(&fm24hi2c,fram->devAddress+1,rData, rSize,1000);
}
/*寫FM24XXX寄存器值*/
static voidWriteDataToFM24(FM24ObjectType *fram,uint8_t *wData,uint16_t wSize)
{
HAL_I2C_Master_Transmit(&fm24hi2c,fram->devAddress,wData,wSize,1000);
}
/*寫保護操作*/
void FM24WriteProtected(FM24WPType wp)
{
if(wp==FM24WP_Enable)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
}
}
對于延時函數我們可以采用各種方法實現。我們采用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數。于是我們可以調用初始化函數如下:
Fm24cxxInitialization(&fm24, //FM24XXX對象實體
0xAE, //FM24XXX設備地址
FM24V10, //FM24XXX對象類型
FM24WriteProtected, //寫保護操作指針
ReadDataFromFM24, //讀FM24XXX對象操作指針
WriteDataToFM24, //寫FM24XXX對象操作指針
HAL_Delay //延時操作指針
);
3.2 、基于對象進行操作
我們定義了對象變量并使用初始化函數給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據并轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。
/*FM24XXX數據操作*/
void FM24ReadWriteData(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;
/*從FM24XXX讀取單個字節,從隨機地址讀取*/
readByte=ReadByteFromFM24xxx(&fm24,regAddress);
/*向FM24XXX寫入單個字節*/
WriteByteToFM24xxx(&fm24,regAddress,writeByte);
/*從FM24XXX讀取多個字節,從指定地址最多到所在頁的結尾*/
ReadBytesFromFM24xxx(&fm24,regAddress,rData,rSize);
/*向FM24XXX寫入多個字節,從指定地址最多到所在頁的結尾*/
WriteBytesToFM24xxx(&fm24,regAddress,wData,wSize);
}
4 、應用總結
我們實現了FM24xxx FRAM存儲器的驅動程序,并基于這一驅動程序設計了簡單的驗證應用,能夠寫入數據并能將其讀出來。這與我們預期的結果是一致的,驅動設計基本正確。
FM24xxx FRAM存儲器內存數組可以使用WP引腳進行寫保護。將WP引腳設置為高狀態(VDD)將寫保護所有地址。果試圖對這些地址進行寫操作,地址計數器不會增加。FM24xxx FRAM存儲器將不會理會寫入受保護地址的數據字節。如將WP設置為低狀態(VSS)將禁用寫保護,此時整個區域都是可以進行寫操作的。
FM24xxx FRAM存儲器與其他非易失性內存技術不同,F-RAM沒有有效的寫延遲。由于底層內存的讀和寫訪問時間相同,因此用戶不會在總線上體驗延遲。整個內存周期的時間比一個總線時鐘還短。因此,任何操作(包括讀或寫)都可以在寫之后立即執行。
源碼下載:https://github.com/foxclever/ExPeriphDriver
評論
查看更多