Infineon 3D Magnetic Sensor TLV493D-A1B6

I am posting my arduino code that I am using for this sensor as an example of how to use it. So far I am impressed with its stability and can be found here 3DSensor.

The first thing you should do is read the data sheet and ensure you have it wired correctly. The most important thing to do is ensure that your SDA/SCL lines are at 3.3V. The UNO has 5V on the SDA/SCL line and so you will need a logic level shifter, which will protect your 3D sensor. The code is rough and I am sure there are some useful tips that I could use to improve the code, hence I am posting it here.

I surface mounted this sensor on a milled out breakout board and wired it on a solder less board to a Huzzah Feather adafruit-feather-huzzah-esp8266. This feather's SDA/SCL lines work at 3.3V, so I did not need a level shifter. I am using 10KOhm pull-up-resistors on the SDA and SCL line. This gives me about a 100Hz sample rate. If wired correctly the sensor can run at 3.3MHz sampling rate, more than I need currently. I have a 0.1uF capacitor between Vdd and GND (datasheet has an example application circuit that I am following).

You will need to run I2C scanner code first to determine the devices I2C address. After, update the HallAddressWrite variable to reflect the address.

The sensor is not yet sold on a breakout board, but you should be able to buy a TSOP-6 pin breakout board and mount the sensor to it.

// Define includes
#include <Wire.h>        // Wire header file for I2C and 2 wire

// 
int HallAddressWrite = 0x1F;   // Device address
byte X_Axis_Register1 = 0x1;  //

// Declare some variables
int const numOfBytes = 7;
int baudRate = 9600;
byte readByte[numOfBytes];
float counter=0.0;

byte configReg = 0x00;  // Address of Configuration
byte powerMode = 0x05;   // Set to low power mode


void setup() {
  Serial.begin(baudRate);            // Set Serial Port speed
  delay(1000);
  Wire.pins(4, 5);
  Wire.begin();                      // Join the I2C bus as a master

  Wire.beginTransmission(HallAddressWrite);       // Address the sensor
  Wire.write(configReg);              // Address the Configuration register
  Wire.write(powerMode);              // Set the Power Mode to Low
  Wire.endTransmission();             // Stop transmitting
  delay(100);
}

// Main Program Infinite loop
void loop() {


  getBytes(HallAddressWrite, X_Axis_Register1, numOfBytes, readByte); //Read first 8 bytes

  float temp = getTemp();
  if(temp<-50){ //re-read address banks, bad measurement
    while(temp<-50){
      getBytes(HallAddressWrite, X_Axis_Register1, numOfBytes, readByte); //Read first 8 bytes
      temp = getTemp();
    }
  }
  Serial.print("\t");
  getMagX();
  Serial.print("\t");
  getMagY();
  Serial.print("\t");
  getMagZ();

  Serial.println("-----");
  delay(250);

}

float getFrameCounter() { //every ADC conversion the frame is incremented,this function captures that
                          //This would be needed probaly in fast mode to ensure data 
                          //was succesfully written to the registries
  int i;
  int a[8];
  int lsb = readByte[3];
  int tableI[8] = { 0, 0, 0, 0, 1, 1, 0, 0};


  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & lsb)
      a[7 - i] = 1*tableI[7-i];
    else
      a[7 - i] = 0;
    counter += a[7 - i];
  }
  Serial.print(counter);
  return counter;
}

float getTemp()
{
  int i;
  int a[8];
  int b[8];
  int tableI[8] = { -2048, 1024, 512, 256, 0, 0, 0, 0};
  int tableII[8] = {128, 64, 32, 16, 8, 4, 2, 1};
  float Celsius = 0;
  int msb = readByte[3];
  int lsb = readByte[6];

  //msb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & msb)
      a[7 - i] = 1 * tableI[7 - i];
    else
      a[7 - i] = 0;
    //    Serial.print(a[7 - i]);Serial.print(" ");
    Celsius += a[7 - i];
  }
  //  Serial.print("\t");
  a[8] = 0; // ascii terminating character

  //lsb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & lsb)
      b[7 - i] = 1 * tableII[7 - i];
    else
      b[7 - i] = 0;
    //    Serial.print(b[7 - i]);Serial.print(" ");
    Celsius += b[7 - i];
  }
  //  Serial.print("\t");
  b[8] = 0; // ascii terminating character

  Celsius -= 320;
  Celsius *= 1.1;
  if(Celsius>-50){
    Serial.print(Celsius);
  }
  return Celsius;

}

