Binary to fractional decimal

Discussion in 'Programmer's Corner' started by kdillinger, Jun 18, 2014.

  1. kdillinger

    Thread Starter Active Member

    Jul 26, 2009
    141
    3
    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:

    Code ( (Unknown Language)):
    1. //I2C Master
    2.  
    3. #include <Wire.h>
    4.  
    5. #define ADDR 0x4C
    6. #define confreg 0x03
    7. #define Internal_High_Byte 0x00
    8. #define Internal_Low_Byte 0x29
    9.  
    10. void setup()
    11. {
    12.   Wire.begin();    //start I2C Master mode
    13.   Serial.begin(9600);
    14. }
    15.  
    16.  
    17. void loop()
    18. {
    19.   byte temp_high;
    20.   byte temp_low;
    21.   byte fraction_temp;
    22.  
    23.   //begin I2C
    24.   Wire.beginTransmission(ADDR);     //select device to read from
    25.   Wire.write(Internal_High_Byte);   //select internal high byte register
    26.   Wire.endTransmission();           //end I2C before restart
    27.   Wire.requestFrom(ADDR, 1);        //request data from the Internal High Byte register we just pointed to
    28.   while(Wire.available())           //capture the byte
    29.   {
    30.     temp_high = Wire.read();        //assign it as the low temp high byte
    31.   }
    32.  
    33.   delay(1000);
    34.  
    35.   Wire.beginTransmission(ADDR);     //select device to read from
    36.   Wire.write(Internal_Low_Byte);    //select internal high low register
    37.   Wire.endTransmission();           //end I2C before restart
    38.   Wire.requestFrom(ADDR, 1);        //request data from the Internal Low Byte register we just pointed to
    39.   while(Wire.available())           //capture the byte
    40.   {
    41.     temp_low = Wire.read();         //assign it as the low temp low byte
    42.   }
    43.  
    44.   Serial.print("Temp = ");
    45.   Serial.print(temp_high, DEC);    //print the integer temp
    46.   Serial.print(".");
    47.   Serial.println(temp_low, DEC);    //print the fractional temp
    48.  
    49. delay(1000);
    50. }
    Some of the data:

    Code ( (Unknown Language)):
    1. Temp = 28.128
    2. Temp = 27.224
    3. Temp = 27.224
    4. Temp = 27.192
    5.  
     
  2. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    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 likes this.
  3. GopherT

    AAC Fanatic!

    Nov 23, 2012
    6,026
    3,789
    Change...
    Serial.println(temp_low, DEC)
    to
    Serial.println(temp_low / 0.256 , DEC)

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

    Thread Starter Active Member

    Jul 26, 2009
    141
    3
    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.
     
  5. djsfantasi

    AAC Fanatic!

    Apr 11, 2010
    2,802
    832
    There may be some confusion due to the order of operations. Do you mean
    Code ( (Unknown Language)):
    1. fract_temp = ((temp_low/32)*125)
    Or
    Code ( (Unknown Language)):
    1. 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
    Code ( (Unknown Language)):
    1. ((64/32)*125), which equals 250
    The second equation is
    Code ( (Unknown Language)):
    1. (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: Jun 20, 2014
    ErnieM likes this.
  6. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    or do one step at a time:

    Code ( (Unknown Language)):
    1.  
    2. unsigned short frac_temp;
    3.  
    4. frac_temp = temp_low >> 5;
    5. frac_temp *= 125;
    6.  
     
  7. kdillinger

    Thread Starter Active Member

    Jul 26, 2009
    141
    3
    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.
     
  8. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    You ought to be able to figure this out by yourself.

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

    Well-Known Member

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

    Mike

    Code ( (Unknown Language)):
    1.  
    2.  
    3.   ...
    4.   Serial.print("Temp = ");
    5.   Serial.print(temp_high, DEC);    // print the integer temp
    6.   Serial.print(".");
    7.   frac = temp_low >> 4;            // fraction, 0..15
    8. //frac += 2;                       // rounding to 2 decimal places
    9. //frac += 4;                       // rounding to 1 decimal place
    10.   Serial.print(digit(),DEC);       // print 1st decimal place
    11.   Serial.print(digit(),DEC);       // print 2nd decimal place
    12.   Serial.print(digit(),DEC);       // print 3rd decimal place
    13.   ...
    14.  
    15.  
    16.   unsigned char digit()            //
    17.   { frac &= 0x0F;                  //
    18.     frac *= 10;                    //
    19.     return (frac >> 4);            //
    20.   }                                //
    21.  
     
    Last edited: Jun 21, 2014
  10. kdillinger

    Thread Starter Active Member

    Jul 26, 2009
    141
    3
    That would be 255.
     
  11. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    So 7 x 125 = 875 doesn't fit into a byte.
     
  12. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    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.
    :)
     
  13. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    +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: Jun 22, 2014
  14. kdillinger

    Thread Starter Active Member

    Jul 26, 2009
    141
    3
    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.
     
  15. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    +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).
     
  16. djsfantasi

    AAC Fanatic!

    Apr 11, 2010
    2,802
    832
    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.
     
    MrChips likes this.
  17. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    Yes, you are correct. Should be N x 4.
    Thanks for pointing out the mistake.
     
  18. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    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:
     
  19. MrChips

    Moderator

    Oct 2, 2009
    12,440
    3,360
    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: Jun 22, 2014
  20. MMcLaren

    Well-Known Member

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

    Regards, Mike

    Code ( (Unknown Language)):
    1.     /*                                                                *
    2.      *  the 16C term in the formulas below is the raw Celsius * 16    *
    3.      *  temperature data from the DS18B20 in the 'temp16' variable.   *
    4.      *  a value of °C*10 or °F*10 is used for the display with a      *
    5.      *  decimal point inserted before the last (tenths) digit.        *
    6.      *                                                                *
    7.      *  10C = 8C + 2C = 16C/2 + (16C+4)/8                             *
    8.      *                                                                *
    9.      *  10F = 18C + 320 = 16C + 2C + 320 = 16C + (16C+4)/8 + 320      *
    10.      *                                                                */
    11.        bin = temp16;            // calculate common '2C' term first
    12.        bin += 4;                // rounding
    13.        bin /= 8;                // now bin = '2C' (with rounding)
    14.        if(flags.7)              // Rt arrow flag = 1 --> Fahrenheit
    15.        { bin += temp16;         // now bin = 16C + 2C = 18C
    16.          bin += 320;            // now bin = 18C + 320 = 10F
    17.        }                        //
    18.        else                     // Rt arrow flag = 0 --> Celsius
    19.        { temp16 /= 2;           // temp16 = 8C
    20.          bin += temp16;         // now bin = 8C + 2C = 10C
    21.        }                        //
    22.        if(negflag = bin.15)     // set/clear "negflag", if negative
    23.          bin = -bin;            // twos complement the temperature
    24.  
     
Loading...