Problems on reading/writing through I2C

Hi all,

I have been trying to communicate through I2C with the following ADC:

http://cds.linear.com/docs/en/datasheet/2493fe.pdf

I have been developing an Android Application in order to read the 4 output bytes of the ADC conversion but is not reading anything. This ADC is particularly different because it doesn’t have any registers to read. Just send some commands or bytes of info and it will do the conversion. Here it would be the execution of the Android application code:

My program, as it is referred in the datasheet, it has to send 2 8-bit word information. The first to select the channel and the operation mode of it (differential in this case) and the second one selects the conversion parameters. The first parameter to write is the 7-bit device address as we put CA1 and CA0 HIGH in the board, it is 0x34 or 0110100 (page 19 of the datasheet). The second parameter would be the register address and as the ADC doesn’t have any like that, I put is 0x00. And the 3rd parameter is the word/data to be sent for selecting the differential channel CH0-CH1 and, the second word the conversion parameters (pages 16 and 17). In addition, the first word has to have two preamble bits and one enable bit and in order to work, they should be 101.
i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0xA0}); //Preamble and Enable bits (101) with Channel CH0-CH1 and differential mode selected (00000)
i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0x13}); //Select the conversion mode (speed, filter..) which refers to 2x speed mode and selecting the 50Hz filter
Then my program waits 100ms for the conversion to be completed. In the datasheet for the speed mode and the filter selected above would be around 80ms but I put 100ms just in case (page 5 of the datasheet).

Then we read the 32bits/4bytes of the conversion:
//After waiting 100ms for the conversion time, we read the output value through the I2C line
i2cModule.readData((byte)0x34, (byte)0x00, (byte)4).onComplete(new CompletionHandler<byte[]>() {
@Override
public void success(byte[] result) {
auxvar = result;
//Log.i("MainActivity", String.format("WHO_AM_I= %d", result[0]));
}
});
After so many tries, it still doesn’t get a response from the device nor it gets any error. Does anyone know how to solve this issue or have any
idea what would be the problem?

Thanks anyway.

