Slow I2C Sensor Reads, is this normal? - Netduino Plus 2 (and Netduino Plus 1) - Netduino Forums
   
Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Slow I2C Sensor Reads, is this normal?

I2C Netduino Plus 2

  • Please log in to reply
13 replies to this topic

#1 berr08

berr08

    New Member

  • Members
  • Pip
  • 9 posts

Posted 07 August 2014 - 05:26 PM

Let me start by saying I have the same code logic wise on an arduino.

 

Trying to read a set of 3 magnet sensors via I2c.  For some reason it is taking almost half a second to got through the read cycle for each sensor, as you will see by the code it takes a few commands to complete a reading.  So I'm getting a full 3 sensor read in 1.3 so not even a full read per second.  On the arduino I am getting around 25-35 reads per second.  I remember reading that certain things are slower on the netduino, but I don't think that meant that huge a difference.  So I assume I am doing something wrong with the I2C maybe?  Can anyone point me in the correct direction?  Code Below: **Note I changed to a using in the loop as it would error out if I tried to make mulitple I2C devices at once...

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

namespace UDPMag_NetduinoPlus2
{
    class MagSensor
    {
        private MagDataStruct magData = new MagDataStruct();

        private bool collect = false;
        public struct MagDataStruct
        {
            public byte mag0Xh;
            public byte mag0Yh;
            public byte mag0Zh;
            public byte mag0Xl;
            public byte mag0Yl;
            public byte mag0Zl;

            public byte mag1Xh;
            public byte mag1Yh;
            public byte mag1Zh;
            public byte mag1Xl;
            public byte mag1Yl;
            public byte mag1Zl;

            public byte mag2Xh;
            public byte mag2Yh;
            public byte mag2Zh;
            public byte mag2Xl;
            public byte mag2Yl;
            public byte mag2Zl;
        }

        public delegate void DataReceived(MagDataStruct data);

        public event DataReceived OnDataReceived = null;

        public bool SensorRunning
        {
            get { return collect; }
        }

        public MagSensor()
        {
            // Create a delegate of type "ThreadStart", which is a pointer to the 
            // worker thread's main function
            ThreadStart delegateWorkerThread = new ThreadStart(Main);

            // Next create the actual thread, passing it the delegate to the main function
            Thread threadWorker = new Thread(delegateWorkerThread);

            // Now start the thread.
            threadWorker.Start();

        }

        public void StartCollecting()
        {
            collect = true;
        }
        public void StopCollecting()
        {
            collect = false;
        }

        #region The actual worker thread code
        /// <summary>
        /// This is a method which does the work of the worker thread.
        /// It waits (sleeps) for a UDP message to be received and passes it using the 
        /// supplied Interface.
        /// </summary>
        private void Main()
        {

            // Loop forever
            while (true)
            {
                if (collect)
                {
                    magData = new MagDataStruct();
                    DateTime t1 = DateTime.Now;

                    for (int i = 0; i < 3; i++)
                    {
                        ushort addy = 0x30; //Mag 0
                        if (i == 1)
                        {
                            addy = 0x31; //Mag 1
                        }
                        else
                        {
                            addy = 0x32; //Mag 2
                        }
                        using (I2CDevice mag = new I2CDevice(new I2CDevice.Configuration(addy, 400)))
                        {
                            runSensor(mag, i);
                        }
                    }

                    DateTime t2 = DateTime.Now;
                    TimeSpan ts = t2 - t1;
                    Debug.Print(ts.Minutes.ToString() + ":" + ts.Seconds.ToString() + ":" + ts.Milliseconds.ToString());
                    
                    // Call Event!
                    if (OnDataReceived != null)
                        OnDataReceived(magData);
                }
            }
        }

        private void runSensor(I2CDevice device, int id)
        {
            i2c_write(device, 0x07, 0x80);                      // Refill Charge Cap
            i2c_write(device, 0x07, 0x20);                      // SET Instruction
            i2c_write(device, 0x07, 0x40);                      // RESET Instruction
            i2c_write(device, 0x07, 0x01);                      // Take Measurement TM Flag == 1
            
            while ((i2c_read(device, 0x06) & 0x01) != 1) { };   // Wait formeasurment to be Done.

            switch (id) //Set byes in data struct.
            {
                case 0:
                    magData.mag0Xl = i2c_read(device, 0x00);
                    magData.mag0Xh = i2c_read(device, 0x01);
                    magData.mag0Yl = i2c_read(device, 0x02);
                    magData.mag0Yh = i2c_read(device, 0x03);
                    magData.mag0Zl = i2c_read(device, 0x04);
                    magData.mag0Zh = i2c_read(device, 0x05);
                    break;
                case 1:
                    magData.mag1Xl = i2c_read(device, 0x00);
                    magData.mag1Xh = i2c_read(device, 0x01);
                    magData.mag1Yl = i2c_read(device, 0x02);
                    magData.mag1Yh = i2c_read(device, 0x03);
                    magData.mag1Zl = i2c_read(device, 0x04);
                    magData.mag1Zh = i2c_read(device, 0x05);
                    break;
                case 2:
                    magData.mag2Xl = i2c_read(device, 0x00);
                    magData.mag2Xh = i2c_read(device, 0x01);
                    magData.mag2Yl = i2c_read(device, 0x02);
                    magData.mag2Yh = i2c_read(device, 0x03);
                    magData.mag2Zl = i2c_read(device, 0x04);
                    magData.mag2Zh = i2c_read(device, 0x05);
                    break;
            }
        }

