Binary to fractional decimal

Thread Starter

kdillinger

Joined Jul 26, 2009
141
I have an Arudino that is reading temperature data from an EMC1182 temperature sensor.

The code is working as expected (go me, it's been 10 years since I coded), and I can display the current temperature to the serial monitor.

The problem is this:

I am reading the internal diode temperature which is located in two registers. A high byte and a low byte. The high byte can easily be displayed in decimal to the serial monitor. For every binary count, the temperature is 1 degree. No need for any kind of conversion here.

However, the low byte is actually fractional. The first 3 MSBs of the byte are used, all other bits are 0. So bit 7 has 0.5 weight, bit 6 has 0.25 weight, and finally bit 5 has 0.125 weight. So 10000000b is actually 0.5 degrees. If I display this to the serial monitor in decimal it will simply say 128. This is correct, but how do I go about creating a decimal fraction from this?

If someone can point to a reference I would be grateful. No need to give me the answer.

Here is my code:

Rich (BB code):
//I2C Master

#include <Wire.h>

#define ADDR 0x4C
#define confreg 0x03
#define Internal_High_Byte 0x00
#define Internal_Low_Byte 0x29

void setup()
{
  Wire.begin();    //start I2C Master mode
  Serial.begin(9600);
}


void loop()
{
  byte temp_high;
  byte temp_low;
  byte fraction_temp;

  //begin I2C
  Wire.beginTransmission(ADDR);     //select device to read from
  Wire.write(Internal_High_Byte);   //select internal high byte register
  Wire.endTransmission();           //end I2C before restart
  Wire.requestFrom(ADDR, 1);        //request data from the Internal High Byte register we just pointed to
  while(Wire.available())           //capture the byte
  {
    temp_high = Wire.read();        //assign it as the low temp high byte
  }
  
  delay(1000);
  
  Wire.beginTransmission(ADDR);     //select device to read from
  Wire.write(Internal_Low_Byte);    //select internal high low register
  Wire.endTransmission();           //end I2C before restart
  Wire.requestFrom(ADDR, 1);        //request data from the Internal Low Byte register we just pointed to
  while(Wire.available())           //capture the byte
  {
    temp_low = Wire.read();         //assign it as the low temp low byte
  }
  
  Serial.print("Temp = ");
  Serial.print(temp_high, DEC);    //print the integer temp
  Serial.print(".");
  Serial.println(temp_low, DEC);    //print the fractional temp
  
delay(1000);
}
Some of the data:

Rich (BB code):
Temp = 28.128
Temp = 27.224
Temp = 27.224
Temp = 27.192
 

MrChips

Joined Oct 2, 2009
30,711
You have a 3-bit result in the most significant bits.
Shift this 5 bits to the right (or divide by 32).
Multiply by 125.
Output the decimal part as a 3-digit number.
 

GopherT

Joined Nov 23, 2012
8,009
Change...
Serial.println(temp_low, DEC)
to
Serial.println(temp_low / 0.256 , DEC)

-or-
Serial.println(temp_low * 3.90625 , DEC)
 

Thread Starter

kdillinger

Joined Jul 26, 2009
141
I will take a look at these.

I tried Mr.Chips suggestion, but it did not seem to work. I am sure I did not code it right.

I did the following:

fract_temp = (temp_low/32 * 125)

When I am back in my home office, I will continue over the weekend.

Thanks.
 

djsfantasi

Joined Apr 11, 2010
9,156
There may be some confusion due to the order of operations. Do you mean
Rich (BB code):
fract_temp = ((temp_low/32)*125)
Or
Rich (BB code):
fract_temp = (temp_low/(32*125))
?

I think you meant the first equation, but it is getting evaluated as the second equation.

Consider the same form with temp_low=64 .
The first equation is
Rich (BB code):
((64/32)*125), which equals 250
The second equation is
Rich (BB code):
(64/(32*125)), which equals 0.016
So the order of operations is important. If there is any ambiguity, it is good practice to use parentheses to clarify the operations desired.
 
Last edited:

Thread Starter

kdillinger

Joined Jul 26, 2009
141
Ok, that works.

I see what I did wrong. I still had temp_low as a byte instead of an unsigned short.

I need to go back and understand what classes can be manipulated with math functions. Trying the above with just 'byte' was not working.
 

MrChips

Joined Oct 2, 2009
30,711
You ought to be able to figure this out by yourself.

What is the largest unsigned integer that can be represented by 8 bits?
 

MMcLaren

Joined Feb 14, 2010
861
Could something like this work if you wanted the option to print one, two, or three decimal places?

Mike

Rich (BB code):
  ...
  Serial.print("Temp = ");
  Serial.print(temp_high, DEC);    // print the integer temp
  Serial.print(".");
  frac = temp_low >> 4;            // fraction, 0..15
//frac += 2;                       // rounding to 2 decimal places
//frac += 4;                       // rounding to 1 decimal place
  Serial.print(digit(),DEC);       // print 1st decimal place
  Serial.print(digit(),DEC);       // print 2nd decimal place
  Serial.print(digit(),DEC);       // print 3rd decimal place
  ...


  unsigned char digit()            //
  { frac &= 0x0F;                  //
    frac *= 10;                    //
    return (frac >> 4);            //
  }                                //
 
Last edited:

THE_RB

Joined Feb 11, 2008
5,438
My preferred method would be to work with ONE variable because both the degrees and fraction are binary.

I would concatenate (join) the degrees and fraction into one simple binary number, then the scaling to decimal degrees can be done in one scaling calc, then you just need to insert a decimal point '.' symbol for display (you probably need to insert degrees chars anyway).

If you need two decimal places, ie; xxx.xx'C Just concatentate the binary data, scale it to degrees *100 (still one scaling operation) and you have xxxxx then insert the decimal point to make xxxxx into xxx.xx.

One of the benefits of keeping the entire degrees.fraction data in one variable is the ability to use it in control code ie; if(temp > x) which is much easier if temp is a single variable.
:)
 

