7

I'm working with some embedded hardware, a Rabbit SBC, which uses Dynamic C 9.

I'm using the microcontroller to read information from a digital compass sensor using one of its serial ports.

The sensor sends values to the microcontroller using a single signed byte. (-85 to 85)

When I receive this data, I am putting it into a char variable

This works fine for positive values, but when the sensor starts to send negative values, the reading jumps to 255, then works its way back down to 0. I presume this is because the last bit is being used to determine the negative/positive, and is skewing the real values.

My inital thought was to change my data type to a signed char.

However, the problem I have is that the version of Dynamic C on the Microcontroller I am using does not natively support signed char values, only unsigned.

I am wondering if there is a way to manually cast the data I receive into a signed value?

Mat
  • 202,337
  • 40
  • 393
  • 406
Sean Taylor
  • 4,948
  • 8
  • 25
  • 27
  • Signed and unsigned values are all just a bunch of bits, it is YOUR interpretation that makes them signed or unsigned. For example, if your hardware produces 2's complement, if you read 0xff, you can either interpret it as -1 or 255 but they are really the same number. – Shahbaz Sep 25 '11 at 14:17
  • @Shahbaz: that's not true. The sign bit affects shift operations, as well as any casts to larger types. – Chris Eberle Sep 25 '11 at 14:18
  • can't you cast it to `int` (assuming that there is a signed int with this compiler). Is there a gcc port for this platform ? – Andre Holzner Sep 25 '11 at 14:20
  • So you have a compiler for a language that is not C, but looks like it. Any C answer you get may be invalid for your compiler. Is there a way to make your compiler be a C compiler? Or for you to get another compiler? – pmg Sep 25 '11 at 14:25
  • @Chris: Casting to larger types I agree. However, how does sign bit affect shifting? If you have 10110101 and shift it, does it matter if the number is viewed as signed or unsigned? – Shahbaz Sep 25 '11 at 14:29
  • 1
    @Shahbaz: you bet it affects shifting! If it's negative and you shift right, it will maintain the sign bit in the correct place. – Chris Eberle Sep 25 '11 at 14:33
  • @pmg Its variation of C with some extra features bolted on for the hardware/multitasksing. Not sure if there is an alternative compiler. – Sean Taylor Sep 25 '11 at 14:53
  • @Chris, you're right! Well I'll be damned! – Shahbaz Sep 25 '11 at 15:49

4 Answers4

11

You just need to pull out your reference book and read how negative numbers are represented by your controller. The rest is just typing.

For example, two's complement is represented by taking the value mod 256, so you just need to adjust by the modulus.

int signed_from_unsignedchar(unsigned char c)
{
    int result = c;
    if (result >= 128) result -= 256;
    return result;
}

One's complement is much simpler: You just flip the bits.

int signed_from_unsignedchar(unsigned char c)
{
    int result = c;
    if (result >= 128) result = -(int)(unsigned char)~c;
    return result;
}

Sign-magnitude represents negative numbers by setting the high bit, so you just need to clear the bit and negate:

int signed_from_unsignedchar(unsigned char c)
{
    int result = c;
    if (result >= 128) result = -(result & 0x7F);
    return result;
}
Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
  • `result -= 256` doesn't mean negation. In fact it has no effect on `result`. `result = 256-result` is what you want – Shahbaz Sep 25 '11 at 14:27
  • 1
    @Shahbaz: He does *not* want to negate anything. He wants to get signed extension when his compiler is only willing to emit an unsigned one. And that is correctly done by subtracting the modulus of the smaller type. – Jan Hudec Sep 25 '11 at 15:16
  • I believe the one's complement case is not correct. The low byte is already correct in that case, so you only need to flip the high bytes, i.e. `result = c ^ ~0xff`. Note, that this will work for both one's complement and two's complement. Not that it matters too much; I wouldn't really expect to see any chip use one's complement these days, since two's complement is simply easier. – Jan Hudec Sep 25 '11 at 15:29
  • Ah yes, the `result` is of type `int`. My bad – Shahbaz Sep 25 '11 at 15:47
  • @Jan True, but it assumes that the host device and the controller are both ones-complement. (Maybe that's a safe assumption.) – Raymond Chen Sep 25 '11 at 15:49
1

I think this is what you're after (assumes a 32-bit int and an 8-bit char):

unsigned char c = 255;
int i = ((int)(((unsigned int)c) << 24)) >> 24;

of course I'm assuming here that your platform does support signed integers, which may not be the case.

Chris Eberle
  • 47,994
  • 12
  • 82
  • 119
1

Signed and unsigned values are all just a bunch of bits, it is YOUR interpretation that makes them signed or unsigned. For example, if your hardware produces 2's complement, if you read 0xff, you can either interpret it as -1 or 255 but they are really the same number.

Now if you have only unsigned char at your disposal, you have to emulate the behavior of negative values with it.

For example:

c < 0

changes to

c > 127

Luckily, addition doesn't need change. Also subtraction is the same (check this I'm not 100% sure).

For multiplication for example, you need to check it yourself. First, in 2's complement, here's how you get the positive value of the number:

pos_c = ~neg_c+1

which is mathematically speaking 256-neg_c which congruent modulo 256 is simply -neg_c

Now let's say you want to multiply two numbers that are unsigned, but you want to interpret them as signed.

unsigned char abs_a = a, abs_b = b;
char final_sign = 0; // 0 for positive, 1 for negative
if (a > 128)
{
    abs_a = ~a+1
    final_sign = 1-final_sign;
}
if (b > 128)
{
    abs_b = ~b+1
    final_sign = 1-final_sign;
}
result = abs_a*abs_b;
if (sign == 1)
    result = ~result+1;

You get the idea!

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
0

If your platform supports signed ints, check out some of the other answers.

If not, and the value is definitely between -85 and +85, and it is two's complement, add 85 to the input value and work out your program logic to interpret values between 0 and 170 so you don't have to mess with signed integers anymore.

If it's one's complement, try this:

if (x >= 128) {
    x = 85 - (x ^ 0xff);
} else {
    x = x + 85;
}

That will leave you with a value between 0 and 170 as well.

EDIT: Yes, there is also sign-magnitude. Then use the same code here but change the second line to x = 85 - (x & 0x7f).

cyco130
  • 4,654
  • 25
  • 34