        private void i2c_write(I2CDevice device, byte register, byte data)
        {
            byte[] trans = { register, data };
            I2CDevice.I2CTransaction[] i2cTx = new I2CDevice.I2CTransaction[1];
            i2cTx[0] = I2CDevice.CreateWriteTransaction(trans);
            device.Execute(i2cTx, 1);
        }

        private byte i2c_read(I2CDevice device, byte register)
        {
            byte[] trans = { register };
            byte[] read = new byte[1];

            I2CDevice.I2CTransaction[] i2cTx = new I2CDevice.I2CTransaction[2];
            i2cTx[0] = I2CDevice.CreateWriteTransaction(trans);
            i2cTx[1] = I2CDevice.CreateReadTransaction(read);

            device.Execute(i2cTx,1);

            return read[0]; //Return 1 byte...
        }
        #endregion
    }
}



#2 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 07 August 2014 - 07:12 PM

.Net  is an interpreted Managed language.

 

As such there are a few things you should not do if you want efficient code.

 

Note that efficiency was not a goal of .Net, robustness was. That doesn't mean you can not be efficient, you just can not go throwing around features that were intended to protect you from yourself, and expect efficiency.

 

In your main loop, you re-allocate (using) a i2c device 3 times every loop. That has to be cleaned up by the garbage collector. Allocate it once outside the loop, then re-use it.

 

Same can be said for your transactions.  There is NO reason to re-create 9 (or worse as I looked more, you are doing bytes?) of them every single loop.  Creating new objects is not free, nor is disposing of them when no longer being used (every loop in this case).

 

I posted an article a while back when I first started playing with the Netduino, showing an efficient way to talk to an i2c IMU; http://www.spiked3.com/?p=421

 

The code in the next post show multiple devices, so look at it also; http://www.spiked3.com/?p=438

 

Bottom line;  Think every time you use 'new', and if it is appropriate. Do not just copy someone else's code without understanding it.


  • TechnoGuy likes this

#3 berr08

berr08

    New Member

  • Members
  • Pip
  • 9 posts

Posted 07 August 2014 - 07:53 PM

Thanks for the reply.  I tried making each device 1x and when making a second i2cdevice I get an 'System.InvalidOperationException'.  So after toying with it for a while the only way I got it to work was with the using, which I know isn't officient which is why I asked what I can do.  I'm on here to hopefully learn more about this device and working with this compact library.  The way I do things, which I assume is differently that you, is to get things to work and then see what can be done to make it better.  Especially in an instance where the community isn't super responsive yet and there are only a handfull of examples I came across which all appearantly did things the wrong way.

 

I assume what I am doing is possible without a library, I see the example you linked to used the M2Mqtt library, was hoping to not use libraries...



#4 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 07 August 2014 - 09:04 PM

mqtt is used separately for m2m.

 

The important thing to realize is you can allocate 1 i2c, then switch configs;

 

I2CDevice i2cBus = new I2CDevice(null);
 
I2CDevice.Configuration accellConfig = new I2CDevice.Configuration(0x53, 400),
               gyroConfig = new I2CDevice.Configuration(0x68, 400);
 
......
 
i2cBus.Config = gyroConfig;
i2cBus.Execute(new I2CDevice.I2CTransaction[] { I2CDevice.CreateWriteTransaction(GYRO_DLPF) }, 100);
System.Threading.Thread.Sleep(5);
 
I create all my recurring transactions once, then re-use them.
 
I2CDevice.I2CTransaction[] accelTrans = new I2CDevice.I2CTransaction[] {
                I2CDevice.CreateWriteTransaction(DATAX0),
                I2CDevice.CreateReadTransaction(readBuf) };
 
And read multiple bytes


#5 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 07 August 2014 - 09:11 PM

