Attiny85 with I2C and Pin Change Interrupts

Hi,

I'm using an Attiny85 to count reed switch pulses on 2 pins, then pass the values via I2c to my ESP8266. It's all running off battery so it goes to sleep until one of the pins wakes up via interrupt.

When I get a high pulse frequency on one of the pins, my Master fails to correctly read the I2C. From doing lots of reading online I think it's due to clash between the pin change interrupt and I2C interrupt, but I'm still very new to this and although I've guessed (probably wrongly!) the problem, I don't know how to solve it.

Here is my slave code - is anybody able to have a look and point out what's probably and obvious error to the educated?

// 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>

const int rainPin = 3; // which pin is rain sensor connected to
const int windPin = 4; // which pin is wind sensor connected to
const int statusLED = 1; //which pin is status LED connected to
const byte chksum1 = 123;
const byte chksum2 = 210;

volatile unsigned int raincount = 0; 
volatile unsigned int windcount = 0;
volatile bool thiswind;
volatile bool lastwind = HIGH;
volatile bool thisrain;
volatile bool lastrain = HIGH;
volatile int response=0;
bool debug=false;
volatile int senddata = 1;

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
    TinyWireS.onReceive(receiveEvent); 
    TinyWireS.onRequest(requestEvent);

    pinMode(rainPin, INPUT);
    digitalWrite(rainPin, HIGH);
    pinMode(windPin, INPUT);
    digitalWrite(windPin, HIGH);
    //pinMode(statusLED, OUTPUT);

    if (debug) {
        digitalWrite(statusLED, HIGH);
        tws_delay(1000);
        digitalWrite(statusLED,LOW);
      }
            
    } // 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
    //WDTCR &= ~_BV(WDIE);                    // DISABLE WATCHDOG ***************PMN HERE*********************
    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 // Count rain gauge bucket tips as they occur
  thisrain = digitalRead(rainPin);
  if (thisrain != lastrain) {  //check if value has changed since last time
    if (digitalRead (rainPin) == LOW) {
      raincount ++;
      //Serial.print("Rain");
    }
    lastrain = thisrain;
  }

  thiswind = digitalRead(windPin);
  if (thiswind != lastwind) {  //check if value has changed since last time
    if (digitalRead(windPin) == HIGH)  {  //value has changed - was it rising edge?
      windcount ++;
      //Serial.print("Wind");
    }
    lastwind = thiswind; //switch state ready for next time
  }
  
}


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

  // Go back to sleep
  sleep();
  tws_delay(1);
}
 
// Gets called when the ATtiny receives an i2c request
void requestEvent()
{
/*
      TinyWireS.send(chksum1);
      TinyWireS.send(lowByte(raincount));
      TinyWireS.send(highByte(raincount));
      TinyWireS.send(lowByte(windcount));
      TinyWireS.send(highByte(windcount));
      TinyWireS.send(chksum2);
      raincount = 0;
      windcount = 0;
*/
 // cli();
  switch (senddata) {
    case 1:
      TinyWireS.send(chksum1);
      senddata = 2;
      break;
    case 2:
      TinyWireS.send(lowByte(raincount));
      senddata = 3;
      break;
    case 3:
      TinyWireS.send(highByte(raincount));
      //raincount = 0;
      senddata = 4;
      break;
    case 4:
      TinyWireS.send(lowByte(windcount));
      senddata = 5;
      break;
    case 5:
      TinyWireS.send(highByte(windcount));
      //windcount = 0;
      senddata = 6;
      break;
    case 6:
      TinyWireS.send(chksum2);
      senddata = 1;
      break;
    default:
      TinyWireS.send(111);
      senddata=1;         
  } 
  //sei();
}


// Gets called when the ATtiny receives an i2c write slave request
void receiveEvent(uint8_t howMany)
{
  cli();
  for(int i = 0; i < howMany; i++)
  {
    // Transmission codes:
      // 1 = Reset Rain Sensor
      // 2 = Reset Wind Sensor
      // 3 = Reboot Rain Sensor
    while(TinyWireS.available())
    {
      response = TinyWireS.receive();
      if (response == 1){
        raincount = 0;
      } 
      else if (response == 2){
        windcount = 0; 
      }
      else if (response == 3) {
        reboot();
      }
      else if (response == 4) {
        senddata=1;
      }
      else {
          // DO NOTHING
       
      }
    } 
  }
  sei();
}

Since the ATTiny has no other work don't use pin change interrupts. Just check the pins if they changed in the loop, it will be a lot faster. You may miss a few pulses while communicating via I2C - but you would miss them anyway if the pulses are really so fast.

I would do, but I need the Attiny to be asleep until one of the pins fires so I can't run them in the loop - or am I missing something?

What makes you say there's a problem? Is there an error when compiling? Does it just not work? What does it do instead of working in that case?

Try to add
USICR|=USIWM0;
after TinyWire initialization. It should enable clock stretching to give you time you need to service the interrupt. Also you should try to make your ISRs as fast as possible. I.e. for the PCINT just read whole port and save it somewhere. Than in loop() check what happened. Something like

ISR {
newPortValue=PINB;
}

loop() {
if (newPortValue!=oldPortValue) {
cli();
doSomething();
oldPortValue=newPortValue;
sei();
}
else sleep();
}

Very helpful, thanks very much.

Just out of interest - what exactly does USICR|=USIWM0; do?

I've put it in setup(), immediately after TinyWireS.begin(I2C_SLAVE_ADDRESS);, is that right?

Needlerp:
Very helpful, thanks very much.

Just out of interest - what exactly does USICR|=USIWM0; do?

I've put it in setup(), immediately after TinyWireS.begin(I2C_SLAVE_ADDRESS);, is that right?

The wrong thing.

You want

USICR|=1<<USIWM0;

Needlerp - where you ever able to get your code functioning correctly? I'm using the exact same setup but having trouble getting the Tiny to wake for the I2C request from the ESP8266.

Apologies for digging up an old thread, this is closest example I've seen to mine.

Thanks!

same - i'm trying to wake an 85 up from sleep using the USI start condition ...

i have confirmed the StartCondition ISR is running (light a led on pb4 during the ISR) but the ESP8266 2wire complains that the address is NAK'd

If i have other I2c devices on the bus, the 85 seems to pollute them, and they don't behave - the BME280 doesn't get recognised for instance

Looking at the impl of the USI_START, it releases the SCL-down by setting 1 into USISIF, the datasheet (pp116) says that 0 is needed to do this? Neither works :confused:

any pointers pls?

apologies - PEBCAK

this post drove me to do some rubber-duck debugging - i had a crucial flag, set in the isr, behind an 'if-sleeping' #define, not marked volatile