void getBytes(byte address, byte registry, int numOfBytes, byte* readByte)
{
  Wire.beginTransmission(address);  //Begin Transmission
  //Ask for data register
  Wire.write(registry);
  Wire.endTransmission();             //End Transmission
  delay(20);                        //at least 12msec for ADC conversion and storage
  Wire.requestFrom(address, numOfBytes);     //Request Transmission
  for (int i = 0; i < numOfBytes; i++) {
    readByte[i] = Wire.read();
  }
  Wire.endTransmission();

}

float getMagX()
{
  int i;
  int a[8];
  int b[8];
  int tableI[8] = { -2048, 1024, 512, 256, 128, 64, 32, 16};
  int tableII[8] = {8, 4, 2, 1, 0, 0, 0, 0};
  float magX = 0;
  int msb = readByte[0];
  int lsb = readByte[4];

  //msb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & msb)
      a[7 - i] = 1 * tableI[7 - i];
    else
      a[7 - i] = 0;
    //    Serial.print(a[7 - i]);Serial.print(" ");
    magX += a[7 - i];
  }
  //  Serial.print("\t");
  a[8] = 0; // ascii terminating character

  //lsb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & lsb)
      b[7 - i] = 1 * tableII[7 - i];
    else
      b[7 - i] = 0;
    //    Serial.print(b[7 - i]);Serial.print(" ");
    magX += b[7 - i];
  }
  //  Serial.print("\t");
  b[8] = 0; // ascii terminating character

  magX *= 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT
  if(abs(magX)<3)   //the sensor has about a 0.2mT | 2Gauss units drift
    magX = 0;       //this is a software filter that suppress most of the noise
  Serial.print(magX);
  return magX;

}

float getMagY()
{
  int i;
  int a[8];
  int b[8];
  int tableI[8] = { -2048, 1024, 512, 256, 128, 64, 32, 16};
  int tableII[8] = {0, 0, 0, 0, 8, 4, 2, 1};
  float magY = 0;
  int msb = readByte[1];
  int lsb = readByte[4];

  //msb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & msb)
      a[7 - i] = 1 * tableI[7 - i];
    else
      a[7 - i] = 0;
    //    Serial.print(a[7 - i]);Serial.print(" ");
    magY += a[7 - i];
  }
  //  Serial.print("\t");
  a[8] = 0; // ascii terminating character

  //lsb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & lsb)
      b[7 - i] = 1 * tableII[7 - i];
    else
      b[7 - i] = 0;
    //    Serial.print(b[7 - i]);Serial.print(" ");
    magY += b[7 - i];
  }
  //  Serial.print("\t");
  b[8] = 0; // ascii terminating character

  magY *= 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT
  if(abs(magY)<3)    //the sensor has about a 0.2mT | 2Gauss units drift
    magY = 0;        //this is a software filter that suppress most of the noise
  Serial.print(magY);
  return magY;

}

float getMagZ()
{
  int i;
  int a[8];
  int b[8];
  int tableI[8] = { -2048, 1024, 512, 256, 128, 64, 32, 16};
  int tableII[8] = {0, 0, 0, 0, 8, 4, 2, 1};
  float magZ = 0;
  int msb = readByte[2];
  int lsb = readByte[5];

  //msb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & msb)
      a[7 - i] = 1 * tableI[7 - i];
    else
      a[7 - i] = 0;
    //    Serial.print(a[7 - i]);Serial.print(" ");
    magZ += a[7 - i];
  }
  //  Serial.print("\t");
  a[8] = 0; // ascii terminating character

  //lsb conversion
  for ( i = 7; i >= 0; i--)
  {
    if ( (1 << i) & lsb)
      b[7 - i] = 1 * tableII[7 - i];
    else
      b[7 - i] = 0;
    //    Serial.print(b[7 - i]);Serial.print(" ");
    magZ += b[7 - i];
  }
  //  Serial.print("\t");
  b[8] = 0; // ascii terminating character

  magZ *= 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT
  if(abs(magZ)<3) //the sensor has about a 0.2mT | 2Gauss units drift
    magZ = 0;     //this is a software filter that suppress most of the noise
  Serial.print(magZ);
  return magZ;

}

Thanks so much for posting this code up - I'm just getting started with this sensor and it's exactly what I was looking for.

Just fyi, Infineon now sell this sensor on a breakout of sorts: http://www.infineon.com/cms/en/product/promopages/sensors-2go/

