Help interpreting data from an I²C sensor

Thread Starter

Mark Hughes

Joined Jun 14, 2016
409
I'm writing an article about Infineon's 3D Sensor Magnetic Field Sensor -- the TLV394D. I decoded the I²C bus in the development kit and used the data to write the Arduino code. I can communicate with bi-directionally with the chip without a problem.

The trouble arises in that I'm uncomfortable publishing my conversion routines. My experience with bit-math is decades old. So if you are experienced in Arduino or C++ programming, please take a look and give some advice, criticisms, other ways to do things, etc...

The data (x,y,z,t) is 12-bit. Stored in the first six registers.

[0] (x11, x10, x9, x8, x7, x6, x5, x4)
[1] (y11, y10, y9, y8, y7, y6, y5, y4)
[2] (z11, z10, z9, z8, z7, z6, z6, z4)
[3] (t11, t10, t9, t8, , , , )
[4] (x3, x2, x1, x0, y3, y2, y1, y0)
[5] ( , , , , z3, z2, z1, z0)
[6] (t7, t6, t5, t4, t3, t2, t1, t0)

I need to first combine the bits of data. Then I need to decode them.

B12 | B11 | B10 | B9 | B8 | B7 | B6 | B5 | B4 | B3 | B2 | B1
-2048|+1024|+512|+256|+128|+64|+32|+16 | +8 | +4 | +2 | +1
Code:
// Include I²C wire interface library
#include <Wire.h>

// Variable Declaration
byte buffer[8];                           // store data from sensor registers.
const byte addr = 0x5E;                   // address of magnetic sensor
const byte ulpm[] = {0x00,0x05};          // ultra low power mode
const byte lpm[] = {0x00,0x05,0x00,0x40}; // low power mode
const byte fm[] = {0x00,0x06,0x00,0x00};  // fast mode (unsupported)

// Setup Commands
void setup() {
  Serial.begin(9600);    // Begin serial connection for debug.
  Wire.begin();          // Begin I²C wire communication

// Set Power Mode (ulpm, lpm, fm)
  Wire.beginTransmission(addr);      // transmit power mode to sensor
  for(int i=0; i<sizeof(ulpm); i++){ //ulpm, lpm, fm
    Wire.write(ulpm[i]);
    }
  Wire.endTransmission();
}

// Main Program Area
void loop() {
// Read sensor registers and store in buffer
  Wire.requestFrom(addr,sizeof(buffer));
    for(int i=0; i<sizeof(buffer)-1; i++){
      buffer[i] = Wire.read();
    }
int x = decodeX(buffer[0],buffer[4]);
int y = decodeY(buffer[1],buffer[4]);
int z = decodeZ(buffer[2],buffer[5]);
int t = decodeT(buffer[3],buffer[6]);

Serial.print(x); Serial.print("\t");Serial.print(y);Serial.print("\t");Serial.print(z);Serial.print("\t");Serial.println(t);
}

// Conversion Routines
int decodeX(int a, int b){
  int ans = (a << 4) + (b >> 4);
  if( ans > 2047){
      ans -= 4096;
      }
   return ans;
  }

int decodeY(int a, int b){
  a <<= 4;
  int ans = (a << 4) + (b &= 16);
  if( ans > 2047){ ans -= 4096;}
  return ans;
}

int decodeZ(int a, int b){
  a <<= 4;
  int ans = (a << 4) + (b &= 16);
  if( ans > 2047){ ans -= 4096;}
  return ans;
}

int decodeT(int a, int b){
  a >>= 4;
  int ans = (a << 8) + b;
  if( ans > 2047){ ans -= 4096;}
  return ans;
}
 

Attachments

Last edited by a moderator:

RK37

Joined Jun 26, 2015
677
Without a more careful evaluation I can't say whether or not I would expect your decode functions to work. All I can say after a quick look is that I think the following approach would be a little more straightforward. First, I would use a bitwise OR instead of addition. Shift the bits around and mask off irrelevant bits as needed, then bitwise OR them together. Then I would more directly implement the format given in the datasheet--i.e., I would check the value of bit 11 (or bit 12 using the datasheet's numbering) and subtract 2048 if it is logic high.

