Main Site Documentation

Multiple I2C devices on single I2C channel?


#1

Multiple I2C devices on single I2C channel.
Is it possible w/o use of extra IO40 expansion board?


#2

yep http://code.tinyclr.com/project/265/4x4-keypad-via-pcf8574ap-port-extender/ this project is driving a PCF8574 and a Newhaven Display on a single I2C bus.


#3

As I understand, declaring multiple instances of I2S will throw an exception. Isnt it? What are port extenders?


#4

You only declare 1 instance of the I2C bus, but you can have multiple I2C configuration declarations.

For example:


I2CDevice commBus = new I2CDevice(new I2CDevice.Configuration(0, 100));
I2CDevice.Configuration display = new I2CDevice.Configuration(0x50, 100);
I2CDevice.Configuration keypad = new I2CDevice.Configuration(0x55, 100);

Greetings


#5

If you are refering to the port extender mentioned in my code.tinyCLR post, it is an IC that adds more IO to the FEZ at the cost of two or 4 pins depending on how it is connected. ( See the schematic in the referenced post). In this case I use the PCF8574 which is an 8-bit quasibidirectional device which is interfaced via I2C (2 pins).
A Quasibidirectional device is a device which can be written and read. It is not true bidirectional because the value read is dependent on the value which has been writen to the device. In the case of the 8574, the device has a weak pull-up. So if you were to write a ‘1’ to a bit on the port, and read it back, it will read as a ‘1’ however if you were to apply a ‘0’ as an input to the port, by shorting it to ground through a switch, it would read ‘0’.
So to be able to read a value on a pin other than the one you wrote, you would write a ‘1’ to that bit in the port.
That’s why this particualar port extender makes a great keyboard scanner.


#6

Thanks a lot!


#7

Hi community, sorry to bounce the topic.

[quote]For example:

I2CDevice commBus = new I2CDevice(new I2CDevice.Configuration(0, 100));
I2CDevice.Configuration display = new I2CDevice.Configuration(0x50, 100);
I2CDevice.Configuration keypad = new I2CDevice.Configuration(0x55, 100);

[/quote]

After that, can I go on in this way (code copied from the wiki):

//create transactions (we need 2 in this example)
I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[2];
 
// create write buffer (we need one byte)
byte[] RegisterNum = new byte[1] { 2 };
xActions[0] = I2CDevice.CreateWriteTransaction(RegisterNum);
// create read buffer to read the register
byte[] RegisterValue = new byte[1];
xActions[1] = I2CDevice.CreateReadTransaction(RegisterValue);
 
// Now we access the I2C bus and timeout in one second 
// if no response
display.Execute(xActions, 1000);

keypad.Execute(xActions, 1000);
 
Debug.Print("Register value: " + RegisterValue[0].ToString());

As an example, ok no write to a keyboard and no read from a display.
Or is this completely wrong and do you have to use another mechanism.

Cu, Wim. (thanks in advance)


#8

Your code writes and reads from display then writes and reads from keyboard!


#9

@ Gus, yes I know. Please think more abstract (was my remark already) :smiley:

My question is about how to read from and and write to the devices (display and keyboard in the example from EriSan500 (Eric)).
I really do know that you shouldn’t write to a keyboard and read from a display.

If unclear, then please reply. Or better I will express myself in a different way:

How to write to the display and how to read from the keyboard if multiple devices are on one I@ C bus?

That is better, less confusion.

Cu, Wim.


#10

Hi WimC,

This code sets the device that will be addressed on the bus


I2CDevice commBus = new I2CDevice(new I2CDevice.Configuration(0, 100));

display and keypad can’t execute transactions, they only store bus configs. To talk to the keyboard you must call this:


I2CDevice commBus = new I2CDevice(keypad);

the commBus can execute a transaction.

I sometimes have up to 7 devices on the I2C bus and all of them use the I2C EEprom type interface, so I ended up creating an I2C_Mem class to talk to all my devices. On top of the Mem class, each device has it’s own class for all the fiddly bits.

For instance(no necessarily nice), this is my I2C_Mem class:


