Go Down

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

#### JTMSR ##### Jan 12, 2011, 10:59 pm
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>

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

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.
day     = (byte) (Serial.read() - 48);
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.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

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.send(0x11);
Wire.endTransmission();

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

temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
temp3231 = (temp3231 * 1. + 32.0; // Convert Celcius to Fahrenheit
return temp3231;

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

return temp3231;
} #1
##### Jan 13, 2011, 12:55 am

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 Fahrenheitreturn temp3231; } else {   //oh noes, no data! }   return temp3231;}`

#### JTMSR #2
##### Jan 13, 2011, 02:51 am
[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! #3
##### Jan 13, 2011, 05:25 am
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 << | (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 << [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 << | (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 << | (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 #4
##### Jan 13, 2011, 01:45 pm
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 #5
##### Jan 13, 2011, 01:50 pm
And the code for the DS3231 to show time and temperature is as follows:

#include <Wire.h>

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

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.
day     = (byte) (Serial.read() - 48);
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.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

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.send(0x11);
Wire.endTransmission();

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

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

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

return temp3231;
}

Go Up