Help with Real Time Clock, I2C, BCD, and Wire.receive() for an I2C newbie

So I'm sure I've run into a common problem or am approaching things completely wrong. I'm trying to set up a bubble logger and need to communicate to a NXP PCF8563 real time clock (http://www.nxp.com/documents/data_sheet/PCF8563.pdf). The included example in the data sheet for reading time: 1. Send a START condition and the slave address for write (A2h). 2. Set the address pointer to 2 (VL_seconds) by sending 02h. 3. Send a RESTART condition or STOP followed by START. 4. Send the slave address for read (A3h). 5. Read VL_seconds. 6. Read Minutes. 7. Read Hours. 8. Read Days. 9. Read Weekdays. 10. Read Century_months. 11. Read Years. 12. Send a STOP condition.

So at first I was just seeing if I could get a time off the clock before I went much further with my code... I could but after a few seconds of requesting the time the minutes, hours, days, months, or even year would change. I was using a bcd to dec function in my code so I decided to strip that out and see what the clock was sending to the arduino in binary. Here's the code that matters: Wire.beginTransmission(RTCI2C); Wire.send(0x02); Wire.endTransmission();

Wire.requestFrom(RTCI2C, 7);

byte tmp; //tmp = bcdToDec(Wire.receive()); tmp = Wire.receive(); Serial.print(tmp, BIN); // Seconds Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Minutes Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Hours Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Days Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Weekdays Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Century_months Serial.print(" "); tmp = Wire.receive(); Serial.print(tmp, BIN); // Years Serial.println(" ");

Here are the results of several queries mostly in a row: 1010110 110101 1010011 1010100 1010011 1000100 10000 1010111 110101 1010011 1010100 1010011 1000100 10000 1011000 110101 1010011 1010100 1011011 1000100 10000 1011001 110101 1010011 1010100 1011011 1000100 10000 0 110110 10011 10100 11 100 10000 1 110110 10011 10100 11 100 10000 10 110110 10011 10100 11 100 10000 11 110110 10011 10100 11 100 10000 100 110110 10011 10100 11 100 10000 101 110110 10011 10100 11 100 10000 110 110110 10011 10100 11 100 10000 110 110110 10011 10100 11 100 10000 111 110110 10011 10100 11 100 10000 1000 110110 10011 10100 1011 100 10000 1001 110110 10011 10100 1011 100 10000 10000 110110 10011 10100 10011 100 10000 10001 110110 10011 10100 10011 100 10000 10010 110110 10011 10100 10011 100 10000 10010 110110 10011 10100 10011 100 10000 10011 110110 10011 10100 10011 100 10000 10100 110110 10011 10100 10011 100 10000

It looks like the seconds are working right and I can see where the minute increments but its like it's not grouping the bits correctly - like everything is sliding over when seconds goes to 0 and then sliding right as seconds adds bits. Do I need to use something other than Wire.receive() in order to get the bits to line up correctly?

Thanks

The only problem is that the leading zero bits are not printed,

it is BCD encoded (see datasheet) 0 : 110110 : 10011 10100 - 11 - 100 10000 seconds = 0 minutes = 36 hours = 14 day = 14 wd = 3 (depends on what is day one) month = 4 year = 10 (needs some offset too I guess)

14 april 14:36:00 (or 7:54 PM) does that makes sense?

try this sketch; it fixes the printing issue by using sprintf()

Wire.beginTransmission(RTCI2C);
  Wire.send(0x02);
  Wire.endTransmission();

  Wire.requestFrom(RTCI2C, 7);

  byte h, m, s, day, mo, yr, wd;
  char buffer[24];

  // FETCH THE DATA
  s = bcd2int(Wire.receive());
  m = bcd2int(Wire.receive());
  h = bcd2int(Wire.receive());
  day = bcd2int(Wire.receive());
  wd = bcd2int(Wire.receive());
  mo = bcd2int(Wire.receive());
  yr = bcd2int(Wire.receive());

  // FORMAT THE DATA
  sprintf(buffer, "%02d:%02d:%02d %04d-%02d-%02d %d ", h, m , s, yr, mo, day, wd & 0x07 );  // !! masking the weekday bits 

  // OUTPUT IT
  Serial.print(buffer);
  Serial.println(" ");

The fact that the weekdays field jumps from 00000011 to 00001011 is just one bit set, a look at the datasheet shows that this bit means nothing. IN the code above I masked the WD var with 0x07 to just have the last 3 bits.

Same must be done with other fields (see par 8.2 datasheet)