using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace FEZ_Panda_Application2
{
    public class I2C_Mem
    {
        private static bool I2C_Busy = false;

        I2CDevice.Configuration Config;
        I2CDevice Mem;

        public I2C_Mem(byte DeviceAddres, int Speed)
        {
            Config = new I2CDevice.Configuration(DeviceAddres, Speed);
        }
        public void Dispose()
        {
            Mem.Dispose();
        }
        public bool DevicePresent()
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[] { 0 };
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[1];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            int i = Mem.Execute(Transactions, 500);

            Mem.Dispose();
            I2C_Busy = false;

            return i > 0;
        }
        public byte[] Read(byte Address, byte Length)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[] { Address };
            byte[] Values = new byte[Length];
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[2];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            Transactions[1] = I2CDevice.CreateReadTransaction(Values);
            int i = Mem.Execute(Transactions, 1000);

            Mem.Dispose();
            I2C_Busy = false;

            return Values;
        }
        public byte[] Read(int Length)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] Values = new byte[Length];
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[1];
            Transactions[0] = I2CDevice.CreateReadTransaction(Values);
            int i = Mem.Execute(Transactions, 500);

            Mem.Dispose();
            I2C_Busy = false;

            return Values;
        }
        public byte Read(byte Address)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[] { Address };
            byte[] Values = new byte[1];
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[2];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            Transactions[1] = I2CDevice.CreateReadTransaction(Values);
            int i = Mem.Execute(Transactions, 100);

            Mem.Dispose();
            I2C_Busy = false;

            return Values[0];
        }
        public void Write(byte Address, byte[] Values)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[Values.Length + 1];
            RegisterAddress[0] = Address;
            Array.Copy(Values, 0, RegisterAddress, 1, Values.Length);
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[1];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            int i=Mem.Execute(Transactions, 500);
            if (i != Values.Length + 1)
            {

            }
            Mem.Dispose();
            I2C_Busy = false;
        }
        public void Write(byte Address, byte Value)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;
            
            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[] { Address, Value };
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[1];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            int i=Mem.Execute(Transactions, 100);

            Mem.Dispose();
            I2C_Busy = false;
        }
        public void Write(byte Value)
        {
            while (I2C_Busy) Thread.Sleep(10);
            I2C_Busy = true;

            Mem = new I2CDevice(Config);

            byte[] RegisterAddress = new byte[] { Value };
            I2CDevice.I2CTransaction[] Transactions = new I2CDevice.I2CTransaction[1];
            Transactions[0] = I2CDevice.CreateWriteTransaction(RegisterAddress);
            Mem.Execute(Transactions, 100);

            Mem.Dispose();
            I2C_Busy = false;
        }
    }
}

And this is a wrapper around that for the G sensor that I use:


using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;
namespace FEZ_Panda_Application2
{
    public class MMA7455L
    {
        public enum Mode
        {
            Standby = 0,
            MeasureMode,
            LevelDetectMode,
            PulseDetectMode
        }
        public enum Range
        {
            Range8g = 0,
            Range2g,
            Range4g
        }
        public float X;
        public float Y;
        public float Z;
        Mode SelectedMode;
        Range SelectedRange;

        I2C_Mem G = new I2C_Mem(0x1D, 400);
        public MMA7455L(Mode Mode, Range Range, InterruptPort DataReady)
        {
            G.Write(0x16, (byte)((1 << 8) + ((int)Range << 2) + Mode));
            DataReady.OnInterrupt += new NativeEventHandler(DataReady_OnInterrupt);
            SelectedMode = Mode;
            SelectedRange = Range;
        }

