Main Site Documentation

G30 Proccessing Lag


#1

I am noticing an odd error, which I assume is related to a garbage collection issue but that’s a shot in the dark. I have an I2C EEPROM as well as an I2C display on the same lines. Tested together in a separate program they run great. However, in my main program where resources are less abundant, the loop that reads the EEPROM address is doing one of two things.

  1. If the code is running through visual studio, it gets stuck midway through this loop (flies through the first 20 or 30 iterations then slows down) until I put a break point ANYWHERE in the code, even if it is code that’s already been executed, everything goes back to normal.

  2. If the code is deployed and being started off power up (No PC used), the same behavior occurs except since a breakpoint can’t be placed, it takes several minutes to make it through the code…normally this takes ~1 second.

Note: Nothing is being printed to the display during this time.

I cannot produce working test code for this as it only happens in my combined program but will upload the I2C class in a bit


#2
using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace _511_
 {

//combined class for display and EEPROM

public enum CodePage : byte
{
    ROM_A = 0x02,
    ROM_B = 0x06,
    ROM_C = 0x0A
}
/// <summary>
/// The default config is adress 0x3C and 400kHz. Run Init() first.
/// Defaults are: 4 lines, 20 characters each line, ROM-B (see datasheet)
/// </summary>
public class I2C
{

    public int I2C_TIMEOUT = 500;
    public int DISPLAY_LINES = 2;
    public int DISPLAY_CHARS = 20;
    public byte CODEPAGE = (byte)CodePage.ROM_B;

    private byte[] Line_Adresses = { 0x80, 0xA0, 0xC0, 0xE0 }; // RAM addresses for line 0-3

    public int SendCommand(I2CDevice I2C_DEV, I2CDevice.Configuration I2C_CFG, byte address, byte Command)
    {
        try
        {
            I2C_DEV.Config = I2C_CFG;
            I2CDevice.I2CTransaction[] Transaction = new I2CDevice.I2CTransaction[1];
            Transaction[0] = I2CDevice.CreateWriteTransaction(new byte[] { 0x00, Command });
            return I2C_DEV.Execute(Transaction, I2C_TIMEOUT);

        }
        catch (Exception ex)
        {
            return 0;
            //Debug.Print(ex.ToString());
        }
    }

    public int SendData(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, byte address, byte Data)
    {
        SHAREDI2C_DEV.Config = I2C_CFG;
        I2CDevice.I2CTransaction[] Transaction = new I2CDevice.I2CTransaction[1];
        Transaction[0] = I2CDevice.CreateWriteTransaction(new byte[] { address, Data }); //0X40 for display data register
        if (SHAREDI2C_DEV.Execute(Transaction, I2C_TIMEOUT) == 1)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    /// <summary>
    /// Display a single line, the screen is not clared, so it also can be used to update a single line.
    /// </summary>
    /// <param name="I2C_DEV"></param>
    /// <param name="Line"></param>
    /// <param name="LineID"></param>
    public void DisplayLine(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, string Line, int LineID)
    {
        if (LineID >= DISPLAY_LINES || LineID < 0) { LineID = 0; } //Makes sure, the LinID has a valid value
        while (Line.Length < DISPLAY_CHARS) { Line = Line + " "; } //Makes sure, the rest of the line is blank, if you use this to update a line.
        char[] cha = Line.ToCharArray();
        SendCommand(SHAREDI2C_DEV, I2C_CFG, 0x00, Line_Adresses[LineID]);
        int CharID = 0;
        foreach (char c in cha)
        {
            if (CharID >= DISPLAY_CHARS) break;
            SendData(SHAREDI2C_DEV, I2C_CFG, 0x40, (byte)c);
            CharID++;
        }

    }

    /// <summary>
    /// Displays a string-array with up to four strings (can be more, but only the first four lines will be displayed).
    /// If there are less than four lines, the content is aligned at the top.
    /// </summary>
    /// <param name="Display"></param>
    public void DisplayAll(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, string[] Display)
    {
        ArrayList AList = new ArrayList();
        foreach (string s in Display) { AList.Add((char[])s.ToCharArray()); }
        SendCommand(SHAREDI2C_DEV, I2C_CFG, 0x00, 0x01);
        int LineID = 0;
        foreach (char[] cha in AList)
        {
            if (LineID >= DISPLAY_LINES) break;
            SendCommand(SHAREDI2C_DEV, I2C_CFG, 0x00, Line_Adresses[LineID]);
            int CharID = 0;
            foreach (char c in cha)
            {
                if (CharID >= DISPLAY_CHARS) break;
                SendData(SHAREDI2C_DEV, I2C_CFG, 0x40, (byte)c);
                CharID++;
            }
            LineID++;
        }
    }

    /// <summary>
    /// Initial setup, call this first before using the display.
    /// </summary>
    /// <param name="I2C_DEV"></param>
    public void Init(I2CDevice I2C_DEV, I2CDevice.Configuration I2C_CFG)
    {


        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x38); // Function set: extended command set (RE=1), lines #
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x39);        // Function selection A:
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x14);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x78); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x5E);        // Display ON/OFF control: display off, cursor off, blink off (default values)
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x6D); // Function set: extended command set (RE=1), lines #
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x0C);        // Function selection A:
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x01);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x06); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #

        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x08);  // Extended function set (RE=1): 5-dot font, B/W inverting disabled (def. val.), 1/2 lines



        if (DISPLAY_LINES == 2)
            Line_Adresses[1] = 0xC0;             // DDRAM address for each line of the display (only for 2-line mode)
    }
    public byte Read(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, int Address)
    {
        SHAREDI2C_DEV.Config = I2C_CFG;
        var Data = new byte[1];
        var xActions = new I2CDevice.I2CTransaction[1];
        xActions[0] = I2CDevice.CreateWriteTransaction(new byte[] { (byte)(Address >> 8), (byte)(Address & 0xFF) });
        Thread.Sleep(5);
        if (SHAREDI2C_DEV.Execute(xActions, 1000) == 0)
        {
          //  Debug.Print("Failed to perform I2C transaction");
        }
        else
        {
            xActions[0] = I2CDevice.CreateReadTransaction(Data);
            Thread.Sleep(5);   // Mandatory after each Write transaction !!!
            if (SHAREDI2C_DEV.Execute(xActions, 1000) == 0)
            {
              //  Debug.Print("Failed to perform I2C transaction");
            }
            else
            {
             //   Debug.Print("Register value: " + Data[0].ToString());
                return Data[0];
            }

        }
        return 0;
    }

    public void Write(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, int Address, byte[] data)
    {
        SHAREDI2C_DEV.Config = I2C_CFG;
        byte[] byte1 = new byte[2];
        byte1 = new byte[] { (byte)(Address >> 8), (byte)(Address & 0xFF) };
        int length = byte1.Length + data.Length;
        byte[] payload = new byte[length];
        byte1.CopyTo(payload, 0);
        data.CopyTo(payload, byte1.Length);
        var xActions = new I2CDevice.I2CTransaction[1];
        //    byte1=[(byte)(Address>>8),(byte)(Address&0xFF)];
        xActions[0] = I2CDevice.CreateWriteTransaction(payload);
        Thread.Sleep(5);
        if (SHAREDI2C_DEV.Execute(xActions, 1000) == 0)
        {
            Debug.Print("Failed to perform I2C transaction");
        }


    }
    /// <summary>
    /// As the function-name tells you, this clears the screen.
    /// </summary>
    /// <param name="I2C_DEV"></param>
    public void ClearScreen(I2CDevice I2C_DEV, I2CDevice.Configuration I2C_CFG)
    {
        SendCommand(I2C_DEV, I2C_CFG, 0x00, 0x01);
    }
}

}

