外设驱动库开发笔记29:DS17887实时时钟驱动

一些时候,在我们的嵌入式产品中需要记录时间,所以我们就需要获取时钟,当然实现的方式多种多样,有的MCU本身就有这一功能,不过精度较低。当我们的应用要求较高时就需要使用专门的实时时钟芯片,如DS17887。在本篇中,我们来实现DS17887的驱动。

1、功能概述

DS17287DS17487DS17887(以下简称DS17x87)将石英晶体和锂电源集成到一个24针的DIP封装中。

1.1、硬件设备

DS17x87电源控制电路允许系统由外部激励供电,如键盘或时间和日期(唤醒)报警。PWR输出引脚是由上述事件之一或其中之一触发,并用于打开外部电源。PWR引脚由软件控制,因此当一项工作完成时,便可关掉系统电源。

对于所有设备,月底的日期将自动调整为31天以下的月份,包括闰年的修正日期。它也运行在24小时或12小时的格式与AM/PM指标。一个精确的温度补偿电路监控VCC的状态。如果检测到主电源故障,设备将自动切换到备用电源。DS17x87包括一个VBAUX输入,用于驱动辅助功能。

1.2、寄存器结构

时间和日历信息是通过读取适当的寄存器字节获得的。通过编写适当的寄存器字节来设置或初始化时间、日历和警报。

1.2.1、寄存器地址分配

DS17887包括有12个时间、日期和报警寄存器、控制寄存器AD,以及仅驻留在bank 1中的两个扩展寄存器。12个时间、日历和警报字节的内容可以是二进制或二进制编码的十进制(BCD)格式。

在控制寄存器BDM位为0时,12个时间、日历和警报字节的内容以BCD码的格式输出。各寄存器具体排布如下:

在控制寄存器BDM位为1时,12个时间、日历和警报字节的内容以二进制码的格式输出。各寄存器具体排布如下:

1.2.2、控制寄存器

DS17887有四个控制寄存器(ABCD)同时位于bank 0bank 1中。这些寄存器在任何时候都是可访问的,甚至在更新周期期间也是如此。

控制寄存器A地址为0x0A,第7位为只读,其它位可读可写。控制寄存器A的结构如下:

UIP是一个可以监视的状态标志。状态为1则表示最新发生了更新。DV2DV1用于设置时钟,DV0用于决定存储区是bank0还是bank1,为0则是bank0,为1则是bank1RS3RS0几位则用于设置速率。

控制寄存器B地址为0x0B,控制寄存器B可读可写。控制寄存器B的结构如下:

SET位为0允许更新,为0禁止更新。PIE为周期型中断控制。AIE为报警中断控制。UIE为更新结束中断使能。SQWE为方波输出使能。DM控制选择存储区(bank0bank1)。

控制寄存器C地址为0x0C,控制寄存器C为只读。控制寄存器C的结构如下:

控制寄存器C实际上是对控制寄存器B各中断配置为的状态指示。

控制寄存器D地址为0x0D,控制寄存器D为只读。控制寄存器D的结构如下:

控制寄存器D只有VRT位有效,这个位表示连接到VBATVBAUX引脚的电池的状态。如果任何一个电源高于内部电压阈值,VRT位将是高的。这个位是不可写的,读的时候应该总是1。如果存在0,则表示内部锂电源耗尽,RTC数据和RAM数据的内容都有问题。

2、驱动设计与实现

在上述介绍中,我们已经清楚了DS17887实时时钟芯片的基本功能及寄存器布置。接下来我们将据此实现器驱动。

2.1、对象定义

我们依然是采用基于对象的操作。所以我们首先需要获得对象,并为这个对象按我们的需要设计驱动。所以在这里我们首先要设计DS17887这个操作对象。

2.1.1、抽象对象类型

我们首先来分析DS17887的特点。DS178874个控制寄存器用以配置操作和展示状态,我们将其作为属性标识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(&ds17887SetCtlPinWriteByteReadByteSetBusDirectionDelayus)

需要说明一下的是第二个参数实际是一个函数指针数组,也就是说每一个控制引脚都需要定义一个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]={yearmonthdayhourminutesecond};

然后调用函数校准时间:

CalibrationDs17887DateTime(&ds17887dateTime);

4、应用总结

