Sunday, November 25, 2012

Raspberry Pi i2c and repeated start condition and MAG3110 magnetometer

i2c is a protocol to communicate with microchips. It is widely used if the required data rate is not that high (up to few thousand bytes per second or similar). Reading data from some client (which could be some sort of sensor, or an Analog-Digital-Converter for example) with the Raspberry Pi is usually as easy as just reading from a device file (/dev/i2c-0 for example), specifying the device address beforehand, due to the magic the underlying kernel driver does. Some i2c clients, such as this MAG3110 magnetometer I'm using, require you to specify a register address to read from on each data access, tough. For this, technically, you need to send a START (to tell the device you want to communicate), then the register address you want to read from, then a REPEATED START (to tell the device you're done with sending the address and want to read the data now), then you can read the data, and then you send a STOP (to tell the device you're done). Sounds easy, right? It is however not quite obvious to me how to do that. Just sending the register address, then reading does not work, since that will send a STOP after the register address has been written, which leads to the device assuming you're done and forgetting about everything.

Other people seem to be confused about this as well. Some threads in the internet even conclude that the i2c controller used in the Raspberry Pi is not capable of this functionality at all. However, this fortunately does not seem to be the case: using the i2cget program, you can specify a register to read from, and it will work just fine (same goes for writing).
Edit on 01.01.2013: Seems to be that it is right what people say after all: The Raspberry Pi does not support repeated start. The stuff below still works for my device, but apparently only by chance; the behaviour of the controller is not correct. Here's a screenshot of the communication:
Communication generated by the code below. After the first data byte (the register address, 4) has been transferred, you can easily spot the spike in SDA (yellow) while SCL (teal) is high, meaning STOP START. This is incorrect.
So, since this is the open source part of the world, we can just go look at what they do, and steal it! ;)
The sources are easy to find (here, for example), and you can just modify i2cget.c to do what you need. Here is my code for accessing the magnetometer mentioned above, as an example. I hope this might help other people which are confused about this too.

Oh, and here's a fancy image of the board with the magnetometer mentioned above:
Board with a three-axis magnetometer, in a 2mm x 2mm DFN package
The actual sensor is the small part in the middle. Doesn't it look nice? :)

I should also mention that the magnetometer itself is pretty awesome! It's a three-axis magnetometer with 16 bits resolution per axis (!), and it costs less than two euros at farnell (actually the whole board shown above is only about two euros in total if you add up the components, PCB, and the solder). The device is well sensitive enough to measure the earth's magnetic field; thus, you can use it to accurately determine its absolute rotation. I did a test which rotates meshes in blender in real-time when you rotate the device -- it worked quite well! It needs a lot of adjustment to the axes tough, they don't really match yet. I'll write about that again if I get it to work nicely.

Here's an example plot when rotating the device around a bit:
Raw data received from magnetometer when rotating it by hand
Here's moving a magnet towards the device, starting from about 1m away:
Moving a large magnet towards the device. t=0 corresponds to ~1m distance. The peaks in the y and z axes in the end originate from the magnet being rotated a bit while moving it away.
Edit on 01.01.2013: Hahaha, look at the photo of the board again -- do you notice something? Yep, half of the GND isn't even connected to anything. That's what happens if you make just a layout without a proper connection diagram + DRC ;) After fixing that with some zero-ohm resistors across the traces, the noise level of the device has gone way down. The curves are much smoother than in the plot above now.

6 comments:

  1. Hi,
    I have the same problems with reading a MLX90614 IR temperature sensor. After reading the kernel module's source code and the BCM2835 pheripherals specs, I tend to believe that there is no way to generate a REPEATED START with PI.

    Did you actually saw the REPEATED START on an oscilloscope?

    Some I2C slave devices do cope with a STOP inbetween writing the register's address and reading the actual data. Could it be that MAG3110 is one of these devices ?

    Best regards,
    Dan.

    ReplyDelete
    Replies
    1. Hmm, you seem to be right indeed! Look at those two screenshots from the communication: http://imgur.com/a/RtFFl
      You can clearly see the STOP START pattern after the first byte (the register index) has been transferred (should be a RESTART there). Also if I set the scope to "I2C restart" it doesn't trigger.

      I really wonder why the device accepts this anyways, since I tried to just send a byte with the register address, then receive a byte before and it didn't work. Maybe I was doing it wrong... or the time delay between the two events was too big?

      Anyways, seems what people say is right after all -- apparently there's no Repeated Start on the Pi's I2C controller! That's a shame. :(

      I changed the article accordingly. Thanks a lot for pointing this out!

      Delete
  2. Hey Dan,

    That's possible. I'll check it on the scope later today and tell you the outcome. ;)

    Cheers,
    Sven

    ReplyDelete
  3. I've recently fixed this problem, at least well enough to read temperature data from a MLX90614 using a SMBus read with ioctl(). The Raspberry Pi's I2C master supports 10-bit addresses, so it can manage repeated starts. I modified i2c-bcm2708.c in the kernel to make it work.

    http://jjackowski.wordpress.com/2013/07/13/i2c-repeated-starts-implemented-on-the-raspberry-pi/

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete