How to Present DS3231's Temp Signal as a float onto LCD?

As a part of Lab Work, we are playing with the following setup (Fig-1) that involves UNO-LCD-DS3231-TWI Bus. Our current interest is to read the temperature signal of the built-in Temperature Sensor of the DS3231 RTC Chip and then present it onto the LCD.

Everything is fine except that we have difficulties to format the contents of the Temperature Registers of the RTC in order to present the Temperature value as float (XX.XX) onto the LCD. The fractional part shows: XX.1 or XX.2 or XX.3 instead of XX.25 or XX.50 or XX.75.

Let us remember that the Temperature Register (Upper, Fig-2) contains the integer part of the Temperature and the Bit-7, 6 of the Temperature Register (Lower, Fig-2) contains the fractional part of the Temperature.


Figure-1: UNO-LCD-DS3231-TWI Bus Setup


Figure-2: Temperature Registers of DS3231 RTC Chip

#include<Wire.h>
#define deviceAddress 0b1101000

#include<LiquidCrystal.h>
LiquidCrystal lcd(A0, A1, A2, A3, 12, 13);
byte x;
void setup() 
{
  Wire.begin();       //TWI Bus is formed  
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
}

void loop() 
{
  Wire.beginTransmission(deviceAddress);    //START, Roll Call
  Wire.write(0x0E);                         //Control Register Address set
  Wire.write(0x20);                         //Start Tempertaure Conversion; CONV bit is set High
  Wire.endTransmission();                   //Transfer above queued data, ACK, STOP
  
  do
  {
    Wire.requestFrom(deviceAddress, 1);     //to check end-of-conversion of TCXO algorithm
    x = Wire.read();                        //read content of Control Register
  }
  while (bitRead(x, 5) != LOW);             //chcek if CONV bit has assumed LL state
  
  
  //--- read Temperature Signal--------
  Wire.beginTransmission(deviceAddress);    //START, Roll Call
  Wire.write(0x11);                         //address of Temp Register (Uppler)
  Wire.endTransmission();                   //Transfer above queued data ACK, STOP

  Wire.requestFrom(deviceAddress, 2);       //Reading Temperature Data
  byte tempUpper = Wire.read();
  byte tempLower = Wire.read();
  
  lcd.print(tempUpper, DEC);        //LCD shows integer part: XX
  lcd.write(0x2E);                  //place point; LCD shows: XX.
  lcd.print(tempLower>>6, DEC);     //shows: 1 or 2 o 3; we want: 25 or 50 or 75
  delay(3000);                                 //3-sec sampling period
  lcd.setCursor(0,0);
}
tempLower>>6

This is leaving the 2 bits (7 and 6) with the fractional part of the temperature. They represent the two to the -1 and -2 powers. The fractional part of the temperature the is therefore

0 0 = .00

0 1 = .25

1 0 = .50

1 1 = .75

You can use lcd.print(25*(tempLower>>6))

@cattledog

Thanks with +1! Your formula works! However, there seems to be an unexpected erratic side effect --- the display time-to-time shows: .05! I have been investigating the source of this spurious signal.

the display time-to-time shows: .05! I have been investigating the source of this spurious signal.

It's when you have .75 followed by 00 which is printing as 0. You will need to do some cursor managment to clear the full field length, or force printing to two digits.

@cattledog

Interesting observation!

BTW: I have fixed the integer part, and you have fixed the fractional part with regards to displaying the result with decimal point without doing any floating point math.

In fact, I have been thinking about the possibility of performing float math on the contents of Temperature Registers so that the formula lcd.print(temp, 2) could be used to show the temperature with 2-digit accuracy after the decimal point.

Untested:

  float temp = Wire.read(); //upper byte
  temp += (float)Wire.read()/256;  //lower byte
  
  lcd.print(temp, 2);

@aarg

Double thanks along with +1! Your codes work!

BTW: Should we cast float in the following statement of your codes as the content of the Temperature Register (Upper) is integer? We are now asking that the integer should be stored as floating point number in binary32 format.

float temp = Wire.read(); //upper byte
===> float temp = (float)Wire.read(); //upper byte