Unfortunately, there are certain areas in the .NET Micro Framework that should have been redesigned, e.g. to support multiple devices...
 
While it is not possible to create second instance of I2CDevice unless you Dispose() the first one, it is possible to have several I2CDevice.Configuration instances and switch among them:
 

// Warning: Code written from head

private I2CDevice.Configuration[] configs = new [] {
  new I2CDevice.Configuration(0x30, 400),
  new I2CDevice.Configuration(0x31, 400),
  new I2CDevice.Configuration(0x32, 400)
};

// Initialize with a dummy configuration, or use any of the above
private I2CDevice mag = new I2CDevice(new I2CDevice.Configuration(0, 0));

...
for(var i = 0; i < configs.Length; i++)
{
  mag.Config = configs[i]; // Select actual device
  runSensor(mag, i);
}

I think there is even a class/library somewhere in the forums... ...I2CBus and probably several  other 'multi' I2C implementations.



#6 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 07 August 2014 - 09:14 PM

Ok,  Spiked just beat me to it.  :)



#7 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 07 August 2014 - 10:16 PM

....a handfull of examples I came across which all appearantly did things the wrong way.....

 

Wait until you look into ultrasounds. I have yet to see a single example on the entire internet, arduino or anything else, done correctly :|



#8 berr08

berr08

    New Member

  • Members
  • Pip
  • 9 posts

Posted 08 August 2014 - 12:41 PM

Thanks again for the help, I have changed the way I am doing this up to take your suggestions.  It is still only geting 2 or 3 reads (of all the sensors) a second, an improvement over the 1 a second but the arduino is getting 25 to 35 reads, so I'm still not where I need it to be.

 

I've changed the code to use the 1 main i2cdevice and swap the config, I created all the transactions outside of the loop and am reading all the data registers in one transaction.  Any other input would be great **braces for newb jokes**  Code below for the MemSic MMC3416xPJ sensor:

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

namespace UDPMag_NetduinoPlus2
{
    class MagSensor
    {
        private MagDataStruct magData = new MagDataStruct();
        private I2CDevice magDevice = null;
        private bool collect = false;
        public struct MagDataStruct
        {
            public byte mag0Xh;
            public byte mag0Yh;
            public byte mag0Zh;
            public byte mag0Xl;
            public byte mag0Yl;
            public byte mag0Zl;

            public byte mag1Xh;
            public byte mag1Yh;
            public byte mag1Zh;
            public byte mag1Xl;
            public byte mag1Yl;
            public byte mag1Zl;

            public byte mag2Xh;
            public byte mag2Yh;
            public byte mag2Zh;
            public byte mag2Xl;
            public byte mag2Yl;
            public byte mag2Zl;
        }

        public delegate void DataReceived(MagDataStruct data);

        public event DataReceived OnDataReceived = null;

        public bool SensorRunning
        {
            get { return collect; }
        }

        public MagSensor()
        {
            // Create a delegate of type "ThreadStart", which is a pointer to the 
            // worker thread's main function
            ThreadStart delegateWorkerThread = new ThreadStart(Main);

            // Next create the actual thread, passing it the delegate to the main function
            Thread threadWorker = new Thread(delegateWorkerThread);

            // Now start the thread.
            threadWorker.Start();

        }

        public void StartCollecting()
        {
            collect = true;
        }
        public void StopCollecting()
        {
            collect = false;
        }

        #region The actual worker thread code
        /// <summary>
        /// This is a method which does the work of the worker thread.
        /// </summary>
        private void Main()
        {
            magDevice = new I2CDevice(null);
            I2CDevice.Configuration mag0 = new I2CDevice.Configuration(0x30, 400);
            I2CDevice.Configuration mag1 = new I2CDevice.Configuration(0x31, 400);
            I2CDevice.Configuration mag2 = new I2CDevice.Configuration(0x32, 400);
            
            I2CDevice.I2CTransaction[] refillTrans = new I2CDevice.I2CTransaction[1];
            refillTrans[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x07, 0x80 });

            I2CDevice.I2CTransaction[] setTrans = new I2CDevice.I2CTransaction[1];
            setTrans[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x07, 0x20 });

