Dallas Realtime Clock DS12887

Hi, I have one of these RTCs (DS12887) and I was wondering if someone could explain the workflow for reading the data/date-time from the chip. I read the datasheet, but I am still not clear. I am not looking for a schematic, just a simple processing workflow.


set CS to HIGH READ the first 8 bits (LSB or MSB?) set AS low set AS high READ next 8 bits etc.


P.S. I know there are other easier I2C based RTCs but I had a few of these hanging around and I really want to use this as a learning opportunity.

This chip has a microprocessor bus, you have a choice of motorola or intel style signalling set by the MOT pin, then access as per timing diagrams - first send an address byte, then read or write the data byte.

For intel mode read it'll be something like:

ensure CS, DS, AS, R/W HIGH (idle state). make bus pins OUTPUTs CS goes LOW put address byte on bus AS goes LOW (strobes address) make bus pins INPUTs DS goes LOW read data from bus DS goes HIGH AS and CS go HIGH.

Thank you very much!

I have been looking everywhere for this.

So to recap,

1 ) Set IDLE state (CS, DS, AS, R/W to HIGH) 2 ) Switch BUS pins MODE to OUTPUT 3 ) Set CS LOW 4 ) Put Address on BUS pins 0x00 Seconds, 0x01 Seconds alarm, 0x02 Minutes, 0x03 Minutes alarm, 0x04 Hours, 0x05 Hours alarm, 0x06 Day of week, 0x07 Day of month, 0x08 Month, 0x09 Year, 0x0A REGISTER A, 0x0B REGISTER B, 0x0C REGISTER C, 0x0D REGISTER D 5 ) Set AS LOW to Commit/Strobe Address 6 ) Switch BUS pins MODE to INPUT 7 ) Set DS LOW 8 ) Read data from bus 9 ) Set DS HIGH 10 ) Return to IDLE state (AS and CS HIGH)

I assume that writing to the bus is as simple as skipping step 6 and swapping AS with R/W?

Thanks again

An example of how to implement a microprocessor compatible bus with arduino can be found here: http://softsolder.com/2009/07/18/arduino-hardware-assisted-spi-synchronous-serial-data-io/

So I have it wired up and signalling seems to be good… however, the data is not as expected.

The returned value for seconds is always 0b00000000… but there is data from other registers… which leave me wondering if I am sending the right address on the bus.

How are AD0 - 7 mapped to the byte value? Which is LSB and MSB?

Here is my code… anyone see what could be going wrong?

const uint8_t REGISTER_SECONDS       = B00000000;   // Seconds
const uint8_t REGISTER_SECONDS_ALARM = B00000001;   // Seconds alarm
const uint8_t REGISTER_MINUTES       = B00000010;   // Minutes
const uint8_t REGISTER_MINUTES_ALARM = B00000011;   // Minutes alarm
const uint8_t REGISTER_HOURS         = B00000100;   // Hours
const uint8_t REGISTER_HOURS_ALARM   = B00000101;   // Hours alarm
const uint8_t REGISTER_DOW           = B00000110;   // Day of week
const uint8_t REGISTER_DOM           = B00000111;   // Day of month
const uint8_t REGISTER_MONTH         = B00001000;   // Month
const uint8_t REGISTER_YEAR          = B00001001;   // Year
const uint8_t REGISTER_A             = B00001010;   // REGISTER A
const uint8_t REGISTER_B             = B00001011;   // REGISTER B
const uint8_t REGISTER_C             = B00001100;   // REGISTER C
const uint8_t REGISTER_D             = B00001101;   // REGISTER D

// dataPins = AD0 - AD7
int dataPins[8] = {2, 3, 4, 5, 6, 7, 8, 9};
// int dataPins[8] = {9, 8, 7, 6, 5, 4, 3, 2}; INVERTED
int CS = 10; // SELECT CHIP
int AS = 11; // STROBE ADDRESS
int DS = 13; // READ

uint8_t seconds;
uint8_t asecs;
uint8_t minutes;
uint8_t amins;
uint8_t hours;
uint8_t ahour;
uint8_t dow;
uint8_t dom;
uint8_t month;
uint8_t year;
uint8_t regA;
uint8_t rebB;
uint8_t rebC;
uint8_t rebD;
uint8_t lastsecs = 0b11111111;