Their board features a microprocessor for talking to the sensor and a separate one to handle USB comms with a PC via their software (uses SEGGER J-Link protocol, which I have no idea about).

It's a nice demonstrator of their system, but more useful for my purposes (and probably yours too) is the fact that they have the I2C pins broken out so that you can either snap off the little Hall sensor board from the tip or use it with the main board connected.

I've reimplemented your code using DSS Circuits' I2C library, as i was having trouble with Wire.h hanging in when using the fast power mode. The alternative I2C library includes a timeout which recovers from a hang most of the time, as well as some other useful features.

I might post up the full code when I've fully debugged it, but the most useful part if you don't care about scanning speed may be how to extract data from the registers.

So here are some functions to read each parameter in a quicker way than using for loops:

int getCounter(){
  char msb = readByte[3];
  msb = msb & 0xC;
  msb = msb >> 2;
  return msb;
}

int getChannel(){
  char msb = readByte[3];
  msb = msb & 0x3;
  return msb;
}

int getPD(){
  char msb = readByte[5];
  msb = msb & 0x10;
  msb = msb >> 4;
  return msb;
}

float getMagX(){
  char msb = readByte[0];
  char lsb = readByte[4];
  lsb = lsb & 0xf0;
  
  unsigned short xx;
  void *vp;
  char *pmsb;
  char *plsb;
  signed short zz;
  vp = &xx;
  plsb = (char *)vp;
  pmsb = plsb + 1;
  *pmsb = msb;
  *plsb = lsb;
  zz = xx & 0xfff0;
  zz = zz / 16;
  return (float)zz * 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT;
}

float getMagY(){
  char msb = readByte[1];
  char lsb = readByte[4];
  lsb = lsb & 0x0f;
  lsb = lsb << 4;
  
  unsigned short xx;
  void *vp;
  char *pmsb;
  char *plsb;
  signed short zz;
  vp = &xx;
  plsb = (char *)vp;
  pmsb = plsb + 1;
  *pmsb = msb;
  *plsb = lsb;
  zz = xx & 0xfff0;
  zz = zz / 16;
  return (float)zz * 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT;
}

float getMagZ(){
  char msb = readByte[2];
  char lsb = readByte[5];
  lsb = lsb & 0x0f;
  lsb = lsb << 4;
  
  unsigned short xx;
  void *vp;
  char *pmsb;
  char *plsb;
  signed short zz;
  vp = &xx;
  plsb = (char *)vp;
  pmsb = plsb + 1;
  *pmsb = msb;
  *plsb = lsb;
  zz = xx & 0xfff0;
  zz = zz / 16;
  return (float)zz * 0.098 * 10.0; //0.098mT/LSB 10Gauss/mT;
}

float getMagT(){
  char msb = readByte[3];
  char lsb = readByte[6];
  msb = msb & 0xf0;
  msb = msb + (lsb >> 4);
  lsb = lsb & 0xf0;
  
  unsigned short xx;
  void *vp;
  char *pmsb;
  char *plsb;
  signed short zz;
  vp = &xx;
  plsb = (char *)vp;
  pmsb = plsb + 1;
  *pmsb = msb;
  *plsb = lsb;
  zz = xx & 0xfff0;
  zz = zz / 16;
  return ((float)zz - 320) * 1.1;
}

It uses some tricks with pointers for x, y, z and T, so it basically takes the msb and lsb for each and joins them into one signed short, without the need to loop through the bits. For the other params, it zeroes out the unnecessary bits and shifts the relevant bits to the right.

Note that to be sure a reading is correct (ADC has finished measurement), you should check that Channel and PD are 0.

Additional resources and hookup information for the TLV493 at http://www.allaboutcircuits.com/technical-articles/tutorial-and-overview-of-infineons-3d-magnetic-2go-kit/

Sorry for the late response.
Thanks Baztastic for the help on the for loop control. Your implementation is definitely more readable and faster than the for loop Colossus I programmed. I will definitely implement this in my code. The write up that mjhughes posted is very good. Overall I like this sensor and I will continue to use them in my future projects.
I do like the breakout board they offer, but I would like to know if they plan on selling them just with the sensor without the Micro controller on it.

mjhuges, you should consider using this method to convert to spherical coordinates rather than using the acrtan() use acrtan2().

