i2c c# documentation

Hello,
I'm trying to connect a distance sensor via the i2c bus to a metawearboard r pro. I'm having some trouble with the code, because the function mbl_mw_datasignal_read_with_parameters accepts only a signal and a pointer as arguments. I tried to use the c++ api, but I get a struct for the read-parameters. Unfortunately I'm not very familiar with c# coding, so I tried marshalling the struct into a pointer. Doing so I get an error message. It would be great if someone could help me! My code:

                    Param_ptr = new IntPtr();

                    I2C.ReadParameters read_params = new I2C.ReadParameters();
                    read_params.deviceAddr = 0x29;
                    read_params.registerAddr = 0x00;

                    Marshal.StructureToPtr(read_params, Param_ptr, true);

                    mbl_mw_datasignal_read_with_parameters(i2c_signal, Param_ptr);

Comments

  • edited May 2017
    You have to first allocate a block of unmanaged memory to hold the struct before calling StructureToPtr.  Since this is unmanaged memory, you are responsible for releasing the memory.  See the example code on the MSDN page:
    IntPtr pointer = Marshal.AllocHGlobal(Marshal.SizeOf<I2C.ReadParameters>());
  • Thanks Eric!
    Now there are no error messages so it should work. Still there is a problem. I have to write to a register with the adress 0x208. If I'm correct this value exceeds the range of a byte, nevertheless the function mbl_mw_i2c_write only accepts bytes as register adress. Is there any way to extend the range of this function?

  • edited May 2017
    Hrm what I2C device are you using?
  • I'm using the pololu VL6180X Time-of-Flight Distance Sensor
    As you can see in the description of the quick setup guide, I have to write to register adresses that exceed the range of a byte.

    https://www.pololu.com/product/2489/resources
  • @tbfs25

    The I2C module is designed to work with 8 bit registers.

    For a 16-bit write, simply pass the high order byte as the register address, and the low order byte as the first byte of the payload.

    For a read to a 16-bit register, depending on how the chip works internally, you might be able to use a write operation to load the 16 bit address, and then follow it with a read re-passing the high order byte as the register address to the read.  If the indexes are merely latched, then it should allow you to access the 16 bit registers.

    To Recap for a read for your example of register 0x208:
    I2C Write (device=0x29, register=0x02, data=0x08)
    I2C Read (device=0x29, register=0x02, <read byte count>)

    We have experienced other limitations with the I2C implementation for chips that defy the convention like this one.  We're looking at a minor refactor of the interface in the future to make it more flexible.

    Please let us know if the Write+Read trick succeeds to read from the 16-bit register.
  • I tried to use the existing Arduino library for the VL6180x as a
    template for my code, but I'm still having trouble with the read
    function though. My read function looks like this:
    public byte readReg(ushort reg)
            {
                var i2c_signal = mbl_mw_i2c_get_data_signal(cppBoard, 1, 0);

                Fn_IntPtr i2cDataHandlerDelegate = new Fn_IntPtr(dataPtr =>
                {
                    var data = Marshal.PtrToStructure<Data>(dataPtr);
                    byte value = Marshal.PtrToStructure<byte>(data.value);
                    ReadValue = value;

                });

                mbl_mw_datasignal_subscribe(i2c_signal, i2cDataHandlerDelegate);

                byte regHigh = (byte)(reg >> 8);
                byte regLow = (byte)reg;
                byte[] val = { regLow };

                I2C.ReadParameters read_params = new I2C.ReadParameters();
                read_params.deviceAddr = i2caddress;
                read_params.registerAddr = regHigh;
                IntPtr i2cPointer = Marshal.AllocHGlobal(Marshal.SizeOf(read_params));
                Marshal.StructureToPtr(read_params, i2cPointer, false);

                mbl_mw_i2c_write(cppBoard, i2caddress, regHigh, val, 2);
                mbl_mw_datasignal_read_with_parameters(i2c_signal, i2cPointer);
                return ReadValue;
            }

    Is
    there a more direct way to read the value of the register? I'm not sure
    if I'm doing a mistake by marshalling the device and register address
    into a pointer. On the other hand, the function
    mbl_mw_datasignal_read_with_parameters only accepts pointers as values.
  • What is wrong with the current code?  What is the expected behavior vs. what is actually happening?
  • The function should read the value of the register, instead if I print the ReadValue it never changes it is always 0. So there is no difference between if I connect the  VL1680x sensor and if I disconnect it, the ReadValue remains the same. 
    I checked if it was an electronic issue, but there is a voltage of approximately 3V at the I2C entries (3V, SDA, SCL) so there is no short circuit. Additionaly, I checked if the VL1680x sensor was working properly, so after connecting it on my Arduino board, it worked perfectly fine. 
    My concern is that the Motion R can't read properly the registers of the sensor and is therefore incapable of communicating with the VL1680x sensor. 
  • The problem here is that you are assuming ReadValue has been assigned a value by the time the readReg function is completed.  Your function should not return until it has received the result of mbl_mw_datasignal_read_with_parameters.
  • Is there any way I could await the result of this function before returning the value? I tried to get different results by changing the sequence of these functions( i.e. by putting the write and read_with_parameters_function before the i2cDataHandlerDelegate), but the result doesn't change. Here is my code again:

    public byte readReg(ushort reg)
            {
                byte regHigh = (byte)(reg >> 8 & 0xFF);
                byte regLow = (byte)(reg & 0xFF);
                byte[] val = { regLow };
                var i2c_signal = mbl_mw_i2c_get_data_signal(cppBoard, 2, i2caddress);

                I2C.ReadParameters read_params = new I2C.ReadParameters();
                read_params.deviceAddr = i2caddress;
                read_params.registerAddr = regHigh;
                IntPtr i2cPointer = Marshal.AllocHGlobal(Marshal.SizeOf(read_params));
                Marshal.StructureToPtr(read_params, i2cPointer, false);

                mbl_mw_i2c_write(cppBoard, i2caddress, regHigh, val, 2);
                mbl_mw_datasignal_read_with_parameters(i2c_signal, i2cPointer);

                i2cDataHandlerDelegate = new Fn_IntPtr(dataPtr =>
                {
                    var data = Marshal.PtrToStructure<Data>(dataPtr);
                    byte value = Marshal.PtrToStructure<byte>(data.value);

                    ReadValue = value;
                });

                mbl_mw_datasignal_subscribe(i2c_signal, i2cDataHandlerDelegate);

                return ReadValue;
               
            }
  • edited July 2017
    You have to subscribe to a signal before you can read it.

    C# provides language-level async programming constructs and async frameworks.  Some of them you might find useful for your particular use case.
  • Thank you Eric!
    I will try to rewrite my functions as asynchronous Tasks with results.
    By the way I took a look on the new c# SDK and realised that the i2c connection (as well as many other things) is much easier to implement with this API. So I tried to rewrite my read and write function with this new API using the Tutorials as a Template.
    By doing so I got this function(which by the way should by asynchronous):
    public async Task<byte> readReg(ushort reg)
            {
                try
                {
                    byte regHigh = (byte)(reg >> 8 & 0xFF);
                    byte regLow = (byte)(reg & 0xFF);
                    byte[] val = { regLow };
                    var serialPassthrough = metawear.GetModule<ISerialPassthrough>();
                    serialPassthrough.WriteI2C(i2caddress, regHigh, val);
                    byte[] result = await serialPassthrough.ReadI2CAsync(i2caddress, regHigh, 1);
                    Debug.WriteLine(result);
                    Debug.WriteLine(result[0]);
                    return result[0];
                }
                catch(Exception e)
                {
                    Debug.WriteLine("Error in display click: " + e.Message);
                    return 0;
                }
            }
    By using this Function I get an error message at the line:
     byte[] result = await serialPassthrough.ReadI2CAsync(i2caddress, regHigh, 1);
    telling: Reading i2c data timed out
    I could rewrite my code using the new c# SDK, as long as my distance sensor works properly.
    I have tried to implement the i2c communication for several weeks now, but there is still no result.
    So I'm wondering if it is even possible to connect the pololu VL6180X to the MetaMotionR. Again if there is any advice you could give me on this issue in general (if there are other parts of the code that could be wrong) it would be great!
     
  • sorry the code I send in the last comment was wrong. Here the correct version:
    public async Task<byte> readReg(ushort reg)
            {
                try
                {
                    byte regHigh = (byte)(reg >> 8 & 0xFF);
                    byte regLow = (byte)(reg & 0xFF);
                    byte[] val = { regLow };
                    var serialPassthrough = metawear.GetModule<ISerialPassthrough>();
                    serialPassthrough.I2C(i2caddress, 1);
                    serialPassthrough.WriteI2C(i2caddress, regHigh, val);
                    byte[] result = await serialPassthrough.ReadI2CAsync(i2caddress, regHigh, 1);
                    Debug.WriteLine(result);
                    return result[0];
                }
                catch(Exception e)
                {
                    Debug.WriteLine("Error in display click: " + e.Message);
                    return 0;
                }
            }
  • Try the example I2C code in the documentation. 

    If that works, then it does appear that the RPro boards cannot talk to the VL6180X with its 10-bit addresses.
  • The example code doesn't work. Here my code (I tried it with both, the id-register from the Barometer and the WHO_AM_I register from the accelerometer):

    var serialPassthrough = metawear.GetModule<ISerialPassthrough>();
    byte[] result = await serialPassthrough.ReadI2CAsync(0x1c, 0x0d, 1);
    Console.WriteLine("WHO_AM_I = " + result[0]);

    I get two different types of error messages. Sometimes I get the message:
    Exception thrown: 'System.TimeoutException:' in System.Private.CoreLib.ni.dll, as well as
    System.TimeoutException: 'Reading i2c data timed out'.

    Actually I get the same exception with the unchanged project freefall detector of the solution of the c# tutorial  as well:
    System.TimeoutException: 'Creating data processor timed out'
    at the line:
    await accelerometer.Acceleration.AddRouteAsync(source =>
                    source.Map(Function1.Rss).Average(4).Find(Threshold.Binary, 0.5f)
                        .Multicast()
                            .To().Filter(Comparison.Eq, -1).Log(data => System.Diagnostics.Debug.WriteLine("In FreeFall"))
                            .To().Filter(Comparison.Eq, 1).Log(data => System.Diagnostics.Debug.WriteLine("Not in FreeFall"))
                );



    Meanwhile, when using my older app, written entirely with the c++ API except the example code, (maybe there could be some interference while using both the c++ API and the c#SDK, even though there is no error when downloading both NUget Packages):
    I get the error message:
    System.NullReferenceException: 'Object reference not set to an instance of an object.'
    In the second case I tried to reference the byte[] result, but nevertheless I get the same error message again.
    My MetaMotionR runs the latest firmware 1.3.3 and I'm using the Visual Studio 2017 Enterprise Edition with installed .NET framework 4.6.1 , 4.6.2 and 4.7 (so my c#compiler should support c# 7.0 features) and the Windows 10 SDK (10.0.15063.0) as required in the source code of your c# SDK
  • edited July 2017
    Mixing two different APIs is not a good idea, stick with one or the other.

    If choosing the C# SDK, try using the start app from the tutorial solution to read the register from the BMP280 sensor

    You may also need to reset your board as well.
  • I reseted the MetamotionR and by using the Starter App I was able to read the register from the BMP280 sensor. Thanks Eric!
    Still,
    by just reading the first regiter I need from my distance sensor I got
    the same error as before ('Reading i2c data timed out'):
             
                var serialPassthrough = metawear.GetModule<ISerialPassthrough>();
                byte[] result = await serialPassthrough.ReadI2CAsync(i2caddress, 0x024, 1);
                Debug.WriteLine(result[0]);

    So I assume that the Board cannot comunicate with the sensor?
  • It would appear so.  As Matt mentioned, MetaWear's I2C module doesn't play well with devices that do not follow standard I2C conventions.
  • Thanks anyway Eric! 
    If that's the case I am probably going to use an analog distance sensor. If you have any other suggestions of digital distance sensors that already work with your MetaMotion or if you already experimented with some, I would be glad to try them out. Anyhow, I will try to come up with a solution and post it as soon as possible!
  • I was able to connect the metamotionR to an arduino device which can again read the signal of the sensor. Everything is implemented with the i2c connection and works perfectly fine
This discussion has been closed.