在一个项目我们需要在每十秒的时间间隔记录一些列数据。所以我们需要以最小为1秒的精度读取系统实时时钟。我们将采用DS17877作为系统的实时时钟,完全能够符合我们的要求。

在我们这个驱动程序中,我们默认将DS17887对象二进制数据格式、24小时制、并使用存储区域bank0。如果需要不同的配置,则可以修改初始化函数中的配置。

还有对控制引脚的控制是一个6个元素的函数指针数组。这6个函数的顺序必须按照枚举Ds17887CtlPinsType的排布顺序。

欢迎关注:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

外设驱动库开发笔记30:宇电AI-BUS通讯驱动

嵌入式系统通常都会与外部设备进行通讯&#xff0c;这就涉及到通讯协议的问题。这些通讯协议有的是标准协议有的厂家自定义的协议&#xff0c;如宇电的AI-BUS。在本篇中&#xff0c;我们将讨论AI-BUS的驱动&#xff0c;以便于与宇电设备的通讯。 1、功能概述 宇电的设备使用基…

步进电机驱动技术3:基于ULN2003的步进电机驱动

在我们的项目中&#xff0c;经常使用到低电压小功率的步进电机&#xff0c;此类步进电机若采用驱动器控制不断成本高也过于复杂&#xff0c;我们可以直接使用场效应管或者达林顿管来实现对其的驱动。在本篇中&#xff0c;我们就来讨论一下基于ULN2003A达林顿管实现对步进电机的…

通讯接口应用笔记2:MAX3160实现多协议通讯

在一些应用需求中&#xff0c;我们需要对外部提供串行通讯端口&#xff0c;但这些端口所通讯的目标设备各有不同&#xff0c;接口协议也有RS232以及RS485和RS422多种。面对这种情况&#xff0c;我们当然可以同时设计多个串口以适应不同需要&#xff0c;但无疑对硬件资源是一种浪…

电机速度曲线规划1:梯形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的梯形曲线规划的问题。 1、基本原理 梯形速度曲线控制算法是工业控制领域应用最为广泛…

文件系统应用笔记之一:FatFS在STM32F4上的移植

在实现如U盘文件读写&#xff0c;SD卡的文件读写等工作时&#xff0c;我们往往需要一个文件系统来支持我们的工作。特别在一些MCU应用中&#xff0c;文件系统的加入能明显改善系统交互的友好性。在这一篇中&#xff0c;我们就来讨论FatFS文件系统在STM32F4上的移植和应用。 1、…

通讯接口应用笔记3:使用W5500实现Modbus TCP服务器

前面我们设计实现了W5500的驱动程序&#xff0c;也讲解了驱动的使用方式。在最近一次的项目应用中&#xff0c;正好有一个使用W5500实现TCP通讯的需求&#xff0c;所以我们就使用该驱动程序轻松实现。这一篇中我们就来说一说基于我们W5500通讯驱动程序实现TCP通讯的过程。 1、…

电机速度曲线规划2:S形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的S型曲线规划的问题。 1、基本原理 S型速度曲线控制算法是工业控制领域另一种常用的加…

USB应用开发笔记之一:STM32上实现USB主机读写U盘

在项目应用中&#xff0c;经常会有对外交换数据的需求。USB接口读写U盘无疑是一种颇为方便的选择。在这一篇中&#xff0c;我们就来讨论如何在STM32上实现USB主机读写U盘文件的方法。 1、应用概述 在我们的产品上有这样一个需求&#xff0c;希望通过大容量的U盘存取数据。我们…

Modbus协议栈综合实例设计

自我们开源了我们的Modbus协议栈之后&#xff0c;就一直有朋友来信说希望提供示例。这次我们整理了几个例子以供参考。 1、应用实例规划 在这次的实例中&#xff0c;我们使用的目标板拥有一个以太网接口、一个RS232串行接口和一个RS485串行接口&#xff0c;所以我们规划实现&a…

ThreadX应用开发笔记之二:移植ThreadX到STM32H7平台

前面我们将ThreadX成功移植到了STM32F4平台&#xff0c;但这只是我们的部分应用。我们希望将ThreadX的优势发挥到我们的更多应用中&#xff0c;所以在这一篇中我们就来实现将ThreadX移植到STM32H7平台中。 1、前期准备 在开始将ThreadX移植到STM32H7平台之前&#xff0c;我们需…

