元器件详解------DS12C887
引言
在嵌入式开发中,经常需要获取或记录时间信息,例如电子时钟、数据记录器、定时开关等。这时候就需要一个**实时时钟(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个特殊功能寄存器(控制/状态寄存器+时间/闹钟/日历寄存器)。
- 用户RAM:114字节的通用静态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将内部数据送往总线AD0~AD7上,以供外部读取。在写操作中,DS的下降沿将使总线 AD0~AD7上的数据锁存在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.0)DS→ 任意IO (例如P2.1)R/W→ 任意IO(例如P2.2)CS→ 任意IO (例如P2.3)IRQ→ 可选,接外部中断引脚(如INT0),并加上拉电阻SQW→ 可选,可接示波器观察波形RESET→ 接VCC(高电平)MOT→ 接GND(选择Intel时序)
这样,MCU就可以通过模拟时序对DS12C887进行读写操作了。
6、软件编程要点
6.1 地址映射
DS12C887内部有128字节,地址0x00~0x7F。其中:
- 0x00:秒(二进制或BCD格式)
- 0x01:秒闹钟
- 0x02:分
- 0x03:分闹钟
- 0x04:时
- 0x05:时闹钟
- 0x06:星期(1~7)
- 0x07:日
- 0x08:月
- 0x09:年
- 0x0A:寄存器A(控制寄存器)
- 0x0B:寄存器B(控制寄存器)
- 0x0C:寄存器C(中断标志寄存器)
- 0x0D:寄存器D(电池状态寄存器)
- 0x0E~0x7F:用户RAM
注意:所有时间寄存器在读取时需先将寄存器B的SET位置1停止更新,或者采用连续读取两次比较的方法来避免数据在读取过程中发生进位变化。
6.2 初始化步骤
- 写寄存器B :清除
SET位允许时钟计数,根据需要开启中断、方波输出等。 - 设置初始时间 :先置
SET=1停止时钟,将时间写入对应寄存器,再清SET启动。 - 配置闹钟(可选):将闹钟值写入秒、分、时闹钟寄存器,并在寄存器B中使能闹钟中断。
6.3 读写时序
以Intel时序为例:
写数据 :先往AD总线上发送地址,然后拉低AS产生下降沿锁存地址;之后将数据放到总线上,拉低R/W(写),再拉低DS产生脉冲,数据被写入芯片。
读数据 :同样先锁存地址,然后置R/W为高(读),拉低DS,此时芯片会将数据输出到总线上,MCU读取即可。
因为DS12C887是标准逻辑,很多单片机都有现成的库函数,可以省去手动模拟的麻烦。
时钟芯片ds12c887的C51驱动程序如下:
c
/*************************************************************
文件名称: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位判断,或读取两次比较。
8. 总结
DS12C887是一款经典且易用的RTC芯片,它将晶振和电池集成在芯片内部,让开发者无需操心振荡电路和电池备份,大大简化了硬件设计。对于想要学习RTC应用或制作电子钟表、定时器项目的初学者来说,它是一个非常友好的选择。
当然,它的并行接口在现代微控制器上略显笨重,需要较多的IO口。如果你追求更简洁的连接,可以考虑I²C接口的DS3231等芯片,但那些芯片通常需要外接晶振和电池。选择哪款取决于你的项目需求和手头资源。