1 wire/RTC iButton

Hi,

I'm new to the world of Arduino and struggling to get my iButton RTC up and running. The iButton is a DS1904 - 1 wire interface with xtal on board.

I wrongly assumed it would be as simple as building a base from some existing 1wire examples, for the last few nights I've been pulling my hair out!

I can search the bus and get the 8 byte device address.

From looking at the datasheet I need to send a bus reset, send 0xCC (to skip ROM CRC check) then send 0x66 to read the clock then read back the clock data.

So I've built the following code:

byte ibutton[8] = {0x24, 0x35, 0x61, 0x33, 0x00, 0x00, 0x00, 0x01};

ds.reset(); // bus master sends reset
ds.select(ibutton); // select scanned device addr
ds.write(0xCC); // skip rom CCh

delay(1000);

ds.write(0x66); // read clock 66h

for (byte i=0;i<32;i++) { //get data

data = ds.read();
_ Serial.print (data*, HEX); // debug*_
};
And this is where I get a bit stuck!
The debug print just throws back FF FF FF FF etc...
It did dawn on me that I probably need to turn on the internal oscillator and set the clock to '0' - but I'm a bit sketchy on how to push that to the iButton.
Apologies for my newbie-ness but I'd rather work at this that take the easy option and buy a prebuilt board from sparkfun etc!
Any tips would be greatly appreciated.

Just a quick though:

Maybe the read() returns -1 as an error code and stores it in a byte?

Excuse me ignorance, but how would I debug this?

As I make it from the datasheet, I should get 5 bytes back - 4 bytes of RTC and 1 control byte - so 32 bits + 8 bits?

Thanks for the quick response by the way :slight_smile:

Well, I've added an additional ds.reset and ds.select before I issue the 0x66 - this seems to work better as I'm getting different data back now.

As the contents of the 5 bytes doesn't seem to change, I don't think that the oscillator has been started.

I've had a dig around and can't seem to find an example of how to set the OSC bit on... the datasheet says the following:

WRITE CLOCK [99h]
The Write Clock command is used to set the real-time clock counter and to write the device control byte.
After issuing the command, the bus master writes first the device control byte, which becomes
immediately effective. After this the bus master sends the least significant byte through the most
significant byte to be written to the real time clock counter. The new time data is copied from the
read/write buffer to the real time clock counter and becomes effective as the bus master generates a Reset
Pulse. If the oscillator is intentionally stopped the real time clock counter behaves as a 4-byte nonvolatile
memory.

So, I know I need to issue 0x99... but what next?

Quick bump and a note to mention I've had no issues reading from the DS18B20 temperature sensors on my 1 wire setup.

Cheers

Still struggling with this, so tidied up my code at lunch and tested when I got home:

#include <OneWire.h>

OneWire  ds(8);  // on pin 8

byte ibutton[8] = {0x24, 0x35, 0x61, 0x33, 0x00, 0x00, 0x00, 0x01};

void setup() // run once, when the sketch starts
{

  Serial.begin(9600);

}

void loop() // run over and over again
{

 int present;   

  present = 0;

 byte ibutton[8] = {0x24, 0x35, 0x61, 0x33, 0x00, 0x00, 0x00, 0x01};
 
 uint8_t ctrl;          // control byte
 uint8_t ctrlout;        // control byte - used for write
 uint32_t rtc;      // 32bit for clock data

present = ds.reset();            // bus master sends reset - returns 1 if present, 0 if not

Serial.print("Presense indicator: ");
Serial.print(present, DEC);
Serial.println();

ds.select(ibutton);      // select scanned device addr
ds.skip();                  // skip ROM

delay(1000);


// ********* TESTING ********
// ds.reset();
// ds.select(ibutton);
// ********** END ***********

ds.write(0x66);            // read clock 66h


ctrl = ds.read(); // read control byte
                  // control byte will be needed for other operations - for example, to set the RTC!

//
// print the control byte as HEX

Serial.print("Control byte is: ");
Serial.print(ctrl, HEX);
Serial.println();

rtc = ((uint32_t)ds.read()); //LSB
rtc |= ((uint32_t)ds.read()) << 8;
rtc |= ((uint32_t)ds.read()) << 16;
rtc |= ((uint32_t)ds.read()) << 24;  //MSB



Serial.print("RTC data is: ");
Serial.print(rtc, HEX);
Serial.println();

ds.reset(); // stop receiving clock data


Serial.println();
  

}

