I've been working on a project wih the NodeMCU V2 Amica with a friend which consist of 2 PN532, an I2C LCD and a MicroSD adapter in SPI.
The 2 PN532 are connected respectively in HSU (Hardware) and I2C (the latter is software I2C because my friend's found out that the NodeMCU doesn't have Hardware I2C) together with the I2C Display.
The problem is that after a while the I2C Display freezes, and the NodeMCU doesn't recognize it anymore.
We've tried this piece of code we've found on thisforum:
int I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SCL, INPUT_PULLUP);
delay(2500); // Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
// IDE a chance to start uploaded the program
// before existing sketch confuses the IDE by sending Serial data.
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(100);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SCL, INPUT);
return 0; // all ok
}
This basically clears the bus and it works only partially because now the display freezes really often (and the board doesn't seem to recnognize it anymore), while before we used that piece of it had took about 7/8 loop to hang.
The main program actually checks if there is a NFC tag (the strange thing about the NFC reader is that sometimes it takes a while to actually scan them, maybe is correlated to the bus problem?) and it updates the timestamp on the Display (we aren't using a RTC module, we get the time from a webserver once at the boot of the board and then we use millis() to sum it up and get the new timestamp).
I'm also sharing the code we are using to "ping" the I2C display on the bus:
byte error;
do{
Wire.beginTransmission(0x27); // --------------> 0x27 is the I2C address of the LCD.
error = Wire.endTransmission();
if(error) {
DEBUG_MSG("Display I2C non trovato\n");
switch(I2C_ClearBus()){
case 0:
Serial.println("Bus cleared!");
break;
case 1:
Serial.println("SCL low!");
break;
case 2:
Serial.println("SDA low causa clock stretch di uno slave");
break;
case 3:
Serial.println("SDA low dopo 20 clock");
break;
}
Wire.begin(I2C_SDA,I2C_SCL);
lcd->begin();}
delay(100);
}while(error);
Thank you in advance for your help and ask me if you haven't understood what I've written as I'm not really good at english