#3

And here is the implementation causing the lag.

            byte[] temp2 = new byte[TabsFinal.Length * sizeof(UInt32)];
            byte[] buffer = new byte[1];
    
            for (var z = 0; z < temp2.Length; z++)
            {
                buffer[0] = I2CManager.Read(SHAREDI2C_DEV, I2C_CFG_MEM, z);
                temp2[z] = buffer[0];
                  //I have tried Thread.Sleep here as well as Debug.GC(true)
            }

#4

If you think that it is possible a GC issue then why don’t you try to replace things that are dynamically allocated with static arrays that are allocated once at startup?

If this is possible, then what you want to do is figure out how large an array needs to be. Then figure out how many of that array will ever be needed at the same time. If it’s entirely unknown then it is possible that static allocation is not going to work. If you can get by with just one or a reasonable number then just make them static.

In your implementation part you make use of a buffer[ ] which gets used over and over again. You should do this anywhere you can.

If this doesn’t work then you at least know that it isn’t the GC.


#5

@jwizard93 Thank you for the reply. All array sizes are properly sized as small as possible, every array, port, etc in the whole program is static except for stuff in the class that I shared above. Is it possible to make the Read() method I am calling static?

The problem lies in here somewhere…I think maybe these vars getting called on each iteration are clogging it up. How can I structure it so Data and xActions aren’t recreated each time Read is called?

