[SOLVED] Two's Complement Conversion

Good Evening,

I posted lately for help with my self-balancing robot project, I was concerned that my sensors were returning faulty values as the range seemed to be only either under 250 or over 65000. As it turns out, it seems that the sensors are returning the negative readings in two's complement which wasn't being displayed correctly. I have attempted to correct for this using an if statement and some bitwise operators (only for the gyro in the X axis at this time). Unfortunately, it does not seem to be working at this time as the data now returns as all zeros when this axis is negative. Am I missing something?

int accel_x;
int accel_y;
int accel_z;

int gyro_x;
int neg_gyro_x = (~gyro_x) + 1; // two's complement conversion
int gyro_y;
int gyro_z;


void setup()
{
  // Init serial output
  Serial.begin(57600);
  
    // Init sensors
  delay(50);  // Give sensors enough time to start
  I2C_Init();
  Accel_Init();
  Gyro_Init();
}

void loop()
{
  Read_Accel();
  Serial.print("#A:");
  Serial.print(accel_x); Serial.print(",");
  Serial.print(accel_y); Serial.print(",");
  Serial.print(accel_z); Serial.println();
  
  Read_Gyro();
  Serial.print("#G:");
  Serial.print(gyro_x); Serial.print(",");
  Serial.print(gyro_y); Serial.print(",");
  Serial.print(gyro_z); Serial.println();
}

// *******************I2C code to read the sensors************************
#include <Wire.h>

// Sensor I2C addresses
#define ACCEL_ADDRESS ((int) 0x53) // 0x53 = 0xA6 / 2
#define GYRO_ADDRESS  ((int) 0x68) // 0x68 = 0xD0 / 2


void I2C_Init()
{
  Wire.begin();
}

void Accel_Init()
{
  Wire.beginTransmission(ACCEL_ADDRESS);
  Wire.write(0x2D);  // Power register
  Wire.write(0x08);  // Measurement mode
  Wire.endTransmission();
  delay(5);
  Wire.beginTransmission(ACCEL_ADDRESS);
  Wire.write(0x31);  // Data format register
  Wire.write(0x08);  // Set to full resolution
  Wire.endTransmission();
  delay(5);
  
  // Adjust the output data rate to 100Hz (50Hz bandwidth)
  Wire.beginTransmission(ACCEL_ADDRESS);
  Wire.write(0x2C);  // Rate
  Wire.write(0x0A);  // Set to 100Hz, normal operation
  Wire.endTransmission();
  delay(5);
}

// Reads x, y and z accelerometer registers
void Read_Accel()
{
  int i = 0;
  byte buff[6];
  
  Wire.beginTransmission(ACCEL_ADDRESS); 
  Wire.write(0x32);  // Send address to read from
  Wire.endTransmission();
  
  Wire.beginTransmission(ACCEL_ADDRESS);
  Wire.requestFrom(ACCEL_ADDRESS, 6);  // Request 6 bytes
  while(Wire.available())  // ((Wire.available())&&(i<6))
  { 
    buff[i] = Wire.read();  // Read one byte
    i++;
  }
  Wire.endTransmission();
  {
    accel_x = (((int) buff[3]) << 8) | buff[2];  // X axis (internal sensor y axis)
    accel_y = (((int) buff[1]) << 8) | buff[0];  // Y axis (internal sensor x axis)
    accel_z = (((int) buff[5]) << 8) | buff[4];  // Z axis (internal sensor z axis)
  }
}

void Gyro_Init()
{
  // Power up reset defaults
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.write(0x3E);
  Wire.write(0x80);
  Wire.endTransmission();
  delay(5);
  
  // Select full-scale range of the gyro sensors
  // Set LP filter bandwidth to 42Hz
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.write(0x16);
  Wire.write(0x1B);  // DLPF_CFG = 3, FS_SEL = 3
  Wire.endTransmission();
  delay(5);
  
  // Set sample rato to 100Hz
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.write(0x15);
  Wire.write(0x09);  //  SMPLRT_DIV = 9 (100Hz)
  Wire.endTransmission();
  delay(5);

  // Set clock to PLL with z gyro reference
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.write(0x3E);
  Wire.write(0x00);
  Wire.endTransmission();
  delay(5);
}

