Announcement

Collapse
No announcement yet.

Details of I2C Reads and Writes

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • AlanG
    replied
    P.S. Jonathan,

    Here's a copy of a typical logcat entry that I'm getting:
    09-11 13:56:03.710 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ ========= Device Information ================================================== =
    09-11 13:56:03.720 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ Type Name Connection
    09-11 13:56:03.720 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ Modern Robotics USB Device Interface Module cdim USB AL00XKJM
    09-11 13:56:03.730 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ Analog Input NO DEVICE ATTACHED USB AL00XKJM; analog port 7
    09-11 13:56:03.730 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ Digital Channel NO DEVICE ATTACHED USB AL00XKJM; digital port 7
    09-11 13:56:03.730 24359-24384/com.qualcomm.ftcrobotcontroller I/RobotCore﹕ Modern Robotics IR Seeker Sensor irseeker3 USB AL00XKJM; I2C port 0
    09-11 13:56:03.730 24359-24384/com.qualcomm.ftcrobotcontroller I/FIRST﹕ ======= INIT FINISH =======

    Leave a comment:


  • AlanG
    replied
    Jonathan,

    The configuration XML file lists a name for the Bosch IMU (e.g., "bno055a") and the correct I2C port number for the CDIM plugin. However, the "Log.i" message in the Android logcat that lists the devices found by FtcRobotController has no references at all to the Bosch IMU or the number of the port into which it's plugged. That "Log.i" message does, however, list MR devices like the IR Seeker 3 and correctly identifies its plugin port.

    I have not plugged the IMU into an Arduino or a IOIO, or anything else like those, so I have no other driver for it but what I have written using the FTC API.

    Leave a comment:


  • Jonathan Berling
    replied
    Initially I was assuming it went into port busy stuck state after you had been using the device for a while, which would suggest a bug in your code. The only way to recover from that is to reset the CDIM, and fix the bug in your code so it doesn't happen again. If it is going into a stuck state before you access it then that suggests a problem outside of either the FTC API or your code.

    The actual I2C protocol is handled by the CDIM. We have been able to properly communicate with any I2C device we have attached to it. However, we haven't tried a Bosch IMU.

    Just to do a sanity check, the I2C port in the hardware configuration wizard should be set to either I2C DEVICE or NOTHING.

    Before we point the finger at the CDIM, have you been able to write a driver that uses the Bosch IMU that doesn't involve the CDIM?

    Leave a comment:


  • AlanG
    replied
    Originally posted by AlanG View Post
    Jonathan,

    My problem is that, when init() is called in my OpMode, then very first 2 things I MUST do are:
    1. wait for the I2c port to be ready, then do a "write" that sets the page register to 0, then
    2. Wait for the I2c port to be ready, then write the RST_SYS bit to the SYS_TRIGGER register.

    I don't do ANY "reads" until after that.

    And I must do this every time an OpMode that uses the IMU starts, not just the first start after a power up.

    I think lots of other FTC coaches will tell you that it's completely impractical to have to power down and power up the robot, every time a team wants to start a different OpMode.
    Jonathan,

    Last night, I found an answer to my question: When my Bosch IMU test OpMode starts up and calls init(), it will ALWAYS find the I2C port in a "stuck busy" condition, unless the power to the IMU has been cycled off, then on . And even when power has been cycled and my two initial "writes" have completed successfully, any other "write" that attempts to reset the IMU or start its internal self-test once again leaves the port in a "stuck busy" state, no matter how long a timeout I set on testing isI2cPortReady(). If the IMU starts up after a power cycle, then only 3 seconds or so seem to be needed as a timeout on testing isI2cPortReady().

    For others intending to use the Bosch IMU or other I2C devices, two methods of power cycling seem to work equally well:
    1. Power cycling the entire Core Device Interface Module, or
    2. Momentarily disconnecting then reconnecting the +5V wire on the ribbon cable that plugs the I2C device into the Device Interface Module. (Perhaps teams can build their robots with normally closed spring-loaded pushbuttons wired in series on the +5V line.)

    But very frankly, there appear to be serious deficiencies in the design of the FTC API, or whatever firmware there is in the Core Device Interface Module, or both.
    I question whether or not the software/firmware design is fully compliant with the I2C Specification UM10204. If an I2C device is built to be software-resettable, then:
    1. The capability to issue that reset should always be available, and
    2. Whatever I2C bus master the device is attached to should be able to respond to the device's reset without getting "stuck".

    You said you have had some experience with the Bosch IMU. If you have some software that circumvents these difficulties, please share it with us as an example.

    Leave a comment:


  • GearTicks
    replied
    Originally posted by Jonathan Berling View Post
    The advantage of using the callback is it gets called right after the phone has read in the most current data from the CDIM.
    So, if we know what the last read/write command sent to the I2CDevice was, we are guaranteed that it has occurred when the callback method runs? I was thinking of writing a class to queue reads and writes in some sort of stack and then issuing the one on top when the callback occurs. About how long would you estimate it would take for the read/write to occur after calling writeCacheToModule() or readCacheFromModule()?

    Leave a comment:


  • AlanG
    replied
    Originally posted by Jonathan Berling View Post
    hey Alan,

    If isI2CPortReady() never returns true or is taking a long time to return true it is usually because the CDIM is in a bad state a needs to be power cycled. The most likely cause of the CDIM being in a bad state is because a device is trying to read or write to the I2C buffer for a given port while the port is busy.

    The suggestion to move the sleep before the the call to isI2CPortReady() is to make sure you have the most current data from the CDIM. By have the most current data you won't try to write to the buffer while isI2cPortReady() is returning true, even though you have already sent a command to the device, but the phone hasn't read back in the most current ready state of the port yet.

    The advantage of using the callback is it gets called right after the phone has read in the most current data from the CDIM. But it's not necessary to use the callback. A potential future optimization that will allow you to remove the sleeps is to block until the callback gets called.

    Let me know if this doesn't answer your question, or if you have more questions.
    Jonathan,

    My problem is that, when init() is called in my OpMode, then very first 2 things I MUST do are:
    1. wait for the I2c port to be ready, then do a "write" that sets the page register to 0, then
    2. Wait for the I2c port to be ready, then write the RST_SYS bit to the SYS_TRIGGER register.

    I don't do ANY "reads" until after that.

    And I must do this every time an OpMode that uses the IMU starts, not just the first start after a power up.

    I think lots of other FTC coaches will tell you that it's completely impractical to have to power down and power up the robot, every time a team wants to start a different OpMode.

    Leave a comment:


  • Jonathan Berling
    replied
    hey Alan,

    If isI2CPortReady() never returns true or is taking a long time to return true it is usually because the CDIM is in a bad state a needs to be power cycled. The most likely cause of the CDIM being in a bad state is because a device is trying to read or write to the I2C buffer for a given port while the port is busy.

    The suggestion to move the sleep before the the call to isI2CPortReady() is to make sure you have the most current data from the CDIM. By have the most current data you won't try to write to the buffer while isI2cPortReady() is returning true, even though you have already sent a command to the device, but the phone hasn't read back in the most current ready state of the port yet.

    The advantage of using the callback is it gets called right after the phone has read in the most current data from the CDIM. But it's not necessary to use the callback. A potential future optimization that will allow you to remove the sleeps is to block until the callback gets called.

    Let me know if this doesn't answer your question, or if you have more questions.

    Leave a comment:


  • AlanG
    replied
    Originally posted by Jonathan Berling View Post
    Hey Alan, by timing out I assume you mean that isPortReady() never returns true. This usually happens when you try to write/read to/from the port when it is not ready. Also remember that restarting the app does not reset the USB devices. You have to power cycle them to do that. While developing software for the various I2C devices if I put the CDIM in a bad state I had power cycle it.

    What if you changed this
    Code:
        try {
          while ((!i2cIMU.isI2cPortReady())
                   && (((rightNow = System.nanoTime()) - loopStart) < 1000000000L)) {
            Thread.sleep(250);//"Snooze" right here, until the port is ready (a good thing) OR 1 billion
            //nanoseconds pass with the port "stuck busy" (a VERY bad thing)
          }
        } catch (InterruptedException e) {
          Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
          return false;
        }
    To this
    Code:
      try {
        snooze(250);
      } catch (InterruptedException e) {
        Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
        return false;
      }
    
      if ((!i2cIMU.isI2cPortReady()) {
        continue;
      }
    That way you would always the most recent data before trying to read if the port is ready. You could also get rid of all of the other calls to snooze(250) in this method. Later, once it's working you could optimize this initial call to snooze out. 250ms is more than enough time for the CDIM to update.

    The callback and isI2cPortReady() should always agree with each other. The method that handles the callback checks the same isI2cPortReady() method to determine if it should call the callback.
    Jonathan,
    The "stuck busy" problem I just reported comes when I'm trying to find the port ready to receive 2 "write" commands:
    1. To set the page register to page 0, so that I can
    2. Write a "RESET" to the IMU chip.
    If I can't EVER find the port ready when the OpMode calls its init() method, then I can't issue the RESET command.

    Are you saying that I have no choice but to power-cycle the Adafruit IMU board ?? Is there a major design bug here - the inability to reset the Core Device Interface Module (and attached devices) without a HARDWARE RESET??

    Leave a comment:


  • AlanG
    replied
    P.P.S.:
    Jonathan,

    I need to refocus my question to you:
    Thanks to David Pierce, mentor of Team 8886, I now know that 0x28*2 is the correct I2C bus address. He examined the schematic for the Adafruit IMU board, and determined that the Bosch chip is wired in such a way that the address is 0x28, not what the datasheet reports as "default". Now that I'm talking to the right address, I find that autoCalibrationOK and other initialization code keeps bombing out on a "port is 'stuck busy'" error due to code like this (from the sample above):
    Code:
        try {
          loopStart = System.nanoTime();
          while ((!i2cIMU.isI2cPortReady())
                   && (((rightNow = System.nanoTime()) - loopStart) < 1000000000L)) {
            Thread.sleep(250);//"Snooze" right here, until the port is ready (a good thing) OR 1 billion
            //nanoseconds pass with the port "stuck busy" (a VERY bad thing)
          }
        } catch (InterruptedException e) {
          Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
          return false;
        }
        if ((rightNow - loopStart) >= 1000000000L) {
          Log.i("FtcRobotController", "IMU I2C port \"stuck busy\" for "
            + (rightNow - loopStart) + " ns.");
          return false;//Signals the "stuck busy" condition
        }
    Is this effort to do Thread.sleep while polling "isI2cPortReady()" rapidly at least a part of my problem??

    Leave a comment:


  • Jonathan Berling
    replied
    Hey Alan, by timing out I assume you mean that isPortReady() never returns true. This usually happens when you try to write/read to/from the port when it is not ready. Also remember that restarting the app does not reset the USB devices. You have to power cycle them to do that. While developing software for the various I2C devices if I put the CDIM in a bad state I had power cycle it.

    What if you changed this
    Code:
        try {
          while ((!i2cIMU.isI2cPortReady())
                   && (((rightNow = System.nanoTime()) - loopStart) < 1000000000L)) {
            Thread.sleep(250);//"Snooze" right here, until the port is ready (a good thing) OR 1 billion
            //nanoseconds pass with the port "stuck busy" (a VERY bad thing)
          }
        } catch (InterruptedException e) {
          Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
          return false;
        }
    To this
    Code:
      try {
        snooze(250);
      } catch (InterruptedException e) {
        Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
        return false;
      }
    
      if ((!i2cIMU.isI2cPortReady()) {
        continue;
      }
    That way you would always the most recent data before trying to read if the port is ready. You could also get rid of all of the other calls to snooze(250) in this method. Later, once it's working you could optimize this initial call to snooze out. 250ms is more than enough time for the CDIM to update.

    The callback and isI2cPortReady() should always agree with each other. The method that handles the callback checks the same isI2cPortReady() method to determine if it should call the callback.

    Leave a comment:


  • AlanG
    replied
    P.S.: After timing out at 1 minute, the logcat reports to me that what should be a PAGE ID of 0 shows consistently as a 0XFF. I'm sure I'm addressing the Bosch IMU at I2C address 0X29 * 2, which the Bosch datasheet says is the default address.

    Leave a comment:


  • AlanG
    replied
    Jonathan,

    My Bosch IMU initialization code now consistently times out at 1 minute the first time it tries to read something quickly, without a callback being registered. The following method is called when I try to do a "new AdafruitIMU" in the init() method of my OPMode "IMUtest":
    Code:
    private boolean autoCalibrationOK(){
      boolean readingEnabled = false, calibrationDone = false;
      long calibrationStart = System.nanoTime(), rightNow = System.nanoTime(),
        loopStart = System.nanoTime();;
    
      while ((System.nanoTime() - calibrationStart) <= 60000000000L) {//Set a 1-minute timeout
        try {
          loopStart = System.nanoTime();
          while ((!i2cIMU.isI2cPortReady())
                   && (((rightNow = System.nanoTime()) - loopStart) < 1000000000L)) {
            Thread.sleep(250);//"Snooze" right here, until the port is ready (a good thing) OR 1 billion
            //nanoseconds pass with the port "stuck busy" (a VERY bad thing)
          }
        } catch (InterruptedException e) {
          Log.i("FtcRobotController", "Unexpected interrupt while \"sleeping\" in autoCalibrationOK.");
          return false;
        }
        if ((rightNow - loopStart) >= 1000000000L) {
          Log.i("FtcRobotController", "IMU I2C port \"stuck busy\" for "
            + (rightNow - loopStart) + " ns.");
          return false;//Signals the "stuck busy" condition
        }
        if (!readingEnabled) {//Start a stream of reads of the calibration status byte
          //The device interface object must do this, because the i2c device object CAN'T do it, in the
          //8 August 2015 beta release of the FTC SDK
          //deviceInterface.enableI2cReadMode(configuredI2CPort, baseI2Caddress,
          //                                   BNO055_CALIB_STAT_ADDR, 2);
          //deviceInterface.enableI2cReadMode(configuredI2CPort, baseI2Caddress,//FOR TESTING ONLY!
          //                                   BNO055_CHIP_ID_ADDR, 1);         //FOR TESTING ONLY!
          deviceInterface.enableI2cReadMode(configuredI2CPort, baseI2Caddress,//FOR TESTING ONLY!
                                             BNO055_PAGE_ID_ADDR, 1);         //FOR TESTING ONLY!
          i2cIMU.setI2cPortActionFlag();//Set this flag to do the next read
          i2cIMU.writeI2cCacheToModule();
          snooze(250);//Give the data time to go from the Interface Module to the IMU hardware
          readingEnabled =true;
          continue;
        } else {//Check the Calibration Status and Self-Test bytes in the Read Cache. IMU datasheet
          // Sec. 3.10, p.47. Also, see p. 70
          i2cIMU.readI2cCacheFromModule();//Read in the most recent data from the device
          snooze(1000);//Give the data time to come into the Interface Module from the IMU hardware
          try {
            i2cReadCacheLock.lock();
            if ( //((i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS + 1] & 0X0C) == 0X0C) &&
              //Is there a problem with commanding Self-Test at the same time as Reset????
              //((i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS] & 0X30) >= 0X20)//WHY NOT >= 0X30????
              //(i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS] == (byte)BNO055_ID)//FOR TESTING ONLY!
              (i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS] == (byte)0X00)//FOR TESTING ONLY!
              ) {
              //See IMU datasheet p.67. As an example, 0X30 checks only gyro calibration complete.
              //Also on that page: Self-Test byte value of 0X0C means MCU and gyro passed self-tests
              Log.i("FtcRobotController", "Autocalibration OK! Cal status byte = "
                + String.format("0X%02X", i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS])
                + ". Self Test byte = "
                + String.format("0X%02X", i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS + 1])
                + ".");
              calibrationDone = true;//Auto calibration finished successfully
            }
          } finally {
            i2cReadCacheLock.unlock();
          }
          if(calibrationDone) return true;
          i2cIMU.setI2cPortActionFlag();   //Set this flag to do the next read
          i2cIMU.writeI2cPortFlagOnlyToModule();
          //At this point, the port becomes busy (not ready) doing the next read
          snooze(250);//Give the data time to go from the Interface Module to the IMU hardware
        }
      }
      Log.i("FtcRobotController", "Autocalibration timed out! Cal status byte = "
        + String.format("0X%02X",i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS])
        + ". Self Test byte = "
        + String.format("0X%02X",i2cReadCache[I2cController.I2C_BUFFER_START_ADDRESS + 1])
        + ".");
      return false;
    }
    (Note: The snooze() method is just a short utility that does Thread.sleep for a certain number of milliseconds inside a try-catch block.) This block of code is the first time I try to enable reading, then repeat a series of reads until I see the IMU give a certain response. (Right now, I'm just trying desperately to read the PAGE_ID as 0, since I've already written a "set page to 0" command to the IMU.

    What am I not understanding about how to read an I2C device at init() time? These are initializations I MUST do before start().

    Leave a comment:


  • Jonathan Berling
    replied
    i2cIMU.readI2cCacheFromModule() is not a blocking method. It tells the I2C controller to read the cache in an asynchronous manner. At some point in the future the read cache will have the new data. Unfortunately we don't have a callback or flag that you can check so you can know that the read is complete. You can just wait for some length of time. Or you can zero out the read cache before calling i2cIMU.readI2cCacheFromModule() then when the values change you know the read is complete.

    Calling sleep in the op mode thread will slow down the response rate of the servo and motor controllers. A future improvement might be to start a new thread for your driver to work in.

    I didn't have a lot of time to look over your code. So if my analysis doesn't make sense feel free to ask more questions.

    Leave a comment:


  • AlanG
    replied
    On Pastebin, I just posted http://pastebin.com/75CFakgh.
    IMUtest is an OpMode which is just a driver program. AdafruitIMU is the class I've defined to implement an interface to a Bosch BNO055 IMU.

    Leave a comment:


  • AlanG
    replied
    Originally posted by Jonathan Berling View Post
    Hey AlanG,

    With Thread.sleep(), you're not blocking the op mode thread, right? Because that will cause problems. The I2C read / write should be complete when the port is ready. It gets kind of tricky to keep track of what is going on, since there are three different devices you have to deal with: the phone, the CDIM, and the Bosch IMU. Most of the errors I have when writing I2C drivers is forgetting this.

    It's been a while since I worked with a Bosch IMU, but I assume that 0x50 (0x28 x 2) is the address you'd use with the CDIM. It's only the I2C addresses that have this 7bit/8bit issue. The register addresses should be consistent.

    If you have the source posted somewhere I could take a quick look at it.
    I'm doing all my Bosch IMU initialization by calls from the OpMode methods init() and start(). If you tell me that my IMU initialization methods can't do Thread.sleep, then I need to be assured that "isI2cPortReady" doesn't return true until the data has gone from the cache to the Module **AND** from the Module to the actual IMU hardware. If what I'm asking is still not clear, then I'll be able to upload my current code to Pastebin later today. Please let me know.

    Leave a comment:

Working...
X