i2c and a Dallas RTC Anyone??

Has anyone used the new i2c functions in the arduino 006 or 007 to read a dallas DS1307 real time clock? How would I go about implementing something like that in code and spitting out the time over the serial port? What Pins should things be hooked to and how would I use the Wire lib to call the time in from the clock chip?

Thanks!

Any direction would be of great help...

-Jeremy

Hi there,

I've done this using the breakout module from Spark Fun. Basically, assuming the atmega8, you connect analog 4 to SDA and A5 to SCL, and attach a pull-up resistor (I think I used 2.2k) from both of these to the 5V line. I used the data sheet for the chip to figure out how to program it and read it over I2C. Here's my code for a circuit that reads the time and displays the hours/minutes/seconds on a single 7 segment display. I use straight C++ outside the IDE, so you can probably kill the #includes.

/* 
 * A single-digit clock
 * (c) 2007 Bob Copeland <me at bobcopeland.com>
 */
#include <WProgram.h>
#include <Wire.h>
#include <HardwareSerial.h>

char bcd_to_7seg[] = { 
    0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x00
};

// pins a-g on 7 seg display
int pins[] = { 11, 10, 7, 3, 4, 8, 12 };

void setup()
{
    int i;

    Wire.begin();
    Serial.begin(9600);

    for (i=0; i<7; i++)
        pinMode(pins[i], OUTPUT);

/*
    // program the time & enable clock
    Wire.beginTransmission(0x68);
    Wire.send(0);
    Wire.send(0x00);
    Wire.send(0x52);
    Wire.send(0x80 | 0x21);
    Wire.endTransmission();
*/
}

void write_digit(char digit)
{
    int i;
    char byte = bcd_to_7seg[digit];
    for (i=0; i<7; i++)
    {
        digitalWrite(pins[i], byte & 1);
        byte >>= 1;
    }
}

void write_number(char num)
{
    write_digit(10);
    delay(50);
    write_digit((num >> 4) & 0x0f);
    delay(200);

    write_digit(10);
    delay(50);
    write_digit(num & 0x0f);
    delay(200);
}

void loop()
{
    // reset register pointer
    Wire.beginTransmission(0x68);
    Wire.send(0);
    Wire.endTransmission();
    delay(100);

    Wire.requestFrom(0x68, 3);
    char secs = Wire.receive();
    char mins = Wire.receive();
    char hrs = Wire.receive();

    write_number(hrs & 0x3f);
    delay(100);
    write_number(mins);
    delay(100);
    write_number(secs & 0x7f);
    delay(1000);
}

BTW I finally got around to taking a picture of the device using the above sketch. It's a one-digit clock using the DS1307. Most of the arduino's outputs are dedicated to the inputs a-g on the 7 segment display. I built the thing on Sparkfun's spiffy little ProtoShield. (Yeah, the picture is too dark...)

nice!

I'm working on a 2 digit clock, myself.. Unfortunately I may have just somehow wrecked my ds1307 - also on a carrier board from sparkfun..

lately when I make a request for data from it, it gives me junk data. I'm not sure how I would have wrecked it, but it certainly appears as such.

I also just ordered the atmega168 so I can use i2c and an LCD in a sketch... currently you can just BARELY do it before you're out of space. Making a request and putting it up on the LCD raw leaves me w/ like 4 bytes.

I also think my code for culling the digits is a little fat. I'll try to post it up once I get home...

-JF

ah.. not wrecked. Just got reset somehow ... I reset the time and it's running like a champ again. Now just waiting for bigger chips and some digits.. :slight_smile:

Yeah one definite improvement would be to read the current time from the host computer then program the chip when first setting up the sketch. I'm too lazy to do that though, since this is only a test for a larger thing I'm doing :slight_smile: So I had just hardcoded it to program 9:48 pm or whatever time it was when I first loaded it up, then relied on the battery backup from then on.

yeah, I thought the same thing - maybe use Processing to send the current time.. but then I wondered how often I would actually be doing that... then hard coded it into the app and tried to time up upload/execution of it..

So yeah.. I wish that the Wire lib didn't take as much space.. What are you using the straight C++ for?

