是否有通用的 I2C 命令来查看设备是否仍存在于总线上?

Is there a general I2C command to see if a device is still present on the bus?

是否有通用的I2C命令来查看设备在初始化一次后是否仍然存在于总线上?例如 OLED 显示器。我问这个的原因是为了避免主程序冻结(当设备断开连接时),因为库代码中存在无限循环,例如 Wire 库。

在MCU启动时,我想检查一个设备是否可用,并在可用时初始化它。我用这个函数做这个并且工作正常.....

bool MyClass::isPnpDeviceAvailable( uint8_t iAddress, bool bIsInitOnce = false )
{
     // Try to start connection
    Wire.beginTransmission( iAddress );

     // End connection without STOP command if already is initialized
    return ( Wire.endTransmission( !bIsInitOnce ) == 0x00 ); // No Error?, return true
}

...但是,当我想检查设备是否仍然存在时,在我执行更新之前,当我这样做时:

// 1.
if( isPnpDeviceAvailable( 0x3C, true )) 
 { /* Cause program hang */ }
// 2.
if( isPnpDeviceAvailable( 0x3C )) 
 { /* Cause display to turn off  */ }

是否有可用的通用命令,say/send 只需 "Hello, are you there" 并等待回复而不发送 START 和 STOP 命令并且不中断 device/bus状态?


这是我制作的带有附加(可选 PNP I2C)显示器的原型设备。

@immibis .

可能更好的解决方案是利用您的更新命令设置一定的超时时间,从而打破阻塞。

Here 似乎有更多关于如何正确实现这一点的信息。

Here's another Q&A 来自 SE Arduino 网站,符合主题。

好吧,弄清楚并测试它需要更长的路程。还制作了一段视频,请参阅此答案底部的 link。所有功劳归功于@user0042,他为我指明了正确的方向。默认的Wire库在稳定性、可靠性方面其实没什么用,所以需要'replace'这样:


I2C 主库 - http://dsscircuits.com/articles/arduino-i2c-master-library


使用这个库有更多好处,它的编译大小更小,请阅读上面的文章以获取更多信息。

我更改了我的软件,'key' 检测总线上的设备可以简化为:

bool TEnjoyPad::isPnpDeviceAvailable( uint8_t iAddress )
{
  return ( I2c.write( (int)iAddress, (int)0x00 ) == 0x00 ); 
}

注意:需要 (int) 类型转换以避免编译器警告,但它在没有编译器警告的情况下也能正常工作。

我发送了一个 **0x00 command** 什么也没做,但是,设备似乎有应答。我在插入时将 returns 设为 true ,否则设为 false 。

I doesn't test it with other i2c devices yet, however, will try later and update this question. For now it seems to working fine. 注意:请参阅以下更新:


PNP 方法

第 1 步

在第一个版本中我没有使用任何电阻器(懒惰)但是稳定总线读数是个好主意。在 +5V 输出端添加两个电阻 (4.7K) 到数据线。这样做非常重要,以避免错误检测并避免您的 Arduino 仍然因此冻结。

步骤 #2

您需要跟踪每个 I2C 设备的 changes/device 状态。我使用三种状态:

  • 已连接
  • 已重新连接(又名已连接)
  • 断开连接(或之前从未连接过)

步骤#3

如果您使用 class 到 'speak' 到设备,则必须在设备可用时动态创建它。在我的示例中,它是这样的:

TOLEDdisplay* display; // declaration
......
......
display = new TOLEDdisplay( SDA, SCL ); // To create it
display->begin(); // it's a pointer to an object so you need to use -> instead of . (simple explanation ;-) )
......
// etc 

步骤#4