It wouldn't hurt, it might be more explicit.

Your codes work!

 float temp = Wire.read(); //upper byte
  temp += (float)Wire.read()/256;  //lower byte 
  lcd.print(temp, 2);

Please explain this to me. The full value set of the lower byte are B11000000, B10000000, B01000000, and B00000000. Dividing by 256 should bring you back to the 3,2,1,0 situation you originally had.

I would have thought

 float temp = Wire.read(); //upper byte
  temp += (float).25*Wire.read()/256;  //lower byte 
  lcd.print(temp, 2);

EDIT: alternatively

 float temp = Wire.read(); //upper byte
  temp += (float)((Wire.read()/256)/4.0)) //lower byte
  
  lcd.print(temp, 2);

B11000000 = 0xC0 = 192 decimal. Dividing by 256. gives .75 which is correct.
I am not sure that storing the bytes into floats will work because they are meant to be part of a twos complement representation of the temperature. It won't handle negative temperatures correctly.
It would be safer to assemble the temperature into a signed 16-bit integer and then divide by 256. to convert to float:

 int16_t i_temp;  // NOT uint16_t
   
  i_temp = WHICH_WIRE.read() << 8;
  i_temp |= WHICH_WIRE.read();
  float temp = i_temp / 256.;

Pete

B11000000 = 0xC0 = 192 decimal. Dividing by 256. gives .75 which is correct.

Thank's. I had a binary brain fart.

It would be safer to assemble the temperature into a signed 16-bit integer and then divide by 256. to convert to float:

Here's the way the Jarzebski library does it

float DS3231::readTemperature(void)
{
    uint8_t msb, lsb;

    Wire.beginTransmission(DS3231_ADDRESS);
    #if ARDUINO >= 100
        Wire.write(DS3231_REG_TEMPERATURE);
    #else
        Wire.send(DS3231_REG_TEMPERATURE);
    #endif
    Wire.endTransmission();

    Wire.requestFrom(DS3231_ADDRESS, 2);

    while(!Wire.available()) {};

    #if ARDUINO >= 100
    msb = Wire.read();
    lsb = Wire.read();
    #else
    msb = Wire.receive();
    lsb = Wire.receive();
    #endif

    return ((((short)msb << 8) | (short)lsb) >> 6) / 4.0f;
}

Please explain this to me.

I must remain sincere to my own ethics. I have received codes from @cattledog, and I have tested it in a real UNO Machine. I have received codes from @aarg, and I have tested the codes in the same machine. The codes are different in their styles; but, they have produced the same result what I have expected as OP. I have extended thanks to both with +1s.

Once, I could not do it. Now, I can do it in the following way being inspired by the Forum Members of this thread:

float tempUpper = (float)Wire.read();
 byte tempLower = Wire.read();

 float tempLowerx = (float) ((tempLower>>7)*0.5 + ((tempLower>>6)&0x01) *0.250);
 float temp = tempLowerx + tempUpper;
 lcd.print(temp, 2);

I have just skimmed through these messages. I am not sure if you will get the negative temperatures correct.

Look at other datasheets to to see the representation of -0.25C

Personally I read the two bytes into a signed int. Then divide by 256.0 to get the f-p result.
You use exactly the same method whether the sensor has 9 bits, 10 bits, ... 12 ... 16 bits of precision.

David.

@cattledog: That code does essentially what I wrote but with a somewhat convoluted way to combine the two bytes into a signed integer which is then divided to give a float result.

@GolamMostafa: You can choose to do it anyway you like but using floats like that will only work for non-negative temperatures. If you will never get temperatures below zero it isn't going to matter but in the long run it is best not to use buggy code.

Pete

I like the method in #9. It's the most straightforward and WYSIWYG. I think when the int temp is promoted to a float and divided by a float, the /256 is not computationally expensive because it just subtracts 8 from the exponent.

Also as pointed out in reply #13, the method is agnostic to precision.

I did say, "untested"...

@GolamMostafa: It is easy enough to test the conversions. If you assume that the high order byte is 0xFC and the low order byte is 0xC0, your code from message #12 will print 252.75. My code prints -3.25 which is correct.

Pete