Sharing I2C bus

I have 2 devices on the I2C bus. I need to be able to access them in different threads as one is handled with an interrupt input and the other is polled continuously.

I create the I2C device in the main programme and when I create each class that has the threads, I pass in the I2C device to the constructor for each class.

In each class, prior to calling the I2C function to set the configuration and execute the transaction, I enclose the calls within a lock {} block. eg. This is the ADC handler.


                lock (McpAdc)
                {
                    SetConfig(0);

                    if (_configDirty)
                    {
                        ConfigDevice();
                    }
                    else if (_conversionMode == ConversionMode.OneShot)
                    {
                        WriteConfigReg();
                    }
                    Result = McpAdc.Execute(_xReadAction, _ReadWriteTimeoutMilliSecs);
                }

In the other class, I do something similar for handling touch.


        lock (_sharedBus)
        {
            _sharedBus.Config = _busConfiguration;
            if (_sharedBus.Execute(_readActions2, 500) == 0)
            {
                Debug.Print("Failed to perform I2C transaction");
            }
        }

The problem is that I am seeing a lot of transaction failures from the first call for the ADC. The other one doesn’t get called until I touch the LCD.

By the way, the old system with just the ADC running on I2C and the touch was SPI, everything was fine without the lock. This has only started since I changed to the shared access.

Is this the correct way to do this or am I going about this the wrong way?

@ Dave McLaughlin -

I have a wrapper class on EMX, that controls 2 I2C slave devices (here is 2 IO40) with different address and it works fine until now, hop it help

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT;
//using GHIElectronics.NETMF.FEZ;

namespace G400S_ModuleTester
{
   // public static partial class FEZ_Components
    //{
        public class IO40 : IDisposable
        {
            static I2CDevice I2C;
            //I2CDevice I2C = null;
            I2CDevice.I2CTransaction[] WriteXaction;
            I2CDevice.I2CTransaction[] ReadXaction;
			
            byte[] DataRead;
            I2CDevice.Configuration config;
			
            public void Dispose()
            {
                I2C.Dispose();
                WriteXaction = null;
                ReadXaction = null;
                DataRead = null;
            }
			
            public IO40(byte address)
            {
                if (address > 7)
                    throw new ArgumentException("address");

                config = new I2CDevice.Configuration((ushort)(0x20|address), 100);
                //I2C = new I2CDevice(config);
                if (I2C == null)
                    I2C = new I2CDevice(config);
                
                WriteXaction = new I2CDevice.I2CTransaction[1];
                WriteXaction[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0, 1 });

                ReadXaction = new I2CDevice.I2CTransaction[2];
                ReadXaction[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0 });
                DataRead = new byte[1];
                ReadXaction[1] = I2CDevice.CreateReadTransaction(DataRead);
                /*
                while (true)
                {
                    int i = I2C.Execute(ReadXaction, 100);
                    if (i == 0)
                    {
                        new Exception("Failed to send I2C data");
                    }
                    Debug.Print(read[0].ToString());
                    Thread.Sleep(2000);
                }
                */
               /* xaction[0] = I2C.CreateWriteTransaction(new byte[] { 0x06, 0x00 });
                if (I2C.Execute(xaction, 100) == 0)
                {
                    new Exception("Failed to send I2C data");
                }
                //xaction[0] = I2C.CreateWriteTransaction(new byte[] { 0x07, 0x00 });
                xaction[0].Buffer[0] = 7;
                if (I2C.Execute(xaction, 100) == 0)
                {
                    new Exception("Failed to send I2C data");
                }*/
            }
			
            private void _WriteRegister(byte reg, byte value)
            {
                SelectThisDevice();
                WriteXaction[0].Buffer[0] = reg;
                WriteXaction[0].Buffer[1] = value;
                int i = I2C.Execute(WriteXaction, 100);
                if (i == 0)
                {
                    new Exception("I2C Failed");
                }
            }
			
            private byte _ReadRegister(byte reg)
            {
                SelectThisDevice();
                ReadXaction[0].Buffer[0] = reg;
                int i = I2C.Execute(ReadXaction, 100);
                if (i == 0)
                {
                    new Exception("I2C Failed");
                }
                return ReadXaction[1].Buffer[0];
            }
			
            public void WritePort(byte port, byte value)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");

                _WriteRegister((byte)(8 + port), value);
            }
			
            public void SetPortInputs(byte port, byte mask)
            {
                SelectThisDevice();
                if (port > 4)
                    throw new ArgumentException("port");

                _WriteRegister((byte)(0x18 + port), mask);
            }
			
            public byte GetPortInputsState(byte port)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");

                return _ReadRegister((byte)(0x18 + port));
            }
			
            public byte ReadPort(byte port)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");
                return _ReadRegister((byte)(0 + port));
            }
			
            public void SetPin(byte port, byte pin)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");
                if (pin > 7)
                    throw new ArgumentException("pin");

                byte b = ReadPort(port);
                b |= (byte) (1 << (pin));
                WritePort(port, b);
            }
			
            public void ClearPin(byte port, byte pin)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");
                if (pin > 7)
                    throw new ArgumentException("pin");

                byte b = ReadPort(port);
                b &= (byte)~(1 << (pin));
                WritePort(port, b);
            }
			
            public bool ReadPin(byte port, byte pin)
            {
                SelectThisDevice();

                byte b = ReadPort(port);
                byte mask = (byte)(1 << pin);
                if((b & mask) > 0)
                    return true;
                return false;
            }

            void SelectThisDevice()
            {
                //if (I2C == null)
                //    I2C = new I2CDevice(config);
                //else
                I2C.Config = config;
            }

            public void MakePinOutput(byte port, byte pin)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");
                if (pin > 7)
                    throw new ArgumentException("pin");

                byte b = GetPortInputsState(port);
                b &= (byte)~(1 << pin);
                SetPortInputs(port, b);

            }
			
            public void MakePinInput(byte port, byte pin)
            {
                SelectThisDevice();

                if (port > 4)
                    throw new ArgumentException("port");
                if (pin > 7)
                    throw new ArgumentException("pin");

                byte b = GetPortInputsState(port);
                b |= (byte)(1 << pin);
                SetPortInputs(port, b);

            }
        }
    //}
}