I'm working on a sunrise clock for myself since I have a tendancey to stay up late trying to learn this stuff and have a difficult time getting up in the morning.. It's oddly circular, this project...

-JF

So yeah.. I wish that the Wire lib didn't take as much space.. What are you using the straight C++ for?

No particular reason, just as a C hacker used to the command line, I like to get the GUI out of the way. I haven't really had a reason to optimize for space yet but may look into it if I get close to the limit.

I've thought about getting this to drive a Nixie tube clock that I plan on building and using the Arduino to control the tubes.

I liked the idea of the RTC, that way when I power the clock I will automatically have the time. Just wondering if it's worth the $15 or if I should just get some push buttons to increment the hour/minutes and set it every time.

I do think though using the RTC breakout board from Sparkfun would save me a lot of code when I can just pull the values from the RTC, convert to binary and send off to the 74141 IC that is driving the Nixie.

I like the RTC approach simply because it's less hassle. If you don't want to drop $15, you can get the DS1307 in a DIP package for $4 from Digikey (of course, then you still need the battery, the crystal, etc). You don't even have to convert the values from the RTC -- they are BCD just like the 74141 expects, but you do have to mask off the AM/PM bits, clock enable, etc.

My project is going to use four large LED-based 7 segment displays, but the clock functionality is kind of a side thing.

I like the RTC approach simply because it's less hassle. If you don't want to drop $15, you can get the DS1307 in a DIP package for $4 from Digikey (of course, then you still need the battery, the crystal, etc). You don't even have to convert the values from the RTC -- they are BCD just like the 74141 expects, but you do have to mask off the AM/PM bits, clock enable, etc.

My project is going to use four large LED-based 7 segment displays, but the clock functionality is kind of a side thing.

I like the RTC approach as well since it is only $15 and it would then mean I have a very accurate clock that I can move and not worry about resetting the time. Plus it will save me room for other logic (I might interface temperature and any other sensors I can find to generate information to display).

Plus this will most likely sit on my desk at work and I might put it away when I leave the office. So plugging it in each morning and have it automatically set the time will be great.

Thanks for the code as well up top! That will definitely come in handy.

Hi there, I have modified slightly the code to give me the full set of data the module outputs, and removed the code specific to the display.

the problem I'm getting is that the data returned doesn't seem to make much sense... :slight_smile:

/* 
 * A single-digit clock
 * (c) 2007 Bob Copeland <me at bobcopeland.com>
 */

#include <Wire.h>


void setup()
{

  Wire.begin();
  Serial.begin(9600);

  /*
    // program the time & enable clock
   Wire.beginTransmission(0x68);
   Wire.send(0);
   Wire.send(0x00);
   Wire.send(0x52);
   Wire.send(0x80 | 0x21);
   Wire.endTransmission();
   */
}

void loop()
{
  // reset register pointer
  Wire.beginTransmission(0x68);
  Wire.send(0);
  Wire.endTransmission();
  delay(100);

  Wire.requestFrom(0x68, 6);
  char secs = Wire.receive();
  char mins = Wire.receive();
  char hrs = Wire.receive();
  char day = Wire.receive();
  char date = Wire.receive();
  char month = Wire.receive();
  char year = Wire.receive();

  Serial.print("time is: ");
  Serial.print( hrs, DEC );
  Serial.print(":");
  Serial.print( mins, DEC );
  Serial.print(":");
  Serial.print( secs, DEC );
  Serial.print(" day, ");
  Serial.print( day, DEC );
  Serial.print(" date, ");
  Serial.print( date, DEC );
  Serial.print(" month, ");
  Serial.print( month, DEC );
  Serial.print(" year, ");
  Serial.print( year, DEC );
  Serial.println();

  delay(1000);

}

this what the module is outputting

time is: 34:49:66 day, 2 date, 5 month, 5 year, 0
time is: 34:49:67 day, 2 date, 5 month, 5 year, 0
time is: 34:49:68 day, 2 date, 5 month, 5 year, 0
time is: 34:49:69 day, 2 date, 5 month, 5 year, 0
time is: 34:49:70 day, 2 date, 5 month, 5 year, 0

I guess it has to do with the clock config, how do you go about configuring the module for your own time? I am a I2C newbie :-? and I'm not making much sense of the address map below:

