CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug Reports on this forum. Send them to support@ccsinfo.com

Two's complement....an easy way?

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
euanw



Joined: 03 Jan 2006
Posts: 4
Location: St Albans, England

View user's profile Send private message

Two's complement....an easy way?
PostPosted: Tue Jan 10, 2006 2:21 pm     Reply with quote

Hi all,

Being very new to both C programming and PICs, I have been muddling through a simple program to drive a 7-segment LED and DS1820 temperature IC from a 16f84a.

Searching the Internet and this site, one thing I noticed is that there doesn't appear to be an easy way to convert a negative temperature to a decimal.

Could someone please review the below code and point out the good and bad points of it? I believe that using a bitwise XOR with a full byte, and adding 1 shoud give a decent result.

Quick explanation: temp_byte0 and temp_byte1 are the first two returned bytees from the DS1820. It returns a sign-extended 16-bit number, therefore if temp_byte1=0xff, it is a negative temperature. I set the sign flag for later on in the program to display a '-' sign on the LED display if necessary.

The problem I'm getting is that when the temperature falls below 0 degrees celcius the display shows '27'.

FYI: I'm not interested in fractions of degrees, so everything is rounded to nearest whole number.

Many thanks in advance

Euan


Code:

void calc_temp(temp_byte0, temp_byte1)
{
   if (temp_byte1 != 0xff)             //Look at 2nd byte from DS1820
   {                                //If it is not all 1 divide 1st byte by 2
      temp = temp_byte0/2;
      sign = 0;
   }
   else
   {
      temp_byte0 = (temp_byte0 ^ 0xff)+1;   //Perform 2's complement
      temp = temp_byte0/2;                //Divide by 2
      sign = 1;
   }
}
PCM programmer



Joined: 06 Sep 2003
Posts: 20336

View user's profile Send private message

PostPosted: Tue Jan 10, 2006 3:08 pm     Reply with quote

Are you asking how to take the absolute value of a signed integer ?

Here is a demo program:
Code:
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

//=====================================
void main()

signed int16 value;

value = -12000;

if(value < 0)      // If negative, get absolute value.
   value = -value;

printf("value = %ld\n\r", value);

while(1);
}
Ttelmah
Guest







PostPosted: Tue Jan 10, 2006 3:45 pm     Reply with quote

Can I suggest a very different way of approaching the original code:

Code:

union combine_bytes {
    int8 b[2];
    signed int16 temperature;
} tval;

void calc_temp(temp_byte0, temp_byte1)
{
   tval.b[0]=temp_byte0;
   tval.b[1]=temp_byte1;
   tval.temperature /=2;
}

You now have tval.temperature, containing the temperature, and signed as well. If you want to extract the sign, then:

Code:

if (tval.temperature<0) sign=1;
else sign=0;
tval.temperature=abs(tval.temperature);


However you don't need to do this, you can just use %ld, in printf, and it'll display the sign as well...

Best Wishes
euanw



Joined: 03 Jan 2006
Posts: 4
Location: St Albans, England

View user's profile Send private message

PostPosted: Tue Jan 10, 2006 4:21 pm     Reply with quote

I think I can see where you're coming from but not sure it will do what is required. Does:

Code:

signed int16 temperature;


convert a two's complement number to decimal?

For some background: the DS1820 gives a two-byte response to temperature conversions. MSB is just used as the sign, so we can ignore that just now as all bits within it will either be 1's (-ve temperature) or 0's (+ve temperature).

However, if the temperature is -ve, then the LSB is the two's complement of the temperature. Giving an example from the datasheet ( http://pdfserv.maxim-ic.com/en/ds/DS1820-DS1820S.pdf ):

Quote:

-25.0C = 11111111 11001110 = FFCEh


Ignoring the MSB leaves: 0b11001110 (or 0xCE)

As the sign is -ve (from MSB), this number is the two's complement of the actual -ve temperature (without the sign). Therefore, converting this two's complement number back to decimal gives us:

00110010, or 50 (decimal)

and divide by two gives temperature = 25. Remember the sign and you get the desired answer of -25 Celcius.

Please correct me if I'm missing the vital point here. I'm keen to learn and obviously the easiest way is to make a fool of myself by trying to explain something I don't fully understand. Very Happy
newguy



Joined: 24 Jun 2004
Posts: 1497
Location: Edmonton, Alberta

View user's profile Send private message

PostPosted: Tue Jan 10, 2006 5:54 pm     Reply with quote

Negative numbers in a computer are represented by the 2's complement of the positive number. So if you declare a variable, number, to be a signed 8 bit integer and initialize it to be -5,

Code:
signed int8 number = -5;


It will be stored internally like this:

First, +5 = 00000101. To find out what -5 will be stored as, first take the 1's complement of +5 to get: 11111010. Now to get the 2's complement, just add 1 to that:
11111010 + 1 = 11111011.

This is -5, and this exactly how the temperature sensor will output a temperature of -5C. You don't have to do anything special to the data once you read it. You simply need to store it in a signed variable, and if you want to display it, just remember to treat it as a signed variable, not an unsigned one.

The only thing you have to do that's special is to take care of the sign bit yourself because the sensor treats the least significant bit as a ".5" flag/bit. Because you don't care about the fractional portion, just take the temperature you read from the sensor, and shift it right one bit (>> 1), which is the same as dividing it by 2.