// Reads x, y and z gyroscope registers
void Read_Gyro()
{
  int i = 0;
  byte buff[6];
  
  Wire.beginTransmission(GYRO_ADDRESS); 
  Wire.write(0x1D);  // Sends address to read from
  Wire.endTransmission();
  
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.requestFrom(GYRO_ADDRESS, 6);  // Request 6 bytes
  while(Wire.available())  // ((Wire.available())&&(i<6))
  { 
    buff[i] = Wire.read();  // Read one byte
    i++;
  }
  Wire.endTransmission();
  {
    gyro_x = (((buff[2]) << 8) | buff[3]);    // X axis (internal sensor -y axis)
    gyro_y = (((buff[0]) << 8) | buff[1]);    // Y axis (internal sensor -x axis)
    gyro_z = (((buff[4]) << 8) | buff[5]);    // Z axis (internal sensor -z axis)
  }
  {
    abs(gyro_x);
    if (gyro_x > 50000) // this statement handles 2's complements.
    {
      gyro_x = neg_gyro_x; // see variable declarations at top
    }
  }
}

Andrew

1 Like

If it is the correct type, then "twos complement" numbers should display correctly, it is the standard for negative integer types, you don't normally have to do bit manipulations yourself. That said, there are one or two devices which handle negative numbers in an obscure or non-standard way.

Your real problem, would appear to be that you are processing the odd-numbered and even-numbered bytes of "buff", the wrong way around in function read_accel().

andrewlumley:

int gyro_x;

int neg_gyro_x = (~gyro_x) + 1; // two's complement conversion
}

Here's what these two lines do:

  • Declare a global integer variable gyro_x. Because it's scope is global, it's initialized to zero.
  • Declare a global integer variable neg_gyro_x. It's explicitly initialized to be the two's complement of gyro_x. gyro_x is zero, so it's two's complement is also zero, and neg_gyro_x is zero, too.

neg_gyro_x is never assigned a different value: it's zero forever. Whenever the test passes in this code, gyro_x will be set to zero:

    if (gyro_x > 50000) // this statement handles 2's complements.
    {
      gyro_x = neg_gyro_x; // see variable declarations at top
    }

It looks like you intended that neg_gyro_x would always take on the value of the two's complement of gyro_x, whenever it's referenced. It won't. It will have the value that it took on the last time it was assigned.

I think that you intended that this line

    abs(gyro_x);

would change the value of gyro_x to its absolute value. It won't do that. It won't do anything at all. To do that, you'd have to say this:

    gyro_x = abs(gyro_x);

That might be all you want to do - that'll leave gyro_x unchanged when it's positive, and reassign it to its own two's complement when it's negative. If you do that reassignment, the test that immediately follows will always fail, since gyro_x will be a value between zero and 32767, and will never be greater than 50000; the test and assignment won't do anything. If that's what you wanted, the program would be clearer if the unnecessary lines were removed.

michinyon:
If it is the correct type, then "twos complement" numbers should display correctly, it is the standard for negative integer types, you don't normally have to do bit manipulations yourself. That said, there are one or two devices which handle negative numbers in an obscure or non-standard way.

Your real problem, would appear to be that you are processing the odd-numbered and even-numbered bytes of "buff", the wrong way around in function read_accel().

What do you mean by processing the bytes backwards, I tried switching the "buff" values around in the bit shift operator but that didn't seem to do what I wanted.

tmd3:
That might be all you want to do - that'll leave gyro_x unchanged when it's positive, and reassign it to its own two's complement when it's negative. If you do that reassignment, the test that immediately follows will always fail, since gyro_x will be a value between zero and 32767, and will never be greater than 50000; the test and assignment won't do anything. If that's what you wanted, the program would be clearer if the unnecessary lines were removed.

