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
Author Message
euanw

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

Two's complement....an easy way?
Posted: Tue Jan 10, 2006 2:21 pm

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.

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: 21418

Posted: Tue Jan 10, 2006 3:08 pm

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

Posted: Tue Jan 10, 2006 3:45 pm

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

Posted: Tue Jan 10, 2006 4:21 pm

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.0°C = 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.
newguy

Joined: 24 Jun 2004
Posts: 1732

Posted: Tue Jan 10, 2006 5:54 pm

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

Posted: Tue Jan 10, 2006 5:57 pm

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

Posted: Wed Jan 11, 2006 3:21 am

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

 Posted: Wed Jan 11, 2006 7:38 am 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 Regards Euan
cmdrdan

Joined: 08 Apr 2005
Posts: 25
Location: Washington

Posted: Wed Jan 11, 2006 3:13 pm

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

 Posted: Thu Jan 12, 2006 3:39 am 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

Posted: Wed Mar 15, 2006 5:13 am

 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: All Posts1 Day7 Days2 Weeks1 Month3 Months6 Months1 Year Oldest FirstNewest First
 All times are GMT - 6 Hours Page 1 of 1

 Jump to: Select a forum Software----------------General CCS C DiscussionCode LibraryEZ App LynxBest Of Hardware----------------CCS ICD / Mach X / Load-n-Go
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