Right shifting in this manner will not preserve the sign bit, since the "total" temperature is spread out over two 8-bit words. Then you have to examine the most significant byte to see if it is loaded with 1's (which means the temperature is negative) or 0's (positive). If the number is negative, you have to manually set the sign bit yourself. Something like this will do it:

Code:
if (MSB == 0xff) {
   bit_set(number,7);
}


Hope this helps to clear it up for you.


Last edited by newguy on Tue Jan 10, 2006 6:00 pm; edited 1 time in total
JBM



Joined: 12 May 2004
Posts: 54
Location: edinburgh, Scotland

View user's profile Send private message

PostPosted: Tue Jan 10, 2006 5:57 pm     Reply with quote

I've used the DS18B20 quite a lot in various applications, and have some code that might come in handy:

Code:
signed int16 read_temp()
{
   int8 high,low;

   mass_convert();
   delay_ms(750);

   ow_reset();
   write_byte(0xCC);
   write_byte(0xBE);         //read scratchpad

   low = read_byte();
   high = read_byte();

   //ignore rest of scratchpad

   return( (signed int16) make16(high,low) );
}


float converttemp(signed int16 data)
{
   float var;

   var = data;
   var = var / 16.0;
   return(var);
}


This shold make it nice and easy to print out stuff, because if you pass the result of read_temp() to converttemp, you get a handy dandy floating point number of the temperature in degrees Celcius. So something like:


Code:

...
printf("The temperature is %f degress Celcius",converttemp(read_temp()) );

should handle all the nastiness for you. If that's not quite what you want, just use the read_temp() function.

Hope this helps
-JBM
Ttelmah
Guest







PostPosted: Wed Jan 11, 2006 3:21 am     Reply with quote

I was actually making the code unneccessarily complex, to 'equate' it to what you were doing, and produce a fairly 'drop in' function. As another poster has explained, the compiler uses two's complement internally to store -ve numbers, when the variable is declared as 'signed'. So, in general, you can for instance, declare (I like unions):

Code:


union types {
    int8 b[2];
    signed int16 stype;
    int16 utype;
} val;



Then if you say "val.stype = -5;" val.utype, will be 65531 (if printed in decimal), or 0xFFFB if printed in hex. val.b[0] will be '0xFB', and val.b[1], will be '0xFF'. Even better, if the variable is 'signed', the compiler knows to handle the sign bit for divisions, making the arithmetic a 'doddle'.
So if you use a union, or make16 (both function in basically the same way, but the union is portable to other compilers, while the 'make16' function, is CCS specific), to put the incoming bytes into a signed int16 variable, the compiler is able to handle the rest. The '%Ld' declaration in printf, will if given a signed value (remember if this is an 'int16', to use 'L' on the printf, to tell it to print a 'long'), automatically generate a sign, if the top bit is set, and correctly convert the value for display.
Basically, using the internal 'signed' types, is the easiest wy to handle this, since the compiler then knows to do the work for you!.

Best Wishes
euanw



Joined: 03 Jan 2006
Posts: 4
Location: St Albans, England

View user's profile Send private message

PostPosted: Wed Jan 11, 2006 7:38 am     Reply with quote

Things are becoming much clearer. Thank you all for your input.
I'll head home tonight and put it into practise and see if I can simplify my code.

I'll report back if I get into difficulties Very Happy

Regards

Euan
cmdrdan



Joined: 08 Apr 2005
Posts: 25
Location: Washington

View user's profile Send private message

PostPosted: Wed Jan 11, 2006 3:13 pm     Reply with quote

Euan --

Here is an adaptation of your original code to do the two's complement arithmetic, plus a little extra to extract the decimal digits from the binary result. How you put the individual digits on the LEDs is left as an exercise for you. Hope this helps....

Dan


Code:


int low_byte;
int high_byte;
int sign;
int hundreds;
int tens;
int units;

void temperature(void)
{
int temp;

/* Handle twos complement results */

   sign = 0;

   if(high_byte != 0)   // negative result, form two's complement
   {
      low_byte ^= 0xFF;
      low_byte++;
      sign = 1;
   }
   
   low_byte >>= 1;      // make into whole degrees
   
/* Extract decimal digits from binary result.  Note that this is INTEGER
   division.... */

   hundreds = low_byte / 100;
   temp = low_byte % 100;
   tens = temp / 10;
   units = temp % 10;
   
/* Display on the LEDs is up to you.... */
}
euanw



Joined: 03 Jan 2006
Posts: 4
Location: St Albans, England

View user's profile Send private message

PostPosted: Thu Jan 12, 2006 3:39 am     Reply with quote

Thanks Dan,

I've actually got completed working code with LED's etc. What I am going to do now, is experiment with all the advice given here and expand my knowledge of best practise programming, and optimise the code as much as possible.

Everyone has been incredibly helpful, so thank you all.


Euan
Guest








PostPosted: Wed Mar 15, 2006 5:13 am     Reply with quote

euanw wrote:
Thanks Dan,

I've actually got completed working code with LED's etc. What I am going to do now, is experiment with all the advice given here and expand my knowledge of best practise programming, and optimise the code as much as possible.

Everyone has been incredibly helpful, so thank you all.


Euan


I have the same problems like you ( starting with C). Could you post me working code in e-mail snoopy01@volja.net .

TNX with regards
Bojan
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group