Comments

  • What board and firmware version are you using?  There was a bug in firmware 1.1.0 (fixed in 1.1.3) where the above code snippet would not work.
  • Hi Eric,

    I am using the C-CPRO board and the firmware it has is the 1.0.4. Is that the reason why it fails to communicate? Thanks for your previous answer
  • I have done what you said, update it to the v1.1.3, but it still doesn't work. I have checked separately if the ADC works, and is working perfectly. I have checked with the logic Analyzer what is happening, and this is the output I am having (I couldn't put all the images in the same response):

    https://i.imgsafe.org/718f939.png
    https://i.imgsafe.org/7273793.png
    https://i.imgsafe.org/73406c3.png
    https://i.imgsafe.org/7421815.png
    https://i.imgsafe.org/41ce232.png
    https://i.imgsafe.org/4280d8b.png
    https://i.imgsafe.org/431cbde.png
    https://i.imgsafe.org/7421815.png

    The first and second link is the first stream I record and it is really weird that it doesn't send the second word of data. But what I don't understand is what happens from the 3rd picture in advance. It starts sending data to write and read from addresses I haven't put in my code. I don't know if you would have any clue what is going on.

    If you need, I can send you through pm what the code looks like in more depth.

  • Is it possible that the C-PRO doesn't have a universal I2C communication? I say it because with other way (like mbed) I can communicate with my adc and retrieve the data without a problem but when trying with the Android SDK of the Metawear, it doesn't work at all. If it can communicate with my device through I2C, how it can be solved or when you will release a fix for this problem?
  • I2C is on all the boards.  Since you have a CPro board, try running this code snippet to see if I2C is setup correctly.  The code reads the ID register on the BMP280 chip and should return 0x58.

    i2cModule.readData(0x77, 0xd0, (byte) 1).onComplete(new CompletionHandler<byte[]>() {
        @Override
        public void success(byte[] result) {
            Log.i("test", String.format("register 0xd0 = 0x%x", result[0]));
        }
    });
  • Hi Eric,

    I have done what you said and retrieves without a problem 0x58. So what does this mean? How I can solve my initial problem?

    Regards
  • It just means that given a correct address, the I2C read is working in the Android API.

    I'm not really sure where to from here so I've let our hardware guy know of this thread; he can provide more assistance with the I2C sensor.
  • By when will he have any update/news about this?
  • edited March 2016
    @gtx77

    The MetaWear i2c implementation follows the typical i2c convention of using read and write register addresses immediately following the device address.  This chip is a little different but it should not be a problem.

    When doing an i2c write, the device address is written, then the register address, and then the data.  For this chip, the first byte of your configuration (0xA0) should be passed in as the register address, and then the second byte (0x13) as the data.  That will perform a proper configuration write as in Figure 3b. on page 16 of the data sheet.

    When doing an i2c read, the device address is written, and then the register address is written.  Then the device address is written again and then the data is read out. So if you pass 0x00 as the register address of the i2c read, it will maintain your previous settings, start a conversion and then read out the data.  The register address write would be treated as a config write, but will be ignored because of the 000 preamble as described on page 16.  If you need to change any settings in the first config byte, such as changing the ADC channel, you could do this by passing the mode into the register address of the i2c read.

    For the case you describe in the initial post, the final resort should look like:
    // config write
    I2C_Write(0x34, 0xA0, 0x13);

    // convert and read
    I2C_Read(0x34, 0x00, 4);

    You could pass the 0xA0, or a slightly different config value, into the register address parameter if you wanted to switch the channel before the next conversion.

    The communications you are picking up with your logic analyzer at address 0x77 are related to the barometric pressure sensor that is on the shared i2c bus.  At bootup the MetaWear firmware resets and initializes all of the on board sensors and it looks like you caught part of that transaction.

    Try out the slightly revised commands and let us know if you are still having issues!

    Cheers,
    Matt
  • Hi Matt,

    Thanks for your response. I have done what you said but it was still the same. However I solved the issue by using the following code:
    //We are going to setup the first communication through i2c to get the differential value of CH0-CH1
    i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte)0xA0, (byte)0x98}); //Preamble and Enable bits (101) with Channel CH0-CH1 and differential mode selected (00000)
    Now it reads fine but I have right now two problems. The first problem is that when my program enter in the "timerModule.scheduleTask" execution loop, to do the writing, wait 150ms, reading and plotting and then repeat it after a period of time (is nearly the same as the one in the GPIO of the sample app); it can only do this execution only for 3-4 seconds (putting the timer at 250ms) it starts doing the fuzzy stuff that you said that was related to the bootup and suddenly my app tries to reconnect to the device and after that it cannot read anything from the I2C. After doing some arranges, it appears that if I leave out from the loop the writing to I2C code, it solves this problem but it is not a solution for me as I will need to write to the device to read other channels as well. Do you know why is happening this or how to solve this issue?

    And the second problem is that when I try to read the data in the success function, I can only manipulate the data just inside that function and out of it I am not able to work with it. What I want t do is the following:
    i2cModule.readData((byte)0x34, (byte)0x00, (byte)4).onComplete(new CompletionHandler<byte[]>() {
    @Override

    public void success(byte[] result) {
    auxvar = result;
    }
    });

    long finalvoltage = ((auxvar[0]&0x3F)<<18)|(auxvar[1]<<10)|(auxvar[2]<<2)|((auxvar[3]&0xC0));
    float diff_volt1 = (float)finalvoltage * 3/16777215;

    LineData data = chart.getData();

    if (startTime == -1) {
    data.addXValue("0");
    startTime= System.currentTimeMillis();
    } else {
    data.addXValue(String.format("%.2f", (System.currentTimeMillis() - startTime) / 1000.f));
    }
    data.addEntry(new Entry(diff_volt1, sampleCount), 0);

    sampleCount++;

    When I try to debug the application and obtain the voltage, auxvar is 0 but inside the loop
    it contains the right data. How I can export the data out of the function?

    If you need anything else or need any further information, please let me know. If you
    require to pass you through pm the code I want to do, let me know.

    Kind Regards,

    Javier
  • What is the full code you are executing in scheduleTask that does not work and what does the working code look like?

    I don't understand why you are storing the result into auxvar when you want to update the chart upon receiving new data.  You should move the chart updating code into the success function, not have it reside outside of it.

    However, if you are trying to do periodic i2c reads with the Timer.scheduleTask function, then you should use the DataSignal class to setup an i2c stream and use the other readData function.
  • Hi Eric,

    What I want to do with my application is to read the values from one channel, then read the value from the other channels, store them in auxiliar variables and repeat again 3 more times. This is done to later make an average of the samples obtained and plot the normalize value made by this and the maximum sample obtained. That is why I want to have the plot outside the success function. The code that is not working is the following:
    timerModule.scheduleTask(new Timer.Task() {

    @Override
    public void commands() {

    for(int l=0; l<4;l++){

    //We are going to setup the first communication through i2c to get the differential value of CH0-CH1
    i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0xA0, (byte) 0x13});

    //SystemClock.sleep(SCAN_PERIOD);
    //We need to wait >80ms for the ADC to finish the conversion value
    synchronized (this) {
    try {
    wait(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    //After waiting 100ms for the conversion time, we read the output value through the I2C line
    i2cModule.readData((byte)0x34, (byte)0xA0, (byte)4).onComplete(new CompletionHandler<byte[]>() {
    @Override
    public void success(byte[] result) {
    auxvar = result;
    //Log.i("MainActivity", String.format("WHO_AM_I= %d", result[0]));
    }
    });

    //Start translation of 20bit to Voltage value
    finalvoltage = ((auxvar[0]&0x3F)<<18)|(auxvar[1]<<10)|(auxvar[2]<<2)|((auxvar[3]&0xC0));
    diff_volt1 = (float)finalvoltage * 3/16777215;

    if (max_sample_measure < (int)diff_volt1){
    max_sample_measure = (int)diff_volt1;
    } else if (min_sample_measure > (int)diff_volt1){
    min_sample_measure = (int) diff_volt1;
    }

    sample_voltage1[l] = diff_volt1;
    finalvoltage = 0;

    The rest continues in the next comment

  •  //We are going to setup the first communication through i2c to get the differential value of CH2-CH3
    i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0xA1, (byte) 0x13});

    //SystemClock.sleep(SCAN_PERIOD);
    synchronized (this) {
    try {
    wait(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    i2cModule.readData((byte)0x34, (byte)0xA1, (byte)4).onComplete(new CompletionHandler<byte[]>() {
    @Override
    public void success(byte[] result) {
    auxvar = result;
    //Log.i("MainActivity", String.format("WHO_AM_I= %d", result[0]));
    }
    });

    //Start translation of 20bit to Voltage value
    finalvoltage = ((auxvar[0]&0x3F)<<18)|(auxvar[1]<<10)|(auxvar[2]<<2)|((auxvar[3]&0xC0));
    diff_volt2 = (float)finalvoltage * 3/16777215;

    if (max_sample_measure < (int)diff_volt2){
    max_sample_measure = (int)diff_volt2;
    } else if (min_sample_measure > (int)diff_volt2){
    min_sample_measure = (int) diff_volt2;
    }

    sample_voltage2[l] = diff_volt2;
    finalvoltage = 0;


    }

    average_sample_voltage1 = (sample_voltage1[0] + sample_voltage1[1] + sample_voltage1[2] + sample_voltage1[3])/4;
    average_sample_voltage2 = (sample_voltage2[0] + sample_voltage2[1] + sample_voltage2[2] + sample_voltage2[3])/4;

    average_volt = (average_sample_voltage1 + average_sample_voltage2)/2;
    normalized_measured = (int) (average_volt/max_measure);

    It finish in the next comment


  • LineData data = chart.getData();

    if (startTime == -1) {
    data.addXValue("0");
    startTime= System.currentTimeMillis();
    } else {
    data.addXValue(String.format("%.2f", (System.currentTimeMillis() - startTime) / 1000.f));
    }
    data.addEntry(new Entry(normalized_measured, sampleCount), 0);

    sampleCount++;

    }


    }, I2C_SAMPLE_PERIOD, false).onComplete(new AsyncOperation.CompletionHandler<Timer.Controller>() {
    @Override
    public void success(Timer.Controller result) {
    result.start();
    }
    });

    That is everything inside the timeschedule.task function. For the working code it
    I have to take out the writing to I2C commands inside the function and put it
    before it enters it. Like this:

    i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0xA0, (byte) 0x13});

    timerModule.scheduleTask(new Timer.Task() {
    .......

    I hope this helps you out. If you need anything else, please let me know. Thanks for
    the help and sorry for the inconvenience if I may cause.
  • The scheduleTask function is only for scheduling MetaWear commands to execute on board.  Any function not a part of the MetaWear library is not a MetaWear command.  Given how you are using the i2c module, I would use Android's Handler class to periodically execute your tasks.

    The code handling the i2c data should be placed in the success function.  The AsyncOperation class is exactly what the class name says, asynchronous, and the success function is for handling the result of the operation.  There is no guarantee on order of execution for code that resides outside the AsyncOperation block.  If you need to have code execute sequentially, then you will have to nest CompletionHandlers inside each other.  This is just have you have to do it in Java.  I have written an outline of what the code would look like, link here.

    Finally, while not necessary, you should put the code handling the diff_volt variables inside a function.  The two code chunks are doing the same thing minus the variables i.e. diff_volt1 vs. diff_volt2 and sample_voltage1 vs. sample_voltage2
  • Hi Eric,

    Thanks for your explanation. I just have one doubt about this. As I will require to have the 4 samples obtained in each pass of the for loop, as the way you have described in your example, once we exit the success function (when have obtained both samples) and repeat again the loop, we won't be able to obtain the previous sample as we have exit the success function which had the data from the previous reading. So the data is lost. How to solve this issue?

    And for the scheduleTask, just one small question. The only function I am using inside of this is the writing/reading of the i2c, the rest is the data manipulation and plotting. Is this the part that blocks the application?

    Thanks for the help. I will let you know when I have implemented and tested the new code.

    Regards.
  • Hi Eric,

    I have tested the new application and at first seems ok, it reads and displays the value obtained from the i2c, there is a big problem which makes the bluetooth communication unstable and disconnects and reconnects every so and often it freezes the application or makes it slow. This is the code using Handler I have implemented:
    @Override
    protected void test_button() {
    final Handler handler = new Handler();
    final Runnable runnable = new Runnable() {
    @Override
    public void run() {
    for(int l=0; l<4;l++) {
    //final int iteration= l;

    i2cModule.writeData((byte) 0x34, (byte) 0x00, new byte[]{(byte) 0xA0, (byte) 0x13});


    i2cModule.readData((byte) 0x34, (byte) 0xA0, (byte) 4).onComplete(new CompletionHandler<byte[]>() {
    @Override
    public void success(byte[] result) {

    auxvar = result;
    //Log.i("test", String.format("register 0 = 0x%x, 1 = 0x%x, 2 = 0x%x, 3 = 0x%x", result[0], result[1], result[2], result[3]));
    //Log.i("MainActivity", String.format("WHO_AM_I= %d", result[0]));
    finalvoltage = auxvar[0] & 0x3F;
    finalvoltage <<= 8;
    finalvoltage += auxvar[1];
    finalvoltage <<= 8;
    finalvoltage += auxvar[2];
    finalvoltage <<= 8;
    finalvoltage += auxvar[3] & 0xC0;
    finalvoltage >>= 6;
    diff_volt1 = (float) Math.abs(finalvoltage) * 3 / 16777215;
    print_value(diff_volt1);

    }
    });
    }
    handler.postDelayed(this, 1000);

    }

    };
    handler.post(runnable);

    }

    It is called when I toggle the Sample button. Do you know why this happens?
    • Does increasing the delay time fix the problem?  What is the smallest delay that causes this issue?
    • What is your device model and Android OS that you are using?
    Manually issuing reads every 1 second should not cause any Bluetooth related issues.
This discussion has been closed.