Thanks.

Hi there, I have modified slightly the code to give me the full set of data the module outputs, and removed the code specific to the display.

the problem I'm getting is that the data returned doesn't seem to make much sense... :slight_smile:

time is: 34:49:66 day, 2 date, 5 month, 5 year, 0
time is: 34:49:67 day, 2 date, 5 month, 5 year, 0
time is: 34:49:68 day, 2 date, 5 month, 5 year, 0
time is: 34:49:69 day, 2 date, 5 month, 5 year, 0
time is: 34:49:70 day, 2 date, 5 month, 5 year, 0

Thanks.

Well first things first, you need to mask off a few bits on the second and the hrs. Note in the original code there was "hrs & 0x3f" when it was writing it, and "secs & 0x7f".. yes that should've been up there on the same line with Wire.receive() but oh well. This is because the 'clock halt' sits in the top bit of secs and there are all kinds of state variables in the top 3 bits of hrs.

Looking at the diagram, note that date is split into two nibbles - the most significant holds '10 date'. The date is stored in BCD so for the 31st day of the month you'd see 0x31 instead of e.g. 0x1f if it were straight binary.

To program the clock (which you may not need to do after fixing the above) just uncomment the code at the top, and pick reasonable values for the time/date. It was originally set for 9:52pm. Make sure the CH bit is zero or the clock won't run. I also think there's a bug up there, 0x80 should've been 0x40 when setting the hours to 24 hour time.

HTH,
Bob

Bob thanks for your reply, the time display issue seems to be solved now.

What I am still struggling with is the module configuration. To set it to 17:31:00 Monday, 2-7-2007 I am doing the following on setup()

Wire.beginTransmission(104); // transmit to device #104, the ds1307
  Wire.send(0);
  Wire.send(0x00);
  
  Wire.send(0);  //sec
  Wire.send(31); //min
  Wire.send(17);  //hour
  Wire.send(2);  //day
  Wire.send(2);  //date
  Wire.send(7);  //month
  Wire.send(7);  //year
  Wire.endTransmission();    // stop transmitting

  delay(100); /* */

when printing back the values, I get:

21:05:32 Sunday, 2/2/2007

something its obviously not working.. :stuck_out_tongue:

Wire.beginTransmission(104); // transmit to device #104, the ds1307
Wire.send(0);
Wire.send(0x00);

You have an extra 0 in there I think. You just need to send one to set the register pointer.

yep, that's done the trick. One final thing if I'm setting the hour to be 6, how do I know if its am or pm? or how can I set it to be 18:00 instead of 6?

Many thanks,

yep, that's done the trick. One final thing if I'm setting the hour to be 6, how do I know if its am or pm? or how can I set it to be 18:00 instead of 6?

That's what the high bits of the hour are for. From the datasheet, you have in the top nibble: bit 2 selects 12 or 24 hour time, bit 1 is either the am/pm indicator or the high bit of the 24 hour time.

So use (0x40 | your_time) for 24-hour, or ( ((is_am) ? 0 : 0x02) | your_time) for am/pm mode.

I also thought about getting this to drive a clock and be very hands off, and for me that means adjusting for DST so once set I shouldn't have to touch it. I've written code that barely fits into an ATMEGA8 that adjusts automatically for DST in the US...granted that you can't set the time w/the code, the 1307 has to already have your standard time in it, but it's pretty neat I think nonetheless. Of course you could just use a in input and flip a switch to tell the arduino to adjust or not, but this is all in software and automagic.

So use (0x40 | your_time) for 24-hour, or ( ((is_am) ? 0 : 0x02) | your_time) for am/pm mode.

This is some really bad advice btw. I need to go back to bit shifting school.

24 hour mode: just use your_hour with no ORs (bit 6 - 0=24 hr, 1=12 hr)
12 hour mode: use -
0x40 | ((is_am) ? 0 : 0x20) | your_hour

your_hour should be binary-coded decimal (so 0x13 == 1pm in 24 hour mode).

I also thought about getting this to drive a clock and be very hands off, and for me that means adjusting for DST so once set I shouldn't have to touch it.

Nice!