Main Site Documentation

Multithreaded i2c Programming


#1

Tried searching the forum but had no luck finding an answer to a specific question.

I’m building a solution which requires multiple different types of i2c slaves. Some of the i2c interaction will be threaded and/or interrupt driven. Since code will execute asynchronously is there a mechanism in .NET micro framework to ensure that the i2c bus is clear to use (e.g. not currently being written to or read from another thread)?

On Linux the kernel takes care of this (i2c_smbus_xfer() in i2c-core.c will lock the i2c adapter before beginning a transfer), so transactions won’t step on each other.

In .NET it appears there is no protection like this, so if one thread is writing to the i2c device, another could start writing a transaction if an interrupt triggers, for instance.

What is the general ‘best practice’ for this in .NET MF? Abstracting a central queue which threads dump their transactions in to process sequentially?


#2

I think it’s reasonably simple for you to implement thread-safe use of the I2C bus and you don’t need to use a queue for this, just using LOCK should be sufficient. You just need to think about how autonomous do you need the operation to be, do you need a full write plus a read to a device or can you consider the read and the write as separate actions.


#3

This is where queuing model broke down for me. There’s already one instance where I need to write to a register, read the output, then immediately write back out to the same device based on the result. I trashed the queue model already. Passing read results back to my modules was a PITA when I tried doing a central queue.

Lock should work. I extended the i2cTransaction class to include a reference to my module base class, which contains the device config.

       public  static I2CDevice i2cBus;
 
        public static TransResult ProcessTransaction(i2cTransaction Transaction, int Timeout)
        {
            lock (i2cBus)
            {
                // set i2c device configuration from module linked to transaction - this was the whole point of creating new transaction class
                i2cBus.Config = Transaction.Module.DeviceConfig;

                // Execute the transaction
                int result = i2cBus.Execute(Transaction.Actions, Timeout);

                if (result == 0)
                {
                    Debug.Print("Failed to perform I2C transaction");
                    return TransResult.Failed;
                }
                else
                {
                    return TransResult.Succeed;
                }
            }

        }

Then a base class (i2cModule) that my various components can derive from;

   class i2cModule 
    {
        I2CDevice.Configuration _DeviceConfig;
        public I2CDevice.Configuration DeviceConfig
        {
            get { return _DeviceConfig; }
            set { _DeviceConfig = value; }
        }

        ushort _i2CAddress;
        public ushort I2CAddress
        {
            get { return _i2CAddress; }
            set { _i2CAddress = value; }
        }

        int _ClockRateKHz;
        public int ClockRateKHz
        {
            get { return _ClockRateKHz; }
            set { _ClockRateKHz = value; }
        }

        public void i2cModule(ushort Address, int ClockRateKHz)
        {
            _DeviceConfig = new I2CDevice.Configuration(Address, ClockRateKHz);
            _i2CAddress = Address;
            _ClockRateKHz = ClockRateKHz;
        }

Then individual modules are derived from that.

A generic register operation to a slave is stubbed out below;

   class i2cMCP23017 : i2cModule
    {
    //snip

      public void WriteToRegister(CREG_BANK0 register, byte[] buffer)
        {
         
            byte[] newbuffer = new byte[buffer.Length +1];
            
            // translate register to CREG_BANK1 if currentbank = bank 1
            byte Register = TranslateRegister(register);

            newbuffer[0] = Register;
            buffer.CopyTo (newbuffer, 1 );

            i2cTransaction trans = new i2cTransaction();
            trans.Module = this;
            trans.AddWriteAction(newbuffer);
            TransResult result = Program.ProcessTransaction(trans, 1000);
            if (result == TransResult.Succeed)
            {
                //contingent actions here
            }
        }

Hopefully this will pattern will be thread friendly later on when I’m handling interrupts.


#4

Oh the other missing part of that. The extended transaction class (this has a reference to the i2cModule) and an easier to use constructor for adding actions.

class i2cTransaction
    {
        public int ActionCount;
        public I2CDevice.I2CTransaction[] Actions;
        public i2cModule Module;
        
        public void AddWriteAction(byte[] buffer)
        {
            ActionCount += 1;
            
            I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[ActionCount];
            Actions.CopyTo (xActions, 0);
            
            xActions[xActions.Length] = I2CDevice.CreateWriteTransaction(buffer);
            Actions = xActions;
        }

        public void AddReadAction(byte[] buffer)
        {
            ActionCount += 1;
             I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[ActionCount];
            Actions.CopyTo (xActions, 0);
            
            xActions[xActions.Length] = I2CDevice.CreateReadTransaction(buffer);
            Actions = xActions;
        }


    }

#5

The reason for abstracting modules like that is I want the ability to support hot-plug i2c devices of various types.

That’ll keep each driver I write friendly with one another, at least, as far as base configuration goes, and let me handle them as a collection, adding and removing at whim.

   class i2cModules : IEnumerable
    {
        private ArrayList _Modules;

        public int Count { get { return _Modules.Count; } }

        public i2cModules() { _Modules = new ArrayList(); }

        public void Add(i2cModule Module) { _Modules.Add(Module); }

        public void AddRange(i2cModules Modules)
        {
            foreach (i2cModule module in Modules)
                _Modules.Add(module);
        }

        public void Remove(i2cModule Module)
        {
            _Modules.Remove(Module);
        }

        public void RemoveAt(int Index)
        {
            _Modules.RemoveAt(Index);
        }

        public IEnumerator GetEnumerator()
        {
            return _Modules.GetEnumerator();
        }

        public i2cModule this[int i]
        {
            get
            { return (i2cModule)_Modules[i]; }
            set
            { _Modules[i] = value; }
        }
    }

#6

Lock on the I2C object works just fine. I do this with the Newhaven touch driver which handles touch in an interrupt and the same I2C bus is used to interface to an ADC in another thread. No issues at all doing it this way.


#7

Thanks Dave for the insight.

In past projects I’ve always had to rely on mutex as (in the desktop world) it’s rare to see a single core CPU these days. With a single core there’s no need.

This is my first venture in to embedded development and it’s taking some mindset adjustments. Far more than I’m accustomed to. I have a few decades of development work behind me and this little foray in to embedded development has my brain tingling in strange places. Writing code for some of these i2c IC’s is challenging. I haven’t needed to get this “low level” since … well, 6502 assembly.

Far cry from writing database forms apps. Refreshing, even. I actually get to use my brain instead of droning out the n00,000th line of code.