public byte Read(I2CDevice SHAREDI2C_DEV, I2CDevice.Configuration I2C_CFG, int Address)
{
    SHAREDI2C_DEV.Config = I2C_CFG;
    var Data = new byte[1];
    var xActions = new I2CDevice.I2CTransaction[1];
    xActions[0] = I2CDevice.CreateWriteTransaction(new byte[] { (byte)(Address >> 8), (byte)(Address & 0xFF) });
    Thread.Sleep(5);
    if (SHAREDI2C_DEV.Execute(xActions, 1000) == 0)
    {
      //  Debug.Print("Failed to perform I2C transaction");
    }
    else
    {
        xActions[0] = I2CDevice.CreateReadTransaction(Data);
        Thread.Sleep(5);   // Mandatory after each Write transaction !!!
        if (SHAREDI2C_DEV.Execute(xActions, 1000) == 0)
        {
          //  Debug.Print("Failed to perform I2C transaction");
        }
        else
        {
         //   Debug.Print("Register value: " + Data[0].ToString());
            return Data[0];
        }

    }
    return 0;
}

#6

I made xActions and Data private static variables in the class…still getting the lag on this part. Each byte read from the EEPROM seems to take like 15-20 seconds each (it is almost instant in my test programs)


#7

You have two devices sharing a resource You have not told us how you are managing how your program accesses each device.

Is it possible that you have two threads, each accessing one of the two devices?

If so, have you added protection to assure that only one thread is accessing the I2C line at a time?

If each routine runs fine separately, but has issues when both are running, then it sounds like they are might be both accessing the I2C resource at the same time.


#8

Sorry I wasn’t clear. They run fine in a separate routine which combines the display and EEPROM. They don’t work when in the routine which contains my motor controller, USB stuff - the full program.

Here is how they are accessed/created before my main:

    public static I2CDevice.Configuration I2C_CFG_DISP = new I2CDevice.Configuration(0x3C, 200);
    public static I2CDevice.Configuration I2C_CFG_MEM = new I2CDevice.Configuration(0x50, 400);
    public static I2CDevice SHAREDI2C_DEV;
    public static I2C I2CManager;

I make an instance of the previously posted class called I2CManager which I use to send all the commands to the shared I2C device. Each time a command is called by I2CManager, I pass in the SharedI2C device and the respective device’s configuration to switch the .config

It works but I am sure there is a better way as this is a crude splicing of two prewritten classes


#9

Is it possible that the motor is generating noise on the I2C line?

