I'm using the TLV493D-A1B6 sensor connected to an Arduino Uno: I've attached all necessary documentation at the bottom.

This is a 3D magnetic field position sensor that detects the magnetic field strength of a nearby magnetic field in the X, Y, and Z directions. I am NOT using the 2GO evaluation board. The sensor uses I2C protocol for communication.

I have this sensor all wired up, soldered correctly to a PCB with pinheadings, and ready to go on my breadboard. Also, I am using a bidirectional logic level converter that is recommended for this sensor. I can confirm this as the I2C scanner sketch detects the sensor and prints out its expected defulat address.

Specifically, I am having trouble understanding the data sheets and how to setup Arduino using the 'Wire.h' library to communicate with the sensor over I2C to obtain the X, Y, Z RAW/decoded readings continuously.

I have tried code from this thread: https://forum.arduino.cc/index.php?topic=419380.0 I have tried code from this dedicated tutorial for this sensor: https://www.allaboutcircuits.com/technical-articles/tutorial-and-overview-of-infineons-3d-magnetic-2go-kit/

I have even tried code from Github for this sensor: https://github.com/IRNAS/TLV493D-3D-Magnetic-Sensor-Arduino-Library/blob/master/TLV493D/TLV493D.h

Using any of the above, I will get readings of "X=-1, Y=-1, Z=-1" or the sensor will output random readings of "255" and "0" when exposed to any magnet.

I've written my own code and have failed vigorously to get anything but random, clearly wrong readouts, this was my latest attempt, I chopped this code down for simplicity and clarity. I've also tried variations using the I2C.h master library, but no luck:

#include "Wire.h"       

const int tlv_addr = 0x1F; // obtained by i2c scanner; all default address of tlv sensor - 0x1F, 0x5E, or 0x3E
const int config_reg = 0x00; // I have scene this in other examples of code for this sensor, I cannot find the "configure" or "command register" address on any of its datasheets
const int lp_mode = 0x05; //Have found this on the data sheet as the low power mode command
// Note: I switched from const byte to const int for HEX assignments because I get a warning when compiling the sketch about this and I found online that this is the solution?