When running as above, I get the following debug output to serial:

Presense indicator: 1
Control byte is: FF
RTC data is: FFFFFFFF

When I uncomment the lines after the ****** TESTING **** comment I get the following:

Presense indicator: 1
Control byte is: 50
RTC data is: 55555555

The value for the control byte isn't as expected (doesn't match up to possible bit settings in the datasheet)

I'm missing something here, just not sure what. My 1 wire bus seems to be setup (as if I take the iButton RTC out and wire for a DS18B20 sensor with some code) it works. The scan routine I've used from the example code picks up the device id too.

Any assistance greatly appreciated! Thanks.

Any suggestions? Looking like I'll need to use the DateTime software library for the time being :frowning:

Oddly enough, I found your post when searching for tips on how to get started with my DS1904. :slight_smile: After a really boneheaded reading of the datasheet, I figured out what I was doing wrong and everything clicked into place. Derived from the sample at the Playground.

From your code and output, everything is working just fine. The output you get with the ** TESTING ** area commented out is all kosher as well.

Excuse the demo code- wanted to make it clear for future readers (and future-me) :slight_smile: :

#include <OneWire.h>

// DS1904 Real Time Clock iButton I/O
OneWire ds(10);  // on pin 10

void setup(void) {
  Serial.begin(9600);
}