        void DataReady_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            GetReadings();
        }
        public void GetReadings()
        {
            //Read raw values
            if (SelectedRange == Range.Range2g || SelectedRange == Range.Range4g)
            {
                byte[] Readings = G.Read(7, 3);
                X = Readings[0];
                Y = Readings[1];
                Z = Readings[2];
                if (Z > 127) Z = Z - 256;
                if (Y > 127) Y = Y - 256;
                if (X > 127) X = X - 256;
            }
            else
            {
                byte[] Readings = G.Read(0, 6);
                X = Readings[0] + (Readings[1] << 8);
                Y = Readings[2] + (Readings[3] << 8);
                Z = Readings[4] + (Readings[5] << 8);
                if (Z > 512) Z = Z - 1024;
                if (Y > 512) Y = Y - 1024;
                if (X > 512) X = X - 1024;
            }

            //Scale values
            switch (SelectedRange)
            {
                case MMA7455L.Range.Range2g:
                    X = X / 64;
                    Y = Y / 64;
                    Z = Z / 64;
                    break;
                case MMA7455L.Range.Range4g:
                    X = X / 32;
                    Y = Y / 32;
                    Z = Z / 32;
                    break;
                case MMA7455L.Range.Range8g:
                    X = X / 64;
                    Y = Y / 64;
                    Z = Z / 64;
                    break;
            }
        }

        public MMA7455L(Mode Mode, Range Range)
        {
            G.Write(0x16, (byte)(Mode + ((int)Range << 2)));
        }
    }
}

Hope this helps…


#11

Yes it probably would.
I use the I2C extension from “Bec a Fuel” with pleasure.
It does my job completely.

Working on several PCA9531’s to show some lights.

Will publish the code as soon it is ready.

Cu, Wim.


#12