All I want the program to do at this point is display the negative integers as say "-31" instead of "65504". The process I was attempting in my code was for the x-axis of the gyro. It would take the absolute value of all returns; check if that was greater than 50000 (i.e. one of the 65*** numbers) and if so take it's complement and add 1 to convert it into a negative integer and display that number instead. What am I doing wrong?

You won't get "65000" values from an int on most Arduinos (exception is the Due
which is 32 bit), since they use signed 16 bit for "int". Which Arduino is this?

Signed integers are 2's complement representation, you never have to do anything
special if you've put the bits in the right place.

What do you mean by processing the bytes backwards, I tried switching the "buff" values around in the bit shift operator but that didn't seem to do what I wanted.

What device are you using ? I find it very hard to believe that the each two bytes of the accelerometer data are given to you in the opposite order that each two bytes of the gyro data are. But that's what your code does. It seems unlikely. Does the datasheet for the device say that ?

Can you collect the actual six bytes you are collecting from each and print them as actual hex values ? I think you are barking up the wrong tree with your twos complement problem.

MarkT:
You won't get "65000" values from an int on most Arduinos (exception is the Due
which is 32 bit), since they use signed 16 bit for "int". Which Arduino is this?

Signed integers are 2's complement representation, you never have to do anything
special if you've put the bits in the right place.

I think this could shed some light as I am using the Arduino Due. How may I default to a signed 16 bit "int"?

How may I default to a signed 16 bit "int"?

Use "short"

1 Like

So when I use "short" I now get positive and negative numbers, however the numbers aren't making a ton of sense, when the acceleration is minimal in the x-axis, the accelerometer is returning about 8500, yet when I lay it down on either side (for maximum gravitational acceleration) I get -500 and 3000 respectively. I don't know how I would ever get any useful data out of this as the conversion factor on the data sheet is 3.9mg/LSB. This would mean maximums of -1.95g and +11.7g, where the data range is only set to ±2g and the accelerometer should be returning ±1g under these conditions...

Okay, so I've done a little research and found this: http://forum.arduino.cc/index.php?topic=147612.0 discussing 16 bit int's on the Arduino Due. I used the library suggested in that thread and I get positive values on one side, negative on the other and 0 in the middle like I was hoping. The only remaining problem on this front is the range, it seems that any more than 30 degrees to either side and the signed int reaches 32267 which as I understand is the largest possible signed 16 bit number. Any further than this and the numbers start to decrease again resulting in the wacky readings at ±1g. How can I scale these values back to remain within the acceptable range while still allowing me to convert the results into an acceleration later using the data sheet's 3.9mg/LSB conversion factor?

What does the raw data from the input devices look like?

The data looks something like this:

#A:-32513,512,-8448
#G:11,-97,-11
#A:-32513,512,-8448
#G:11,-97,-11
#A:-32513,768,-8448
#G:12,-99,-12
#A:32767,768,-8960
#G:13,-103,-13
#A:31999,512,-8960
#G:15,-103,-15
#A:31999,512,-8960
#G:15,-103,-15
#A:32767,256,-8704

Notice how at first the accelerometer is reading -32513 in the x-axis but when I tilt it father it rolls over to +32767.

So its offset by 2^15 rather than signed 2's complement - you just have
to flip the top bit:

  val += 0x8000 ;  // or val ^= 0x8000

MarkT:
So its offset by 2^15 rather than signed 2's complement - you just have
to flip the top bit:

  val += 0x8000 ;  // or val ^= 0x8000

(I just read your signature about no messages, my mistake) i just wanted a little clarification over your suggestion. Why do you believe that it is offset seeing as when the board is held still and level it reads close to zero?

The sensor isn't outputing 2's complement, its outputing an offset value, you need to
remove the offset to get 2's complement. This isn't unusual for sensors, it takes a
knowledge of binary arithmetic/representation and reading the datasheet carefully
to see whether you have to do any conversion code your self.

Alright, well it seems that I've finally got it working. The second problem ended up being that my MSB and LSB for the accelerometer bit shift operation was back wards which were messing up the readings, thank you all very much for your help and have a great weekend!