DS12C887是時鍾芯片DS12C885集成了電池和晶振的版本. 如果拆掉DS12C887的外殼, 能看到里面就是DS12C885.
功能特性
- 能輸出世紀、年、月、日、時、分、秒等時間信息
- 集成電池, 外部掉電時, 時間不會丟失.
- 有12小時和24小時兩種模式. 在12小時制模式中, 用AM和PM區分上午和下午
- 時間的存儲方式有兩種: 一種用二進制數表示, 另一種是用BCD碼表示
- 帶有128 byte RAM, 其中
- 11 byte用來存儲時間信息
- 4 byte用來存儲DS12C887的控制信息, 稱為控制寄存器
- 113 byte 通用RAM, 供用戶使用.
- 可以對DS12C887進行編程, 實現多種方波輸出
- 可對其內部的三路中斷通過軟件進行屏蔽
- 芯片內部有一個精密的溫度補償電路用來監視Vee的狀態, 如果檢測到主電源故障, 該器件可以自動切換到備用電源供電
- VBAeKUP引腳用於支持可充電電池或超級電容, 內部包括一個始終有效的涓流充電器.
- 可以通過一個讀寫復用的單字節並行接口訪問, 該接口支持Intel和Motorola模式
引腳功能

GND、 VCC
VCC接+5V輸入, 當VCC輸入為+5V時, 用戶可以訪問DS12C887內RAM中的數據, 並可對其進行讀寫操作. 當VCC的輸入小於+4.25V時, 禁止用戶對內部RAM進行讀寫操作, 此時用戶不能正確獲取芯片內的時間信息. 當VCC的輸入小於+3V時, DS12C887會自動將電源發換到內部自帶的鋰電池上.
MOT
模式選擇腳. DA12C887有兩種工作模式: Motorola模式和Intel模式,
- MOT接VCC時是Motorola模式,
- MOT接GND或者浮空時, 是Intel模式. 一般使用Intel模式.
SQW
方波輸出腳. 當供電電壓VCC大於4.25V時, SQW腳可進行方波輸出, 此時用戶可以通過對控制寄存器編程來得到13種方波信號的輸出
AD0-AD7
地址和數據復用的雙向傳輸總線,
- 地址信息出現在總線周期的前半部分,然后在AS的下降沿被鎖存.
- 寫入數據在Motorola模式時在DS的下降沿被鎖存, 在Intel模式時在R/W的上升沿被鎖存
- 在讀周期, 輸出的數據在DS(Motorola模式時為DS和R/W高電平, Intel模式時為DS低電平R/W高電平)的后沿
- 在Motorola模式下DS變低電平, 或Intel模式下DS變高電平, 會結束讀周期回到高阻態
簡化一下, 對於Intel模式
- 地址信息在AS的下降沿被鎖存.
- 寫入數據在R/W的上升沿被鎖存
- 在讀周期, 輸出的數據在DS低電平, R/W高電平的后沿
- DS變高電平, 會結束讀周期回到高阻態
AS
Address Strobe, 地址閘(選通輸入)腳, 在進行讀寫操作時, AS的下降沿將AD0-AD7上出現的地址信息鎖存到DS12C887上, 而下一個上升沿清除AD0-AD7上的地址信息, 不論片選CS是否有效, DS12C887都將執行該操作.
Address Strobe Input. A positive-going address-strobe pulse serves to demultiplex the bus. The falling edge of AS causes the address to be latched within the device. The next rising edge that occurs on the AS bus clears the address regardless of whether CS is asserted. An address strobe must immediately precede each write or read access. If a write or read is performed with CS deasserted, another address strobe must be performed prior to a read or write access with CS asserted.
DS/RD
Data Strobe or Read Input, 數據閘(選通)或讀輸入腳, 該引腳有兩種工作模式
- MOT接VCC時, 為Motorola工作模式, 在這種工作模式中, 每個總線周期的后一部分的DS為高電平, 被稱為數據選通, 在讀操作中, DS的上升沿使DS12C887將內部數據送往總線AD0-AD7上, 以供外部讀取, 在寫操作中, DS的下降沿將使總線 AD0-AD7上的數據鎖存在DS12C887中.
- MOT接地時, 為Intel模式, 這時DS腳的功能為輸出使能(Output Enable)信號, 定義讀取的周期
R/W
讀/寫輸入端. 該管腳也有2種工作模式
- 當MOT接VCC時, 工作在Motorola模式, 此時該引腳的作用是區分進行的是讀操作還是寫操作, 當R/W為高電平時為讀操作, R/W 為低電平時為寫操作.
- 當MOT接GND時, 工作在Intle模式, 此時該引腳僅作為RAM上的一個"可寫"信號, 作為寫允許輸入, 數據在信號上升沿被鎖存. the R/W signal is an active-low signal. In this mode, the R/W pin operates in a similar fashion as the write-enable signal (WE) on generic RAMs. Data are latched on the rising edge of the signal.
RESET
低電平有效
IRQ
中斷請求輸出, 低電平有效. 當對應的中斷開啟且中斷對應的bit有效時輸出低電平. 讀取C寄存器可以清除中斷. IRQ腳是一個開漏輸出, 需要外部的上拉電阻接至VCC
Active-Low Interrupt Request Output. The IRQ pin is an active-low output of the device that can be used as an interrupt input to a processor. The IRQ output remains low as long as the status bit causing the interrupt is present and the corresponding interrupt-enable bit is set.
The processor program normally reads the C register to clear the IRQ pin. The RESET pin also clears pending
interrupts. When no interrupt conditions are present, the IRQ level is in the high-impedance state. Multiple interrupting devices can be connected to an IRQ bus, provided that they are all open drain. The IRQ pin is an open-drain output and requires an external pullup resistor to V CC.
CS
片選, 低電平有效, CS必須在以下狀態中保持低電平:
- Motorola模式: DS和AS
- Intel模式: DS和RW
片內數據的地址和范圍
控制地址
- 0AH
- 0BH
- 0CH
- 0DH
RAM地址
- 0EH - 31H
- 33H - 7FH
時鍾數值地址 Binary Mode (DM = 1)
- 00H: 低六位, 秒, [0, 3B]
- 01H: 低六位, 秒, [0, 3B] 鬧鍾
- 02H: 低六位, 分, [0, 3B]
- 03H: 低六位, 分, [0, 3B] 鬧鍾
- 04H: 低五位, 時, [0, 17] (24小時制)
- 05H: 低五位, 時, [0, 17] (24小時制) 鬧鍾
- 06H: 低二位, 日, [0, 07] 周中日
- 07H: 低五位, 日, [0, 1F] 月中日
- 08H: 低四位, 月, [0, 0C] 月
- 09H: 低七位, 年, [0, 63] 00至99
Binary模式下, 沒有世紀信息

