Netduino home hardware projects downloads community

Jump to content


Photo

I2CBus


  • Please log in to reply
22 replies to this topic

#1 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 137 posts
  • LocationNew York, NY

Posted 24 October 2010 - 04:34 AM

This code is a derivative of the I2CBus class Pavel Bansky developed. This will help you easily manage multiple I2C devices on a single bus. My I2CBus implements the Singleton pattern. Please note this is done this way because you can only have one instance of I2CDevice and it can sometimes be hard to manage multiple I2C devices.

I have attached the I2CBus class, an example I2C sensor driver, and the Program.cs of the example Netduino application.

In the first code snippet below we will need to create an instance of the I2CBus class and also our example I2C sensor driver. When we call the constructor for the example sensor we will pass in the I2C device address of the example sensor.

    public class Program
    {
        // Example I2C sensor.
        private static ExampleSensor _exampleSensor;

        public static void Main()
        {
            // Create a new I2C bus instance at startup.
            I2CBus i2cBus = I2CBus.GetInstance();

            _exampleSensor = new ExampleSensor(0x79);

            // write something to a register.
            _exampleSensor.WriteSomethingToSpecificRegister();

            byte[] data = _exampleSensor.ReadSomething();
        }

    }

This next code snippet shows what our example I2C sensor driver looks like. The constructor creates an I2CDevice configuration that contains the configuration information for this example sensor.

When we want to perform a read or write transaction on this I2C device, we talk to it through the I2CBus instance. When reading or writing, we will pass the I2CBus Write/Read methods the example sensor I2C device configuration along with the transaction timeout and other bytes to be read or written. We pass the I2C device configuration so that the I2CBus knows what device to read from or write to.

    /// <summary>
    /// This is an I2C sensor.
    /// </summary>
    public class ExampleSensor
    {
        private I2CDevice.Configuration _slaveConfig;
        private const int TransactionTimeout = 1000; // ms
        private const byte ClockRateKHz = 59;
        public byte Address { get; private set; }

        /// <summary>
        /// Example sensor constructor
        /// </summary>
        /// <param name="address">I2C device address of the example sensor</param>
        public ExampleSensor(byte address)
        {
            Address = address;
            _slaveConfig = new I2CDevice.Configuration(address, ClockRateKHz);
        }


        public byte[] ReadSomething()
        {
            // write register address
            I2CBus.GetInstance().Write(_slaveConfig, new byte[] {0xF2}, TransactionTimeout);

            // get MSB and LSB result
            byte[] data = new byte[2];
            I2CBus.GetInstance().Read(_slaveConfig, data, TransactionTimeout);

            return data;
        }

        public byte[] ReadSomethingFromSpecificRegister()
        {
            // get MSB and LSB result
            byte[] data = new byte[2];
            I2CBus.GetInstance().ReadRegister(_slaveConfig, 0xF1, data, TransactionTimeout);

            return data;
        }

        public void WriteSomethingToSpecificRegister()
        {
            I2CBus.GetInstance().WriteRegister(_slaveConfig, 0x3C, new byte[2] { 0xF4, 0x2E }, TransactionTimeout);
        }


    }

And last, but not least, here is the I2CBus class (implements Singleton pattern and is thread safe).

    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.");
            }
        }

        /// <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);
        }

    }

You will find the I2CBus and other classes attached to this post. Just read the documentation in the I2CBus class, look at the example code, and you should be able to figure it out. If not, then just post a question :)

Attached Files



#2 MattW

MattW

    Member

  • Members
  • PipPip
  • 23 posts

Posted 24 October 2010 - 07:42 AM

Thanks for the code! I was just reading a blog about the I2CBus the other day... :)

#3 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 137 posts
  • LocationNew York, NY

Posted 24 October 2010 - 05:25 PM

I just realized that the I2CBus class should have implemented the Singleton pattern. I have updated the initial post and also the file attachments to reflect the changes. This Singleton pattern implementation is thread safe too.

#4 Shawn

Shawn

    New Member

  • Members
  • Pip
  • 7 posts

Posted 21 January 2011 - 02:42 AM

Will this code work on a Netduino Plus? I have been trying to get I2C working on my board but have had no success. My programs compile but I never see activity on the I2C pins A4 and A5. I compiled this code on my NetDuino plus and again, no activity on the pins. Looking at the schematic how are the MUX1 and MUX2 lines controlled that switch A4 and A5 pins between the I2C hardware analog input hardware on the AT91? Shawn