So, for example, with decodeY(), I would shift left variable a by 4 bits, then use (b & 0x0F) to clear the upper 4 bits of buffer[4], then I would use ans = (a | b), then I would use if(ans & 0x800) {ans = ans - 2048}.

Does that make sense to you? Am I missing something?
 

Thread Starter

Mark Hughes

Joined Jun 14, 2016
409
Without a more careful evaluation I can't say whether or not I would expect your decode functions to work. All I can say after a quick look is that I think the following approach would be a little more straightforward. First, I would use a bitwise OR instead of addition. Shift the bits around and mask off irrelevant bits as needed, then bitwise OR them together. Then I would more directly implement the format given in the datasheet--i.e., I would check the value of bit 11 (or bit 12 using the datasheet's numbering) and subtract 2048 if it is logic high.

So, for example, with decodeY(), I would shift left variable a by 4 bits, then use (b & 0x0F) to clear the upper 4 bits of buffer[4], then I would use ans = (a | b), then I would use if(ans & 0x800) {ans = ans - 2048}.

Does that make sense to you? Am I missing something?
@RK37
Thanks Robert -- I'll get to work on that straight away. Looking back at it a week and half later I think I have a bit of a better perspective as well. I see some errors in the posted code. But I like the shift & logic approach vs. my mix of addition & shift & logic & errors.
Thanks again for your time and expertise.
 
Last edited:

Thread Starter

Mark Hughes

Joined Jun 14, 2016
409
If it doesn't work, it was Tim's idea.
@RK37
So something along these lines? (Although I'm going to see if I can't clear up decodeX a bit and then double-check everything for accuracy)

Code:
*  Begin Conversion Routines */
int decodeX(int a, int b){
/* Shift all bits of register 0 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 shift in as zero.
* Shift all bits of register 4 to the right 4 positions.  Bit 8 becomes bit 4.  Bits 4-8 shift in as zero.
* Add results
*/
  int ans = ( a << 4 ) | (((b & B11110000) >> 4) & B00001111);
  if( ans > 2047){ ans -= 4096; } // Interpret bit 12 as +/-
  return ans;
  }

int decodeY(int a, int b){
/* Shift all bits of register 1 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 4 are true.  Add to previous answer.
*/

  int ans = (a << 4) | (b & B00001111);
  if( ans > 2047){ ans -= 4096;} // Interpret bit 12 as +/-
  return ans;
}

int decodeZ(int a, int b){
/* Shift all bits of register 2 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 5 are true.  Add to previous answer.
*/
  int ans = (a << 4) | (b & B00001111);
  if( ans > 2047){ ans -= 4096;}
  return ans;
}

int decodeT(int a, int b){
/* Determine which of the last 4 bits of register 3 are true.  Shift all bits of register 3 to the left
* 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 6 are true.  Add to previous answer.
*/
  int ans;
  a &= B11110000;
  ans = (a << 4) | b;
  if( ans > 2047){ ans -= 4096;}
  return ans;
}
 

Thread Starter

Mark Hughes

Joined Jun 14, 2016
409
I don't see why you're subtracting 4096. The datasheet says to subtract 2048 if bit 12 is logic high.
'Cause Tim told me to do it!
@RK37
Okay -- corrected that to if( ans > 1024){ ans -= 2048;}

Here's a current data table (with the error you just pointed out corrected). I'm still fighting with some errors in my code. For example, the Temperature measurements are all in the neighborhood of 344. Pg 9 has a 340 offset subtracted and the result multiplied by 1.1. Okay, that gives a temp of 4-5 C. That doesn't seem right.

Data is stable, except that at the center point of the joystick a slight deviation from center causes the values to jump as the flux lines pass from positive to negative.

