Существует ли общая команда 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).
@immibis сделал очень хорошую мысль.
Вероятно, лучшее решение — использовать команду обновления с определенным тайм-аутом, который устраняет блокировку в этой точке.
Вот кажется, есть еще немного информации о том, как правильно это понять.
Вот еще один вопрос& с сайта SE Arduino, соответствующего теме.
Хорошо, чтобы разобраться и проверить это, потребуется больше времени. Также сделал видео об этом, смотрите ссылку внизу этого ответа. Все кредиты идут на @ user0042, который указывает мне правильное направление. Библиотека Wire по умолчанию на самом деле бесполезна, когда речь заходит о стабильности и надежности, поэтому ее необходимо «заменить» следующим образом:
Основная библиотека I2C —
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
Шаг 1
В первой версии я не использовал резисторы (ленивость), но это хорошая идея для стабилизации показаний шины. Добавьте два резистора (4,7 кОм) на выходе + 5 В к линиям данных. Это очень важно сделать, чтобы избежать ложных обнаружений и чтобы ваш Arduino мог все еще зависнуть из-за этого.
Шаг 2
Вы должны отслеживать изменения / состояние устройства каждого устройства I2C. Я использую три состояния:
Шаг 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
Шаг № 4
Перед каждым обновлением необходимо проверить доступность и состояние инициализации (три состояния, упомянутые в шаге № 3). Это очень важно, чтобы избежать ненужных задержек / выполнения кода (стресс).
Шаг № 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 outputThis 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
}
}