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

2024-03-16

是否有通用的 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  */ }

是否有可用的通用命令,可以说/发送一个“你好你在听吗”并等待答复而不发送 START 和 STOP 命令且不中断设备/总线状态?


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


好吧,弄清楚并测试它需要更长的旅程。还制作了一个视频,请参阅此答案底部的链接。所有功劳都归功于@user0042,他为我指明了正确的方向。默认的 Wire 库在稳定性、可靠性方面实际上没有什么用处,因此需要将其“替换”为:


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


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

我更改了我的软件,检测总线上设备的“密钥”可以简化为:

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

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

我发送一个**0x00 command**但是,该设备似乎没有任何反应。我创建的函数在插入时返回 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 方法

Step #1

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

Step #2

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

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

Step #3

如果您使用类与设备“对话”,则必须在设备可用时动态创建该类。在我的例子中,它是这样的:

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 

Step #4

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

  • 如果之前没有连接,则需要创建类
  • 如果之前已连接(重新连接),则必须重新初始化(类和设备)

Step #5

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

Step #6

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


一些示例代码

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

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 https://www.youtube.com/watch?v=ODWqPQJk8Xo


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


UPDATE:

我的方法似乎适用于多个 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
   }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 为什么在排序输入上插入到树中比随机输入更快?

    现在我一直听说从随机选择的数据构建二叉搜索树比有序数据更快 这仅仅是因为有序数据需要显式重新平衡以将树高度保持在最低限度 最近我实现了一个不可变的treap http en wikipedia org wiki Treap 一种特殊的二叉搜
  • 快速 log2(float x) 实现 C++

    我需要在 C 中非常快速地实现 log2 float x 函数 我发现了一个非常有趣的实现 而且速度非常快 include
  • 为什么迭代器类型推导失败? [复制]

    这个问题在这里已经有答案了 为什么这在 C 中不起作用 为什么我不能限制foo的参数为std vector
  • 为基于架构的 XML 文件创建 WPF 编辑器

    这是场景 我们的服务器产品之一使用大型 XML 配置文件 该文件的布局相当好 并且针对 XSD 文件进行了验证 现在是时候构建一个配置 GUI 来维护这个文件了 我想深入研究 WPF 来完成它 我可以为每个配置部分布置一个单独的表单 每次向
  • C# 中输入按键

    我尝试了这段代码 private void textBox1 KeyPress object sender KeyPressEventArgs e if Convert ToInt32 e KeyChar 13 MessageBox Sho
  • 仅针对某些异常类型中断

    我知道异常处理是一件非常重要的事情 我们在所有项目中都在这样做 主要原因是记录客户发生的错误 这工作正常 根本不是问题 但是 当我仍在使用 Visual Studio 编码和运行应用程序时 我根本不需要任何异常处理 我希望调试器正好停在应用
  • C 中的双重否定:是否保证返回 0/1?

    Is x 标准保证返回0 1 请注意 我是not询问 C 其中定义了 bool 类型 是的 在 C99 中 请参阅 6 5 3 3 4 逻辑非运算符的结果 是0如果其操作数的值比较 不等于0 1如果其操作数的值比较等于 0 结果具有类型in
  • 我们什么时候应该在.NET中使用NativeMemory.Alloc()? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 NET6 C 引入NativeMemory类 但我不知道什么时候应该使用NativeMemory Alloc 而不是普通的数组实例化
  • 无法在 Visual Studio 和 vcpkg 中构建 cmake 项目(致命错误 C1083)

    我今天安装了vcpkg 启用了与Visual Studio的集成 即 vcpkg集成安装 并开始安装库 我基本上安装了 cpprestsdk 并触发了 boost 库的安装 然后我在 Visual Studio CMake 中打开该项目 当
  • C# 中不区分大小写的替换不使用正则表达式?

    有没有一种方法可以在不使用 C 中的正则表达式的情况下对字符串进行不区分大小写的替换 像这样的东西 string x Hello x x Replace hello hello world 你可以尝试类似的东西 string str Hel
  • 代码块 - 使用大地址感知标志进行编译

    如何使用以下命令在 64 位系统上编译 32 位应用程序LARGE ADRESS AWARE使用代码块标记 我需要使用超过 2GB 的内存 应该是添加的情况 Wl large address aware到链接标志 我不使用 CodeBloc
  • 这个元组创建习惯有名字吗?

    On the 增加邮件列表 http lists boost org Archives boost 2014 06 214213 php LouisDionne 最近发布了以下创建类似元组的实体的巧妙技巧 include
  • C# 中的类和模块有什么用

    有人可以解释一下类和模块之间的区别吗 你什么时候使用其中一种而不是另一种 我正在使用 C 更新 我的意思是相当于 VB 模块的 C 版本 这在很大程度上取决于您所指的 模块 Visual Basic 的模块 C 中没有真正等效的 VB Ne
  • 从 cin 读取整数序列并将它们存储在向量中

    这就是我读取整数的方法std cin并将它们存储在向量中 int number vector
  • 什么是 C++11 扩展 [-Wc++11-extensions]

    我需要一些帮助来了解此错误发生的位置 警告 非静态数据成员的类内初始化是 C 11 扩展 Wc 11 extensions 这是它来自的代码部分 typedef struct Hand bool straight false bool fl
  • 没有 FPU 的处理器中的浮点计算

    是否可以在没有浮点单元的嵌入式处理器中执行浮点运算 是的 您只需要在软件中完成即可 你的编译器可能会提供支持 http gcc gnu org onlinedocs gccint Soft float library routines ht
  • 如何使用 MongoDB 实现 ASP.NET Core 3.1 Identity?

    是一个 API 用于简化后端和逻辑代码来管理用户 密码 个人资料数据 角色 声明 令牌 电子邮件确认等 对于 Visual Studio 来说 支撑脚手架 https learn microsoft com en us aspnet cor
  • OpenSSL:无需 SSL_read() / SSL_write() 即可执行加密/解密

    我已经用 C 语言编写了一个基于事件的网络库 现在我想通过 OpenSSL 添加 SSL TLS 支持 而不是使用SSL read and SSL write 我宁愿让 OpenSSL 只执行传出 传入数据的加密 解密 让我自己传输 接收数
  • 查找文本文件中每行的行大小

    如何计算每行中的字符或数字数量 是否有类似 EOF 的东西更像是行尾 您可以遍历行中的每个字符并不断增加计数器直到行尾 n 遇到 确保以文本模式打开文件 r 而不是二进制模式 rb 否则流不会自动将不同平台的行结束序列转换为 n 人物 这是
  • 如何在c#中获取斐波那契数

    伙计们 我有一个关于斐波那契的问题 如何获得斐波那契数列 该数字也将以用户输入结束 例如 如果我输入 21 则输出必须为 0 1 1 2 3 5 8 13 21 这是我的代码 static void Main string args int

随机推荐