在每次更新之前,您需要检查可用性和初始化状态(步骤#3 中提到的三种状态)。这对于避免不必要的 delays/code 执行(压力)非常重要。

  • 如果之前没有连接,您需要创建 class
  • 如果之前连接过(重新连接),您必须重新初始化(class 以及设备)

步骤 #5

您需要在循环或中断中检查更改。最好循环而不是中断。

第 6 步

检测到更改时执行更新。在真正更新之前使用大约 200 毫秒的小延迟。


一些示例代码

您不能使用此代码,但是,它可以让您了解如何设计代码。我使用了很多宏来简化我的实际代码,因此更容易阅读:

void TEnjoyPad::showAbout() // only showed at initialization
{
  __tepClearDisplay();

  __tepSetDisplayText( "ENJOYPAD v1.0"     , TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE1 );
  __tepSetDisplayText( "(c) 2017 codebeat" , TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE2 );


  __tepRefreshDisplay();
  setDelay( 2000 );
  updateDisplay();

}

void TEnjoyPad::updateDisplay()
{
 if( !__tepDisplayIsInit() )
  { return; }


 __tepDrawDisplayBitmap( TEP_DISPLAY,           // bitmap
                         0, TEP_DISPLAY_LINE0,  // x,y
                         TEP_DISPLAY_WIDTH,
                         TEP_DISPLAY_HEIGHT
                        );

  uint8_t i = TEP_MIN_MODE - 1;

  __tepDrawDisplayClearRect( 0, 10, 128, 35 );

  while( ++i <= TEP_MAX_MODE )
  {
    if ( emuMode != i )
    {
      // erase area, delete what's NOT selected
      __tepDrawDisplayClearRect( TEP_DISPLAY_MODE_ICON_X + ((i - 1) * (TEP_DISPLAY_MODE_ICON_WIDTH + TEP_DISPLAY_MODE_ICON_SPACING)),
                                 TEP_DISPLAY_MODE_ICON_Y,
                                 TEP_DISPLAY_MODE_ICON_WIDTH,
                                 TEP_DISPLAY_MODE_ICON_HEIGHT
                               );
    }
    else {
            __tepSetDisplayText( TEP_MODE_GET_NAME(i), TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE1 );
         }
  }

  __tepRefreshDisplay();
}

void TEnjoyPad::beginDisplay( bool bIsFound = false )
{
  static bool bWasConnected = false;

  bIsFound = bIsFound?true:isPnpDeviceAvailable( TEP_PNP_ADDR_DISPLAY );

  if( bIsFound )
  {
    if( !bWasConnected  )
    {
      if( pnpStates[ TEP_PNP_IDX_DISPLAY ] )
      {
        // Reset
        setDelay( 200 );
        // Reset display
        bIsFound = isPnpDeviceAvailable( TEP_PNP_ADDR_DISPLAY );
        if( bIsFound )
        {
          __tepDisplay->begin(); 
          updateDisplay();
        } 
      }
     else {
            // (re-)connected" );
            __tepCreateDisplay(); // This macro checks also if class is created
            __tepInitDisplay();
            showAbout();

             // Set class is created
            pnpStates[ TEP_PNP_IDX_DISPLAY ] = TEP_PNP_ADDR_DISPLAY;
          }  
    }

    bWasConnected = bIsFound;
  }
  else { 
            // Disconnected           
            bWasConnected = false; 
       }  
} 

 // In a loop I call this function:
uint8_t TEnjoyPad::i2CPnpScan()
{
  uint8_t iAddress = 0x7F; // 127
  bool    bFound   = false;
  uint8_t iFound   = 0;

  //Serial.println( "Scanning PNP devices...." );
  while ( --iAddress )
  {
    //Serial.print( "Scanning address: 0x" );
    //Serial.println( iAddress, HEX );

    if( iAddress == TEP_PNP_ADDR_DISPLAY )
     { beginDisplay( bFound = isPnpDeviceAvailable( iAddress ) ); 
       iFound+=bFound;
     }
  }

  return iFound;
}

演示视频

我还创建了一个演示视频,概念证明,向您展示此方法工作正常。您可以在 YouTube 上观看视频: https://www.youtube.com/watch?v=ODWqPQJk8Xo


感谢大家的帮助,希望这些信息也能帮助到其他人。


更新:

我的方法似乎适用于多个 I2C 设备。我写了这个更新的 I2CScanner:


您可以使用的 I2CScanner 代码:

/*
 ----------------------------------------
 i2c_scanner - I2C Master Library Version

 Version 1 (Wire library version)
    This program (or code that looks like it)
    can be found in many places.
    For example on the Arduino.cc forum.
    The original author is not know.

 Version 2, Juni 2012, Using Arduino 1.0.1
     Adapted to be as simple as possible by Arduino.cc user Krodal

 Version 3, Feb 26  2013
    V3 by louarnold

 Version 4, March 3, 2013, Using Arduino 1.0.3
    by Arduino.cc user Krodal.
    Changes by louarnold removed.
    Scanning addresses changed from 0...127 to 1...119,
    according to the i2c scanner by Nick Gammon
    http:www.gammon.com.au/forum/?id=10896

 Version 5, March 28, 2013
    As version 4, but address scans now to 127.
    A sensor seems to use address 120.

 Version 6, November 27, 2015.
    Added waiting for the Leonardo serial communication.

 Version 7, September 11, 2017 (I2C Master Library version)
    - By codebeat
    - Changed/Optimize code and variable names
    - Add configuration defines
    - Add fallback define to standard Wire library
    - Split functionality into functions so it is easier to integrate 
    - Table like output


 This sketch tests the standard 7-bit addresses between
 range 1 to 126 (0x01 to 0x7E)
 Devices with higher addresses cannot be seen.

 ---------------------
 WHY THIS NEW VERSION?

 The Wire library is not that great when it comes to stability, 
 reliability, it can cause the hardware to freeze because of
 infinite loops inside the library when connection is lost or
 the connection is unstable for some reason. Because of that
 the Wire library is also not suitable for plug and play 
 functionality, unplugging an I2C device will immediately
 lock the hardware (if you want to talk to it) and you 
 need to reset the hardware. I will not recover on itselfs.  

 Another reason is the way to check if a device is plugged-in
 or not. The methods of the Wire library doesn't allow to 
 do this because it resets/stop the I2C device when it is
 already started/available.



 Benefits of the I2C Master Library:
 - More flexible;
 - Faster;
 - Smaller compile size;
 - Idiot proof;
 - Self recovering (no hardware freeze);    
 - Able to check for availability of devices without 
   interrupt bus status and/or device (see the 
   example function isDeviceAvailable() how to achieve 
   this)
   .

 More info at:
 http://dsscircuits.com/articles/arduino-i2c-master-library
 You can also download the library there.

 PRECAUTIONS:
 It is a good idea to stabilize the readouts of the bus. 
 Add two resistors (4.7K) on the +5V output to the data lines. 
 Only one pair is required, don't use more or different resistors.
 It is very important to do this to avoid false detections and to 
 avoid your Arduino can still freeze because of that. 

 NOTICE:
 When selecting the default Wire library, this scanner will probably 
 not show the side effects I am talking about because the code 
 don't talk to the device and the connection to a device is extremely 
 short period of time.
*/

// *** Uncomment this if you want to use the default Wire library.
//#define I2C_LIB_WIRE

 // Some settings you can change if you want but be careful
#define I2C_MIN_ADDRESS     0x01
#define I2C_MAX_ADDRESS     0x7F
#define I2C_UPDATE_TIMEOUT  3000
#define I2C_I2CLIB_TIMEOUT  1000
#define I2C_I2CLIB_FASTBUS  true


 // Errorcodes that are normal errors when I2C device does
 // not exists.
#define I2C_I2CLIB_ERROR_NOT_AVAIL  32
#define I2C_WIRELIB_ERROR_NOT_AVAIL  2


// -------------------------------------------------------------


#ifdef I2C_LIB_WIRE
 #define I2C_ERROR_NOT_AVAIL I2C_WIRELIB_ERROR_NOT_AVAIL
  // Compile size with Wire library: 6014 bytes
 #include <Wire.h>
 #pragma message "Compiled with Wire library"
#else 
 #define I2C_ERROR_NOT_AVAIL I2C_I2CLIB_ERROR_NOT_AVAIL
  // Compile size with I2C Master library: 5098 bytes
 #include <I2C.h>
 #define Wire I2c
 #pragma message "Compiled with I2C Master library"
#endif


// -------------------------------------------------------------


int iLastError = 0;

bool isDeviceAvailable( uint8_t iAddress )
{
 #ifdef I2C_LIB_WIRE
  // Wire:
  // The i2c_scanner uses the return value of the Write.endTransmisstion 
  // to see if a device did acknowledge to the address.
  Wire.beginTransmission( iAddress );
  iLastError = Wire.endTransmission();  
 #else
   // I2C Master Library:
   // Just send/write a meaningless 0x00 command to the address 
   // to figure out the device is there and the device answers.
  iLastError = Wire.write( (int)iAddress, (int)0x00 ); 
  // Notice: The (int) typecasting is required to avoid compiler  
  //         function candidate notice. 
 #endif  

 return ( iLastError == 0x00 ); 
}

byte findI2Cdevices( bool bVerbose = true ) 
{
  byte nDevices = 0;

  if( bVerbose )
   { Serial.println("Scanning..."); }

  for(byte iAddress = I2C_MIN_ADDRESS; iAddress < I2C_MAX_ADDRESS; iAddress++ ) 
  {
    if( bVerbose )
    {
     Serial.print("Address 0x");
     if( iAddress < 16 ) 
      { Serial.print("0"); }

     Serial.print( iAddress, HEX );
     Serial.print(": ");
    }

    if( isDeviceAvailable( iAddress ) )
    {
      if( bVerbose )
       { Serial.println("FOUND  !"); }
      nDevices++;
    }
    else { 
            if( bVerbose )
            {
              Serial.print( "<NO DEVICE FOUND" ); 
              if( iLastError != I2C_ERROR_NOT_AVAIL )
              {
                Serial.print( " - ERRCODE: " );
                Serial.print( iLastError );
              }
              Serial.println( ">" ); 
            }
         }    
  }

  if( bVerbose )
  {
    if( nDevices > 0 )
    { 
      Serial.print( nDevices );
      Serial.println( " device(s) found\n" ); 
    }
    else { Serial.println( "No I2C devices found\n"); }

    Serial.print( "Press CTRL+A, CRTL+C to copy data.\n" );
  }

  return nDevices;
}

void setupI2C()
{
 Wire.begin();

 #ifndef I2C_LIB_WIRE  
  // This is important, don't set too low, never set it zero.
  Wire.timeOut( I2C_I2CLIB_TIMEOUT ); 

  #ifdef I2C_I2CLIB_FASTBUS
   if( I2C_I2CLIB_FASTBUS )
    { Wire.setSpeed(1); }
  #endif
 #endif
}

void setupSerial()
{
 Serial.begin(9600);
 while (!Serial); // Leonardo: wait for serial monitor
 Serial.println("\nI2C Scanner");
}

// -------------------------------------------------------------

void setup()
{
  setupI2C();
  setupSerial();
}

void loop()
{
    // Skip the Arduino slow down housekeeping after the loop() 
    // function, we stay here forever ;-) 
   while(1)
   {
     findI2Cdevices();
     delay( I2C_UPDATE_TIMEOUT ); // wait n seconds for next scan
   }
}