[SOLVED] Interrupt Conflict handling for ATTiny45/85

Hi All,

I'm working on a simple PoC to handle interrupts for ATTiny. Once it is working fine, i'll incorporate the same into my project.

My project is bluetooth based (using SPP-C bluetooth chip with breakout board: Product Link); the breakout board has 6 pins: MCU-INT, RX, TX, VCC, GND and CLEAR.

So in my project I need the ATTiny go to into POWER DOWN sleep mode to save power and perform operations only when Bluetooth is connected i.e ATTiny must be powered down when bluetooth is not connected.

My current logic is to see for interrupt by connecting MCU-INT to PB5/PCINT5 (yes, I will be using RESETDISABLE fuse setting later on once code is fully finalized; Will be uploading the code and then setting the fuse bit). As of now I'm testing the interrupt logic using a different pin(PB1).

When a bluetooth connection is made MCU-INT goes high and I can detect the interrupt. However the confirmation I need over here is that: I got to know that ATTiny45/85 has only 1 available ISR for all PCINTn processing and I need to use SoftwareSerial as well for bluetooth.

Hence I modified the SoftwareSerial.cpp and SoftwareSerial.h code to handle both my interrupts as well as RX interrupts. Here is the code:

Main code:

//Includes
#include <SoftwareSerial.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define INTERRUPTPIN PCINT1 //this is PB1 per the schematic

#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) //OR
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) //AND

void setup()
{
    cli();//disable interrupts during setup

    pinMode(LEDPIN, OUTPUT); //we can use standard arduino style for this as an example
    digitalWrite(LEDPIN, go2sleep); //set the LED to LOW

    sbi(PCMSK,INTERRUPTPIN);
    sbi(GIMSK,PCIE); 

    sei(); 
}

void loop()
{
  if(go2sleep)
  {
    // POWER DOWN sleep code
  }else
  {
    //normal operation code
  }
}

SoftwareSerial.h (defined few pins at the top):

..

#ifndef SoftwareSerial_h
#define SoftwareSerial_h

#define READPIN PINB1                // my change
#define LEDPIN 0                        // my change

static volatile boolean go2sleep = true; // my change

#include <inttypes.h>
#include <Stream.h>

..

SoftwareSerial.cpp (modified the existing ISR and hence its aliases - see at the end):

//

...
// The receive routine called by the interrupt handler
//
void SoftwareSerial::recv()
{

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Preserve the registers that the compiler misses
// (courtesy of Arduino forum user *etracer*)
  asm volatile(
    "push r18 \n\t"
    "push r19 \n\t"
    "push r20 \n\t"
    "push r21 \n\t"
    "push r22 \n\t"
    "push r23 \n\t"
    "push r26 \n\t"
    "push r27 \n\t"
    ::);
#endif  

  uint8_t d = 0;

  // If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
  {
    // Disable further interrupts during reception, this prevents
    // triggering another interrupt directly after we return, which can
    // cause problems at higher baudrates.
    setRxIntMsk(false);

    // Wait approximately 1/2 of a bit width to "center" the sample
    tunedDelay(_rx_delay_centering);
    DebugPulse(_DEBUG_PIN2, 1);

    // Read each of the 8 bits
    for (uint8_t i=8; i > 0; --i)
    {
      tunedDelay(_rx_delay_intrabit);
      d >>= 1;
      DebugPulse(_DEBUG_PIN2, 1);
      if (rx_pin_read())
        d |= 0x80;
    }

    if (_inverse_logic)
      d = ~d;

    // if buffer full, set the overflow flag and return
    uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
    if (next != _receive_buffer_head)
    {
      // save new data in buffer: tail points to where byte goes
      _receive_buffer[_receive_buffer_tail] = d; // save new byte
      _receive_buffer_tail = next;
    } 
    else 
    {
      DebugPulse(_DEBUG_PIN1, 1);
      _buffer_overflow = true;
    }

    // skip the stop bit
    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN1, 1);

    // Re-enable interrupts when we're sure to be inside the stop bit
    setRxIntMsk(true);

  }

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Restore the registers that the compiler misses
  asm volatile(
    "pop r27 \n\t"
    "pop r26 \n\t"
    "pop r23 \n\t"
    "pop r22 \n\t"
    "pop r21 \n\t"
    "pop r20 \n\t"
    "pop r19 \n\t"
    "pop r18 \n\t"
    ::);
#endif
}

uint8_t SoftwareSerial::rx_pin_read()
{
  return *_receivePortRegister & _receiveBitMask;
}

//
// Interrupt handling
//

/* static */
inline void SoftwareSerial::handle_interrupt()
{
  if (active_object)
  {
    active_object->recv();
  }
}

#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
 byte pinState;           // my change
  pinState = (PINB >> READPIN)& 1; //// my change
  if (pinState >0) //look at the pin state on the pin PINB register- returns 1 if high - my change
  {
   go2sleep = !go2sleep;
   digitalWrite(LEDPIN, go2sleep);
  } // my change  end
  SoftwareSerial::handle_interrupt();         // existing code called as it is (should i call it upon a condition?)
}
#endif

#if defined(PCINT1_vect)
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
#endif

#if defined(PCINT2_vect)
ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));
#endif

#if defined(PCINT3_vect)
ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect));
#endif

...

To-be logic after PoC: go2sleep is currently used to toggle a LED. Going further I will be changing the boolean to false and clearing interrupt in ISR (to perform normal operations) and re setting the interrupt (and making go2sleep = true) when my android app sends a quit command via Bluetooth.
Normal operations code has been developed both on the ATTiny and Arduino side and its working fine. I thought of adding the POWER DOWN sleep mode to save power as well. Hence I need to add this additional interrupt handling functionality.

In SoftwareSerial.cpp I made changes to call my code on receiving interrupt. That works fine but I'm a bit concerned about the existing code. Should i call the receive portion on any condition? If yes which? or should I just call it as it is after I have checked for MCU-INT from bluetooth.

As far as I can see in the method void SoftwareSerial::recv():

// If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
  {
    // Disable further interrupts during reception, this prevents
    // triggering another interrupt directly after we return, which can
    // cause problems at higher baudrates.
    setRxIntMsk(false);

It does check whether the interrupt is for RX or not. Would that be enough?

Thanks :slight_smile:

Edit: I managed to use RX interrupts of SoftwareSerial for waking up.