時鍾數值地址 BCD Mode (DM = 0)
BCD模式下, 因為是用於直接顯示, 因此在存儲中, 每個值都根據實際的顯示位數被拆成一至二個數字
- 32H, 值[00, 99], BCD模式下有世紀信息

STC89C52 驅動 DS12C877
連接
- 操作DS12C887時鍾芯片共需要13條信號線, 分別是並行數據地址復用線ADO~AD7, CS, AS, R/W, DS 和 IRQ
- MOD接GND
- RESET接VCC
- DS, AS, R/W, CS分別連接P1.0 - P1.4
- IRQ是DS12C887的中斷請求, 需要連接STC89C52外部中斷, 這里連接到P3.2
左側
MOT => GND
AD0 => P0.0
...
AD7 => P0.7
GND => GND
右側
VCC => VCC
SQW
IRQ => P3.2
RESET => VCC
DS => P1.0
RW => P1.1
AS => P1.2
CS => P1.4
USB2TTL連線
TX => P3.0
RX => P3.1
Keil 51 代碼
讀取時間信息的測試代碼
#include <reg52.h>
#include <stdio.h>
typedef unsigned int u16;
typedef unsigned char u8;
u8 year, month, date, hour, minute, second, week_day;
u8 alarm_hour, alarm_minute, alarm_second;
u8 reg_a, reg_b, reg_c;
void delay(u16 z) {
u16 x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
sbit DS12_DS = P1^0;
sbit DS12_RW = P1^1;
sbit DS12_AS = P1^2;
sbit DS12_CS = P1^4;
sbit DS12_IRQ = P3^2;
void DS12C887_write(u8 addr, u8 dat)
{
delay(1);
DS12_CS = 0; // ds12c887de 使能端 cs=0
delay(1);
DS12_AS=1;
DS12_DS=1;
DS12_RW=1;
delay(1);
P0 = addr;
delay(1);
DS12_AS=0;
DS12_RW=0;
P0 = dat;
DS12_RW=1;
DS12_AS=1;
delay(1);
DS12_CS = 1; // ds12c887de 使能端 cs=1
delay(1);
}
u8 DS12C887_read(u8 addr)
{
u8 ds_date;
DS12_AS=1;
DS12_DS=1;
DS12_RW=1;
delay(1);
DS12_CS = 0;
delay(1);
P0 = addr;
delay(1);
DS12_AS=0;
DS12_DS=0;
P0 = 0XFF;
ds_date=P0;
DS12_DS=1;
DS12_AS=1;
DS12_CS = 1;
return ds_date;
}
void DS12C887_init()
{
DS12C887_write(0x0b, 0x26);
DS12C887_write(0x0a, 0x20);
}
void main()
{
P0 = 0x00; // P0口清零
P1 = 0xff; // P1口全1
DS12C887_init();//ds12c887 初始化
EA =1; // 開啟中斷, 開啟TIM1中斷, 開啟外部中斷0
IT0 =1;
EX0 =1;
// 初始化UART
TMOD = 0x20;
SCON = 0x40;
TH1 = 256 - 11.0592 * 1000 * 1000 / 12 / 32 / 9600 + 0.5;
TCON |= 0x40;
SCON |= 0x02;
// 初始化UART結束
/* 初始化寫入
DS12C887_write(9,11); // Year
DS12C887_write(8,8); // Month
DS12C887_write(7,7); // Date
DS12C887_write(6,7); // Week Day
DS12C887_write(4,9); // Hour
DS12C887_write(2,27); // Minute
DS12C887_write(0,25); // Second
*/
while(1) {
second = DS12C887_read(0x00);
alarm_second = DS12C887_read(0x01);
minute = DS12C887_read(0x02);
alarm_minute = DS12C887_read(0x03);
hour = DS12C887_read(0x04);
alarm_hour = DS12C887_read(0x05);
date = DS12C887_read(0x07);
month = DS12C887_read(0x08);
year = DS12C887_read(0x09);
reg_a = DS12C887_read(0x0a);
reg_b = DS12C887_read(0x0b);
reg_c = DS12C887_read(0x0c);
printf("%bX-%bX-%bX ", reg_a, reg_b, reg_c);
printf("%bX-%bX-%bX ", year, month, date);
printf("%bX:%bX:%bX\r\n", hour, minute, second);
}
}
void exter() interrupt 0
{
// 這里處理中斷
}
SDCC代碼(基於HML_FwLib_STC89)
下面的代碼, 都是通過IRQ腳的中斷來實現的, 啟用中斷后, DS12C887每秒都會將IRQ腳電壓拉低, 此時0x0C的IRQF位會被置1, 如果不清除這個標志位, IRQ下次就不會被觸發, 清除標志位根據手冊Any time the IRQF bit is 1, the IRQ pin is driven low. This bit can be cleared by reading Register C or with a RESET.,
- 讀取寄存器0x0C
- 使用RESET
如果需要基於下面的代碼做改動, 讀取寄存器0x0C這行reg_c = DS12C887_read(0x0c);要留意不能隨便刪, 不然只顯示一次就再沒新數據了.
#include "hml/hml.h"
#define DS12_DS P1_0
#define DS12_RW P1_1
#define DS12_AS P1_2
#define DS12_CS P1_4
#define DS12_IRQ P3_2
uint8_t reg_a, reg_b, reg_c;
uint8_t year, month, date, week_day, hour, minute, second;
uint8_t alarm_hour, alarm_minute, alarm_second;
void DS12C887_write(uint8_t addr, uint8_t w_data)
{
DS12_CS = 0;
NOP();
DS12_AS = 1;
DS12_DS = 1;
DS12_RW = 1;
NOP();
P0 = addr;
NOP();
DS12_AS = 0;
DS12_RW = 0;
P0 = w_data;
DS12_RW = 1;
DS12_AS = 1;
NOP();
DS12_CS = 1;
}
uint8_t DS12C887_read(uint8_t addr)
{
uint8_t ds_date;
DS12_AS = 1;
DS12_DS = 1;
DS12_RW = 1;
DS12_CS = 0;
P0 = addr;
DS12_AS = 0;
DS12_DS = 0;
P0 = 0XFF;
ds_date = P0;
DS12_DS = 1;
DS12_AS = 1;
DS12_CS = 1;
return ds_date;
}
void print_time(void)
{
reg_a = DS12C887_read(0x0a);
reg_b = DS12C887_read(0x0b);
reg_c = DS12C887_read(0x0c);
second = DS12C887_read(0x00);
minute = DS12C887_read(0x02);
hour = DS12C887_read(0x04);
week_day = DS12C887_read(0x06);
date = DS12C887_read(0x07);
month = DS12C887_read(0x08);
year = DS12C887_read(0x09);
alarm_second = DS12C887_read(0x01);
alarm_minute = DS12C887_read(0x03);
alarm_hour = DS12C887_read(0x05);
UART_sendHex(reg_a);
UART_sendByte('-');
UART_sendHex(reg_b);
UART_sendByte('-');
UART_sendHex(reg_c);
UART_sendByte(' ');
UART_sendHex(year);
UART_sendByte('-');
UART_sendHex(month);
UART_sendByte('-');
UART_sendHex(date);
UART_sendByte(' ');
UART_sendHex(hour);
UART_sendByte(':');
UART_sendHex(minute);
UART_sendByte(':');
UART_sendHex(second);
UART_sendByte('\n');
}
void DS12C887_init()
{
// 0B: SET PIE AIE UIE SQWE DM 24/12 DSE
DS12C887_write(0x0b, 0x36);
// 0A: UIP DV2 DV1 DV0 RS3 RS2 RS1 RS0
DS12C887_write(0x0a, 0x20);
alarm_second = DS12C887_read(0x01);
alarm_minute = DS12C887_read(0x03);
alarm_hour = DS12C887_read(0x05);
}
void sys_init(void)
{
UART_configTypeDef uc;
uc.baudrate = 115200;
uc.baudGenerator = PERIPH_TIM_2;
uc.interruptState = DISABLE;
uc.interruptPriority = UTIL_interruptPriority_0;
uc.mode = UART_mode_1;
uc.multiBaudrate = DISABLE;
uc.receiveState = ENABLE;
UART_config(&uc);
enableAllInterrupts();
}
void main(void)
{
DS12C887_init();
sys_init();
EA = 1;
IT0 = 1;
EX0 = 1;
/*
DS12C887_write(0x00, 50); //sec
DS12C887_write(0x01, 0);
DS12C887_write(0x02, 58); //min
DS12C887_write(0x03, 0);
DS12C887_write(0x04, 17); //hour
DS12C887_write(0x05, 0);
DS12C887_write(0x06, 4); //week
DS12C887_write(0x07, 21); //day
DS12C887_write(0x08, 8); //moth
DS12C887_write(0x09, 14); //year
*/
while (1);
}
void Timer0IRQ(void) __interrupt (0)
{
print_time();
}
觀察時序
這是手冊上的時序說明(Intel mode, read)

用邏輯分析儀進行觀察, 采樣率12MHz, 單次讀取(AD0-AD3)

用邏輯分析儀進行觀察, 采樣率12MHz, 單次讀取(AD4-AD7)

連續讀取, 依次讀取了0x0A, 0x0B, 0x0C, 0x00(AD0-AD3)

連續讀取, 依次讀取了0x0A, 0x0B, 0x0C, 0x00(AD4-AD7)

觀察記錄
- 單次讀取之間, 間隔27us
- CS下拉后2.16us, 往AD口輸入地址
- DS下拉后, AD口的地址數據立即被清零
- DS下拉后2.16us, AD出現數據, 此時可以讀出數據
- DS上拉后, AD數據立即上拉(全1)
- CS -> AS -> DS, 下拉和上拉之間的間隔都是1us
STC12操作DS12C887
在STC12C5AxxS2系列上, 使用STC89的代碼無法正常讀取, 因為STC12的GPIO准雙向模式較STC89加強了電流能力, 在DS12C887返回數據時, 有一定概率會被P0=0xFF;干擾. 解決的方案是在給完地址, DS下拉后, 立即將P0口的狀態變為高阻態, 在讀取完返回數據后, 再將P0口設置回准雙向口. 代碼如下, 根據DS12C887手冊, 幾個下拉時間點之間的間隔要求都是幾十到一百個ns, 所以不需要延遲.
void DS12C887_write(uint8_t addr, uint8_t w_data)
{
DS12_CS = 0;
DS12_AS = 1;
DS12_DS = 1;
DS12_RW = 1;
P0 = addr;
DS12_AS = 0;
DS12_RW = 0;
P0 = w_data;
DS12_RW = 1;
DS12_AS = 1;
DS12_CS = 1;
}
uint8_t DS12C887_read(uint8_t addr)
{
uint8_t ds_date;
DS12_AS = 1;
DS12_DS = 1;
DS12_RW = 1;
DS12_CS = 0;
P0 = addr;
DS12_AS = 0;
DS12_DS = 0;
/**
* Set P0 to high impedance. This is the tricky part, if the mode remains quasi-bidirectional,
* you need to set P0 = 0xFF in order to get output data, but this will also interfere with the output.
* This is quite different from STC89.
*/
P0M0 = 0x00;
P0M1 = 0xFF;
ds_date = P0;
DS12_DS = 1;
DS12_AS = 1;
DS12_CS = 1;
/** Restore P0 to default quasi-bidirectional */
P0M0 = 0xFF;
P0M1 = 0x00;
return ds_date;
}
參考
- STC12的代碼例子 https://github.com/IOsetting/HML_FwLib_STC12/blob/master/example/gpio/gpio_ds12c887_his.c
- https://blog.csdn.net/dai_wen/article/details/81050120
- http://www.51hei.com/bbs/dpj-184639-1.html
- https://blog.csdn.net/huang873036652/article/details/91460594
- http://www.51hei.com/bbs/dpj-29544-1.html
- http://news.eeworld.com.cn/mcu/ic466119.html
- c51的printf格式 https://www.keil.com/support/man/docs/c51/c51_printf.htm
- DS12C887拆解 https://www.yleee.com.cn/shop/7344.html