my 2 cents, Rob

Thanks for that but there must be something more to it -- tried the sketch and here are the results: 20:25:41 0016-36-20 3 20:25:48 0016-36-20 3 20:25:50 0016-36-20 3 20:25:50 0016-36-20 3 20:25:51 0016-36-20 3 20:25:52 0016-36-20 3 20:25:53 0016-36-20 3 20:25:54 0016-36-20 3 20:25:55 0016-36-20 3 20:25:56 0016-36-20 3 20:25:57 0016-36-20 3 84:25:64 0016-68-84 3 84:25:64 0016-68-84 3 84:25:65 0016-68-84 3 84:25:67 0016-68-84 3 84:25:69 0016-68-84 3 84:25:70 0016-68-84 3 84:25:71 0016-68-84 3 84:25:71 0016-68-84 3 84:25:72 0016-68-84 3 84:25:73 0016-68-84 3 84:25:80 0016-68-84 3 84:25:81 0016-68-84 3 84:25:82 0016-68-84 3 84:25:83 0016-68-84 3 84:25:83 0016-68-84 3 84:25:84 0016-68-84 3 84:25:85 0016-68-84 3 84:25:86 0016-68-84 3 84:25:86 0016-68-84 3 84:25:87 0016-68-84 3 84:25:88 0016-68-84 3 84:25:89 0016-68-84 3 84:25:89 0016-68-84 3 20:32:00 0016-04-20 3 20:32:01 0016-04-20 3 20:32:02 0016-04-20 3 20:32:04 0016-04-20 3 20:32:05 0016-04-20 3 20:32:06 0016-04-20 3 20:32:07 0016-04-20 3

have you masked the unused bits as stated in par 8.2 datasheet? (i showed the weekdays) And maybe check the quality of your wires??? :)

Bit masks worked great. Do I need to apply masks before I send the time to the clock as well?

No, if you send the time it is 'masked' allready as those positions XXX are 0 allready.

Please post the working code for later reference

I still don't understand how the century flag works so it's basically ignored - I'm also reading day of week but not outputting it. I also haven't quite figured out how to set the time yet... but slogging through the data sheet. Here's the working code for retrieving the date and time:

// Time will be represented [YY]-[MM]-[DD]T[hh]:[mm]:[ss]
byte second, vlSecond, vl, minute, hour, dayOfWeek, dayOfMonth, month, year;

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val) { return ( (val/16*10) + (val%16) ); }

// Gets the date and time from the PCF8563
void getDate()
{
  // Reset the register pointer
  Wire.beginTransmission(RTCI2C);
  Wire.send(0x02);
  Wire.endTransmission();
  Wire.requestFrom(RTCI2C, 7);
                                                    // Bits
                                                 //  7    6    5    4    3    2    1    0
  vlSecond     = Wire.receive();                 //  VL   --------------Seconds----------
  minute     = bcdToDec(Wire.receive() & 0x7F);  //  x    --------------Minutes----------
  hour       = bcdToDec(Wire.receive() & 0x3F);  //  x    x    ---------Hours------------
  dayOfMonth = bcdToDec(Wire.receive() & 0x3F);  //  x    x    ---------Days-------------
  dayOfWeek  = bcdToDec(Wire.receive() & 0x07);  //  x    x    x    x    x    --Weekdays-
  month      = bcdToDec(Wire.receive() & 0x1F);  //  C    x    x    ----Month------------
  year       = bcdToDec(Wire.receive());         //  -------------------Years------------
  
  vl         = bcdToDec(vlSecond & 0x80);        // Strip out seconds to provide integrity check
  second     = bcdToDec(vlSecond & 0x7F);        // Strip out integrity check to provide seconds

  char buffer[24];

  // FORMAT THE DATA
  sprintf(buffer, "%02d-%02d-%02dT%02d:%02d:%02d  %02d", year, month, dayOfMonth, hour, minute, second, vl);

  // OUTPUT IT
  Serial.print(buffer);
  Serial.println(" ");

}

Got set date/time working – crudely but here’s the code in its entirety:

// Real Time Clock control
// RTC:
//  NXP PCF8563: http://www.nxp.com/documents/data_sheet/PCF8563.pdf
//  I2C-bus slave address: read A3h (1010.0011) and write A2h (1010.0010)
//  0E = Timer_control

#include <Wire.h>
#define RTCI2C  (B1010001)

// Time will be represented [YY]-[MM]-[DD]T[hh]:[mm]:[ss]
byte second, vlSecond, vl, minute, hour, dayOfWeek, dayOfMonth, month, year;