外设驱动库开发笔记31:S-Modlue远红外气体传感器驱动

在气体分析类产品中&#xff0c;我们经常会用到远红外气体传感器。我们就在碳氢类气体成分分析中使用了S-Modlue远红外气体传感器。接下来&#xff0c;我们将讨论S-Modlue远红外气体传感器驱动的设计与实现。 1、功能概述 S-MODULE EVO 使用非分散红外检测技术NDIR&#xff0c…

外设驱动库开发笔记32:HLPM025K3 PM2.5传感器驱动

现在人们对大气环境及室内环境都比较关注。PM2.5在生活中也是常见的词汇。在有些产品中就要求检测PM2.5的数值。检测PM2.5的手段多种多样&#xff0c;在要求不高时我们通常可以采用激光模块。在这一篇中&#xff0c;我们将讨论HLPM025K3 PM2.5传感器驱动的设计与实现。 1、功能…

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

LCD1602是一种工业字符型液晶&#xff0c;能够同时显示16x02即32个字符。LCD1602液晶显示的原理是利用液晶的物理特性&#xff0c;通过电压对其显示区域进行控制&#xff0c;即可以显示出图形。在这一章我们就来讨论LCD1602液晶显示屏驱动的设计与实现。 1、功能概述 LCD1602液…

滤波器开发之四:基于算术平均的中值滤波器

在信号采集系统中&#xff0c;除了我们感兴趣的数据外&#xff0c;难免会有一些来自于环境的干扰信号。但我们总希望我们得到的数据是纯净而真实的&#xff0c;为了达到这个目标&#xff0c;我们不得不想办法去除这些干扰信号&#xff0c;于是滤波器就成为我们必不可少的帮手。…

LwIP应用开发笔记之十一:LwIP带操作系统UDP服务器

我们已经实现了在FreeRTOS系统上的LwIP的移植工作&#xff0c;但只是简单的在系统平台上跑了起来。我们还希望能做更多的事情&#xff0c;这一节我们就在FreeRTOS系统上实现基于LwIP的UDP服务器。 1、UDP协议简述 UDP协议全称是用户数据报协议&#xff0c;在网络中它与TCP协议…

滤波器开发之五:基于算术平均的限幅滤波器

通过AD采集数据时&#xff0c;我们总是希望采集到的数据是纯净而真实的&#xff0c;而实际上环境中存在太多的干扰信号&#xff0c;为了让我们得到的数据尽可能地接近实际值&#xff0c;我们需要降低这些干扰信号的影响。所以软件实现的数字滤波器应运而生&#xff0c;这一篇我…

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

现在OLED显示屏在嵌入式系统中应用的越来越多。对于一些显示信息不太复杂&#xff0c;以显示信息为主的需求&#xff0c;我们一般会选择OLED显示屏。在这一篇中&#xff0c;我们将讨论OLED显示屏驱动的设计与实现。 1、功能概述 从使用的情况来说&#xff0c;较为常用的是0.96…

外设驱动库开发笔记35:迪文触摸屏驱动

有些时候嵌入式系统也需要显示更为复杂的图形&#xff0c;需要更丰富的数据展示。为此&#xff0c;我们需要更大&#xff0c;色彩更丰富&#xff0c;带触屏的显示屏&#xff0c;当然性价比更高就最好了。在我们的项目中遇到此类需求&#xff0c;我们有时会选择DWIN触摸屏。在本…

快速实现一个室内空气质量检测仪

冬天我们大多会关闭门窗&#xff0c;而依靠暖通空调设备来维持室内温度。而在保证居室温度的同时&#xff0c;我们也希望保持居室内大气环境的健康度。鉴于此&#xff0c;我们设计了一个简单的室内空气质量检测器。 1、系统概述 我们依靠暖通空调设备来维持室内温度、湿度和通…

外设驱动库开发笔记36:NTC负温度系数热电阻测温驱动

在嵌入式产品中&#xff0c;温度检测非常常见。在成本比较敏感而精度要求较低时&#xff0c;NTC电阻是个不错的选择。在这一篇中&#xff0c;我们将讨论如何和设计并实现一个通用的NTC驱动&#xff0c;以便在后续的项目中更方便的复用。 1、功能概述 NTC是指随温度上升电阻呈指…