Go Down

Topic: DS3231 below freezing and dreaded 2's complement (Read 5887 times) previous topic - next topic

JTMSR

I am using a DS3231 and would like to use the integral temperature monitor.  Unfortunately, the code I have been able to scrape together will not negotiate temperatures below freezing, returning a value of "127" at freezing.  Since this will be used as an outdoor temperature monitor, I really would need this capability.  I have read that this is easily resolved by using a 2's complement solution and adding a 1 to the result when the most significant data bit is 1111.  Newbie that I am, I cannot for the life of me get any of the suggested methods to work.  I've posted the code without the 2's complement (or rather insult) below.  Can anyone suggest a '2s complement solution for this?

#include <Wire.h>

#define DS3231_I2C_ADDRESS 104

byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];

byte tMSB, tLSB;
float temp3231;

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

void loop()
{
 
 watchConsole();
 
 
 get3231Date();
 
 Serial.print(weekDay); Serial.print(", "); Serial.print(month, DEC); Serial.print("/"); Serial.print(date, DEC); Serial.print("/20"); Serial.print(year, DEC); Serial.print(" - ");
 Serial.print(hours, DEC); Serial.print(":"); Serial.print(minutes, DEC); Serial.print(":"); Serial.print(seconds, DEC);
 
 Serial.print(" - Temp: "); Serial.println(get3231Temp());

 delay(1000);
}

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

void watchConsole()
{
 if (Serial.available()) {      // Look for char in serial queue and process if found
   if (Serial.read() == 84) {      //If command = "T" Set Date
     set3231Date();
     get3231Date();
     Serial.println(" ");
   }
 }
}
 
void set3231Date()
{
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)T0042061310111
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209

 seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.  
 minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
 hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
 day     = (byte) (Serial.read() - 48);
 date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
 month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
 year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.send(0x00);
 Wire.send(decToBcd(seconds));
 Wire.send(decToBcd(minutes));
 Wire.send(decToBcd(hours));
 Wire.send(decToBcd(day));
 Wire.send(decToBcd(date));
 Wire.send(decToBcd(month));
 Wire.send(decToBcd(year));
 Wire.endTransmission();
}


void get3231Date()
{
 // send request to receive data starting at register 0
 Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
 Wire.send(0x00); // start at register 0
 Wire.endTransmission();
 Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

 if(Wire.available()) {
   seconds = Wire.receive(); // get seconds
   minutes = Wire.receive(); // get minutes
   hours   = Wire.receive();   // get hours
   day     = Wire.receive();
   date    = Wire.receive();
   month   = Wire.receive(); //temp month
   year    = Wire.receive();
     
   seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
   minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
   hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
   day     = (day & B00000111); // 1-7
   date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
   month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
   year    = (((year & B11110000)>>4)*10 + (year & B00001111));
 }
 else {
   //oh noes, no data!
 }
 
 switch (day) {
   case 1:
     strcpy(weekDay, "Sun");
     break;
   case 2:
     strcpy(weekDay, "Mon");
     break;
   case 3:
     strcpy(weekDay, "Tue");
     break;
   case 4:
     strcpy(weekDay, "Wed");
     break;
   case 5:
     strcpy(weekDay, "Thu");
     break;
   case 6:
     strcpy(weekDay, "Fri");
     break;
   case 7:
     strcpy(weekDay, "Sat");
     break;
 }
}

float get3231Temp()
{
 //temp registers (11h-12h) get updated automatically every 64s
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.send(0x11);
 Wire.endTransmission();
 Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
 
 if(Wire.available()) {
   tMSB = Wire.receive(); //2's complement int portion
   tLSB = Wire.receive(); //fraction portion
   
   temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
   temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
   temp3231 = (temp3231 * 1.8) + 32.0; // Convert Celcius to Fahrenheit
return temp3231;

 }
 else {
   //oh noes, no data!
 }
   
 return temp3231;
}




Coding Badly


Please report back if this works...

Code: [Select]
float get3231Temp()
{
//temp registers (11h-12h) get updated automatically every 64s
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.send(0x11);
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 2);

if(Wire.available()) {
  tMSB = Wire.receive(); //2's complement int portion
  tLSB = Wire.receive(); //fraction portion

  temp3231 = ((((short)tMSB << 8) | (short)tLSB) >> 6) / 4.0;
  temp3231 = (temp3231 * 1.8) + 32.0; // Convert Celcius to Fahrenheit
return temp3231;

}
else {
  //oh noes, no data!
}
 
return temp3231;
}


JTMSR

[smiley=happy.gif]
Ahhhhhhhhhhhhhh . . . .YES!  As ironic as the term 2's complement. . . the resolution by Coding Badly!  THANK YOU!!!!!

Now. . .if I may indulge . . .nd perhaps learn as well. . .what did that snippit do????

THANK YOU AGAIN!

THIS IS THE SOLUTION!

Coding Badly

Most modern compilers (like GCC) and processors (like the AVR) use two's complement for signed data-types.  There is no need to do anything special.  Just let the compiler and processor do what the do naturally.

  temp3231 = ((((short)tMSB << 8) | (short)tLSB) >> 6) / 4.0;

[glow](short)tMSB[/glow] is execute first.  This tells the compiler to treat tMSB as a signed 16 bit value.  At this point, it doesn't matter if the value is signed or unsigned.  We just need the value to be 16 bits.  Later it does need to be signed so we'll make it signed now.

((short)tMSB [glow]<< 8[/glow]) is execute next.  The 16 bit tMSB value is shifted 8 bits to the left.  After the eight bit shift, the least significant eight bits are zero and the most significant eight bits are tMSB.