Bx | By | Bz | T
2042 | 90 | 175 | 345
12 | 4 | 229 | 344
8 | 2033 | 228 | 343

I think next I'm going to throw the scope on it and have it decode the I2C Bus for a read cycle, do the math by hand and compare it to what the Arduino is telling me.

Here's the current code (although I'm in the middle of editing it to change a few things)
Code:
/*   Infinenon 3D Magnetic I2C
// Include I²C wire interface library
#include <Wire.h>

// Variable Declaration
const byte addr = 0x5E; // address of magnetic sensor 0x5E or 0x3E

// Read Registers
byte buffer[8]; // store data from sensor read registers

// Write Registers
/*  Mode1_Int   Bxxxxx1xx // Interrupt Enable "1" / Disable "0"
*  Mode1_Fast  Bxxxxxx1x // Fast Mode Enable "1" / Disable "0" must b 0 for power down
*  Mode1_Low   Bxxxxxxx1 // Low Power Mode Enable "0" / Disable "1"
*  Mode2_T     B1xxxxxxx // Temperature Measurement Enable "1" / Disable "0"
*  Mode2_LP    Bx1xxxxxx // LP Period "1" = 12ms / "0"=100ms
*  Mode2_PT    Bxx1xxxxx // Parity test Enable "1" / Disable "0"
*/
byte Mode1_Int  = B00000100; // Interrupt Enabled
byte Mode1_Fast = B00000010; // Fast Mode Enabled
byte Mode1_Low  = B00000001; // Low Power Mode Disabled
byte Mode2_T    = B10000000; //Enable Temperature Measurements
byte Mode2_LP   = B01000000; //Low Power Period "O" is 100ms, "1" is 12ms.
byte Mode2_PT   = B00000000; //Parity Test Enable "1"


const byte ulpm[] = {0x00,0x05,0x00,0x00};// ultra low power mode
const byte lpm[] = {0x00,0x05,0x00,0x40}; // low power mode
const byte fm[] = {0x00,0x06,0x00,0x00};  // fast mode (unsupported)

/* Begin Setup */
void setup() {
  Serial.begin(9600);    // Begin serial connection for debug.
  Wire.begin();            // Begin I²C wire communication

/* Read all registers, although only interested in configuration data
*  stored in buffers 7,8,9, as 0-6 might be empty or invalid at the moment.
*/
  Wire.requestFrom(addr,sizeof(buffer));
  for(int i=0; i<sizeof(buffer)-1; i++){
    buffer[i] = Wire.read();
    }

// Set Power Mode (ulpm, lpm, fm)
  Wire.beginTransmission(addr);    
  for(int i=0; i<sizeof(ulpm)-1; i++){
    Wire.write(ulpm[i]);           
    }
  Wire.endTransmission();
}
/* End of Setup */

/* Main Program Loop */
void loop() {
  delay(500);
// Read sensor registers and store in buffer
  Wire.requestFrom(addr,sizeof(buffer));
    for(int i=0; i<7; i++){
      buffer[i] = Wire.read();
    }

// Goto decode functions below 
int x = decodeX(buffer[0],buffer[4]);
int y = decodeY(buffer[1],buffer[4]);
int z = decodeZ(buffer[2],buffer[5]);
int t = decodeT(buffer[3],buffer[6]);

Serial.print(x); Serial.print("\t");Serial.print(y);Serial.print("\t");Serial.print(z);Serial.print("\t");Serial.println(t);
}
/* End of Main Program Loop */

/*  Begin Conversion Routines */
int decodeX(int a, int b){
/* Shift all bits of register 0 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 shift in as zero.
* Shift all bits of register 4 to the right 4 positions.  Bit 8 becomes bit 4.  Bits 4-8 shift in as zero.
* Add results
*/
  int ans = ( a << 4 ) | (((b & B11110000) >> 4) & B00001111);
  if( ans > 1023){ ans -= 2048; } // Interpret bit 12 as +/-
  return ans;
  }

