[SOLVED] ATTINY85 I2C stops after a while

Hello to all,

I’m currently building a wifi weather station. For monitoring wind speed, wind direction and rain, I use ATTINYs.

One ATTINY85 is used for wind speed and later on for rain gauge. Currently I use a reed switch for wind detection, maybe other sensors like hall will be better but it works as starting point.

However, after digging into I2C with Attiny, I got some working code using TinyWireS Library. Main Time, ATTINY is in sleep mode, at pin change interrupt, I count wind variables and go back to sleep. If I2C request occurs, it sends data to master.

All this works great for a couple of minutes but it then stops responding to I2C calls. It is build up on a breadboard, I added external Pullups at ATTINY for SCL and SDA (4k7 Ohm)

I also tried it without sleep and without pin change interrupts, but it still crashes after a while. Master (Wemos D1 mini) is reading every 2 seconds for debugging purposes.

Does anyone has some hints to me what’s going wrong? This is my slave code, ATTINY85 is running at 8MHz internal

// Code for the ATtiny85

#include <TinyWireS.h>
#define I2C_SLAVE_ADDRESS 0x4 // Address of the slave

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

// pins of wind vane positions
const int pinWindCount = 3;

const byte chksum1 = 200; // begin of transfer
const byte chksum2 = 251; // end of transfer


volatile int response = 0;
volatile int senddata = 1;
uint8_t windcount1 = 0;
uint8_t windcount2 = 0; // Overflow variable 1
uint8_t windcount3 = 0; // Overflow variable 2
volatile bool thiswind;
volatile bool lastwind = HIGH;

void reboot() {
  cli();
  WDTCR = 0xD8 | WDTO_1S;
  sei();

  wdt_reset();
  while (true) {}

} //reboot

void setup()
{
  MCUSR &= ~(1 << WDRF); // reset status flag
  wdt_disable();
  tws_delay(100);
  TinyWireS.begin(I2C_SLAVE_ADDRESS); // join i2c network
  USICR |= 1 << USIWM0; // Clock stretching?
  TinyWireS.onRequest(requestEvent);

  pinMode(pinWindCount, INPUT_PULLUP);

  digitalWrite(pinWindCount, HIGH);



} // setup


void sleep() {

  GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
  PCMSK |= _BV(PCINT3);                   // Use PB3 as interrupt pin
  //   PCMSK |= _BV(PCINT4);                   // Use PB4 as interrupt pin

  ADCSRA &= ~_BV(ADEN);                   // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT3);                  // Turn off PB3 as interrupt pin
  //  PCMSK &= ~_BV(PCINT4);                  // Turn off PB4 as interrupt pin
  sleep_disable();                        // Clear SE bit
  ADCSRA |= _BV(ADEN);                    // ADC on
  //WDTCR |= _BV(WDIE);                    // Watchdog on

  sei();                                  // Enable interrupts
} // sleep


ISR(PCINT0_vect) {
  // DO NOTHING HERE, CATCH EVERYTHING IN LOOP 
}



void loop()
{
  // This needs to be here
  TinyWireS_stop_check();

  // Check wind count
  thiswind = digitalRead(pinWindCount);
  if (thiswind != lastwind) {  //check if value has changed since last time
    if (thiswind == HIGH)  {  //value has changed - was it rising edge?

      // do we get an overflow at windcount 1?
      if (windcount1 == 255 )
      {
        // do we get an overflow on windcount 2?
        if (windcount2 == 255)
        {
          windcount3++;
        }
        windcount2++;
      }
      windcount1++;
    }
    lastwind = thiswind; //switch state ready for next time
  }

  // Go back to sleep
  tws_delay(2); // wait 2ms for software debouncing
  sleep();

}



// Gets called when the ATtiny receives an i2c request
void requestEvent()
{

  // cli();
  switch (senddata) {

    case 1: // first byte requested send start checksum
      TinyWireS.send(chksum1);
      senddata = 2;
      break;

    case 2: // 2nd byte requested, send first counter
      TinyWireS.send(windcount1);
      senddata = 3;
      break;

    case 3: // 3rd byte requested, send first overflow counter
      TinyWireS.send(windcount2);
      senddata = 4;
      break;

    case 4: // 4th byte requested, send 2nd overflow counter
      TinyWireS.send(windcount3);
      senddata = 5;
      break;

    case 5: // 5th byte requested, send end checksum
      TinyWireS.send(chksum2);
      senddata = 1;
      break;

    default:
      TinyWireS.send(111);
      senddata = 1;
  }
  // sei();
}