[glow](short)tLSB[/glow] is next.  We're telling the compiler to treat tLSB as a signed 16 bit value.  The two sides of the expression are now the same data-type.

The bitwise-or ... (((short)tMSB << 8) [glow]|[/glow] (short)tLSB) ... is next.  The "top byte" is tMSB as a signed 16 bit value.  The "bottom byte" is tLSB as a signed 16 bit value.  After the bitwise-or we have a 16 bit signed value with tMSB as the most significant byte and tLSB as the least significant byte.

((((short)tMSB << 8) | (short)tLSB) [glow]>> 6[/glow]) is next.  We shift the 16 bit signed value right six bits because the data sheet for the DS3231 indicates the value is shifted left by six bits.  Because the value is signed, "sign bits" are shifted in from the left (it's called an "arithmetic shift").

((((short)tMSB << 8) | (short)tLSB) >> 6) [glow]/ 4.0[/glow] is the last step.  Because "4.0" is a floating-point value we force the compiler to use floating-point arithmetic.  Because the numerator is a signed 16 bit value, the sign is preserved.  We divide by four because the value returned from the DS3231 is multiplied by four.

Make sense?

JTMSR

Excellent term-by-term explanation that will serve others well when they encounter this issue.  Thank you for the solution, and more importantly, for the clear explanation of what the snippet of code does in this application.

The DS3231 in my application is a part of an arduino based wireless weather station project that I am working on.  The platform permits almost instantaneous wind gust reporting, along with average wind and current values - with real-time-stamping and now - with temperature.

Although the DS1321 data sheet states accuracy of the temperature output at an abysmal +/- 3 degrees C (corresponding to +/- 5.4 degrees F!!!) my initial testing indicates it is vastly better, closely tracking two reference thermometers within 0.5 degrees over the range of 24 to 65 degrees (so far!).  Coupled with resolution of 0.25 degrees C (0.45 degrees F)  I'd certainly like to make this useable for me (and perhaps others) as a 'freebie' with the real-time clock.  I may have just gotten a 'good' temperature output with my clock and others might require some scaling calibration.

Thanks kindly for the assistance Coding Badly.  I hope to do the same as I gain more knowledge and experience with this platform.  In the meantime, we all benefit from those with vastly more experience and patience to not only provide a solution, but to explain why it works.

For those who would like the DS3231 Real time Clock provide day, date, time and temperature readings, the code follows in the next replay (as there was not enough room in this one).

JTMSR

And the code for the DS3231 to show time and temperature is as follows:

#include <Wire.h>

#define DS3231_I2C_ADDRESS 104

byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];

byte tMSB, tLSB;
float temp3231;

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

void loop()
{

watchConsole();


get3231Date();

Serial.print(weekDay); Serial.print(", "); Serial.print(month, DEC); Serial.print("/"); Serial.print(date, DEC); Serial.print("/20"); Serial.print(year, DEC); Serial.print(" - ");
Serial.print(hours, DEC); Serial.print(":"); Serial.print(minutes, DEC); Serial.print(":"); Serial.print(seconds, DEC);

Serial.print(" - Temp: "); Serial.println(get3231Temp());

delay(1000);
}

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

void watchConsole()
{
if (Serial.available()) {      // Look for char in serial queue and process if found
  if (Serial.read() == 84) {      //If command = "T" Set Date
    set3231Date();
    get3231Date();
    Serial.println(" ");
  }
}
}

void set3231Date()
{
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)T0042061310111
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209

seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.  
minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
day     = (byte) (Serial.read() - 48);
date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.send(0x00);
Wire.send(decToBcd(seconds));
Wire.send(decToBcd(minutes));
Wire.send(decToBcd(hours));
Wire.send(decToBcd(day));
Wire.send(decToBcd(date));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.endTransmission();
}


void get3231Date()
{
// send request to receive data starting at register 0
Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
Wire.send(0x00); // start at register 0
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

if(Wire.available()) {
  seconds = Wire.receive(); // get seconds
  minutes = Wire.receive(); // get minutes
  hours   = Wire.receive();   // get hours
  day     = Wire.receive();
  date    = Wire.receive();
  month   = Wire.receive(); //temp month
  year    = Wire.receive();
     
  seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
  minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
  hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
  day     = (day & B00000111); // 1-7
  date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
  month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
  year    = (((year & B11110000)>>4)*10 + (year & B00001111));
}
else {
  //oh noes, no data!
}

switch (day) {
  case 1:
    strcpy(weekDay, "Sun");
    break;
  case 2:
    strcpy(weekDay, "Mon");
    break;
  case 3:
    strcpy(weekDay, "Tue");
    break;
  case 4:
    strcpy(weekDay, "Wed");
    break;
  case 5:
    strcpy(weekDay, "Thu");
    break;
  case 6:
    strcpy(weekDay, "Fri");
    break;
  case 7:
    strcpy(weekDay, "Sat");
    break;
}
}

float get3231Temp()
{
//temp registers (11h-12h) get updated automatically every 64s
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.send(0x11);
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 2);

if(Wire.available()) {
  tMSB = Wire.receive(); //2's complement int portion
  tLSB = Wire.receive(); //fraction portion

  temp3231 = ((((short)tMSB << 8) | (short)tLSB) >> 6) / 4.0; // Allows for readings below freezing - Thanks to Coding Badly
  temp3231 = (temp3231 * 1.8) + 32.0; // Convert Celcius to Fahrenheit
return temp3231;

}
else {
  //oh noes, no data!
}

return temp3231;
}

Go Up