void SpericalCoordinates()
{

  float r = 0;
  float pheta = 0;
  float phi = 0;
  r = sqrt(magX * magX + magY * magY + magZ * magZ);
      phi[i] = atan2(magZ, sqrt(magX * magX + magY * magY)) ; //elevation about the x-y plane
    pheta[i] = atan2(magY, magX); // azimuth rotation about z-axis
}

Thank you all for posting your code, it's very helpful !
I am building an open source magnetic scanner using this sensor, and the datasheet isn't always clear...
@baztastic Have you successfully debugged your code ?
I am very interested in your complete version of it, especially if you managed to get the fast mode running !
@IceNinja with your code, I frequently get 3.92mT when the sensor is in a zero B field. Do you also have this problem ? (I am now using a MEGA 2560, but I also had this problem on an UNO)

Yes ! I got it working !

I used the sketch from Mark J. Hughes, and adapted it to use the I2C library from dsscircuits.com.
It is quite fast !

/*   Infinenon 3D Magnetic I2c
 *   TLV493D
 *   Initial sketch by Mark J. Hughes for AllAboutCircuits.com
 *   Modified and adapted for the OpenMagneticScanner by Trublion
 *   jeremy@trublion.org
 */

//--- Begin Includes ---//
#include <I2C.h>       // http://dsscircuits.com/articles/arduino-i2c-master-library

// Variable Declaration
const byte addr = 0x5E; // default address of magnetic sensor 0x5E, 0x3E or 0X1F
byte rbuffer[10];       // store data from sensor read registers
byte delaytime = 1;     // time to wait before next read
bool PRINT_RAW_VALUES = false;