void loop(void) {
  byte i;
  byte present = 0;
  byte data[8];
  byte addr[8];

  if ( !ds.search(addr)) {
      Serial.print("No more addresses found.\n");
      ds.reset_search();
      delay(500);  // for readability
      return;
  }

  Serial.print("ROM: ");
  for( i = 0; i < 8; i++) {
    Serial.print(addr[i], HEX);
    Serial.print(" ");
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {
      Serial.print("CRC is not valid!\n");
      return;
  }

  if ( addr[0] != 0x24) {
      Serial.print("\t\tDevice is not a DS1904 family device.\n");
      return;
  }

/*
  // write!
  Serial.println("writing to RTC...");
  present = ds.reset();
  ds.select(addr);
  ds.write(0x99,1);   // write RTC
  ds.write(0xA0);
  ds.write(0x02);
  ds.write(0x03);
  ds.write(0x05);
  ds.write(0x08);
  present = ds.reset();
  delay(1500);     // unknown if wait needed
*/

  // read!
  present = ds.reset();
  ds.select(addr);
  ds.write(0x66,1);   // read RTC

  Serial.print("PR: ");
  Serial.print(present, HEX);
  for ( i = 0; i < 5; i++) {
    data[i] = ds.read();
  }
  Serial.print(" CTRL BYTE:  ");
  Serial.print(data[0], BIN);
  Serial.print("\n\ttime:  ");
  for ( i = 1; i < 5; i++) {
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }


  Serial.println();
}

Uncomment the // write section if you want to set an initial time, or to start the RTC. Out of the box, my RTC had a control byte of 0x50 and all four time bytes were 0x55. To get use out of it, you need to turn on the oscillator- if you don't, it's essentially a 4-byte battery backed RAM store. :slight_smile:

Control Byte (see DS1904 Datasheet)
          OSC1 OSC2
          Us \ /
byte     /--\ ||xx      
0x50     0101 0000 = user bits are 0101; OSC1 and OSC2 are 0 => clock not running
0xA0     1010 0000 = user bits are 1010; OSC1 and OSC2 are 0 => clock not running
0xAC     1010 1100 = user bits are 1010; OSC1 and OSC2 are 1 => clock running

Example Output:
ROM: 24 C7 7B 33 0 0 0 DC PR: 1 CTRL BYTE:  10101100
      time:  A0 4 5 8

Hopefully that'll get you started! :slight_smile:

Regards,
Aaron

To check your bit shiftage, I stuck this at the bottom of my sketch:

  Serial.print("\ntime:  ");
  rtc = ((uint32_t)data[1]); //LSB
  rtc |= ((uint32_t)data[2]) << 8;
  rtc |= ((uint32_t)data[3]) << 16;
  rtc |= ((uint32_t)data[4]) << 24;  //MSB
  Serial.print(rtc, DEC);
  Serial.println();

And it works great! Thanks for that bit- I'm used to thinking in Smalltalk or Ruby, not bits and bytes- the math I would've written to converted those four bytes into seconds would've been total mess. :stuck_out_tongue:

Great stuff guys this got me on the right track for getting this i button stuff working. One thing that I am hesitant to point out as it got me to really sit down and spend some time reading the data sheet. But the code posted below will only read the clock (after you uncomment it and write the "time" to it) and write to it. To turn the clock on you need to change the code in the "write" section so that it reads like this:

/*
  // write!
  Serial.println("writing to RTC...");
  present = ds.reset();
  ds.select(addr);
  ds.write(0x99,1);   // write RTC - this is the write code
  ds.write(0xAC);  //This is the control byte.  AC in hex = 10101100
                   //read the datasheet and you will see that this is important
                   //to start the internal osc's... Or to make the clock start
                   //counting seconds.  --ril3y
  ds.write(0x02);  //0x02 is a random time set it with your own
  ds.write(0x03);  //same with this
  ds.write(0x05);  //this
  ds.write(0x08);  //and this
  present = ds.reset();
  delay(1500);     // unknown if wait needed
*/

So to repost my entire sketch that will start the clock and then read too..

#include <OneWire.h>

// DS1904 Real Time Clock iButton I/O
OneWire ds(10);  // on pin 10

void setup(void) {
  Serial.begin(9600);
}

void loop(void) {
  byte i;
  byte present = 0;
  byte data[8];
  byte addr[8];

  if ( !ds.search(addr)) {
      Serial.print("No more addresses found.\n");
      ds.reset_search();
      delay(500);  // for readability
      return;
  }

  Serial.print("ROM: ");
  for( i = 0; i < 8; i++) {
    Serial.print(addr[i], HEX);
    Serial.print(" ");
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {
      Serial.print("CRC is not valid!\n");
      return;
  }

  if ( addr[0] != 0x24) {
      Serial.print("\t\tDevice is not a DS1904 family device.\n");
      return;
  }

/*
  // write!
  Serial.println("writing to RTC...");
  present = ds.reset();
  ds.select(addr);
  ds.write(0x99,1);   // write RTC - this is the write code
  ds.write(0xAC);  //This is the control byte.  AC in hex = 10101100
                   //read the datasheet and you will see that this is important
                   //to start the internal osc's... Or to make the clock start
                   //counting seconds.  --ril3y
  ds.write(0x02);  //0x02 is a random time set it with your own
  ds.write(0x03);  //same with this
  ds.write(0x05);  //this
  ds.write(0x08);  //and this
  present = ds.reset();
  delay(1500);     // unknown if wait needed
*/

  // read!
  present = ds.reset();
  ds.select(addr);
  ds.write(0x66,1);   // read RTC

  Serial.print("PR: ");
  Serial.print(present, HEX);
  for ( i = 0; i < 5; i++) {
    data[i] = ds.read();
  }
  Serial.print(" CTRL BYTE:  ");
  Serial.print(data[0], BIN);
  Serial.print("\n\ttime:  ");
  for ( i = 1; i < 5; i++) {
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }


  Serial.println();
  
}

I will say you get a lot of of reading the datasheet. Read it cover to cover and you will really get a better grasp of what is going on.

Ril3y
blog.synthetos.com

I've posted an overview... not couched in Arduino terms... of working with 1-Wire chips at...

I would heartily second what ril3y said...

You get a lot of of reading the datasheet

... but read the datasheets for several 1-Wire chips... e.g. the DS1820 and, say, the DS2423 (a counter chip... supplies something that I think the Arduino could use!). Gradually you will discover that large parts of each datasheet are repeated in every 1-Wire datasheet, and you will learn what bits you can skim, and what bits are important.