void setup() {
  Wire.begin();   //join I2C bus
  Serial.begin(9600); //start Serial
  while(!Serial); //wait for Serial to be available

  Wire.beginTransmission(tlv_addr); //initiate communication with sensor
  Wire.write(config_reg); //set pointer/access configuration register
  Wire.write(lp_mode); //from what I read, the sensor defaults to power down mode on start up, so I send the low power mode command to the configure register to activate the sensor in low power mode which should cause it to start "sensing"
  Wire.endTransmission(); //end configurations
  delay(100); //delay to allow time for sensor the update
  Serial.print("Setup Complete\n\n");

void loop(){
  Wire.requestFrom(tlv_addr, 3); //request the 3 raw, UN-decoded bytes from the sensor's readings of X, Y, Z axes

  //---- Read in each byte of sensor data one by one for each axis ----//
  byte Bx = Wire.read(); //Have scene the "*" added to the beginning of the variable on a lot of code, not sure why this is necessary?
  byte By = Wire.read();
  byte Bz = Wire.read();

  //---- Print Out Sensor Axis Readings ----//
  Serial.println("X= ");
  Serial.print(Bx);       //Have also scene "&" added to the beginning of the variable on a lot of code like Serial.print(&Bx);? 
  Serial.print("\tY= ");
  Serial.print("\tZ= ");

  //Expecting something like this: 01011011
  //Getting -1 or 255 or 0 randomly?

  delay(1000); //arbitrary delay

And furthermore, I understand that these values needed to be decoded to make sense of the actual data, I have no idea what is going on the register map sheet and why they are shifting different bits of segments of each byte, seemingly like totally picking the bytes apart then reordering them, and then casting them as integers to get real data?

I am very new to Arduino and especially to I2C. If you could please help me understand especially what I'm missing from the data sheet, or what I'm doing wrong in the simplest of terms, I want to intuitively understand whats going on at every step so I can do this again with another I2C sensor on my own. If anyone could please help me with this.

sensor: https://www.infineon.com/cms/en/product/sensor/magnetic-position-sensor/3d-magnetic-sensor/tlv493d-a1b6/

datasheet: https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6-DS-v01_00-EN.pdf?fileId=5546d462525dbac40152a6b85c760e80

Register Map: https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6_3DMagnetic-UM-v01_03-EN.pdf?fileId=5546d46261d5e6820161e75721903ddd

1 Answer 1


In your code you read only the 3 first registers and write them to the variables Bx to Bz. These registers correspond to the correct magnetic values, but only the highest parts of them. The sensor measures 12-bit values - due to the higher precision - , but one register can only hold 8 bits. So the big 12-bit values are splitted into two parts. You will have to read all parts and put them together correctly to get meaningful results. Also the format of the values is a signed 12-bit value in two's complement, meaning that the decimal value is calculated by multiplying the value of the 12th bit with -1. You will have to convert this to a 16-bit two's complement for using the int type.

In the following part I will use the bitwise AND ( & ), the bitwise OR ( | ) and the bit-shifting operators ( << and >>). I will only shortly explain, how to use them. If you are not familiar with them, please refer to a proper C/C++ tutorial, which can explain this in more detail than this answer.

  1. The bitwise AND will compair each bit of the left value with the corresponding bit of the right value. Only if both bits are 1, the corresponding bit in the output will be 1, 0 in every other case. I use it to mask some bits of the value away, effectively deleting these bits. Also note the different writing of literal values: In hexadecimal 0x0F or binary 0b00001111.
  2. The bitwise OR will work as the bitwise AND, but will put a 1 into a bit, when at minimum one of the corresponding input bits is 1, and outputs 0 to this bit, if the input bits are both 0. I use this, to put all bytes of one value together into one variable.
  3. The shifting operators << and >> will shift the bit values to one side. The bits, that are getting out of the values range are lost. On the other side zeros get shifted into the value. I use it for the proper alignment in bytes of a bigger value.

enter image description here

This is the important figure from the user manual. It tells you, that you will find bits 11 to 4 of the Bx value in the first register (11 is the most significant bit). The rest of the Bx value is to be found in the 5th register in the 4 most significant bits (bits 3 to 0 of the Bx value). So to get the correct values, we first need to read all the data (I will include the temperature here):

// Request 7 bytes from the sensor and return, if it didn't send enough (do not read, if there is nothing to read)
if(Wire.requestFrom(tlv_addr, 7) < 7) return;
// Read all registers to variables
byte bx_high = Wire.read();
byte by_high = Wire.read();
byte bz_high = Wire.read();
byte temp_high = Wire.read();
byte bxy_low = Wire.read(); // This has to be read together. We will split the values later
byte bz_low = Wire.read();
byte temp_low = Wire.read();

Now we put together the splitted values in one variable each. To avoid complications with types, I use unsigned int here, which is like byte, but containing 16 bit. For Bx we have to shift the high part 4 bits to the left, since the lower part needs 4 bits of space. Also we have to seperate the lower parts of Bx and By. Since the lower part of Bx lies in the highest 4 bits of the read value, we need to shift them right for alignment with the least significant bit. The rest works similar, but is a bit easier. The temperature value uses another alignment, but the principle is the same.

unsigned int bx_value = (bx_high << 4) | ((bxy_low & 0xF0)>>4);
unsigned int by_value = (by_high << 4) | (bxy_low & 0x0F);
unsigned int bz_value = (bz_high << 4) | (bz_low & 0x0F);
unsigned int temp_value = (temp_high >> 4) | temp_low;

Now we have packed the 12-bit values into 16-bit unsigned integers. We now have to convert them to 16-bit signed integers. But if we simply cast them, this would result in false values, since the complemented bit here is the 16th, not the 12th like in the sensor. For solving this we use the answer from this question:

int16_t Bx = (int16_t)(bx_value << 4) / 16 ;

There is one difference from the answer: We have a 12-bit two's complement, not a 14-bit one. So we have to shift 4 bits, not 2. This corresponds to dividing by 16 (a factor of 2 for each shifted bit).

Now you should have the correct measured values from the sensor.

You may have a second issue with your code. It read the registers every second, without regard to the state of the sensor. The datasheet says:

This sensor does not incorporate shadow-buffers for readout. Thus, it is mandatory to read the sensor data not during a running conversion to avoid corrupted reads.

So you might also get garbled values because you sometimes read the registers during the sensor writes in them. This may be not a great issue here (maybe it only happens once in a while). But you can circumvent this problem by either using the INT pin of the sensor for triggering a I2C read operation (at best via an interrupt) or by configuring the sensor to run in the "Master Controlled Mode", where it only does a new measurement, when the old values have been read from the registers.

  • This a great help and a tremendous amount of clarity, thank you so much for your answer.
    – dandev
    Jun 26, 2018 at 20:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.