Raptor and I2C with Multiple Devices

I am using Sockets 6 & 7 for two I2C devices. These devices function ok when included in the code on their own but fail when two are included in the code. Have I got something wrong?


        static I2CDevice.Configuration conDeviceA;
        static I2CDevice.Configuration conDeviceB;
        static public I2CDevice MyI2C;

        public static void Main()
        {
            Program myApplication = new Program();
            inQueue.Clear();
            outQueue.Clear();
            conDeviceA = new I2CDevice.Configuration(0x1E, 400);
            conDeviceB = new I2CDevice.Configuration(0x19, 400);
            MyI2C = new I2CDevice(conDeviceA);

			...........................................
			
                byte[] HMC5883write = new byte[2] { 0x02, 0x01 };
                byte[] HMC5883read = new byte[6];
                byte[] HMC6343write = new byte[1] { 0x50 };
                byte[] HMC6343read = new byte[6];

			............................................
			
                    I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[2];
                    xActions[0] = I2CDevice.CreateWriteTransaction(HMC5883write);
                    xActions[1] = I2CDevice.CreateReadTransaction(HMC5883read);
                    // read data from the device
                    MyI2C.Config = conDeviceA;
                    int read = MyI2C.Execute(xActions, 2000);

                    // make sure the data was read
                    if (read != HMC5883read.Length + HMC5883write.Length)
                    {
//                        throw new Exception("Could not read from device.");
                    }
                    else
                    {
                        int X = (HMC5883read[0] << 8) + HMC5883read[1];
                        int Z = (HMC5883read[2] << 8) + HMC5883read[3];
                        int Y = (HMC5883read[4] << 8) + HMC5883read[5];
                        double headingRaw = ExMath.Atan(Y / X);
                        double headingDegrees = ExMath.RadiansToDegrees(headingRaw) + 22.45;
                        magnetic = System.Math.Round(headingDegrees).ToString();
                        Debug.Print("Location:\tX=" + X + "\tY=" + Y + "\tZ= " + Z + " Heading: " + magnetic);
                    }
                    Thread.Sleep(1000);
                    I2CDevice.I2CTransaction[] xActions1 = new I2CDevice.I2CTransaction[2];
                    xActions1[0] = I2CDevice.CreateWriteTransaction(HMC6343write);
                    xActions1[1] = I2CDevice.CreateReadTransaction(HMC6343read);
                    // read data from the device
                    MyI2C.Config = conDeviceB;
                    int read1 = MyI2C.Execute(xActions1, 1000);

                    // make sure the data was read
                    if (read1 != HMC6343read.Length + HMC6343write.Length)
                    {
                        throw new Exception("Could not read from device.");
                    }
                    int heading = (HMC6343read[0] << 8) + HMC6343read[1];
                    int pitch = (HMC6343read[2] << 8) + HMC6343read[3];
                    int roll = (HMC6343read[4] << 8) + HMC6343read[5];
                    Debug.Print("Heading: " + heading + " Pitch: " + pitch + " Roll: " + roll);

					.....................................................

In order to make this work, you are going to need to include a class to arbitrate between your devices on the I2C bus. Here’s a version I used (originally developed by Pavel Bansky)

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

// A modified version of Pavel Banksy's original work

namespace Pervasive.Digital.Drivers
{
    public class I2CBus : IDisposable
    {
        private static I2CBus _instance = null;
        private static readonly object LockObject = new object();

        public static I2CBus GetInstance()
        {
            lock (LockObject)
            {
                if (_instance == null)
                {
                    _instance = new I2CBus();
                }
                return _instance;
            }
        }


        private I2CDevice _slaveDevice;

        private I2CBus()
        {
            this._slaveDevice = new I2CDevice(new I2CDevice.Configuration(0, 0));
        }

        public void Dispose()
        {
            this._slaveDevice.Dispose();
        }

        /// <summary>
        /// Generic write operation to I2C slave device.
        /// </summary>
        /// <param name="config">I2C slave device configuration.</param>
        /// <param name="writeBuffer">The array of bytes that will be sent to the device.</param>
        /// <param name="transactionTimeout">The amount of time the system will wait before resuming execution of the transaction.</param>
        public void Write(I2CDevice.Configuration config, byte[] writeBuffer, int transactionTimeout)
        {
            // Set i2c device configuration.
            _slaveDevice.Config = config;

            // create an i2c write transaction to be sent to the device.
            I2CDevice.I2CTransaction[] writeXAction = new I2CDevice.I2CTransaction[] { I2CDevice.CreateWriteTransaction(writeBuffer) };

            lock (_slaveDevice)
            {
                // the i2c data is sent here to the device.
                int transferred = _slaveDevice.Execute(writeXAction, transactionTimeout);

                // make sure the data was sent.
                if (transferred != writeBuffer.Length)
                    throw new Exception("Could not write to device.");
            }
        }

        /// <summary>
        /// Generic read operation from I2C slave device.
        /// </summary>
        /// <param name="config">I2C slave device configuration.</param>
        /// <param name="readBuffer">The array of bytes that will contain the data read from the device.</param>
        /// <param name="transactionTimeout">The amount of time the system will wait before resuming execution of the transaction.</param>
        public void Read(I2CDevice.Configuration config, byte[] readBuffer, int transactionTimeout)
        {
            // Set i2c device configuration.
            _slaveDevice.Config = config;

            // create an i2c read transaction to be sent to the device.
            I2CDevice.I2CTransaction[] readXAction = new I2CDevice.I2CTransaction[] { I2CDevice.CreateReadTransaction(readBuffer) };

            lock (_slaveDevice)
            {
                // the i2c data is received here from the device.
                int transferred = _slaveDevice.Execute(readXAction, transactionTimeout);

                // make sure the data was received.
                if (transferred != readBuffer.Length)
                    throw new Exception("Could not read from device.");
            }
        }

        public int Execute(I2CDevice.Configuration config, I2CDevice.I2CTransaction[] xactn, int xactnTimeout)
        {
            var result = -1;

            _slaveDevice.Config = config;

            lock (_slaveDevice)
            {
                // the i2c data is received here from the device.
                result = _slaveDevice.Execute(xactn, xactnTimeout);
            }
            return result;
        }

        /// <summary>
        /// Read array of bytes at specific register from the I2C slave device.
        /// </summary>
        /// <param name="config">I2C slave device configuration.</param>
        /// <param name="register">The register to read bytes from.</param>
        /// <param name="readBuffer">The array of bytes that will contain the data read from the device.</param>
        /// <param name="transactionTimeout">The amount of time the system will wait before resuming execution of the transaction.</param>
        public void ReadRegister(I2CDevice.Configuration config, byte register, byte[] readBuffer, int transactionTimeout)
        {
            byte[] registerBuffer = { register };
            Write(config, registerBuffer, transactionTimeout);
            Read(config, readBuffer, transactionTimeout);
        }

        /// <summary>
        /// Write array of bytes value to a specific register on the I2C slave device.
        /// </summary>
        /// <param name="config">I2C slave device configuration.</param>
        /// <param name="register">The register to send bytes to.</param>
        /// <param name="writeBuffer">The array of bytes that will be sent to the device.</param>
        /// <param name="transactionTimeout">The amount of time the system will wait before resuming execution of the transaction.</param>
        public void WriteRegister(I2CDevice.Configuration config, byte register, byte[] writeBuffer, int transactionTimeout)
        {
            byte[] registerBuffer = { register };
            Write(config, registerBuffer, transactionTimeout);
            Write(config, writeBuffer, transactionTimeout);
        }

        /// <summary>
        /// Write a byte value to a specific register on the I2C slave device.
        /// </summary>
        /// <param name="config">I2C slave device configuration.</param>
        /// <param name="register">The register to send bytes to.</param>
        /// <param name="value">The byte that will be sent to the device.</param>
        /// <param name="transactionTimeout">The amount of time the system will wait before resuming execution of the transaction.</param>
        public void WriteRegister(I2CDevice.Configuration config, byte register, byte value, int transactionTimeout)
        {
            byte[] writeBuffer = { register, value };
            Write(config, writeBuffer, transactionTimeout);
        }
    }
}

And here’s an example of using i2cbus.cs:

 ReadSample(int i)
{
    _singleCommand[0] = (byte)(Register.LIS3DH_REG_OUT_X_L | Register.SequentialMode);
    _readTrans[0] = I2CDevice.CreateWriteTransaction(_singleCommand);
    _readTrans[1] = I2CDevice.CreateReadTransaction(_singleSampleBuffer);
    I2CBus.GetInstance().Execute(_i2cConfig, _readTrans, 100);
    _singleSampleBuffer.CopyTo(_sampleBuffer, i * 6);
     return _singleSampleBuffer;
}

Many thanks Mcalsyn. I will try your code today!

@ mcalsyn
Thank you for your earlier reply and code. I am trying to use a Compass module (HMC5883L) and a Sparkfun module (HMC6343) with a Raptor:


//From HMC5883L Datasheet:
//Write Mode (02) - send 0x3C 0x02 0x01 (Single-measurement mode)
//Wait 6 ms or monitor status register or DRDY hardware interrupt pin
//Send 0x3D 0x06 (Read all 6 bytes. If gain is changed then this data set is using previous gain)
//Convert three 16-bit 2's compliment hex values to decimal values and assign to X, Z, Y, respectively.
....................................................................
            conHMC5883 = new I2CDevice.Configuration(0x1E, 400);
            conHMC6343 = new I2CDevice.Configuration(0x19, 400);
....................................................................
                byte[] HMC5883write = new byte[2] { 0x02, 0x01 };
                byte[] HMC5883read = new byte[6];
                byte[] HMC6343write = new byte[1] { 0x50 };
                byte[] HMC6343read = new byte[6];
....................................................................
                    I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[2];
                    xActions[0] = I2CDevice.CreateWriteTransaction(HMC5883write);
                    xActions[1] = I2CDevice.CreateReadTransaction(HMC5883read);
                    // read data from the device
		    int read = I2CBus.GetInstance().Execute(conHMC5883, xActions, 100);
                    // make sure the data was read
                    if (read != HMC5883read.Length + HMC5883write.Length)
                    {
                        throw new Exception("Could not read from device.");
                    }
....................................................................
                    I2CDevice.I2CTransaction[] xActions1 = new I2CDevice.I2CTransaction[2];
                    xActions1[0] = I2CDevice.CreateWriteTransaction(HMC6343write);
                    xActions1[1] = I2CDevice.CreateReadTransaction(HMC6343read);
                    // read data from the device
       		    int read1 = I2CBus.GetInstance().Execute(conHMC6343, xActions1, 100);

                    // make sure the data was read
                    if (read1 != HMC6343read.Length + HMC6343write.Length)
                    {
                        throw new Exception("Could not read from device.");
                    }

With both devices connected the Compass module will not read whereas the HMC6343 (although second) does read.
On its own the Compass module (HMC5883L) will read ok.
Your help is most appreciated.
Regards,
Kevin

The interactions I see in your code for the 5883 don’t match what I see in the datasheet ([url]http://cdn.sparkfun.com/datasheets/Sensors/Magneto/HMC5883L-FDS.pdf[/url] bottom of page 18 ). The delay (or a wait for DRDY) is missing and I also don’t see any of the setup transactions. I also only see 0x02 0x01 and not the required 0x3C 0x02 0x01. It’s hard to debug partial code examples, and doubly hard when I don’t have experience with that module, but it’s not clear that the interactions with the 5883 are correct.

A general rule of thumb for I2C: Always start with a simple transaction - just reading the device ID. If you attach both devices, can you read the ID from each? If not, you’re not going to get very far with more complex transactions. If you CAN read the ID from each device, then you need to start suspecting that you are interacting with one of the devices wrongly and it is interfering with subsequent interactions on the bus.

In a real pinch with I2C or SPI, I often find myself reaching for the logic analyzer when datasheet archaeology and trial & error fail me.

@ mcalsyn
My code for my robot is quite large and almost complete. I thought I would just add an extra I2C device ( the compass module was working by itself).
Regarding the address 0x3C: conHMC5883 = new I2CDevice.Configuration(0x1E, 400); I hope that I have translated 0x3C to 7-bit 0x1E correctly?
The 0x02 0x01 is to set the device into single measurement mode (which is the default mode). If I jump this transaction and go direct to read:


                    I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[1];
                    xActions[0] = I2CDevice.CreateReadTransaction(HMC5883read);
                    // read data from the device
		    int read = I2CBus.GetInstance().Execute(conHMC5883, xActions, 100);
                    // make sure the data was read
                    if (read != HMC5883read.Length)
                    {
                        throw new Exception("Could not read from device.");
                    }

which works ok. I don’t understand how the code can find the 6 bytes of data register. I thought that the writing of a transaction to 0x02 set up the read at 0x03 but the above code returns the 6 bytes of data no problem. Next I will add back the code for the HMC6343. Many thanks for your help.
Regards,
Kevin.

@ KG1 - I suggest you read https://www.ghielectronics.com/docs/12/i2c
there is an example with multiple devices, but read the whole page before…
Your code is wrong, and I think you don’t understand how I2C works.
Do some search by yourself, it will pay a lot.
And yes, start reading the ID of the devices, it is a very simple transaction.