boolean getBit(uint8_t myVarIn, uint8_t whatBit) {
  boolean bitState;
  bitState = myVarIn & (1 << whatBit);
  return bitState;

uint8_t setBit(uint8_t myVarIn, uint8_t whatBit, boolean bitState) {
  if (bitState) {
    myVarIn |= (1 << whatBit);
  else {
    myVarIn &= ~(1 << whatBit);
  return myVarIn;

void setIdle() {
  digitalWrite(CS, HIGH);
  digitalWrite(AS, HIGH);
  digitalWrite(WR, HIGH);
  digitalWrite(DS, HIGH);

void switchRead() {
  for (int i=0; i<7; i++) {
    pinMode(dataPins[i], INPUT);

void switchWrite() {
  for (int i=0; i<7; i++) {
    pinMode(dataPins[i], OUTPUT);

uint8_t readBusData() {
  uint8_t dataBuffer = 0b00000000;
  for (int i=0; i<8; i++) {
    boolean state = digitalRead(dataPins[i]);
    dataBuffer = setBit(dataBuffer, i, state);
  return dataBuffer;

void writeBusData(uint8_t data) {
  for (int i=0; i<8; i++) {
    digitalWrite(dataPins[i], getBit(data, i));

void readSeconds() {
  seconds = getRegisterData(REGISTER_SECONDS);

uint8_t getRegisterData(uint8_t registerAddr) {
  uint8_t dataBuffer = 0b00000000;
  digitalWrite(CS, LOW);
  digitalWrite(AS, LOW);
  digitalWrite(DS, LOW);
  dataBuffer = readBusData();
  digitalWrite(DS, HIGH);
  digitalWrite(AS, HIGH);
  digitalWrite(CS, HIGH);
  return dataBuffer;

void getRTCDate() {
  seconds = getRegisterData(REGISTER_SECONDS);
  minutes = getRegisterData(REGISTER_MINUTES);
  hours   = getRegisterData(REGISTER_HOURS);
  dow     = getRegisterData(REGISTER_DOW);
  dom     = getRegisterData(REGISTER_DOM);
  month   = getRegisterData(REGISTER_MONTH);
  year    = getRegisterData(REGISTER_YEAR);

void setup() {
  pinMode(CS, OUTPUT);
  pinMode(AS, OUTPUT);
  pinMode(WR, OUTPUT);
  pinMode(DS, OUTPUT);

void loop() {
  Serial.print(") ");
  Serial.print("  ");

  if (lastsecs != seconds) {
    lastsecs = seconds;
    for (int i=0; i<8; i++) {
      //Serial.print(getBit(seconds, i));


I here some code I found for controlling a DS12887 Microprocessor Bus based RTC… I can’t seem to grasp exactly what’s going on here… can someone tear it apart and provide a working example for the Arduino?


//  Name   : ds12887.c                                                     // 
//  Author : Timothy Reitmeyer                                           // 
//  Notice: 
//         :                                       // 
//  Date   : 05-05-2005                                                  // 
//  Version: 1.00                       // 
#define CLK_SECS     0
#define CLK_SECS_ALM 1
#define CLK_MINS     2 
#define CLK_MINS_ALM 3 
#define CLK_HRS      4 
#define CLK_HRS_ALM  5 
#define CLK_DOW      6 
#define CLK_DOM      7 
#define CLK_MON      8 
#define CLK_YR       9 
#define REGA         10
#define REGB         11
#define REGC         12
#define REGD         13
#define nvram_min    14
#define nvram_max    127 

const char dow_text[8][4] = 

const char month_text[13][4] = 

struct rtc_pin_def 
  BOOLEAN cs_bar;
  BOOLEAN rw_bar;
  BOOLEAN reset_bar;
  BOOLEAN irq_bar;
  BOOLEAN rclr_bar;
  BOOLEAN swq;
  int8 ad;

struct rtc_rega_struc 
} rega_var; 
struct rtc_regb_struc 
} regb_var;//0b00001100 
struct rtc_regc_struc 
  BOOLEAN   bit0; 
  BOOLEAN   bit1; 
  BOOLEAN   bit2; 
  BOOLEAN   bit3; 
  BOOLEAN   AF;//1=current time has matched the alarm time. 
} regc_var; 
struct rtc_regd_struc 
  BOOLEAN   bit0; 
  BOOLEAN   bit1; 
  BOOLEAN   bit2; 
  BOOLEAN   bit3; 
  BOOLEAN   bit4; 
  BOOLEAN   bit5; 
  BOOLEAN   bit6; 
} regd_var;

struct rtc_pin_def  rtc; //pins for the rtc 
struct rtc_pin_def  rtc_tris;//tris for the rtc 
#byte rtc  = 0xF82      //note: no semicolin 0x82=C0 on a 18F452 
#byte rtc_tris = 0xF94  //tris location for port C pin 0 
#define rtc_tris_r() rtc_tris=0;rtc_tris.ad=0xFF//read data is input 
#define rtc_tris_w() rtc_tris=0;rtc_tris.ad=0//write data is output 

void init_rtc(void); 
char read_rtc(char addr); 
void write_rtc(char addr,char data); 
void set_time(void); 

void write_rtc(char addr,char data) 
  rtc.cs_bar=0;//chip active 
  rtc.ad=addr; //addr is on bus 
  rtc.rw_bar=0;//write mode 
  rtc.ds=0;    //data strob idle 
  rtc.as=1;    //addr strob 
  delay_cycles(1); // pause 
  rtc.as=0;    //latch address 
  rtc.ds=1;    //data strob idle 
  rtc.ad=data; //data is on bus 
  delay_cycles(1); // pause 
  rtc.ds=0;    //latch data 
  rtc_tris_r(); //set the tris of C  and D to ALL INPUTS 

char read_rtc(char addr) 
  char data; 
  rtc_tris=0b11100000;//set the tris of C for setting address 
  rtc_tris.ad=0x00; //set the tris of D for setting address 
  rtc     =0b00011110;//set C for for setting address 
  rtc.ad=addr;      //put address on bus 
  #asm nop #endasm    //pause 
  rtc.as=0;           //latch 
  delay_cycles(1); // pause 
  rtc_tris.ad=0xFF; //set the tris of D for reading data 
  rtc.ds=0;           //release 
  #asm nop #endasm    //pause 
  data=rtc.ad;      //read the data from the bus 
  rtc_tris_r(); //set the tris of B  and D to ALL INPUTS 

void init_rtc() 
  rtc_tris.reset_bar=0;//set the tris of C 
  rtc.reset_bar=0;     //reset 
  delay_ms(200);// delay the required time for reset 
  rtc.reset_bar=1;  //release 
  rtc_tris_r(); //set the tris of C & D to ALL INPUTS 
  while(rega_var.UIP);//wait for update to finish 
  if(regd_var.VRT) fprintf(DEBUG,"Lithium battery OK\n\r"); 
  else fprintf(DEBUG,"Lithium battery dead\n\r"); 

  write_rtc(REGA,0b11110100); //set reg A 
  write_rtc(REGB,0b11111100); //(msb) SET,PIE,AIE,UIE,SQWE,DM,MIL,DSE (lsb) 

void set_time() 
  char yr, mn, dt, dy, hr, min, sec; 
  char input[10]; 
  if(regb_var.DM=1) /* Binary data */ 

    fprintf(DEBUG,"\nEnter the year (0-99): "); 
    fgets(input,DEBUG);//scanf("%bd", &yr); 
    fprintf(DEBUG,"\n\rread %u\n\r",yr); 

    fprintf(DEBUG,"Enter the month (1-12): "); 
    fgets(input,DEBUG);// scanf("%bd", &mn); 
    fprintf(DEBUG,"\n\rread %u\n\r",mn); 

    fprintf(DEBUG,"Enter the date (1-31): "); 
    fgets(input,DEBUG);// scanf("%bd", &dt); 
    fprintf(DEBUG,"\n\rread %u\n\r",dt); 

    fprintf(DEBUG,"Enter the day (1-7): "); 
    fgets(input,DEBUG);// scanf("%bd", &dy); 
    fprintf(DEBUG,"\n\rread %u\n\r",dy); 

    if(regb_var.MIL=1) /* if 24 hour mode */ 
      fprintf(DEBUG,"Enter the hour (1-23): "); 
      fgets(input,DEBUG);//scanf("%bd", &hr); 
      fprintf(DEBUG,"\n\rread %u\n\r",hr); 
      fprintf(DEBUG,"Enter the hour (1-11): "); 
      fgets(input,DEBUG);//scanf("%bd", &hr); 
      fprintf(DEBUG,"\n\rread %u\n\r",hr); 
      fprintf(DEBUG,"A)M or P)M (A/P) " ); 
      fgets(input,DEBUG);//scanf("%1bs", &min); 
      if(input == 'P' || input == 'p') 
      hr |= 0x80; /* add PM indicator */ 
    fprintf(DEBUG,"Enter the minute (0-59): "); 
    fgets(input,DEBUG);//scanf("%bd", &min); 
    fprintf(DEBUG,"\n\rread %u\n\r",min); 
    fprintf(DEBUG,"Enter the second (0-59): "); 
    fgets(input,DEBUG);//scanf("%bd", &sec); 
    fprintf(DEBUG,"\n\rread %u\n\r",sec); 

  regb_var.SET=1;//  REGB |= 0x80; /* inhibit update while writing to clock */ 
  write_rtc(REGB,(int8)regb_var); //set reg B 

  write_rtc(REGB,(int8)regb_var); //set reg B 

I here some code I found for controlling a DS12887 Microprocessor Bus based RTC...

The code is actually very well written, mostly in C, making it quite portable.

I can't seem to grasp exactly what's going on here.

In that event, it is a lot easier to just write your own from the datasheet.

The code is actually very well written, mostly in C, making it quite portable.

Maybe I wasn't clear. I am not looking for a language translation, I was looking for a Arduino port. The real issue is that I do not understand the underlying differences between PIC and Arduino programming (in regards to code methods).

In that event, it is a lot easier to just write your own from the datasheet.

I was fully intending to do so, the datasheet (like many) is TOO complete and technical. It requires a degree in Electronics to understand. I tried to decipher the signalling on my own but I haven't found any good references to give me clues how to read the signal diagrams, so it's a little like shooting in the dark.

I thought my point then was that the code is mostly C. So it requires very little changes to be ported to Arduino.

As to datasheet, that's pretty much a pre-requisite for embedded programmers.

Or at least the good ones.

I’m not sure, because this is not standard ‘C’/C++, but I suspect this part of the code is defining some memory mapped I/O ports:

struct rtc_pin_def  rtc; //pins for the rtc 
struct rtc_pin_def  rtc_tris;//tris for the rtc 
#byte rtc  = 0xF82      //note: no semicolin 0x82=C0 on a 18F452 
#byte rtc_tris = 0xF94  //tris location for port C pin 0 
#define rtc_tris_r() rtc_tris=0;rtc_tris.ad=0xFF//read data is input 
#define rtc_tris_w() rtc_tris=0;rtc_tris.ad=0//write data is output

I don’t imagine you will be using memory mapped access on the Arduino, so you need to understand what mechanism you’re going to use to access the device’s control registers.

The code also uses various I/O functions (fprintf, fgets, that sort of thing) which you would need to either get working by setting up the ‘C’ stdio environment in your sketch, or rework this code to send and receive this data via your sketch using whatever communication mechanism you want (serial port, push buttons + LCD, etc).

Also the code uses various primitive types such as int8 which are not defined as standard in the Arduino environment. You would need to either edit the code to use the equivalent Arduino/AVR Libc types, or create some typedefs or #defines for these types to associate them with the existing type definitions.

I will give you two approaches (they are equally simple) that hopefully will get you going.

1) Take the existing code: try to make your code as compatible with the existing code.

Two issues that may trip you, depending on your familiarity with C.

The code uses bit fields, and hard-coded address for IO registers (port register and direction register).

Both are fairly easy to deal with:

#define BOOLEAN unsigned char //remap boolean to bit fields


struct rtc_pin_def 
  BOOLEAN cs_bar:1; //map'd to bits
  BOOLEAN as:1; //map'd to bits
... //need to check for compiler endianness here

#define RTC_PORT PORTB //rtc port on pb
#define RTC_TRIS  DDRB //direction register
struct rtc_pin_def *rtc = (struct rtc_pin_def *) &RTC_PORT; //point rtc to rtc_port
struct rtc_pin_def *rtc_tris = (struct rtc_pin_def *) &RTC_TRIS; //point rtc_tris to the port direction register

    rtc->cs_bar=0;//chip active

With these changes, your code will look 90% like the PIC code.

The downside here is that the code isn't very Arduino - it is fundamentally a C-piece that is ported to the Arduino. It is fast but not quite as portable.

  1. Rewrite the whole port operation related pieces. You let the function calls / interfaces remain the same but rewrite the port operations in Aruidno. For example
#define rtc_cs_bar  1 //_cs on pin 1
#define rtc_rw_bar 2 //_rw pin on pin 2

After that, you can operate pins accordingly.

This approach preserves the function interface but performs each function with Arduino calls.

It is slower but more portable.

You've sort of cross-posted this thread - keep all the conversation in one thread so we don't end up answering the same questions twice please, or at least provide a pointer to the other thread: http://arduino.cc/forum/index.php/topic,138324.0.html

You’ve sort of cross-posted this thread - keep all the conversation in one thread so we don’t end up answering the same
questions twice please, or at least provide a pointer to the other thread: http://arduino.cc/forum/index.php/topic,138324.0.html

Thanks, I was intending to keep the topics separate. While they are related, they cover two very distinct components. One is explicitly code and the other is about the electronic signalling.

I did post code in this thread but it was in an attempt to convey a working concept of my understanding of the process.

In any event, I agree that there should be a relevant cross-link to each post so that there is a clear relation between threads.

In my opinion these threads are closely related, so I've merged them. One asks for an explanation of existing code, the other discusses how the chip works. Basically the same thing.

There seems to be some sort of Arduino code here:


I Googled to find that. The comments appear to be in Chinese (Google Translate might help there) but the code may he helpful.

Thanks to everyone who provided the valuable information! I have finally got it to work. I am cleaning up code and I will create a wire diagram which I will post to github… unless someone here thinks it would be better hosted somewhere else.

I would like to create a library or maybe add an extension to the existing RTC Library, although it seems that it’s specifically I2C based.

If anyone has suggestions I am all ears. :slight_smile:

DS12887 is so outdated that there is very little value, in my view, to make a library for it.

If you are to make a library, you can make its interface as consistent with the existing rtc libraries so that people can simply swap out the hardware / library and recompile their user code.

I know it's an old chip. but for PCB harvesters, it's something cheap and readily available. The great thing about the RTCs on PC motherboards is that they all share a common standard that "should" work. (not to mention an integrated oscillator and battery)

Now that you have it worked out, here is what I do, following the 2nd approach I outlined earlier.

//write dat to addr
void rtc_write(unsigned char addr, unsigned char dat) {
  digitalWrite(RTC_CS, LOW);   //select the chip
  digitalWrite(RTC_RW, HIGH); digitalWrite(RTC_DS, HIGH);  //ds/rw high
  digitalWrite(RTC_AS, HIGH);  //as high
  RTC_ADDR_PORT = addr;                               //output the address
  RTC_ADDR_DDR = 0xff;             //addr as output
  digitalWrite(RTC_AS, LOW);                   //latch the address
  RTC_DELAY();                     //Pwel 150ns
  digitalWrite(RTC_RW, LOW);                   //enter write mode
  RTC_ADDR_PORT = dat;                                //write the data
  RTC_DELAY();                     //Pwel 150ns
  digitalWrite(RTC_RW, HIGH);                   //write the data
  digitalWrite(RTC_AS, HIGH);      //latch the whole thing
  digitalWrite(RTC_CS, HIGH);      //unselect the chip
  RTC_ADDR_DDR = 0x00;              //adr pins default to input

The timing follows the datasheet.

The rtc_read() routine is very similar. You can then build your whole interface based on the read/write routines.

please provide me the code.. i am an engineering student trying to get this RTC to work