Reading signed ints through I2C

I am currently working with this accelerometer

When flat on the table, the value will jump between 0 and 65535. When flipped 180 degrees, there is again a discontinuity where it jumps between 0 and 65535.

To clarify, on the 0 side, the number will increase until it reaches 90 degrees and then decrease again back down to 0. The 65535 side does the same.

I would guess that this has to do with the fact that the sensor is (in theory) sending a signed number, but the arduino is reading it as an unsigned one.

[xl and xh also exhibit similar behavior, so it doesn’t seem to be an issue with adding the high and low bits together]

Code:

#include <Wire.h>
int add = 0x1E;
signed int xh = 0.00;
signed int xl = 0.00;
float x = 0.00;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin();
  
  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0x00); //00000000 0x00
  Wire.endTransmission();
  
  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0x50); //01010000 0x50
  Wire.endTransmission();

  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0xD0); //11010000 0xD0
  Wire.endTransmission();
}

void loop() {
  // put your main code here, to run repeatedly:
  Wire.beginTransmission(add);
  Wire.write(0x06);
  Wire.endTransmission();
  
  Wire.requestFrom(add, 1);
  while(Wire.available() == 0); //
  xl = Wire.read();
  
  Wire.beginTransmission(add);
  Wire.write(0x07);
  Wire.endTransmission();

  Wire.requestFrom(add, 1);
  while(Wire.available() == 0);
  xh = Wire.read() << 8;

  x = xh | xl; // * 360 / 65535

  Serial.println(x);
}

Any thought?

signed int xh = 0.00; "int" is a contraction of "integer"

Whoops! I had that as a float before to try some stuff, which is why I made it 0.00. Even with just setting it to 0 doesn't yield any better results :frowning:

int is signed by default. You don’t need to say “signed”.

It is going wrong at the point where you do the binary-or and then immediately convert that into a float without giving the compiler any clues on what you want it to do. It seems to think that the result of the binary-or is unsigned, despite starting with signed ints.

Additionally, it seems there may be something wrong with your accelerometer. Sitting on the desk (on Earth) is like sitting in a dragster car doing a 7-second pass down the quarter mile. (That’s eyeball-squishingly fast, if you don’t know.) You should not get readings near zero unless the accelerometer is on its side.

This

  Wire.beginTransmission(add);
  Wire.write(0x06);
  Wire.endTransmission();
  Wire.requestFrom(add, 1);
  while(Wire.available() == 0); //
  xl = Wire.read();
  
  Wire.beginTransmission(add);
  Wire.write(0x07);
  Wire.endTransmission();

  Wire.requestFrom(add, 1);
  while(Wire.available() == 0);
  xh = Wire.read() << 8;

is a painfully inefficient and slow way of reading out the acceleration. You can in fact read 3x16 bit values in one read operation, as a little study of the device data sheet will show. From the data sheet:

The accelerometer automatically increments through its sequential registers, allowing data to be read from multiple registers following a single SAD+R command as shown below in Sequence 4 on the following page.

I don’t have that accelerometer to test, but something like this should work for up to six byte (X, Y and Z acceleration register) transfers:

  Wire.beginTransmission(add);
  Wire.write(0x06);
  Wire.endTransmission(false); //keep bus active
  Wire.requestFrom(add, 2,true); //send stop when done with transfer
  int xl = Wire.read();  //Wire.available() is not required
  int xh = Wire.read()<<8;
  int x_acc = xh|xl; //corrected thanks to Koepel below

As mentioned above, if the accelerometer is functioning correctly, it will report 1 g along the positive vertical axis, when it is sitting on a table. Flip it over and it will report -1 g.

electricviolin:

while(Wire.available() == 0); //

Please don’t use that while-statement. It is nonsense code.

Which Arduino board do you use ?
When you use an Arduino Uno then an integer is 16 bits, but an Arduino is a 5V board, and that will damage the sensor.
When you use a 3.3V Arduino MKR or Zero, then an integer is 32 bits.

According the datasheet, the data is a signed integer and the lower bytes comes first. The Wire library uses unsigned bytes, but the Wire.read() returns an integer to allow an error code of -1.

I prefer local variables, and I prefer to get the data from the sensor copied into a variable before converting it to a float.
Since the sensor uses a 16-bit signed integer, I want to get that data into a 16-bit signed integer.

You may put the bytes from the sensor in integer or in bytes, and then combine them into a 16-bits signed integer.

int xl = Wire.read();
int xh = Wire.read()<<8;
int16_t xRaw = xh | xl;
float x = float( xRaw);  // convert to a float if you want a float

@jremington, the ‘x’ is still a float in the sketch by electricviolin, as @MorganS already wrote.

@electricviolin, please show us the complete sketch of what you have so far.

Yes. Probably a 32-bit board. The conversion to float is signed by default but with only the bottom two bytes of a 32-bit integer filled in, you don't provide data for the sign bit.

Okay so I took a look at what everyone said and got a bit of help from a friend. This is what I have now:

#include <Wire.h>
int add = 0x1E;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin();
  
  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0x00); //00000000 0x00
  Wire.endTransmission();
  
  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0x50); //01010000 0x50
  Wire.endTransmission();

  Wire.beginTransmission(add);
  Wire.write(0x18);
  Wire.write(0xD0); //11010000 0xD0
  Wire.endTransmission();
}

void loop() {
  // put your main code here, to run repeatedly:
  Wire.beginTransmission(add);
  Wire.write(0x06);
  Wire.endTransmission();
  
  Wire.requestFrom(add, 2, true);
  short cxl = Wire.read();
  short cxh = Wire.read() << 8; 

  short x = cxl | cxh;
  Serial.println(x);
}

[by the way, I am sure there is a more efficient way to write to the registers than I have done in the void setup, if anyone has any suggestions, please let me know!].

I think when I said “flat on the table”, there may have been a bit misleading. I believe the accelerator is working fine. Since I am reading the x-axis, the max value should be when it is sideways according to the axes given in the datasheet.

For a stationary sensor, the maximum acceleration values reported for any axis will be observed when that axis is pointing directly up or down, positive or negative as appropriate.