#5 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7092 posts
  • LocationNew York, NY

Posted 21 January 2011 - 03:05 AM

Hi Shawn, I2C communication requires pull-up resistors. The Netduino will pull the voltage "low" to GND to send data, but the pull-up resistors keep the line high the rest of the time. This is how I2C works... The MUX1/MUX2 lines are automatically switched in the background (to digial/I2C mode or to analog mode). Chris

#6 Shawn

Shawn

    New Member

  • Members
  • Pip
  • 7 posts

Posted 21 January 2011 - 03:19 AM

I have only been working with I2C for 20 years, you think I would have remembered that ;)

#7 ritchie

ritchie

    New Member

  • Members
  • Pip
  • 7 posts

Posted 01 November 2011 - 10:37 PM

This code is a derivative of the I2CBus class Pavel Bansky developed. This will help you easily manage multiple I2C devices on a single bus. My I2CBus implements the Singleton pattern. Please note this is done this way because you can only have one instance of I2CDevice and it can sometimes be hard to manage multiple I2C devices.


Hi phantomtypist, what is the license for your I2CBus class?

Thanks and regards,

r.

#8 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1716 posts
  • LocationVenezia, Italia

Posted 02 November 2011 - 01:10 PM

Phantomtypist, thanks for your work and for sharing it, but...
...I really can't feel the reason about a singleton would be better than a static declaration.

There are several strange things, IMHO:
  • a singleton instance never dies, so it makes no sense the IDisposable implementation;
  • even considering the IDisposable pattern, once you dispose it (calling Dispose), calling Dispose again probably will throw, and does not kill the hosted instance;
  • the LockObject has no sense, because you are locking just the I2CBus instance creation, but the further usage is not thread-safe at all;
  • consider two threads using the I2CBus: they don't work properly.
The I2CDevice instance is a mutex by itself, because when you instantiate it, automatically none else can instantiate it again. However it's *NOT* a real mutex, but you are guaranteed that none else can open it.
So, the best way to use the I2C driver is just as it is offered by the Micro Framework.

If you want a different flavor of I2C driver, you may consider hosting the I2CDevice instance in a Disposable class (e.g. ExampleSensor). The ideal usage should be as follows:

using (ExampleSensor sensor = new ExampleSensor())
{
   // do something with "sensor"
}

The thread-safety on drivers like I2C or SPI is a much more complex task, and cannot be solved with a simple lock.
Hope it helps.
Cheers
Biggest fault of Netduino? It runs by electricity.

#9 Miha Markic

Miha Markic

    Advanced Member

  • Members
  • PipPipPip
  • 67 posts
  • LocationNova Gorica, Slovenia

Posted 12 December 2011 - 02:04 PM