What happens if you run everything except for the motor?

Are you saying it is not possible for simultaneous access of both devices on I2C line?


#10

Ah crap I was mistaken…it happens in my test program as well as the main program (they are combined in both instances). I thought they were working in the test program but I just went back and checked and I do get the lag certain times the code is ran. I am not trying to simultaneously access them…only one is accessed then the I2CDevice.Config is set to the other device to call that device’s command.

In my test program, the lag doesn’t happen each time. I just ran the program and read each byte / printed to the display one at a time so fast that it was a blur.

  var result = new uint[bytes.Length / sizeof(UInt32)];

        for (var i = 0; i < result.Length; i++)
        {
            result[i] = BitConverter.ToUInt32(temp, i * sizeof(UInt32));
            I2CManager.DisplayAll(SHAREDI2C_DEV, I2C_CFG_DISP, new string[] { result[i].ToString()});
        
        }
        I2CManager.DisplayAll(SHAREDI2C_DEV, I2C_CFG_DISP, new string[] { "test complete" });

If I deploy the code and run without the PC, the error occurs every time.


#11

@Mike The bug only seems to appear in the test program when deployed and ran without the PC. Every time I am running it in visual studio it is working great, that’s why I thought it was only a problem in the program with the motor and everything. In that program, the bug occurs both deploying the code and when running through visual studio


#12

Sounds like timing.

try increasing the sleep durations.

check the datasheets for the devices to see if there are any timing requirements.


#13

Which sleep duration? The ones in Read()?


#14

Yes. Also after displaying.

Also check the datasheets. Could have timing requirements for how often device can be read or written…


#15

posted the wrong loop before but you got the idea

  for (int z = 0; z < bytes.Length; z++)
        {
            buffer[0] = I2CManager.Read(SHAREDI2C_DEV, I2C_CFG_MEM, z);
            temp[z] = buffer[0];
            I2CManager.DisplayAll(SHAREDI2C_DEV, I2C_CFG_DISP, new string[] { temp[z].ToString() });
        }

#16

@Mike WHOA I just realized when the byte is being printed to the display within the loop it is being read (and not in a separate loop after), the problem goes away! So switching the I2C config in between EEPROM calls makes it work. That makes zero sense to me.

I need to change it so I don’t have to do this for it to work. The values actually aren’t supposed to be printed to the display I was doing it just for testing


#17

Not 100% sure what you are saying…

Sounds like you might be writing to display too fast?

Does the display datasheet have a limitation on write command intervals?


#18

@Mike Nothing is wrong with the display.

This loop works every time when deployed:

 for (int z = 0; z < bytes.Length; z++)
        {
            buffer[0] = I2CManager.Read(SHAREDI2C_DEV, I2C_CFG_MEM, z);
            temp[z] = buffer[0];
            I2CManager.DisplayAll(SHAREDI2C_DEV, I2C_CFG_DISP, new string[] { temp[z].ToString() });
        }

And this loop never works:

   for (int z = 0; z < bytes.Length; z++)
            {
                buffer[0] = I2CManager.Read(SHAREDI2C_DEV, I2C_CFG_MEM, z);
                temp[z] = buffer[0];
              
            }

When the display is being written to right after the EEPROM, the lag completely goes away. If I only write to the EEPROM in that loop, it lags like hell and takes minutes to get through the loop as opposed to about half a second.


#19

What happens to temp [ ] if you aren’t doing anything with it?

Do you mean Reading from the EEPROM?

I don’t know what is happening here. Somewhere on your PC (likely Program Files (x86)) is a program called ildasm.exe. You should open executable of your application within ildasm for each different variation. Then maybe you might find what the big differences are when you make these changes.

It has GUI that you can use to find a particular function, and then when double clicked it will show you what the compiler made of it. You just miiiight be able to determine the problem.


#20

Yes I am reading from the EEPROM in those loops. Your question about temp is unclear to me though.