            I2CDevice.I2CTransaction[] resetTrans = new I2CDevice.I2CTransaction[1];
            resetTrans[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x07, 0x40 });

            I2CDevice.I2CTransaction[] takeTrans = new I2CDevice.I2CTransaction[1];
            takeTrans[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x07, 0x01 });
            
            byte[] statusBit = new byte[1];
            I2CDevice.I2CTransaction[] statusTrans = new I2CDevice.I2CTransaction[2];
            statusTrans[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x06 });
            statusTrans[1] = I2CDevice.CreateReadTransaction(statusBit);
            
            byte[] dataXl = new byte[1];
            byte[] dataXh = new byte[1];
            byte[] dataYl = new byte[1];
            byte[] dataYh = new byte[1];
            byte[] dataZl = new byte[1];
            byte[] dataZh = new byte[1];
            I2CDevice.I2CTransaction[] getReadings = new I2CDevice.I2CTransaction[12];
            getReadings[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x00 });
            getReadings[1] = I2CDevice.CreateReadTransaction(dataXl);
            getReadings[2] = I2CDevice.CreateWriteTransaction(new byte[] { 0x01 });
            getReadings[3] = I2CDevice.CreateReadTransaction(dataXh);
            getReadings[4] = I2CDevice.CreateWriteTransaction(new byte[] { 0x02 });
            getReadings[5] = I2CDevice.CreateReadTransaction(dataYl);
            getReadings[6] = I2CDevice.CreateWriteTransaction(new byte[] { 0x03 });
            getReadings[7] = I2CDevice.CreateReadTransaction(dataYh);
            getReadings[8] = I2CDevice.CreateWriteTransaction(new byte[] { 0x04 });
            getReadings[9] = I2CDevice.CreateReadTransaction(dataZl);
            getReadings[10] = I2CDevice.CreateWriteTransaction(new byte[] { 0x05 });
            getReadings[11] = I2CDevice.CreateReadTransaction(dataZh);

            // Loop forever
            while (true)
            {
                if (collect)
                {
                    //DateTime t1 = DateTime.Now;

                    //Sensor 0:
                    magDevice.Config = mag0;
                    magDevice.Execute(refillTrans, 1);
                    magDevice.Execute(setTrans, 1);
                    magDevice.Execute(resetTrans, 1);
                    magDevice.Execute(takeTrans, 1);
                    while ((statusBit[0] & 0x01) != 1)
                    {
                        magDevice.Execute(statusTrans, 1);
                    }
                    magDevice.Execute(getReadings, 1);

                    magData.mag0Xl = dataXl[0];
                    magData.mag0Xh = dataXh[0];
                    magData.mag0Yl = dataYl[0];
                    magData.mag0Yh = dataYh[0];
                    magData.mag0Zl = dataZl[0];
                    magData.mag0Zh = dataZh[0];

                    //Sensor 1:
                    magDevice.Config = mag1;
                    magDevice.Execute(refillTrans, 1);
                    magDevice.Execute(setTrans, 1);
                    magDevice.Execute(resetTrans, 1);
                    magDevice.Execute(takeTrans, 1);
                    while ((statusBit[0] & 0x01) != 1)
                    {
                        magDevice.Execute(statusTrans, 1);
                    }
                    magDevice.Execute(getReadings, 1);

                    magData.mag1Xl = dataXl[0];
                    magData.mag1Xh = dataXh[0];
                    magData.mag1Yl = dataYl[0];
                    magData.mag1Yh = dataYh[0];
                    magData.mag1Zl = dataZl[0];
                    magData.mag1Zh = dataZh[0];

                    //Sensor 2:
                    magDevice.Config = mag2;
                    magDevice.Execute(refillTrans, 1);
                    magDevice.Execute(setTrans, 1);
                    magDevice.Execute(resetTrans, 1);
                    magDevice.Execute(takeTrans, 1);
                    while ((statusBit[0] & 0x01) != 1)
                    {
                        magDevice.Execute(statusTrans, 1);
                    }
                    magDevice.Execute(getReadings, 1);

                    magData.mag2Xl = dataXl[0];
                    magData.mag2Xh = dataXh[0];
                    magData.mag2Yl = dataYl[0];
                    magData.mag2Yh = dataYh[0];
                    magData.mag2Zl = dataZl[0];
                    magData.mag2Zh = dataZh[0];
                    
                    //DateTime t2 = DateTime.Now;
                    //TimeSpan ts = t2 - t1;
                    //Debug.Print(ts.Minutes.ToString() + ":" + ts.Seconds.ToString() + ":" + ts.Milliseconds.ToString());
                    
                    // Call Event!
                    if (OnDataReceived != null)
                        OnDataReceived(magData);
                }
            }
        }
        #endregion
    }
}



#9 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 08 August 2014 - 02:45 PM