//--- Begin Setup ---//
void setup() {
  Serial.begin(115200);      // Begin serial connection for debug.
  I2c.begin();              // Begin I²C I2c communication
  I2c.timeOut(100);
  I2c.write(addr, 0x00,0x05);
} 
//--- End of Setup --//

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

  // 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(rbuffer[3] & B00000011 != 0){ // If bits are not 0, TLV is still reading Bx, By, Bz, or T
    Serial.println("Data read error!");

  }
  else {
    if(PRINT_RAW_VALUES){
        Serial.print(x);
        Serial.print("\t");
        Serial.print(y);
        Serial.print("\t");
        Serial.print(z);
        Serial.print("\t");
        Serial.println(t);
    }
    else{
        Serial.print(convertToMag(x));
        Serial.print("\t");
        Serial.print(convertToMag(y));
        Serial.print("\t");
        Serial.print(convertToMag(z));
        Serial.print("\t");
        Serial.println(convertToCelsius(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 >= 2048){ ans = 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 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 >= 2048){ ans = 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 >= 2048){ ans = 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 >= 2048){ ans -= 4096;}
  return ans;
}


float convertToMag(int a){
  return a * 0.098;
}

float convertToCelsius(int a){
  return (a-320)* 1.1;
}

As a side note not really related to Arduino (but this is the post most people getting started with this sensor will see first), I also translated it to Python in order to use it on a beaglebone black.
As of today, the python implementation of libmraa has a bug line 1128.
You need to modify the read function :

def read(self, length):
    """
    read(I2c self, int length) -> int

    Parameters
    ----------
    length: int
  
    """
    return _mraa.I2c_read(self, length)

The function outputs a binary that is interpreted as a string by python. You then need struct.unpack to get the values.

I think there is still a problem with the timeout. Will check it out.

## Requires to fix the read function in python I2C MRAA, line 1128 :


    # def read(self, length):
    #     """
    #     read(I2c self, int length) -> int

    #     Parameters
    #     ----------
    #     length: int
        
    #     """
    #     return _mraa.I2c_read(self, length)


import time
import mraa
import struct


delaytime = 0.001

print("Initialising the sensor")

I2C_ADDRESS = 0x5e
magSensor = mraa.I2c(2, True)
magSensor.address(I2C_ADDRESS)

rbuffer = bytearray([0]*10)


magSensor.writeReg(0x00, 0x05)

print("Sensor set to low power mode")


def decodeX( a,  b):
	ans = ( a << 4 ) | (((b & 0b11110000) >> 4) & 0b00001111)
	if( ans >= 2048):
		ans = ans - 4096
	return ans


def decodeY( a,  b):
	ans = (a << 4) | (b & 0b00001111)
	if( ans > 2048):
		ans = ans - 4096
	return ans


def decodeZ( a,  b):
	ans = (a << 4) | (b & 0b00001111)
	if( ans >= 2048):
		ans = ans - 4096
	return ans

def decodeT( a,  b):
	a &= 0b11110000
	ans = (a << 4) | b
	if( ans > 2048):
		ans -= 4096
	return ans

def convertToMag(a):
	return a * 0.098

def convertToCelsius(a):
	return (a-320)* 1.1

while (True):
	time.sleep(delaytime)
	answer = magSensor.read(rbuffer, 7)
	rbuffer = struct.unpack('BBBBBBB', answer)
	x = decodeX(rbuffer[0],rbuffer[4])
	y = decodeY(rbuffer[1],rbuffer[4])
	z = decodeZ(rbuffer[2],rbuffer[5])
	t = decodeT(rbuffer[3],rbuffer[6])

	if((rbuffer[3] & 0b00000011) != 0): # If bits are not 0, TLV is still reading Bx, By, Bz, or T
		print("Data read error!")
	else:
		#print("%d \t %d \t %d \t %d" % (x,y,z,t)) # Raw values
		print("%f \t %f \t %f \t %f" % (convertToMag(x),convertToMag(y),convertToMag(z),convertToCelsius(t)))

@jeremypatrickdahan

I have adapted my code to account for some of the tips that both mjhughes and baztastic offered.

Using the code you posted are you still getting the 3.92mT in a zero B field? If so I think it has something to do with the bit readout or translation of the registries, i.e...combination of the wrong registries.

No, with the code I posted I don't get the 3.92mT noise.
However, I don't get new readouts at 100Hz. The sensor outputs the same data five or six times before updating the values (and sometimes, I get five times the same values, then a new one just once, then six times another one).
I tried changing the delay time but it doesn't work.
For now the speed is still acceptable in my application, but I would like to use the sensor at its max speed, in fast mode, to try to visualize AC fields.

Could anyone get the /INT interrupt on the SCL line to work?

Basically what I'm doing is right after setting the sensor to a power mode I attach an interrupt to the SCLpin with attachPinInterrupt(SCLpin, interrupt, RISING) in the setup function. In the interrupt function I set interrupt_flag from 0 to 1, which lets the master advance in the loop function to read out the sensor values. Before reading the values, I detach the interrupt from the pin with detachPinInterrupt(SCLpin) so there won't be interrupts all the time while master and slave are communicating. Right after reading the last byte I set the interrupt_flag to 0 and set attachPinInterrupt(SCLpin, interrupt, RISING). This is the end of the loop and it starts with waiting for the interrupt again. However, when doing it like this, the interrupt is never triggered and thus the loop never passes the interrupt_flag condition...

Here's the code (Thanks a lot for your code, it's a bit of mosaic from various places). Also note that I use the Simblee, which uses some different functions.

int interrupt(uint32_t dummyPin) {
  Serial.println("/INT");
  interrupt_flag = HIGH;
  return 0;
}

void loop() {

 if(!interrupt_flag) {
   return;
 }
 
 detachPinInterrupt(SCLpin);

   // Read sensor registers and store in rbuffer    
   Wire.requestFrom(addr[0],sizeof(rbuffer));
   for(int i=0; i < 7; i++){
     rbuffer[i] = Wire.read();
   }
   
//    //Check if conversions finished. If not, re-run the read. Not needed with interrupt
//   while((rbuffer[5] & B00010000 != 16) || (rbuffer[3] & B00000011 != 0)) //TODO: Check Condition See detailed documentation page 27-28.
//   {
//     Serial.println("NOT RDY");
//     Wire.requestFrom(addr[0],sizeof(rbuffer));
//     for(int i=0; i < 7; i++){
//       rbuffer[i] = Wire.read();
//     }
//     delay(1);
//   }
   
   interrupt_flag = LOW;
   attachPinInterrupt(SCLpin, interrupt, RISING);

   SimbleeCOM.send(rbuffer, sizeof(rbuffer));
}

I'm planning on using multiple of these sensors. Since the /INT line is on the SCL and thus doesn't have it's own pin I don't know which sensor is ready to be read out... Does it even make sense to use /INT in this scenario, or should I just poll the sensors in my requested frequency? I would've like to use the interrupts so I could put the sensors to sleep in between reads. Since it's for a wireless device minimizing the current draw is favored...

So what I'm doing for now is using the master controlled mode and set the sensors to power down mode manually after each read, wait x ms and then power them up for the next read. This seems to work quite well.

I want to know how to make a code using three 3d magnetic sensor and try to calculate a gradient ? each sensor reads it's value