MrChips

Joined Oct 2, 2009
30,711
+1

I agree with RB. In embedded small MCU systems I avoid using floating point arithmetic. I use scaled integers instead. I can do dewpoint determination from temperature and relative humidity to 2 decimal places in less that 2k of code.

Suppose your temperature reading is 20.875 °C
Your high byte is 0x14
Your low byte is 0xE0

Concatenate the two into one 16-bit unsigned integer = 0x14E0 = 5344

Simply shift right 5 bits
and multiply by 125

(5344/32) x 125 = 20875

If you wanted fast execution by avoiding the multiplication you can do
(N/32) x (128 - 3) = (N/4) - 3 x (N/32) = (N/4) - (N/16) - (N/32)

Correction:
(N/32) x (128 - 3) = (N x 4) - 3 x (N/32) = (N x 4) - (N/16) - (N/32)
 
Last edited:

Thread Starter

kdillinger

Joined Jul 26, 2009
141
That would be something I can look into later to improve future projects.

I am happy with that I have now since it works.

Thanks for the suggestion.
 

ErnieM

Joined Apr 24, 2011
8,377
My preferred method would be to work with ONE variable because both the degrees and fraction are binary.
+1 again. QFT.

This cannot be overstated. Nothing is faster and more compact on any platform then integer arithmetic. So if is oft correct to do what you must to keep data as an integer by selecting the units.

Here the temperature is of the form XX.XXX, or 3 significant digits to the right of the decimal point. If instead the data was used as milli-degrees it would be of the form XXXXX, or nothing to the right of the DP, everything to the left.

You don't need stick to the traditional milli, micro, nano, ect. qualifiers. You can use say 10-1 or 10-2 freely, just keep your comments consistent (which you should do anyway).
 

djsfantasi

Joined Apr 11, 2010
9,156
If you wanted fast execution by avoiding the multiplication you can do
(N/32) x (128 - 3) = (N/4) - 3 x (N/32) = (N/4) - (N/16) - (N/32)
Isn't that (4xN)-(N/16)-(N/32) ?

(N/32)x128 = ((Nx128)/32)
128/32=4

?

In any case, this technique is a great idea! Think it's great to see methods of using integer arithmetic. The idea of using (128-3) for 125 is brilliant.
 

THE_RB

Joined Feb 11, 2008
5,438
...
...(N/32) x (128 - 3) = (N x 4) - 3 x (N/32) = (N x 4) - (N/16) - (N/32)
Gee that takes me back!

I remember doing a 24bit compound calc in asm on a OTP PIC with a stack of 2 (where you could only use CALLs on half the ROM, the other half only GOTOs anyway) and splitting the whole calc into 32 little chunks... And performing one chunk per loop while polling inputs and outputs etc between loops to try and get it fast enough on a 4MHz PIC with 1MHz insts...

And having to use a 10 minute UV eraser before I could re-program the PIC each time to check progress (no debugger in those days)... Shudder. :eek:
 

MrChips

Joined Oct 2, 2009
30,711
I do stuff like this quite frequently when working with embedded systems.
A classic example is converting from °C to °F.

F = C x 9/5 + 32

In order to avoid doing a division:

F x 10 = C x 9 x 10/5 + 320 = C x 18 + 320 = (C x 16) + (C x 2) + 320

which also eliminates the multiplication.

Now we have °F scaled by 10 for 1 decimal place.

(Of course, I would also have C already scaled by 10 or 100.)
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
FWIW, that's how I do it for DS18B20 sensors which produce raw Celsius * 16 temperature data;

Regards, Mike

Rich (BB code):
    /*                                                                *
     *  the 16C term in the formulas below is the raw Celsius * 16    *
     *  temperature data from the DS18B20 in the 'temp16' variable.   *
     *  a value of °C*10 or °F*10 is used for the display with a      *
     *  decimal point inserted before the last (tenths) digit.        *
     *                                                                *
     *  10C = 8C + 2C = 16C/2 + (16C+4)/8                             *
     *                                                                *
     *  10F = 18C + 320 = 16C + 2C + 320 = 16C + (16C+4)/8 + 320      *
     *                                                                */
       bin = temp16;            // calculate common '2C' term first
       bin += 4;                // rounding
       bin /= 8;                // now bin = '2C' (with rounding)
       if(flags.7)              // Rt arrow flag = 1 --> Fahrenheit
       { bin += temp16;         // now bin = 16C + 2C = 18C
         bin += 320;            // now bin = 18C + 320 = 10F
       }                        //
       else                     // Rt arrow flag = 0 --> Celsius
       { temp16 /= 2;           // temp16 = 8C
         bin += temp16;         // now bin = 8C + 2C = 10C
       }                        //
       if(negflag = bin.15)     // set/clear "negflag", if negative
         bin = -bin;            // twos complement the temperature
 
Top