int command = 0;

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

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val) { return ( (val/10*16) + (val%10) ); }
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val) { return ( (val/16*10) + (val%16) ); }

// Gets the date and time from the PCF8563
void getDate()
{
  // Reset the register pointer
  Wire.beginTransmission(RTCI2C);
  Wire.send(0x02);
  Wire.endTransmission();
  Wire.requestFrom(RTCI2C, 7);
                                                  // Bits
                                                 //  7    6    5    4    3    2    1    0
  vlSecond     = Wire.receive();                 //  VL   --------------Seconds----------
  minute     = bcdToDec(Wire.receive() & 0x7F);  //  x    --------------Minutes----------
  hour       = bcdToDec(Wire.receive() & 0x3F);  //  x    x    ---------Hours------------
  dayOfMonth = bcdToDec(Wire.receive() & 0x3F);  //  x    x    ---------Days-------------
  dayOfWeek  = bcdToDec(Wire.receive() & 0x07);  //  x    x    x    x    x    --Weekdays-
  month      = bcdToDec(Wire.receive() & 0x1F);  //  C    x    x    ----Month------------
  year       = bcdToDec(Wire.receive());         //  -------------------Years------------
  
  vl         = bcdToDec(vlSecond & 0x80);        // Strip out seconds to provide integrity check
  second     = bcdToDec(vlSecond & 0x7F);        // Strip out integrity check to provide seconds

  char buffer[24];

  // FORMAT THE DATA
  sprintf(buffer, "%02d-%02d-%02dT%02d:%02d:%02d  %02d", year, month, dayOfMonth, hour, minute, second, vl);

  // OUTPUT IT
  Serial.print(buffer);
  Serial.println(" ");

}
void setDate()                
{

   year= (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   month = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   dayOfMonth = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   hour  = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   minute = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   second = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.  

   Wire.beginTransmission(RTCI2C);
   Wire.send(0x02);
   Wire.send(decToBcd(second));   
   Wire.send(decToBcd(minute));
   Wire.send(decToBcd(hour));     
   Wire.send(decToBcd(dayOfMonth));
   Wire.send(decToBcd(5));          // Weekdays
   Wire.send(decToBcd(month));
   Wire.send(decToBcd(year));
   Wire.endTransmission();
}

void loop() {
     if (Serial.available()) {      // Look for char in serial que and process if found
      command = Serial.read();
      switch(command) {
        case 'T':    // Set Time/Date - Looking for T[YY][MM][DD][hh][mm][ss]
          setDate();
          getDate();
          break;
        case 't':    // Get Time/Date
          getDate();
          break;
      }

     }
     command = 0;
     delay(100);
}

Nice, next step is to make a library/class out of it ;)

Done :slight_smile:

edit changed it so all the time/date bytes are public and prevented the getDate from printing - that way whatever program it’s used with can manipulate the data.

RTC_PCF8563.zip (1.58 KB)

OK, some remarks on the lib.

  • I would make the fields seconds, hours, days etc public so people can use the fields they want to, in the way they want to.
  • The function getDate would fill the fields.

  • I would remove the Serial.print() from getDate as not everyone will want to print the time & date through hardware Serial; some people want to use Software Serial, others want to timestamp a logentry in a file on SDcard.

  • The Serial.print(...) code makes a good example sketch.

  • setDate() should be parametrized, so setDate(int y, byte mo, byte day, byte h, byte m, byte s);

  • Here again the SerialRead() is not the way other people might want to use it. (e.g. setRTC from an NTP server)

This would make the library usable for a broader public.

Done

Thanks for all your help!

RTC_PCF8563.zip (2.48 KB)

Looks much better!

Still "nagging" you for improvements

  • setDate() is overloaded is the extended version needed (to set the dayofweek)? doesn't the RTC calculate it if other fields are set? If jan 1st is a monday and you set in on tuesday is there some conflict?

  • The year is an offset, so it should be documented in the code

  • A link to the datasheet allways makes sense

setDate() - I suppose I could remove the overload but it just seems easier to have two methods - one for standard time - YY-MM-DD hh:mm:ss and one for all the date variables. I don't see anything except alarms that's calculated off dayofweek and they are not implemented in this library -- if I have the time perhaps I will work on that.

Where and how should I document the year offset?

There has always been a link to the datasheet/pdf at the top of the RTC_PCF8563.cpp -- do I need to put it in .h and the example?