What core are you using? If you're using the David Mellis one you should change over to TinyCore- it has native support for I2C so you won't need to mess around with external libraries.

I had some DACs that used I2C that worked for a bit and would stop. My issue was that I did not tie all the grounds together to a common one! Just another thing to check for, Alex.

Thank you for your answers.

You’re right, I’m using David Mellis Core. I switched over to SpenceKonde TinyCore and dropped TinyWire as suggested.

Because Wemos provides Power to Attiny, they share common ground. Also there is 0,1uF capacitor between VCC and GND on ATTINY as well as external pullup resistors to SCL and SDA.

Sadly, it did not work. I get the same behavior as before. It works a while and stops responding to I2C requests.

My slave code looks like this right now

// Code for the ATtiny85

#include <Wire.h>
#define I2C_SLAVE_ADDRESS 0x4 // Address of the slave

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

// pins of wind vane positions
const int pinWindCount = 3;

const byte chksum1 = 200; // begin of transfer
const byte chksum2 = 251; // end of transfer


volatile int response = 0;
volatile int senddata = 1;
uint8_t windcount1 = 0;
uint8_t windcount2 = 0; // Overflow variable 1
uint8_t windcount3 = 0; // Overflow variable 2
volatile bool thiswind;
volatile bool lastwind = HIGH;

void reboot() {
  cli();
  WDTCR = 0xD8 | WDTO_1S;
  sei();

  wdt_reset();
  while (true) {}

} //reboot

void setup()
{
  MCUSR &= ~(1 << WDRF); // reset status flag
  wdt_disable();

  Wire.begin(I2C_SLAVE_ADDRESS); // join i2c network
  USICR |= 1 << USIWM0; // Clock stretching?
  Wire.onRequest(requestEvent);

  pinMode(pinWindCount, INPUT_PULLUP);
  digitalWrite(pinWindCount, HIGH);

} // setup


void sleep() {

  GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
  PCMSK |= _BV(PCINT3);                   // Use PB3 as interrupt pin
  //   PCMSK |= _BV(PCINT4);                   // Use PB4 as interrupt pin

  ADCSRA &= ~_BV(ADEN);                   // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT3);                  // Turn off PB3 as interrupt pin
  //  PCMSK &= ~_BV(PCINT4);                  // Turn off PB4 as interrupt pin
  sleep_disable();                        // Clear SE bit
  ADCSRA |= _BV(ADEN);                    // ADC on
  //WDTCR |= _BV(WDIE);                    // Watchdog on

  sei();                                  // Enable interrupts
} // sleep


ISR(PCINT0_vect) {
  // DO NOTHING HERE, CATCH EVERYTHING IN LOOP
}



void loop()
{

  // Check wind count
  thiswind = digitalRead(pinWindCount);
  if (thiswind != lastwind) {  //check if value has changed since last time
    if (thiswind == HIGH)  {  //value has changed - was it rising edge?

      // do we get an overflow at windcount 1?
      if (windcount1 == 255 )
      {
        // do we get an overflow on windcount 2?
        if (windcount2 == 255)
        {
          windcount3++;
        }
        windcount2++;
      }
      windcount1++;
    }
    lastwind = thiswind; //switch state ready for next time
  }

  // Go back to sleep
  delay(2); // wait 2ms for software debouncing
  sleep();

}



// Gets called when the ATtiny receives an i2c request
void requestEvent()
{

  // cli();
  switch (senddata) {

    case 1: // first byte requested send start checksum
      Wire.write(chksum1);
      senddata = 2;
      break;

    case 2: // 2nd byte requested, send first counter
      Wire.write(windcount1);
      senddata = 3;
      break;

    case 3: // 3rd byte requested, send first overflow counter
      Wire.write(windcount2);
      senddata = 4;
      break;

    case 4: // 4th byte requested, send 2nd overflow counter
      Wire.write(windcount3);
      senddata = 5;
      break;

    case 5: // 5th byte requested, send end checksum
      Wire.write(chksum2);
      senddata = 1;
      break;

    default:
      Wire.write(111);
      senddata = 1;
  }
  // sei();
}

