外设驱动库开发笔记29:DS17887实时时钟驱动
一些時候,在我們的嵌入式產品中需要記錄時間,所以我們就需要獲取時鐘,當然實現的方式多種多樣,有的MCU本身就有這一功能,不過精度較低。當我們的應用要求較高時就需要使用專門的實時時鐘芯片,如DS17887。在本篇中,我們來實現DS17887的驅動。
1、功能概述
DS17287、DS17487和DS17887(以下簡稱DS17x87)將石英晶體和鋰電源集成到一個24針的DIP封裝中。
1.1、硬件設備
DS17x87電源控制電路允許系統由外部激勵供電,如鍵盤或時間和日期(喚醒)報警。PWR輸出引腳是由上述事件之一或其中之一觸發,并用于打開外部電源。PWR引腳由軟件控制,因此當一項工作完成時,便可關掉系統電源。
對于所有設備,月底的日期將自動調整為31天以下的月份,包括閏年的修正日期。它也運行在24小時或12小時的格式與AM/PM指標。一個精確的溫度補償電路監控VCC的狀態。如果檢測到主電源故障,設備將自動切換到備用電源。DS17x87包括一個VBAUX輸入,用于驅動輔助功能。
1.2、寄存器結構
時間和日歷信息是通過讀取適當的寄存器字節獲得的。通過編寫適當的寄存器字節來設置或初始化時間、日歷和警報。
1.2.1、寄存器地址分配
DS17887包括有12個時間、日期和報警寄存器、控制寄存器A到D,以及僅駐留在bank 1中的兩個擴展寄存器。12個時間、日歷和警報字節的內容可以是二進制或二進制編碼的十進制(BCD)格式。
在控制寄存器B的DM位為0時,12個時間、日歷和警報字節的內容以BCD碼的格式輸出。各寄存器具體排布如下:
在控制寄存器B的DM位為1時,12個時間、日歷和警報字節的內容以二進制碼的格式輸出。各寄存器具體排布如下:
1.2.2、控制寄存器
DS17887有四個控制寄存器(A、B、C和D)同時位于bank 0和bank 1中。這些寄存器在任何時候都是可訪問的,甚至在更新周期期間也是如此。
控制寄存器A地址為0x0A,第7位為只讀,其它位可讀可寫。控制寄存器A的結構如下:
UIP是一個可以監視的狀態標志。狀態為1則表示最新發生了更新。DV2、DV1用于設置時鐘,DV0用于決定存儲區是bank0還是bank1,為0則是bank0,為1則是bank1。RS3到RS0幾位則用于設置速率。
控制寄存器B地址為0x0B,控制寄存器B可讀可寫。控制寄存器B的結構如下:
SET位為0允許更新,為0禁止更新。PIE為周期型中斷控制。AIE為報警中斷控制。UIE為更新結束中斷使能。SQWE為方波輸出使能。DM控制選擇存儲區(bank0或bank1)。
控制寄存器C地址為0x0C,控制寄存器C為只讀。控制寄存器C的結構如下:
控制寄存器C實際上是對控制寄存器B各中斷配置為的狀態指示。
控制寄存器D地址為0x0D,控制寄存器D為只讀。控制寄存器D的結構如下:
控制寄存器D只有VRT位有效,這個位表示連接到VBAT和VBAUX引腳的電池的狀態。如果任何一個電源高于內部電壓閾值,VRT位將是高的。這個位是不可寫的,讀的時候應該總是1。如果存在0,則表示內部鋰電源耗盡,RTC數據和RAM數據的內容都有問題。
2、驅動設計與實現
在上述介紹中,我們已經清楚了DS17887實時時鐘芯片的基本功能及寄存器布置。接下來我們將據此實現器驅動。
2.1、對象定義
我們依然是采用基于對象的操作。所以我們首先需要獲得對象,并為這個對象按我們的需要設計驅動。所以在這里我們首先要設計DS17887這個操作對象。
2.1.1、抽象對象類型
我們首先來分析DS17887的特點。DS17887有4個控制寄存器用以配置操作和展示狀態,我們將其作為屬性標識DS17887當前的狀態。DS17887最主要的返回值就是時間數據,我們將其作為屬性返回時間數據。我們將控制引腳的控制,寄存器的讀寫、總線方向設定等作為DS17887對象的操作。由此我們抽象的DS17887對象類型如下:
/* 定義DS17887對象類型 */ typedef struct Ds17887Object{uint8_t ctlReg[4];???????????? //控制寄存器uint16_t dateTime[6];????? //讀取的系統時間void (*SetCtlPin[6])(DS17887PinValue value); //控制引腳操作void (*WriteByte)(uint16_t data);????????????//寫一個字節uint16_t (*ReadByte)(void);??????????????????//讀一個字節void (*SetBusDirection)(DS17887BusDirection direction);//設置總線方向void (*Delayus)(volatile uint32_t nTime);?????? //延時ms操作指針 }Ds17887ObjectType;2.1.2、對象初始化函數
對象必須先初始化才可使用,所以我們還需要設計對象的初始化函數。初始化函數除了為對象屬性賦初始值和給操作指定函數指針外,還需要檢測參數的合法性以及對硬件設備做必要的配置。基于此我們設計DS17887的初始化函數如下:
/*對DS17887進行初始化配置*/ void Ds17887Initialization(Ds17887ObjectType *ds17887,DS17887CtlPinOperation *SetCtlPin,WriteByteToDs17887 WriteByte,ReadByteFromDs17887 ReadByte,Ds17887SetBusDirection SetBusDirection,Ds17887Delayus Delayus) {if((ds17887==NULL)||(SetCtlPin==NULL)||(WriteByte==NULL)||(ReadByte==NULL)||(SetBusDirection==NULL)||(Delayus==NULL)){return;}for(int i=0;i<6;i++){ds17887->dateTime[0]=0;ds17887->SetCtlPin[i]=SetCtlPin[i];}ds17887->WriteByte=WriteByte;ds17887->ReadByte=ReadByte;ds17887->SetBusDirection=SetBusDirection;ds17887->Delayus=Delayus;/*將ALE、RD與WR復位*/SetCtlPin[DS17887_ALE](Reset);SetCtlPin[DS17887_WR](Reset);SetCtlPin[DS17887_RD](Reset);/*設置寄存器B和A的值,啟動DS17887*/WriteDataToDS17887(ds17887,DS17887_Reg_B,0x06);WriteDataToDS17887(ds17887,DS17887_Reg_A,0x20)//讀取DS17887的時間GetDateTimeFromDs17887(ds17887); }2.2、對象操作
我們定義一個對象的目的最終是為了操作這個對象獲取我們需要的數據。DS17887實時時鐘最基本的操作就是對個寄存器的讀寫,進而引申為對事實時間的獲取及校準。這一節我們就據此來實現DS17887對象的操作函數。
2.2.1、讀數據操作
DS17887的讀操作時序如下圖所示:
我們根據以上時序圖來開發DS17887對象的讀操作函數如下:
/*從DS17887讀數據*/ static uint16_t ReadDataFromDS17887(Ds17887ObjectType *ds17887,uint16_t address) {/*將片選信號置位,失能片選*/ds17887->SetCtlPin[DS17887_CS](Set);/*將RD與WR置位*/ds17887->SetCtlPin[DS17887_WR](Set);ds17887->SetCtlPin[DS17887_RD](Set);ds17887->Delayus(2);/*置位ALE*/ds17887->SetCtlPin[DS17887_ALE](Set);/*將地址數據總線的模式改為輸出*/ds17887->SetBusDirection(Out);/*寫寄存器地址*/ds17887->WriteByte(address);/*將片選信號置位,使能片選*/ds17887->SetCtlPin[DS17887_CS](Reset);ds17887->Delayus(2);/*復位ALE*/ds17887->SetCtlPin[DS17887_ALE](Reset);ds17887->Delayus(2);/*復位RD*/ds17887->SetCtlPin[DS17887_RD](Reset);ds17887->Delayus(10);/*將地址數據總線的模式改為輸入*/ds17887->SetBusDirection(In);ds17887->Delayus(40);/*讀取數據*/uint16_t readData=0;readData=ds17887->ReadByte();ds17887->Delayus(4);/* 將RD置位,并將CS信號置位,失能芯片 */ds17887->SetCtlPin[DS17887_RD](Set);ds17887->SetCtlPin[DS17887_CS](Set);ds17887->Delayus(4);/*將ALE置位*/ds17887->SetCtlPin[DS17887_ALE](Set);ds17887->Delayus(20);return readData; }2.2.2、寫數據操作
DS17887實時時鐘的寫操作時序如下圖所示:
我們根據以上時序圖來開發DS17887對象的寫操作函數如下:
/*向DS17887寫數據*/ static void WriteDataToDS17887(Ds17887ObjectType *ds17887,uint16_t address,uint16_t data) {/*將DS17887的片選信號失能*/ds17887->SetCtlPin[DS17887_CS](Set);/*將RD與WR置位*/ds17887->SetCtlPin[DS17887_WR](Set);ds17887->SetCtlPin[DS17887_RD](Set);ds17887->Delayus(2);/*將ALE信號置高*/ds17887->SetCtlPin[DS17887_ALE](Set);/*將地址數據總線的模式改為輸出*/ds17887->SetBusDirection(Out);/*寫寄存器地址*/ds17887->WriteByte(address);/*將片選信號置位,使能片選*/ds17887->SetCtlPin[DS17887_CS](Reset);ds17887->Delayus(4);/*復位ALE信號*/ds17887->SetCtlPin[DS17887_ALE](Reset);ds17887->Delayus(4);/*復位WR*/ds17887->SetCtlPin[DS17887_WR](Reset);/*寫數據*/ds17887->WriteByte(data);ds17887->Delayus(4);/* 將WR置位,并將CS信號置位,失能芯片 */ds17887->SetCtlPin[DS17887_WR](Set);ds17887->SetCtlPin[DS17887_CS](Set);?ds17887->Delayus(4);/*將ALE置位*/ds17887->SetCtlPin[DS17887_ALE](Set);ds17887->Delayus(10); }2.2.3、時間數據獲取
我們操作DS17887的根本目的就是獲取系統時鐘,在我們實現了對DS17887寄存器的讀寫操作后,我們可以據此得到時鐘數據。
/*從實時時鐘模塊讀取時間*/ void GetDateTimeFromDs17887(Ds17887ObjectType *ds17887) {/*讀取系統時間值*/ds17887->dateTime[0]=ReadDataFromDS17887(ds17887,DS17887_Year);//系統時間年ds17887->Delayus(5);ds17887->dateTime[1]=ReadDataFromDS17887(ds17887,DS17887_Month);//系統時間月ds17887->Delayus(5);ds17887->dateTime[2]=ReadDataFromDS17887(ds17887,DS17887_Date);//系統時間日ds17887->Delayus(5);ds17887->dateTime[3]=ReadDataFromDS17887(ds17887,DS17887_Hour);//系統時間時ds17887->Delayus(5);ds17887->dateTime[4]=ReadDataFromDS17887(ds17887,DS17887_Minute);//系統時間分ds17887->Delayus(5);ds17887->dateTime[5]=ReadDataFromDS17887(ds17887,DS17887_Second);//系統時間秒ds17887->Delayus(5); }2.2.4、時間校準
獲取的時鐘數據也許會存在偏差,這時就需要對系統的時鐘進行校準。停止時間更新后,修改時間寄存器的數據,然后再開啟計時就完成了時間的校準。
/*校準DS17887的時間*/ void CalibrationDs17887DateTime(Ds17887ObjectType *ds17887,uint16_t * dateTime) {/*將ALE、RD與WR復位*/ds17887->SetCtlPin[DS17887_ALE](Reset);ds17887->SetCtlPin[DS17887_WR](Reset);ds17887->SetCtlPin[DS17887_RD](Reset);/*初始化控制寄存器,以便校準時間*/WriteDataToDS17887(ds17887,DS17887_Reg_A,0x20);WriteDataToDS17887(ds17887,DS17887_Reg_B,0x06);WriteDataToDS17887(ds17887,DS17887_Reg_B,0x80);/*設置系統時間值*/WriteDataToDS17887(ds17887,DS17887_Year,dateTime[0]);//系統時間年WriteDataToDS17887(ds17887,DS17887_Month,dateTime[1]);//系統時間月WriteDataToDS17887(ds17887,DS17887_Date,dateTime[2]);//系統時間日WriteDataToDS17887(ds17887,DS17887_Hour,dateTime[3]);//系統時間時WriteDataToDS17887(ds17887,DS17887_Minute,dateTime[4]);//系統時間分WriteDataToDS17887(ds17887,DS17887_Second,dateTime[5]);//系統時間秒/*設置寄存器B和A的值,啟動DS17887*/WriteDataToDS17887(ds17887,DS17887_Reg_B,0x06);WriteDataToDS17887(ds17887,DS17887_Reg_A,0x20);//讀取DS17887的時間GetDateTimeFromDs17887(ds17887); }3、驅動的使用
我們實現了DS17887的驅動,那么如何使用這一驅動呢?其實與我們在第二節中開發驅動的流程是一致的,先定義對象,再操作對象。
3.1、聲明并初始化對象
我們前面已經定義了DS17887的對象類型。我們要得到一個DS17887對象,首先要使用Ds17887ObjectType聲明一個DS17887對象變量,如下:Ds17887ObjectType ds17887。
有了這個變量后并不能馬上使用它進行操作,還需要先對它使用Ds17887Initialization初始化函數進行初始化。這個函數有很多參數,其中有5個參數是函數指針。
/* 定義DS17887控制引腳操作函數指針 */ typedef void (*DS17887CtlPinOperation)(Ds17887PinValueType value);/* 定義DS17887寫數據操作函數指針 */ typedef void (*WriteByteToDs17887)(uint16_t data);/* 定義DS17887讀數據操作函數指針 */ typedef uint16_t (*ReadByteFromDs17887)(void);/* 定義設置數據地址總線方向函數指針 */ typedef void (*Ds17887SetBusDirection)(Ds17887BusDirectionType direction);/* 定義延時操作函數指針類型 */ typedef? void (*Ds17887Delayus)(volatile uint32_t nTime);所以我們要譯者5個函數指針類型定義相應的函數,并將這些函數作為參數傳遞給初始化函數實現對DS17887對象的使用。調用初始化函數如下:
void Ds17887Initialization(&ds17887,SetCtlPin,WriteByte,ReadByte,SetBusDirection,Delayus)
需要說明一下的是第二個參數實際是一個函數指針數組,也就是說每一個控制引腳都需要定義一個DS17887CtlPinOperationType類型的操作函數,并組成數組傳遞進來。這個數據必須按照enum Ds17887CtlPins枚舉定義的順序,即:
/* 定義DS17887控制引腳的種類 */ typedef enum Ds17887CtlPins{DS17887_CS,DS17887_WR,DS17887_RD,DS17887_ALE,DS17887_KS,DS17887_RCLR }Ds17887CtlPinsType;3.2、基于對象進行操作
完成了對對象的初始化就可以實現對對象的操作了。其實對DS17887的操作比較簡單無非就是獲取時間數據和校準時間數據。
獲取時間數據就是調用GetDateTimeFromDs17887函數來實現就可以了。前面初始化完成的DS17887對象就是其參數。調用如下:
GetDateTimeFromDs17887(&ds17887);
而校準時間數據就是調用CalibrationDs17887DateTime函數來校準時間。前面初始化完成的DS17887對象就是其參數,此外需要輸入標準時間數據作為第二個參數。
uint16_t dateTime[6]={year,month,day,hour,minute,second};
然后調用函數校準時間:
CalibrationDs17887DateTime(&ds17887,dateTime);
4、應用總結
在一個項目我們需要在每十秒的時間間隔記錄一些列數據。所以我們需要以最小為1秒的精度讀取系統實時時鐘。我們將采用DS17877作為系統的實時時鐘,完全能夠符合我們的要求。
在我們這個驅動程序中,我們默認將DS17887對象二進制數據格式、24小時制、并使用存儲區域bank0。如果需要不同的配置,則可以修改初始化函數中的配置。
還有對控制引腳的控制是一個6個元素的函數指針數組。這6個函數的順序必須按照枚舉Ds17887CtlPinsType的排布順序。
歡迎關注:
?
總結
以上是生活随笔為你收集整理的外设驱动库开发笔记29:DS17887实时时钟驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LwIP应用开发笔记之十一:LwIP带操
- 下一篇: SHA256 的C语言实现