Well, I guess you could save a few ticks by retrieving all the X/Y/Z data in one transation - the datasheet says "Multiple data bytes can be written or read to numerically sequential registers without the need of another START condition", so it should be possible to use
...
var data = new byte[6];
getReadings = new I2CDevice.I2CTransaction[]
{
  I2CDevice.CreateWriteTransaction(new byte[] { 0x00 }), // Start at 0x00
  I2CDevice.CreateReadTransaction(data) // Read 6 bytes
};
...
You could then have three class member arrays and copy each result at once (Array.Copy()), instead of 6 individual byte assignments. There are other minor optimizations that come in mind, but I am afraid there will not be major improvement - you'd probably need to measure how long each part of the code takes (e.g. duration of the Execution() method calls) to see, where is possible chance of improvement. You can use for example Stopwatch class, but keep in mind the resolution of system timer (~21 µs Netduino gen 1; 1 µs Netduino gen 2). Unfortunately, the interpreter has major performance impact.

#10 berr08

berr08

    New Member

  • Members
  • Pip
  • 9 posts

Posted 08 August 2014 - 03:46 PM

Thanks that did speed it up some more doing the 1 read of 6 bytes.  I implemented the stopwatch function and here are the findings:

 

Setting the Device Config: 0Ms - 40Ms

magDevice.Config = mag0;

Sending the setup routine:  159Ms - 165Ms

magDevice.Execute(refillTrans, 1);
magDevice.Execute(setTrans, 1);
magDevice.Execute(resetTrans, 1);
magDevice.Execute(takeTrans, 1);

Checking reading is ready (Status): 0Ms - 40Ms

while (((statusBit[0] & 0x01) != 1))
{
     magDevice.Execute(statusTrans, 1);
}

Reading the 6byte reading: 40Ms

magDevice.Execute(getReadings, 1);

//#Definition from outside loop:
//byte[] dataXYZ = new byte[6];
//I2CDevice.I2CTransaction[] getReadings = new I2CDevice.I2CTransaction[2];
//getReadings[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x00 });
//getReadings[1] = I2CDevice.CreateReadTransaction(dataXYZ);

Assigning to the Byte Structure 0Ms

magData.mag0Xl = dataXYZ[0];
magData.mag0Xh = dataXYZ[1];
magData.mag0Yl = dataXYZ[2];
magData.mag0Yh = dataXYZ[3];
magData.mag0Zl = dataXYZ[4];
magData.mag0Zh = dataXYZ[5];

The entire loop of the 3 sensors takes: 600Ms - 650Ms

 

In order to get to the arduino # of readings I need the 3 sensors to take about 30 milliseconds, which I'm thinking might not be near possible?  Unless I'm still doing some really dumb things, which is always possible LOL.



#11 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 08 August 2014 - 07:07 PM

Well, from the above the duration of I2C.Execute() method call is about 40 ms. Thus, even if you were able to consolidate the setup sequence into one I2C Execute() call, you'd still need at least three: setup, status check, data readout and that would be at least 3*40 = 120 ms, which is still 4× slower than Arduino.

 

IMHO the only way to get required speedup is to modify the firmware, i.e. create a native method that does all the I2C transactions to read data from the device.

 

Technical note: The .NET Micro Framework code is so slow in comparison to Arduino not only because it is interpreted, but also because there are several layers of abstraction - in this particular case, I2C methods take hundreds of instructions to execute parameter checking and marshalling, initializing internal structures, moving data forward and backward, mimicking asynchronous behavior using queues and completions etc. I can imagine writing a simple specialized I2C function that will not do anything but direct I2C calls, basically the same what Arduino code does - then it will be at least as fast, more likely faster (limited by I2C communication speed).



#12 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 08 August 2014 - 07:17 PM

As far as I know .Net in general checks every array access to make sure your index is in bounds.

 

Making some of your data a plain byte, versus new byte[1]   may help quite a bit.



#13 berr08

berr08

    New Member

  • Members
  • Pip
  • 9 posts

Posted 11 August 2014 - 01:35 PM

Thanks guys, it seems that the netduino isn't the way to go for this project atleast since the speed of the readings needs to be so high.  I look forward to using this again when the project requirements will allow.  Thanks again to all for your help with this!

 

Robert.



#14 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 11 August 2014 - 06:03 PM

What sensor(s) are you using? Curious if you could 'charge' all 3 at the same time to save some time.

 

There is no doubt and Arduino without any sort of firmware will be faster than a .Net managed device, but my experience is my Netduino can always be fast enough for me. 

 

I had pretty good luck with a 9 DoF IMU, which was 3 i2c devices with 6 bytes of data each - similar data size to your application. I ended up switching to an mpu6050 that does all the math on chip, and lowered the complexity immensely. In the end it took like 2 real lines of code to do 3D rendered AHRS.







Also tagged with one or more of these keywords: I2C, Netduino Plus 2

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

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