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