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
Edit: I managed to use RX interrupts of SoftwareSerial for waking up.