int decodeY(int a, int b){
/* Shift all bits of register 1 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 4 are true.  Add to previous answer.
*/

  int ans = (a << 4) | (b & B00001111);
  if( ans > 1024){ ans -= 2048;} // Interpret bit 12 as +/-
  return ans;
}

int decodeZ(int a, int b){
/* Shift all bits of register 2 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 5 are true.  Add to previous answer.
*/
  int ans = (a << 4) | (b & B00001111);
  if( ans > 1024){ ans -= 2048;}
  return ans;
}

int decodeT(int a, int b){
/* Determine which of the last 4 bits of register 3 are true.  Shift all bits of register 3 to the left
* 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 6 are true.  Add to previous answer.
*/
  int ans;
  a &= B11110000;
  ans = (a << 4) | b;
  if( ans > 1024){ ans -= 2048;}
  return ans;
}

/*  Begin Conversion Routines */

/*  r=sqrt(x^2+y^2+z^2)
*  Θ=acos(z/r)
*  if x > 0 -> Φ=atan(y/x)
*  if x = 0 & y > 0 -> Φ=pi/2
*  if x = 0 & y < 0 -> Φ=-pi/2
*  if x < 0 & y >= 0 -> Φ=atan(y/x)+pi
*  if x < 0 & y < 0 -> Φ=atan(y/x)-pi
*/
Mod edit: created table from data
 
Last edited by a moderator:

Thread Starter

Mark Hughes

Joined Jun 14, 2016
409
Okay -- so this is the version of code that it going to go in the article. It implements the read/write back stipulated in the datasheet, one of the error checks (to see if the sensor is done collecting data before the arduino reads the data, if not, it will adjust the time in between reads), and hopefully the binary makes the settings a bit more understandable to the reader.\

Code:
/*   Infinenon 3D Magnetic I2C
*   TLV493D
*   by Mark J. Hughes
*   for AllAboutCircuits.com
*   20160817
*/

//--- Begin Includes ---//
#include <Wire.h>       // I²C Libraries

// Variable Declaration
const byte addr = 0x5E; // default address of magnetic sensor 0x5E or 0x3E
byte rbuffer[9];        // store data from sensor read registers
byte wbuffer[3];        // store data for sensor write registers.
byte debugcounter;      // variable for debug counter
byte delaytime = 10;    // time to wait before next read.  Delay will increase with errors.

//--- Begin Write Registers ---//
/* 
*  Mode 1 is the second write register
*  Mode1_Int   Bxxxxx1xx  Interrupt Enable "1" / Disable "0"
*  Mode1_Fast  Bxxxxxx1x  Fast Mode Enable "1" / Disable "0" must be 0 for power down
*  Mode1_Low   Bxxxxxxx1  Low Power Mode Enable "1" / Disable "0"
* 
*  Mode 2 is the fourth write register
*  Mode2_T     B1xxxxxxx  Temperature Measurement Enable "1" / Disable "0"
*  Mode2_LP    Bx1xxxxxx  LP Period "1" = 12ms / "0"=100ms
*  Mode2_PT    Bxx1xxxxx  Parity test Enable "1" / Disable "0"
* 
*/
//                       Reg 1      Reg 2      Reg 3      Reg 4          
const byte ulpm[] = { B00000000, B00000101, B00000000, B00000000 }; // ultra low power mode
const byte lpm[]  = { B00000000, B00000101, B00000000, B01000000 }; // low power mode
const byte fm[]   = { B00000000, B00000110, B00000000, B00000000 }; // fast mode (unsupported)
const byte pd[]   = { B00000000, B00000001, B00000000, B00000000 }; // power down mode.
//--- End Write Registers ---//