I’m having a problem getting my head around this.
I understand that there is 1 bus and you can have loads of devices on it.
In my case I want the BMP085 & TMP102 to work on the same bus.
Loading the code that has been posted for there two devices works fine.
( http://code.tinyclr.com/project/235/bmp085---i2c-barometric-pressure-sensor/ ) &
( http://code.tinyclr.com/project/309/digital-temperature-sensor-breakout---tmp102/ )
Merging them together fails, as too be expected because I believe they call for there own buses.
So I tried the TMP102 on its own and tried to split the bus/object as per the on-line tutorial.
( http://wiki.tinyclr.com/index.php?title=I2C_-_EEPROM )

all that happens is I get a runtime error. What am I doing wrong?


            // is the I2C bus definition (combined with object?)
            //_TMP102=new I2CDevice(new I2CDevice .Configuration(_sensorAddress, 100));
            // Splitting this up..

            // object
            I2CDevice.Configuration _TMP102 = new I2CDevice.Configuration(_sensorAddress, 100);

            // bus

            I2CDevice _TMP102b = new I2CDevice(_TMP102);

Yours Simon M.


#13

You can only have one I2CDevice created at a time, so you must create one, use it, dispose of it, then you can create the other etc…

Looked at the source quickly. Wish we could standardize on one interface model.

I can move this code over to my(yes i’m biased :)) model, with multiple device access, but I won’t be able to test anything.

You can also move it over. Look at the I2C_Mem class that I posted earlier in this thread and the MMA7455 class as an example.

Then you must convert all the CreateReadTransaction stuff to simpler Read(byte Address, byte Length)
and Write(byte Address, byte[] Data) commands.


#14

You can keep one instance of the I2CDevice object and simply switch configuration, no need to dispose of it each time you want to communicate with different physical device.


#15

This is what I thought based upon
( http://wiki.tinyclr.com/index.php?title=I2C_-_EEPROM ),
and this is what I was trying to achieve when I came unstuck. So how do I unpick

 _TMP102=new I2CDevice(new I2CDevice .Configuration(_sensorAddress, 100));

in to the above format?

I looked at your code Errol, and it didn’t line up with what I had seen in the above. However I’m happy to give it ago!

Yours Simon M.


#16

Look at the example in the “Accessing Multiple Devices” section of the wiki page that you have mentioned.
It shows how to switch configuration to effectively communicate with multiple devices on the same bus.


#17

Thanks for getting back to me so quickly Architect,
I thought I had when I took the line

_TMP102=new I2CDevice(new I2CDevice .Configuration(_sensorAddress, 100));

and split it thus

I2CDevice.Configuration _TMP102 = new I2CDevice.Configuration(_sensorAddress, 100);

And

I2CDevice _TMP102b = new I2CDevice(_TMP102);

This then throws up a [quote]An unhandled exception of type ‘System.NullReferenceException’ occurred in[/quote] error.

I 'm assuming that this is because it can’t ref my I2C device?

Yours Simon M.


#18

For completeness, this is the code I’m working on from [quote]Richard9[/quote]


using System;
using System .Threading;

using Microsoft .SPOT;
using Microsoft .SPOT .Hardware;

namespace Sensor
    {
    public class TMP102
        {
        I2CDevice _TMP102 = null;

        public enum ADD0
            {
            Gnd,
            Vcc,
            SDA,
            SCL
            }

        public enum ConversionRate
            {
            quarter_Hz,
            one_Hz,
            four_Hz,
            eight_Hz
            }

        public enum ThermostatMode
            {
            ComparatorMode,
            InterruptMode
            }

        public enum AlertPolarity
            {
            activeLow,
            activeHigh
            }

        public enum ConsecutiveFaults
            {
            one,
            two,
            four,
            six
            }

        private enum Registers
            {
            Temperature=0x00,
            Configuration=0x01,
            T_low=0x02,
            T_high=0x03
            }

        private ushort _sensorAddress;

        private byte[] _registerNum = new byte[1] { (byte) Registers .Configuration };
        private byte[] _registerValue = new byte[2];

        private float _temperature = 0.0f;

        private bool _oneShotMode;
        private AlertPolarity _alertPolarity;
        private ThermostatMode _thermostatMode;
        private ConsecutiveFaults _consecutiveFaults;

        public bool Init(ADD0 addressSelect)
            {
            return Init(addressSelect, false, AlertPolarity .activeHigh, ConversionRate .four_Hz, ThermostatMode .ComparatorMode, ConsecutiveFaults .one, 0, 0);
            }

        public bool Init(
            ADD0 addressSelect=ADD0.Gnd,
            bool oneShotMode=false,
            AlertPolarity alertPolarity=AlertPolarity.activeHigh,
            ConversionRate conversionRate=ConversionRate.four_Hz,
            ThermostatMode thermostatMode=ThermostatMode.ComparatorMode)
            {
            return Init(addressSelect, oneShotMode, alertPolarity, conversionRate, thermostatMode, ConsecutiveFaults .one, 0, 0);
            }

        private bool Init(
            ADD0 addressSelect,
            bool oneShotMode,
            AlertPolarity alertPolarity,
            ConversionRate conversionRate,
            ThermostatMode thermostatMode,
            ConsecutiveFaults consecutiveFaults,
            ushort limitHigh,
            ushort limitLow)
            {
            // Sleep past first conversion
            Thread .Sleep(30);

            switch(addressSelect)
                {
                case ADD0 .Gnd: _sensorAddress=0x90>>1; break;
                case ADD0 .Vcc: _sensorAddress=0x92>>1; break;
                case ADD0 .SDA: _sensorAddress=0x94>>1; break;
                case ADD0 .SCL: _sensorAddress=0x96>>1; break;
                }
            
            // this is the I2C bus definitiion? (combined with object?)
            //_TMP102=new I2CDevice(new I2CDevice .Configuration(_sensorAddress, 100));
            // Splitting this up..

            // object
            I2CDevice.Configuration _TMP102 = new I2CDevice.Configuration(_sensorAddress, 100);

            // bus

            I2CDevice _TMP102b = new I2CDevice(_TMP102);

            _alertPolarity=alertPolarity;
            _oneShotMode=oneShotMode;
            _thermostatMode=thermostatMode;
            _consecutiveFaults=consecutiveFaults;

            _registerNum[0]=(byte) Registers .Configuration;
            int bytesTransfered = ReadRegister();

            if(bytesTransfered==3)
                {
                if(_oneShotMode)
                    _registerValue[0]=(byte) (_registerValue[0]|0x01);
                else
                    _registerValue[0]=(byte) (_registerValue[0]&0xfe);

                if(_thermostatMode==ThermostatMode .InterruptMode)
                    _registerValue[0]=(byte) (_registerValue[0]|0x02);
                else
                    _registerValue[0]=(byte) (_registerValue[0]&0xfd);

                if(_alertPolarity==AlertPolarity .activeLow)
                    _registerValue[0]=(byte) (_registerValue[0]|0x04);
                else
                    _registerValue[0]=(byte) (_registerValue[0]&~0x04);

                switch(conversionRate)
                    {
                    case ConversionRate .quarter_Hz: _registerValue[1]=(byte) ((_registerValue[1]&0x3f)|(0x00<<6)); break;
                    case ConversionRate .one_Hz: _registerValue[1]=(byte) ((_registerValue[1]&0x3f)|(0x01<<6)); break;
                    case ConversionRate .four_Hz: _registerValue[1]=(byte) ((_registerValue[1]&0x3f)|(0x02<<6)); break;
                    case ConversionRate .eight_Hz: _registerValue[1]=(byte) ((_registerValue[1]&0x3f)|(0x03<<6)); break;
                    }

                bytesTransfered=WriteRegister();
                Thread .Sleep(30);
                }

            return (bytesTransfered==3);
            }

        public float Read()
            {
            int bytesTransfered;

            if(_oneShotMode)
                {
                _registerNum[0]=(byte) Registers .Configuration;
                ReadRegister();

                if((_registerValue[0]&0x01)==0x01)
                    {
                    // Toggle OS bit
                    _registerValue[0]|=0x80;
                    WriteRegister();

                    // Sleep so conversion can start
                    Thread .Sleep(1);

                    // Wait for OS bit to toggle back to 1
                    do
                        {
                        ReadRegister();
                        }
                    while((_registerValue[0]&0x80)==0x00);
                    }
                }

            _registerNum[0]=(byte) Registers .Temperature;
            bytesTransfered=ReadRegister();

            if(bytesTransfered==3)
                {
                int temp = ((_registerValue[0]<<4)|(_registerValue[1]>>4));

                if((temp&0x0800)==0x0800)
                    {
                    _temperature-=1;
                    _temperature=~temp;
                    _temperature=temp&0x0FFF;

                    _temperature=(float) temp*-0.0625f;
                    }
                else
                    {
                    _temperature=(float) temp*0.0625f;
                    }
                }

            return _temperature;
            }

        public float asCelcius()
            {
            return _temperature;
            }

        public float asFahrenheit()
            {
            return (asCelcius()*9.0f/5.0f)+32.0f;
            }

        public float asKelvin()
            {
            return (asCelcius()+273.15f);
            }

        public float asRankine()
            {
            return (asKelvin()*9.0f/5.0f);
            }

        private int ReadRegister()
            {
            I2CDevice.I2CTransaction[] xActions = new I2CDevice .I2CTransaction[2];

            xActions[0]=I2CDevice .CreateWriteTransaction(_registerNum);
            xActions[1]=I2CDevice .CreateReadTransaction(_registerValue);

            return _TMP102 .Execute(xActions, 30); // << This line gives the error type 'System.NullReferenceException' 

            }

        private int WriteRegister()
            {
            I2CDevice.I2CTransaction[] xActions = new I2CDevice .I2CTransaction[1];

            xActions[0]=I2CDevice .CreateWriteTransaction(new byte[] { _registerNum[0], _registerValue[0], _registerValue[1] });

            return _TMP102 .Execute(xActions, 30);
            }
        }
    }


#19

Oh man. You need to clean up you variable names a bit. You have class variable which is _TMP102 of type I2CDevice. In your init method you have _TMP102 which is I2CDevice.Configuration. the name itself will hide the class variable of different type. And then you create I2CDevice and assign it to another local variable _TMP102b. Both variables will go out of scope and the objects will be collected or worse - leaked.

So your class’ _TMP102 is never assigned and when you try to access it in another class method it is null.


#20

Hi Architect,
Thanks for getting back to me again.
The code is a streight steal from http://code.tinyclr.com/project/309/digital-temperature-sensor-breakout—tmp102/.

When you say [quote]And then you create I2CDevice and assign it to another local variable _TMP102b. Both variables will go out of scope and the objects will be collected or worse - leaked.
[/quote] what do I need to do to correct this? Where will I need to declare it?

Yours Simon M.

With out stating the screaming obvious I’m a hardware engineer who is happy with op-amps / filters Logic and mnemonics. So when I see I2C, I think shift register, what could be difficult? :-[