Question

I am using LIS3DH sensor with ATmega128 to get the acceleration values to get motion. I went through the datasheet but it seemed inadequate so I decided to post it here. From other posts I am convinced that the sensor resolution is 12 bit instead of 16 bit. I need to know that when finding g value from the x-axis output register, do we calculate the two'2 complement of the register values only when the sign bit MSB of OUT_X_H (High bit register) is 1 or every time even when this bit is 0.

From my calculations I think that we calculate two's complement only when MSB of OUT_X_H register is 1.

But the datasheet says that we need to calculate two's complement of both OUT_X_L and OUT_X_H every time.

Could anyone enlighten me on this ?

Sample code

 int main(void)
 {      
     stdout = &uart_str;            
     UCSRB=0x18; // RXEN=1, TXEN=1 
     UCSRC=0x06; // no parit, 1-bit stop, 8-bit data
     UBRRH=0;
     UBRRL=71; // baud 9600

     timer_init();

     TWBR=216; // 400HZ
     TWSR=0x03;
     TWCR |= (1<<TWINT)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN);//TWCR=0x04;
     printf("\r\nLIS3D address: %x\r\n",twi_master_getchar(0x0F));  
     twi_master_putchar(0x23, 0b000100000);
     printf("\r\nControl 4 register 0x23: %x", twi_master_getchar(0x23));       
     printf("\r\nStatus register %x", twi_master_getchar(0x27));
     twi_master_putchar(0x20, 0x77);

     DDRB=0xFF;
     PORTB=0xFD;
     SREG=0x80; //sei();


     while(1)
     {              
         process();
     }
 }
 void process(void){
    x_l = twi_master_getchar(0x28);
    x_h = twi_master_getchar(0x29);
    y_l = twi_master_getchar(0x2a);
    y_h = twi_master_getchar(0x2b);
    z_l = twi_master_getchar(0x2c);
    z_h = twi_master_getchar(0x2d);
    xvalue = (short int)(x_l+(x_h<<8));
    yvalue = (short int)(y_l+(y_h<<8));
    zvalue = (short int)(z_l+(z_h<<8));
    printf("\r\nx_val: %ldg", x_val);
    printf("\r\ny_val: %ldg", y_val);
    printf("\r\nz_val: %ldg", z_val);
 }

I wrote the CTRL_REG4 as 0x10(4g) but when I read them I got 0x20(8g). This seems bit bizarre.

Was it helpful?

Solution

Do not compute the 2s complement. That has the effect of making the result the negative of what it was.

Instead, the datasheet tells us the result is already a signed value. That is, 0 is not the lowest value; it is in the middle of the scale. (0xffff is just a little less than zero, not the highest value.)

Also, the result is always 16-bit, but the result is not meant to be taken to be that accurate. You can set a control register value to to generate more accurate values at the expense of current consumption, but it is still not guaranteed to be accurate to the last bit.

OTHER TIPS

the datasheet does not say (at least the register description in chapter 8.2) you have to calculate the 2' complement but stated that the contents of the 2 registers is in 2's complement.

so all you have to do is receive the two bytes and cast it to an int16_t to get the signed raw value.

uint8_t xl = 0x00;
uint8_t xh = 0xFC;
int16_t x =  (int16_t)((((uint16)xh) << 8) | xl);

or

uint8_t xa[2] {0x00, 0xFC}; // little endian: lower byte to lower address
int16_t x = *((int16*)xa);

(hope i did not mixed something up with this)

I have another approach, which may be easier to implement as the compiler will do all of the work for you. The compiler will probably do it most efficiently and with no bugs too.

Read the raw data into the raw field in:

typedef union
{
    struct
    {
        // in low power - 8 significant bits, left justified
        int16 reserved : 8;
        int16 value    : 8;
    } lowPower;

    struct
    {
        // in normal power - 10 significant bits, left justified
        int16 reserved : 6;
        int16 value    : 10;

    } normalPower;

    struct
    {
        // in high resolution - 12 significant bits, left justified
        int16 reserved : 4;
        int16 value    : 12;

    } highPower;
    // the raw data as read from registers H and L
    uint16  raw;
} LIS3DH_RAW_CONVERTER_T;

than use the value needed according to the power mode you are using.

Note: In this example, bit fields structs are BIG ENDIANS. Check if you need to reverse the order of 'value' and 'reserved'.

The LISxDH sensors are 2's complement, left-justified. They can be set to 12-bit, 10-bit, or 8-bit resolution. This is read from the sensor as two 8-bit values (LSB, MSB) that need to be assembled together.

If you set the resolution to 8-bit, just can just cast LSB to int8, which is the likely your processor's representation of 2's complement (8bit). Likewise, if it were possible to set the sensor to 16-bit resolution, you could just cast that to an int16.

However, if the value is 10-bit left justified, the sign bit is in the wrong place for an int16. Here is how you convert it to int16 (16-bit 2's complement).

1.Read LSB, MSB from the sensor:

[MMMM MMMM] [LL00 0000]
[1001 0101] [1100 0000] //example = [0x95] [0xC0] (note that the LSB comes before MSB on the sensor)

2.Assemble the bytes, keeping in mind the LSB is left-justified. //---As an example.... uint8_t byteMSB = 0x95; //[1001 0101] uint8_t byteLSB = 0xC0; //[1100 0000]

//---Cast to U16 to make room, then combine the bytes---
assembledValue = ( (uint16_t)(byteMSB) << UINT8_LEN ) | (uint16_t)byteLSB;
/*[MMMM MMMM LL00 0000]
  [1001 0101 1100 0000] = 0x95C0 */

//---Shift to right justify---
assembledValue >>= (INT16_LEN-numBits);
/*[0000 00MM MMMM MMLL]
  [0000 0010 0101 0111] = 0x0257 */

3.Convert from 10-bit 2's complement (now right-justified) to an int16 (which is just 16-bit 2's complement on most platforms).

Approach #1: If the sign bit (in our example, the tenth bit) = 0, then just cast it to int16 (since positive numbers are represented the same in 10-bit 2's complement and 16-bit 2's complement).
If the sign bit = 1, then invert the bits (keeping just the 10bits), add 1 to the result, then multiply by -1 (as per the definition of 2's complement).

convertedValueI16 = ~assembledValue;          //invert bits
convertedValueI16 &= ( 0xFFFF>>(16-numBits) ); //but keep just the 10-bits
convertedValueI16 += 1;                       //add 1
convertedValueI16 *=-1; //multiply by -1
/*Note that the last two lines could be replaced by convertedValueI16 = ~convertedValueI16;*/
//result = -425 = 0xFE57 = [1111 1110 0101 0111]

Approach#2: Zero the sign bit (10th bit) and subtract out half the range 1<<9

//----Zero the sign bit (tenth bit)----
convertedValueI16 = (int16_t)(  assembledValue^( 0x0001<<(numBits-1) )  );  
/*Result = 87 = 0x57 [0000 0000 0101 0111]*/

//----Subtract out half the range----
convertedValueI16 -= ( (int16_t)(1)<<(numBits-1) ); 
  [0000 0000 0101 0111] 
 -[0000 0010 0000 0000]
= [1111 1110 0101 0111];
/*Result = 87 - 512 = -425 = 0xFE57

Link to script to try out (not optimized): http://tpcg.io/NHmBRR

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top