一些时候,在我们的嵌入式产品中需要记录时间,所以我们就需要获取时钟,当然实现的方式多种多样,有的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实时时钟驱动相关推荐

  1. 外设驱动库开发笔记54:外设库驱动设计改进的思考

      不知不觉中我们已经发布了五十多篇外设驱动的文章.前段时间有一位网友提出了一些非常中肯的建议,这也让我们开始考虑怎么优化驱动程序设计的问题.在这一篇中我们将来讨论这一问题. 1.问题分析   首先我 ...

  2. 外设驱动库开发笔记34:OLED显示屏驱动

      现在OLED显示屏在嵌入式系统中应用的越来越多.对于一些显示信息不太复杂,以显示信息为主的需求,我们一般会选择OLED显示屏.在这一篇中,我们将讨论OLED显示屏驱动的设计与实现. 1.功能概述 ...

  3. 外设驱动库开发笔记23:AT24Cxx外部存储器驱动

    在我们的应用开发过程中,经常会使用到外部的EEPROM外部存储器来保存一些参数和配置数据等.而比较常用的就是AT24Cxx系列产品,这一节我们来开发用于操作AT24Cxx系列产品的驱动. 1.功能概述 ...

  4. 外设驱动库开发笔记18:MS5837压力变送器驱动

    绝对压力的检测是常见的需求.在我们的系统中也常常会遇到.而MS5837压力传感器也是我们进场会采用的方案.在这篇里我们将讨论并实现MS5837压力传感器的驱动. 1.功能概述 MS5837压力传感器是 ...

  5. 外设驱动库开发笔记16:MS5536C压力变送器驱动

    压力检测也是经常会遇到的需求,比如环境压力或者低压气体等都会用到压力检测.这类检测压力都比较低,一般不会超过大气压,有时甚至是负压.这一篇我们要讨论的MS5536C就属于这类器件.接下来我们将设计并实 ...

  6. 外设驱动库开发笔记14:DS18B20温度变送器驱动

    在一时候我们需要相对简单的检测温度信号,而DS18B20就是一款功能和应用都相对简单的温度传感器,通过单线就可以实现检测温度信号的需求.这一篇我们就来实现操作DS18B20获取温度数据的驱动. 1.功 ...

  7. 外设驱动库开发笔记33:LCD1602液晶显示屏驱动

      LCD1602是一种工业字符型液晶,能够同时显示16x02即32个字符.LCD1602液晶显示的原理是利用液晶的物理特性,通过电压对其显示区域进行控制,即可以显示出图形.在这一章我们就来讨论LCD ...

  8. 外设驱动库开发笔记40:AT25xxx外部存储器驱动

      我们在前面开发过AT24CXX系列EEPROM存储器,它使用的是I2C接口.不过有时候我们也会使用SPI接口的EEPROM存储器.在这一篇我们将来讨论AT25XXX系列EEPROM存储器的驱动设计 ...

  9. 外设驱动库开发笔记17:MS5803压力变送器驱动

    很多时候我们需要检测被控对象的绝对压力,而且在我们的多款产品中也有这样的需求.当然检测绝对压力的传感器有很多,我们经常使用MS5803来实现压力检测.本篇中我们将设计并实现MS5803系列压力传感器的 ...

最新文章

  1. 2021年大数据Spark(三十六):SparkStreaming实战案例一 WordCount
  2. 统计学习导论 Chapter5 -- Resampling Methods
  3. 生成报告配置xml_自动化测试报告太丑?Allure拯救你!
  4. 用一个中介对象来封装一系列的对象交互
  5. Python之队列和数据库
  6. java原生的ajax怎么写,用原生js实现 ajax方法
  7. Oracle触发器1-介绍
  8. 精确光源(Punctual Light Sources)
  9. 几个清华和交大学霸的公众号,值得学习
  10. Hyperledger Fabric教程(10)-- peer命令-链码chaincode
  11. openat函数用法示例
  12. 极路由1 1s 2 3 刷机 / 恢复 /强刷教程
  13. 软件工程师和程序员到底有多大的区别?
  14. FPGA数字信号处理(十七)多级CIC滤波器Verilog设计
  15. 淘宝HSF 框架使用 总结
  16. 右耳Python小作业--快递分拣
  17. SYBASE公司的PowerDesigner下载与安装
  18. C语言 execve()函数使用方法
  19. MATLAB求解非线性方程模型
  20. 颠覆者-读周鸿祎新书

热门文章

  1. 中文电子病例命名实体识别项目
  2. day45-前端CSS
  3. 04.MyBatis别名的设置和类型转换器
  4. hdu5693 D gamehdu 5712 D++ game
  5. 一至七-----小东西
  6. 【转】eclipse中egit插件使用
  7. 抽象线程之Parallel类
  8. 操作系统学习笔记-01-1.1课程概述
  9. math python 向上取整_Python成为专业人士笔记-各数学运算操作深度剖析
  10. hashcode是什么意思_什么才是 Java 的基础知识?