//--- Begin Setup ---//
void setup() {
  Serial.begin(115200);      // Begin serial connection for debug.
  Wire.begin();              // Begin I²C wire communication

/* Read all registers, although only interested in configuration data
* stored in rbuffers 7,8,9, as 0-6 might be empty or invalid at the moment.
*/
  Wire.requestFrom(addr,sizeof(rbuffer));
  for(int i=0; i<sizeof(rbuffer)-1; i++){
    rbuffer[i] = Wire.read();
    }
// Write Register 0H is non configurable.  Set all registers to 0
wbuffer[0] = B00000000;

// Read Register 7H 6:3 -> Write Register 1H 6:3
wbuffer[1] = rbuffer[7] && B01111000;

// Read Register 8H 7:0 -> Write Register 2H 7:0
wbuffer[2] = rbuffer[8];

// Read Register 9H 4:0 -> Write Register 3H 4:0 (Mod2)
wbuffer[3] = rbuffer[9] && B00001111;
 
// Set Power Mode (ulpm, lpm, fm, pd)
for(int i=0; i<sizeof(wbuffer)-1; i++){
  wbuffer[i] |= lpm[i];
}

  Wire.beginTransmission(addr);       
  for(int i=0; i<sizeof(wbuffer)-1; i++){
    Wire.write(wbuffer[i]);              
    }
  Wire.endTransmission();
}
//--- End of Setup --//

//--- Begin Main Program Loop --//
void loop() {
 
delay(delaytime); // wait time between reads.
// Read sensor registers and store in rbuffer
  Wire.requestFrom(addr,sizeof(rbuffer));
    for(int i=0; i<7; i++){
      rbuffer[i] = Wire.read();
    } 

// Goto decode functions below    
int x = decodeX(rbuffer[0],rbuffer[4]);
int y = decodeY(rbuffer[1],rbuffer[4]);
int z = decodeZ(rbuffer[2],rbuffer[5]);
int t = decodeT(rbuffer[3],rbuffer[6]);

if(debugcounter % 15 == 0){   // reprint x, y, z, t header every 15 lines.
Serial.print("x"); Serial.print("\t");Serial.print("y");Serial.print("\t");Serial.print("z");Serial.print("\t");Serial.println("t");
}
j++;               // increment debug counter.

if(rbuffer[3] & B00000011 != 0){ // If bits are not 0, TLV is still reading Bx, By, Bz, or T
  Serial.println("Data read error!");
  delaytime += 10;
} else { Serial.print(x); Serial.print("\t");Serial.print(y);Serial.print("\t");Serial.print(z);Serial.print("\t");Serial.println(t);}
}
//-- End of Main Program Loop --//

//-- Begin Buffer Decode Routines --//
int decodeX(int a, int b){
/* Shift all bits of register 0 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0:3 shift in as zero.
* Determine which of bits 4:7 of register 4 are high, shift them to the right four places -- remask in case
* they shift in as something other than 0.  bitRead and bitWrite would be a bit more elegant in next version
* of code.
*/
  int ans = ( a << 4 ) | (((b & B11110000) >> 4) & B00001111);
  if( ans > 1023){ ans -= 2048; } // Interpret bit 12 as +/-
  return ans;
  }

int decodeY(int a, int b){
/* Shift all bits of register 1 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 shift in as zero.
* Determine which of the first four bits of register 4 are true.  Add to previous answer.
*/

  int ans = (a << 4) | (b & B00001111);
  if( ans > 1024){ ans -= 2048;} // Interpret bit 12 as +/-
  return ans;
}

int decodeZ(int a, int b){
/* Shift all bits of register 2 to the left 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 5 are true.  Add to previous answer.
*/
  int ans = (a << 4) | (b & B00001111);
  if( ans > 1024){ ans -= 2048;}
  return ans;
}

int decodeT(int a, int b){
/* Determine which of the last 4 bits of register 3 are true.  Shift all bits of register 3 to the left
* 4 positions.  Bit 8 becomes bit 12.  Bits 0-3 are zero.
* Determine which of the first four bits of register 6 are true.  Add to previous answer.
*/
  int ans;
  a &= B11110000;
  ans = (a << 4) | b;
  if( ans > 1024){ ans -= 2048;}
  return ans;
}
//-- End Buffer Decode Routines --//
 
Top