There is a reason why is better to go with singleton instead of static - easier unit testing. Wait, there is another one - you can you other instances instead of provided default (albeit not in OP's case).
Miha Markic, Microsoft MVP C#
Righthand .net consulting and software development
http://blog.rthand.com/ http://www.rthand.com

#10 Miha Markic

Miha Markic

    Advanced Member

  • Members
  • PipPipPip
  • 67 posts
  • LocationNova Gorica, Slovenia

Posted 12 December 2011 - 02:07 PM

About Dispose - even though the static variable never goes out of scope it might be still useful to call Dispose on it. .net guideline is that Dispose should be multi-call tolerant - IOW you can call it as many times as you wish and it shouldn't throw.
Miha Markic, Microsoft MVP C#
Righthand .net consulting and software development
http://blog.rthand.com/ http://www.rthand.com

#11 Miha Markic

Miha Markic

    Advanced Member

  • Members
  • PipPipPip
  • 67 posts
  • LocationNova Gorica, Slovenia

Posted 12 December 2011 - 02:18 PM

But in general I agree - the code isn't well written at all and probably unnecessary as you noted. B)
Miha Markic, Microsoft MVP C#
Righthand .net consulting and software development
http://blog.rthand.com/ http://www.rthand.com

#12 cyberpex

cyberpex

    New Member

  • Members
  • Pip
  • 8 posts

Posted 13 December 2012 - 10:47 PM

Hi!

I want use your code for an IMU 10DOF device, the GY-80 from dealextreme. It has an accelerometer, gyro, magnetometer and barometer, all in one with i2c bus.

I receive readings from barometer, but only fixed values for the others sensors.

Please, help me to discovery where i'm wrong. I'll attach my project.

Thanks a lot

Posted Image

Attached Files



#13 Dave VanderWekke

Dave VanderWekke

    Advanced Member

  • Members
  • PipPipPip
  • 857 posts
  • LocationHighland Lakes, New Jersey, USA

Posted 14 December 2012 - 12:05 PM

The Addresses don't look correct to me. Where did you get the specs and addresses from?

Try the following instead:

BMP085 pressureSensor = new BMP085(0x77, BMP085.DeviceMode.Standard);
ADXL345 accelerometer = new ADXL345(0x53);
HMC5883L magnetometer = new HMC5883L(0x1E);
L3G4200D gyroscope = new L3G4200D(0x68);

Software Architect, Electronic Engineer, Car Enthusiast, Chef, Blogger, Paranormal Investigator (and Skeptic). I'm like MacGyver, Alton Brown, Doc Brown and Peter Venkman all in one.
My Blog Site: JerseyTechGuy's Blog
Facebook:JerseyTechGuy on Facebook
Twitter: @JerseyTechGuy

My Netduino 2 Tutorials
My Netduino Based Paranormal Investigation Tools
 


#14 NooM

NooM

    Advanced Member

  • Members
  • PipPipPip
  • 490 posts
  • LocationAustria

Posted 14 December 2012 - 01:27 PM

i prefere i different i2c bus style, its even easier to use. its leand to netmf-toolboxes bus systems. usage: class test : MultiI2C { public test() :base(address, frequenzy) { } void readstuff() { base.Read(...); } } //edit i like your write/read register functions, ill add them to mine, assimilated :D //edit: new version added. i havent tested it, but it should work. //edit: tested, works, could read the identification registers of my hcm module with the new i2c class wia ReadRegister.

Attached Files



#15 cyberpex

cyberpex

    New Member

  • Members
  • Pip
  • 8 posts

Posted 14 December 2012 - 07:41 PM

The Addresses don't look correct to me. Where did you get the specs and addresses from?

Try the following instead:

BMP085 pressureSensor = new BMP085(0x77, BMP085.DeviceMode.Standard);
ADXL345 accelerometer = new ADXL345(0x53);
HMC5883L magnetometer = new HMC5883L(0x1E);
L3G4200D gyroscope = new L3G4200D(0x68);


Dave,

thanks for the reply, but addres is OK 0xEF >> 1 = 0x77, ie 0xEF is adress + read bit (0x77<<1+1)

i can read from my devices, but only barometer sensor give me dynamics values, the others only fixed values

#16 Dave VanderWekke

Dave VanderWekke

    Advanced Member

  • Members
  • PipPipPip
  • 857 posts
  • LocationHighland Lakes, New Jersey, USA

Posted 14 December 2012 - 08:07 PM


Dave,

thanks for the reply, but addres is OK 0xEF >> 1 = 0x77, ie 0xEF is adress + read bit (0x77<<1+1)

i can read from my devices, but only barometer sensor give me dynamics values, the others only fixed values


You are absolutely correct. Being lazy I went onto Google and typed Online Bitshift calculator and some site came up. Whacked in the values and they shifted wrong. Gotta go back through my browser history and find that site and let them know the shift right doesn't work correctly.

So... back to square one, yep checked them in scientific calculator and all are correct.

I have all of these sensors except my replacement BMP085 which should be in the mailbox today. I'll try the software with my individual sensors to see if it works. I looked through all the code and nothing jumps out at me.

Software Architect, Electronic Engineer, Car Enthusiast, Chef, Blogger, Paranormal Investigator (and Skeptic). I'm like MacGyver, Alton Brown, Doc Brown and Peter Venkman all in one.
My Blog Site: JerseyTechGuy's Blog
Facebook:JerseyTechGuy on Facebook
Twitter: @JerseyTechGuy

My Netduino 2 Tutorials
My Netduino Based Paranormal Investigation Tools
 


#17 NooM

NooM

    Advanced Member

  • Members
  • PipPipPip
  • 490 posts
  • LocationAustria

Posted 15 December 2012 - 04:36 AM

thats the code i use (with my multi i2c i posted earlier) the readings are fine, every movement is noticed (ofc thats just a little "preview" - you should implement the interrupt when data is ready and read than!) its very cool that the register pointer incements automatically, so you need only one read instruction for all 6 data registers

Attached Files

  • Attached File  HMC58.cs   1.14KB   76 downloads


#18 mtylerjr

mtylerjr

    Advanced Member

  • Members
  • PipPipPip
  • 101 posts
  • LocationSan Jose, California

Posted 16 December 2012 - 05:59 AM

thats the code i use (with my multi i2c i posted earlier)

the readings are fine, every movement is noticed

(ofc thats just a little "preview" - you should implement the interrupt when data is ready and read than!)


its very cool that the register pointer incements automatically, so you need only one read instruction for all 6 data registers



I'm trying to write a little i2c driver for the Macetech centipede shield (64 pin GPIO expander shield) - actually the driver should work with two of them both connected on the same I2C bus. It is intended to drive a small christmas display with 32 bright RGB leds (so, Im going to use 96 outputs to drive the LEDs)

The way the Centipede shield is set up, there are 4 MCP23017 chips on each shield- and when you use two in conjunction on the same i2c bus (you need to set one's address jumper differently) you end up with 8 individual MCP23017 chips on the bus, each with it's own i2c address (0 through 7) and 16 GPIO pins on each port (128 pins total)

I should be able to create 8 multiI2c derived objects (one for each chip) each constructed with the chip's address in the config, right?

Also, this is my first attempt at i2C on the netduino (Netduino Plus 2) platform. I was frustrated today trying to gather initial information, seeing the myriad approaches, and seemingly conflicting info. (I get frustrated easily trying to sort through information like this - it's not a knock on anyone)

From what I can gather, I2C is now possible on the Netduino Plus 2, if:
1] You flash with the latest firmware (4.2.1.2)
2] According to Chris Walker, "if you are using a Netduino Plus 2...please remove the SecretLabs.NETMF.Hardware.NetduinoPlus.dll assembly and add in SecretLabs.NETMF.Hardware.Netduino.dll instead."
3] I need to add my own pullup resistors for pins 4 and 5