This is how my master looks like

//Code for the Arduino Mega<
#include <Wire.h>
#include "WindSpeed.h"


WindSpeed wind;

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.begin(9600); // start serial for output

  Serial.println("alive");
  pinMode(0, OUTPUT);
  digitalWrite(0, HIGH);
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);

wind.begin();

}

void loop()
{
  Serial.println("loop");

 // Serial.println(wind.getSpeed());
 
for (int i = 0; i<5; i++)
{
  Serial.println(wind.getByte());
}
//Serial.println(wind.getByte());
  delay(2000);
}
/*=========================================================================
    I2C ADDRESS/BITS
    -----------------------------------------------------------------------*/
#define WINDSPEED_ADDRESS                (0x4)
/*=========================================================================*/

/*--- Class Definition  */
class WindSpeed {

  public:
    WindSpeed(void);
    bool  begin(uint8_t addr = WINDSPEED_ADDRESS);
    unsigned long getSpeed();
    unsigned int getByte();

  private:
    uint8_t   _i2caddr;
};

/*------------ Class Implementation ------*/

//----------------------------
// Class Constructor
//----------------------------
WindSpeed::WindSpeed()
{ }

//----------------------------------
// Set I2C addres of Wind speed sensor
//----------------------------------
bool WindSpeed::begin(uint8_t a)
{
  _i2caddr = a;

  return true;
}

//----------------------------------
// Get wind speed as integer
//----------------------------------
unsigned long WindSpeed::getSpeed()
{
  unsigned int data = 0;
  unsigned long speed = 888;
  unsigned int windcount1 = 0;
  unsigned int windcount2 = 0;
  unsigned int windcount3 = 0;

  // read start checksum
  Wire.requestFrom(_i2caddr, 1);
  while (Wire.available())
  {
    data = Wire.read();
  }

  delay(5);

  // start checksum was valid,read first byte
  if (data == 200)
  {

    Wire.requestFrom(_i2caddr, 1);
    while (Wire.available())
    {
      windcount1 = Wire.read();
    }
    delay(5);

    // read first overflow counter
    Wire.requestFrom(_i2caddr, 1);
    while (Wire.available())
    {
      windcount2 =  Wire.read();
    }
    delay(5);

    // read 2nd overflow counter
    Wire.requestFrom(_i2caddr, 1);
    while (Wire.available())
    {
      windcount3 = Wire.read();
    }
  }

  delay(5);

  // get stop checksum
  Wire.requestFrom(_i2caddr, 1);
  while (Wire.available())
  {
    data = Wire.read();
  }

  if (data == 251)
  {
    speed = ( windcount3 * 255 * 255) + (windcount2 * 255) + windcount1;
    return speed;
  }

  return 777;

}

//----------------------------------
// Read single byte
//----------------------------------
unsigned int WindSpeed::getByte()
{
  Wire.requestFrom(_i2caddr, 1);
  while (Wire.available())
  {
    return Wire.read();
  }
}

Could it be that master is asking too fast (5ms). What is a common time to transfer one byte when ATTINY runs at 8MHz?

Paddy_noob:
Because Wemos provides Power to Attiny, they share common ground.

What voltage and clock speed are you running the ATTiny at?

It runs at 3.3V 8MHz internal clock.

Oh man, it seems to work right now. It runs since several minutes without any issue. It never worked for such a long period so I assume it is solved now.

I've found some hint on a different thread. The solution is to add this line of code to master (Wemos D1 mini pro, so it's an ESP8266 Device)

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  Wire.setClockStretchLimit(1500);
  Serial.begin(9600); // start serial for output

Setting clock stretching limit to 1500 did the trick.

I've found it here:[Solved] ESP8266 to Nano I2C Problems. - Networking, Protocols, and Devices - Arduino Forum

Thank you for your help