2026/4/6 18:23:08
网站建设
项目流程
在嵌入式开发中经常需要获取或记录时间信息例如电子时钟、数据记录器、定时开关等。这时候就需要一个实时时钟RTC芯片。市面上RTC芯片众多但对于刚入门的新手来说DS12C887无疑是一个绝佳的起点——它几乎把最复杂的部分都封装好了让你可以专注于软件逻辑而不必担心硬件设计的坑。本文将详细介绍DS12C887的功能、内部结构、引脚定义、典型电路以及简单的编程思路。希望能够帮助新手朋友快速上手。1、DS12C887能做什么DS12C887是一个功能完善的RTC芯片它的本职工作就是计时它可以输出年、月、日、时、分、秒并且能自动处理闰年时间跨度从1970年到2099年。此外它还额外提供了以下功能内置电池与晶振出厂时内部集成了锂电池和32.768kHz晶振主电源断电后仍能维持时钟运行长达10年。128字节RAM:其中14字节用于时钟和控制寄存器剩余114字节可供用户自由存储数据且数据随时钟一起保持掉电不丢失。闹钟功能可设置一到三个闹钟时分秒匹配当时间到达时芯片会通过中断引脚通知MCU。可编程方波输出可以SQW引脚输出2Hz~8kHz的方波用于其他电路的时钟源。2、DS12C887是否适合学习传统的RTC芯片如DS1302、DS3231通常需要外接晶振和备份电池这对初学者来说增加了布线和焊接难度而且容易因晶振不起振、电池接触不良导致失败。DS12C887将晶振和电池密封在黑色外壳内用户只需连接电源和数据线即可工作极大降低了入门门槛。此外它的接口是标准的并行接口读写时序简单直观非常适合用51单片机、Arduino等学习控制。当然现代的MUC也可以通过GPIO模拟时序与之通信。3、芯片内部结构DS12C887内部框图可以简化为以下部分振荡器与分频器内部晶振产生的32.768kHz信号经过分频得到1Hz时钟。时钟计数器链对1Hz进行累加计算出秒、分、时……同时处理闰年。闹钟比较器将当前时间与闹钟寄存器设定的值比较一致时产生中断。控制逻辑与寄存器组包含14个特殊功能寄存器控制/状态寄存器时间/闹钟/日历寄存器。用户RAM114字节的通用静态RAM地址从0x0E开始。4、引脚功能详解DS12C887的引脚排列如图1所示各管脚的功能说明如下GND、VCC直流电源其中VCC接5V输入GND接地当VCC输入为5V时用户可以访问DS12C887内的RAM中的数据并可对其进行读、写操作当VCC的输入小于4.25时禁止用户对内部RAM进行读、写操作此时用户不能获取芯片内的时间信息当VCC的输入小于3V时DS12C887会自动将电源换到内部自带的锂电池上以保证内部的电路能够正常工作。MOT模式选择脚DS12C887有两种工作模式即Motorola模式和lntel模式当MOT接VCC时选用的工作模式是Motorola模式当MOT接GND时选用的是lntel模式本文主要讨论lntel模式。SQW方波输出脚当供电电压VCC大于4.25V时SQW脚可以进行方波输出此时用户可以通过对控制寄存器编程来得到13种方波信号输出。AD0AD7复用地址数据总线该总线采用时分复用技术在总线周期的前半部分出现在AD0AD7上的是地址信息可用来选通DS12C887内的RAM总线周期的后半部分出现在AD0~AD7上的是数据信息。AS地址选通输入脚在进行读写操作时AS的上升沿将AD0AD7上出现的地址信息锁存到DS12C887上而下一个下降沿清除AD0AD7上的地址信息不论是否有效DS12C887都将执行操作。DS/RD数据选择或读输入脚该引脚有两种工作模式当MOT接VCC时选用Motorola工作模式在这种工作模式中每个总线周期的后一部分的DS为高电平被称为数据选通。在读操作中DS的上升沿使DS12C887将内部数据送往总线AD0AD7上以供外部读取。在写操作中DS的下降沿将使总线 AD0AD7上的数据锁存在DS12C887中当MOT接GND时选用Intel工作模式在该模式中该引脚是读允许输入脚即Read Enable。R/W读/写输入端该管脚也有2种工作模式当MOT接VCC时R/W工作在Motorola模式。此时该引脚的作用是区分进行的是读操作还是写操作当R/W为高电平时为读操作R/W为低电平时为写操作当MOT接GND时该脚工作在Intle模式此时该作为写允许输入即Write Enable。CS片选输入低电平有效。IRQ中断请求输入低电平有效该脚有效对DS12C887内的时钟、日历和RAM中的内容没有任何影响仅对内部的控制寄存器有影响在典型的应用中RESET可以直接接VCC这样可以保证DS12C887在掉电时其内部控制寄存器不受影响。5、典型硬件连接假设我们使用AT89C52单片机采用lintel时序模式MOT引脚接地。连接如下AD0-AD7→ P0口注意P0需外接上拉电阻因为它是开漏输出AS→ 任意IO 例如P2.0DS→ 任意IO 例如P2.1R/W→ 任意IO例如P2.2CS→ 任意IO 例如P2.3IRQ→ 可选接外部中断引脚如INT0并加上拉电阻SQW→ 可选可接示波器观察波形RESET→ 接VCC高电平MOT→ 接GND选择Intel时序这样MCU就可以通过模拟时序对DS12C887进行读写操作了。6、软件编程要点6.1 地址映射DS12C887内部有128字节地址0x000x7F。其中0x00秒二进制或BCD格式0x01秒闹钟0x02分0x03分闹钟0x04时0x05时闹钟0x06星期170x07日0x08月0x09年0x0A寄存器A控制寄存器0x0B寄存器B控制寄存器0x0C寄存器C中断标志寄存器0x0D寄存器D电池状态寄存器0x0E0x7F用户RAM注意所有时间寄存器在读取时需先将寄存器B的SET位置1停止更新或者采用连续读取两次比较的方法来避免数据在读取过程中发生进位变化。6.2 初始化步骤写寄存器B清除SET位允许时钟计数根据需要开启中断、方波输出等。设置初始时间先置SET1停止时钟将时间写入对应寄存器再清SET启动。配置闹钟可选将闹钟值写入秒、分、时闹钟寄存器并在寄存器B中使能闹钟中断。6.3 读写时序以Intel时序为例写数据先往AD总线上发送地址然后拉低AS产生下降沿锁存地址之后将数据放到总线上拉低R/W写再拉低DS产生脉冲数据被写入芯片。读数据同样先锁存地址然后置R/W为高读拉低DS此时芯片会将数据输出到总线上MCU读取即可。因为DS12C887是标准逻辑很多单片机都有现成的库函数可以省去手动模拟的麻烦。时钟芯片ds12c887的C51驱动程序如下/************************************************************* 文件名称ds12c887.c 适用范围时钟芯片ds12c887的驱动程序 *************************************************************/ include /* 命令常量定义 */ #define CMD_START_DS12C887 0x20 /* 开启时钟芯片*/ #define CMD_START_OSCILLATOR 0x70 /* 开启振荡器处于抑制状态 */ #define CMD_CLOSE_DS12C887 0x30 /* 关掉时钟芯片*/ /* 所有的置位使用或操作清除使用与操作 */ #define MASK_SETB_SET 0x80 /* 禁止刷新 */ #define MASK_CLR_SET 0x7f /* 使能刷新 */ #define MASK_SETB_DM 0x04 /* 使用HEX格式*/ #define MASK_CLR_DM 0xfb /* 使用BCD码格式 */ #define MASK_SETB_2412 0x02 /* 使用24小时模式 */ #define MASK_CLR_2412 0xfd /* 使用12小时模式 */ #define MASK_SETB_DSE 0x01 /* 使用夏令时 */ #define MASK_CLR_DSE 0xfe /* 不使用夏令时*/ /* 寄存器地址通道定义 */ xdata char chSecondsChannel _at_ 0xdf00; xdata char chMinutesChannel _at_ 0xdf02; xdata char chHoursChannel _at_ 0xdf04; xdata char chDofWChannel _at_ 0xdf06; xdata char chDateChannel _at_ 0xdf07; xdata char chMonthChannel _at_ 0xdf08; xdata char chYearChannel _at_ 0xdf09; xdata char chCenturyChannel _at_ 0xdf32; xdata char chRegA _at_ 0xdf0a; xdata char chRegB _at_ 0xdf0b; xdata char chRegC _at_ 0xdf0c; xdata char chRegD _at_ 0xdf0d; /* 函数声明部分 */ void StartDs12c887(void); void CloseDs12c887(void); void InitDs12c887(void); unsigned char GetSeconds(void); unsigned char GetMinutes(void); unsigned char GetHours(void); unsigned char GetDate(void); unsigned char GetMonth(void); unsigned char GetYear(void); unsigned char GetCentury(void); void SetTime(unsigned char chSeconds,unsigned char chMinutes,unsigned char chHours); void SetDate(unsigned char chDate,unsigned char chMonth,unsigned char chYear); /************************************************************* 函数功能该函数用来启动时钟芯片工作 应用范围仅在时钟芯片首次使用时用到一次 入口参数 出口参数 *************************************************************/ void StartDs12c887(void) { chRegA CMD_START_DS12C887; } /************************************************************* 函数功能该函数用来关闭时钟芯片 应用范围一般用不到 入口参数 出口参数 *************************************************************/ void CloseDs12c887(void) { chRegA CMD_CLOSE_DS12C887; } void InitDs12c887() { StartDs12c887(); chRegB chRegB | MASK_SETB_SET; /* 禁止刷新 */ chRegB chRegB MASK_CLR_DM | MASK_SETB_2412 MASK_CLR_DSE; /* 使用BCD码格式、24小时模式、不使用夏令时 */ chCenturyChannel 0x21; /* 设置为21世纪 */ chRegB chRegB MASK_CLR_SET; /* 使能刷新 */ } /************************************************************* 函数功能该函数用来从时钟芯片读取秒字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetSeconds(void) { return(chSecondsChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取分字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetMinutes(void) { return(chMinutesChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取小时字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetHours(void) { return(chHoursChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取日字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetDate(void) { return(chDateChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取月字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetMonth(void) { return(chMonthChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取年字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetYear(void) { return(chYearChannel); } /************************************************************* 函数功能该函数用来从时钟芯片读取世纪字节 应用范围 入口参数 出口参数 *************************************************************/ unsigned char GetCentury(void) { return(chCenturyChannel); } /************************************************************* 函数功能该函数用来设置时钟芯片的时间 应用范围 入口参数chSeconds、chMinutes、chHours是设定时间的压缩BCD码 出口参数 *************************************************************/ void SetTime(unsigned char chSeconds,unsigned char chMinutes,unsigned char chHours) { chRegB chRegB | MASK_SETB_SET; /* 禁止刷新 */ chSecondsChannel chSeconds; chMinutesChannel chMinutes; chHoursChannel chHours; chRegB chRegB MASK_CLR_SET; /* 使能刷新 */ } /************************************************************* 函数功能该函数用来设置时钟芯片的日期 应用范围 入口参数chDate、chMonth、chYear是设定日期的压缩BCD码 出口参数 *************************************************************/ void SetDate(unsigned char chDate,unsigned char chMonth,unsigned char chYear) { chRegB chRegB | MASK_SETB_SET; /* 禁止刷新 */ chDateChannel chDate; chMonthChannel chMonth; chYearChannel chYear; chRegB chRegB MASK_CLR_SET; /* 使能刷新 */ }7. 注意事项电池寿命尽管内部电池号称10年寿命但长时间不使用时应确保VCC供电避免电池过度消耗。上拉电阻IRQ和SQW引脚为漏极开路必须外接上拉电阻通常4.7kΩ才能输出高电平。初始化时钟第一次使用或更换电池后需要先设置时间否则读出的时间可能是随机的。数据格式时间寄存器默认采用BCD码格式例如0x59表示59秒也可通过寄存器B设置为二进制格式。初学者建议使用BCD便于查看。避开更新周期DS12C887每秒会更新一次时间若在读数据过程中刚好发生更新可能读到错误值。稳妥的做法是先读取寄存器C的UF位判断或读取两次比较。