I have some other questions already, if anyone here can help me find the correct answers, I would be very appreciative. Im trying to finish this in the next couple of days so I can set it up at work.

a] How do I calculate the correct value for the pullup resistors?
b] What should I pass for the clock i2c clock speed when running on the ND+2
c] I am powering the aux+ of the macetech shields with an external 5V supply, to drive the 32 (i.e. 96) leds, and the ND+2 is powered by its own 5V (currently my USB port) - is there anything i need to watch out for?


If anyone is interested, the display is a small christmas tree that will have 32 RGB lights - the pattern for the tree will be sent to my ND+2, from a small RFID reader that will read a UHF rfid tag placed directly on it. I work in an RFID development company, and everyone has long range (100m XC3 protocol) readers, and the idea is that anyone can transmit from their own devices to that tag and write a new pattern, which my reader will pick up as it polls the tag periodically. Anyone in the building will be able to change the display of the tree using their development readers.

The RFID stuff is a piece of cake. Its this i2c on the ND+2 that is proving to be my bottleneck...

#19 NooM

NooM

    Advanced Member

  • Members
  • PipPipPip
  • 490 posts
  • LocationAustria

Posted 16 December 2012 - 08:51 AM

mty: i use 4.7kohm resistors, they are fine, you need them. (pullup, both sda and scl) iam using mcp23017 too with my multii2c, its fine, even using multiple stuff, and also its threadsafe. they are really easy to setup for output, but you realla have to read the datasheet carfeully to use them as input ports (a lot to setup) - also you can use one as one 16bit i/o chip or 2x 8bit io chips clock speed: you get them from your devices datasheet, but i think 400khz is max (at least for my netduino mini) anything lower than what the datasheet says is fine too. when you have multiple power sources you connect gnd together, except when they are opto coupled. (dont connevt v+ together!)

#20 cyberpex

cyberpex

    New Member

  • Members
  • Pip
  • 8 posts

Posted 31 December 2012 - 08:42 PM

...I have all of these sensors except my replacement BMP085 which should be in the mailbox today. I'll try the software with my individual sensors to see if it works. I looked through all the code and nothing jumps out at me...

Thanks mtylerjr!

 

i have no time to try right now, but soon i'll do it.






0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2010-2014 Secret Labs LLC  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.