and using it:

        IO40 io40_ic1;
        IO40 io40_ic2;
        IO40 io40_ic3;       
        public Hardware()
        {
            InitHardware();
            io40_ic1 = new IO40(3);
            io40_ic2 = new IO40(1);
            io40_ic3 = new IO40(0);
        }

@ Dave McLaughlin - You supposed to lock on the same object

It should be. I am passing in the I2C device to each constructor. It’s just named different in each class.

Here is the code that’s used to create each class.


            _I2Cdevice = new I2CDevice(new I2CDevice.Configuration(0x38, 400));

            ai0 = new Mcp342x(Mcp342x.InputChannel.Ch1, Mcp342x.Resolution.SixteenBits,
                              Mcp342x.ConversionMode.Continuous, Mcp342x.ProgGain.x1, 3, _I2Cdevice);
            
            CapTouchDriver CapDriver = new CapTouchDriver(_I2Cdevice);

That would work because your calls to the class will, from the look of it, be in the same thread. What would happen in your code if you were to create this and call them from different threads?

I’ve done this before where I have 2 I2C devices and change the configuration on the fly. This works really well as long as the calls are from the same thread. The issue I have is that I am calling them from different threads so I need some way to make it thread safe and I though lock would take care of this. It’s a shame that C# NETMF does not have semaphores that I have been used to with a Real Time OS like UCOS. :frowning:

Maybe a singleton pattern works for you.

This may be another issue altogether. I have disabled the ADC I2C bus access for now and only have it running with the touch driver. I also tried it with the touch disable and only the ADC working. Lots of bus errors in both cases.

This is the same driver that Simon created for the capacitive display that works fine on my G120 board.

The I2C ADC on this board works fine with a ChipworkX module (which it was originally designed for)

The only difference to this and the ChipworkX based system is the fitting of this 5" LCD and that it has I2C for the touch driver connected to the same I2C as the ADC’s. The bus is pulled up with 2K2 resistors. It has a 200mm FPC cable from the main board to the LCD board. This works fine with the original ChipworkX board and the 4.3" LCD with an SPI connected touch screen driver.

I’ll hook up the logic analyser today to check whats happening.

@ Dave McLaughlin - Have you checked voltage level found on the I2C bus ? May be you have double resistors in parallel on the bus. I use on G400HDR 2 of my ADC MCP3424 board on the same bus (the boards are chained) and I have no problem with I2C. I didn’t test in concurrent context with 2 different thread, but I can do that. I think that “lock()” will be enough, but you need to resend again config to mcp3424 each time you request a conversion and remember to poll until RDY bit =0, before leaving lock(). This can be tricky with this chip.