Attachinterrupt on ATTINY85

I'm trying to use/adapt some code on the ATTINY85 which specifies attachinterrupt, thus:

attachInterrupt(0, interruptFunction, RISING);

As I understand it, this is specifying INT0.

I want to use the interrupt (coming from an accelerometer) to wake up the ATTINY from deep sleep.

As I understand it, INT0 on the ATTINY is PB2, and I need to use that pin for SPI communication. Can I specify another pin to be INT0?

I'm a beginner, please be gentle.

This may help

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=105493

Thanks I can get pin change interrupts to work but was hoping to be able to use attachinterrupt.
However I am hoping I've thought of a way to use the PCI.

robertjenkins:
Can I specify another pin to be INT0?

Unfortunately no, it's stuck on that pin. The pin change interrupt will probably work, I've run into similar limitations and it's worked out fine. A little extra code will probably be necessary. First thing in the ISR, check the state of the input pin (high or low). If it's high, then that's the rising edge, else the ISR can just return.

I would not use digitalRead() in the ISR. Just check the port directly, which is much quicker (important in an ISR). If you need tips on that, let us know.

No need to write any code. There is a ready to use library available...
https://code.google.com/p/arduino-tiny/downloads/list?can=2&q=PinChangeInterrupt

Aww now what fun is that :wink:

After the last three weeks, it will be heaven to not be writing any code! So, to answer your question, much fun!

Ironically, I will very likely use the downtime to continue coding on the Tiny Core. :smiley:

That sounds like what I would call "recreational coding" :smiley:

I'm a bit stuck on this. I am using an accelerometer. When I send it to sleep, its interrupt pin goes HIGH. When it wakes up, because it detects motion, it goes LOW.

The sequence I want is this:

Put accelerometer to sleep
Put ATTINY85 to sleep
Wait for the accelerometer to wake up and send the interrupt pin LOW
Use this interrupt to wake up the ATTINY
Do stuff
repeat

I need to use the pin change interrupt, but I only want the interrupt to do anything if the accelerometer interrupt pin goes LOW (i.e. it's waking up) rather than HIGH (i.e. it's going to sleep).

The bare bones of the code I'm using is this:

//set up the interrupts
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

const int intpin = 4; 
int asleep;

void setup(){
pinMode (intpin, INPUT_PULLUP);
sbi(GIMSK,PCIE); // Turn on Pin Change interrupt
 sbi(PCMSK,PCINT4); // Which pins are affected by the interrupt
}

void loop(){
   accelsleep(); // put the accelerometer to sleep. Function is defined elsewhere, it seems to work
   system_sleep();  //wait for the interrupt
   if (asleep==0) //i.e if the accelerometer is not asleep, i.e. it's awake
   delay(5000); //actually I would have stuff happening here
}

void accelsleep(){
    //Set the threshhold and time
    SPIwriteTwoRegisters(0x20, 300);//300 is the threshhold
    SPIwriteOneRegister(0x22, 10); //10 is the time
 
    //Set the ACT_INACT_CTL bit 1
    SPIwriteOneRegister(0x27, 0x03);
  
    // Map awake status to interrupt 1
    SPIwriteOneRegister(0x2A, 0x90);
    SPIwriteOneRegister(0x2D, 0x02);
}

void SPIwriteOneRegister(byte regAddress, byte regValue){
 
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0A);  // write instruction
  SPI.transfer(regAddress);
  SPI.transfer(regValue);
  digitalWrite(slaveSelectPin, HIGH);
}
 
 
int SPIreadTwoRegisters(byte regAddress){
  int twoRegValue = 0;
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0B);  // read instruction
  SPI.transfer(regAddress); 
  twoRegValue = SPI.transfer(0x00);
  twoRegValue = twoRegValue + (SPI.transfer(0x00) << 8);
  digitalWrite(slaveSelectPin, HIGH);
 
 
  return twoRegValue;
} 

void SPIwriteTwoRegisters(byte regAddress, int twoRegValue){
  byte twoRegValueH = twoRegValue >> 8;
  byte twoRegValueL = twoRegValue;
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0A);  // write instruction
  SPI.transfer(regAddress); 
  SPI.transfer(twoRegValueL);
  SPI.transfer(twoRegValueH);
  digitalWrite(slaveSelectPin, HIGH);
}  



 void system_sleep() {
  cbi(ADCSRA,ADEN); // Switch Analog to Digital converter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
  sleep_mode(); // System sleeps here
  sbi(ADCSRA,ADEN);  // Switch Analog to Digital converter ON
}


ISR(PCINT0_vect) {
  asleep=digitalRead(intpin); //if the interrupt has gone low, then the asleep should be LOW
  SPIreadOneRegister(0x0B); //this is to clear the interrupt register on the accelerometer
}

What actually happens is that the accelerometer goes to sleep - what I want - and its interrupt pin goes high.
Then motion causes it to wake up - again what I want - and the interrupt pin goes low (actually to 0.05V, which I guess is low enough)
But the accelerometer never goes back to sleep again.

Is the problem maybe that I am using digitalRead against your advice? How do I read the port directly?
I am sure the library mentioned would be useful but I'm afraid I don't understand how to use it.

   if (asleep=0) //i.e if the accelerometer is not asleep, i.e. it's awake

How does assigning a value in an if statement do anything useful?

   accelsleep(); // put the accelerometer to sleep. Function is defined elsewhere, it seems to work
   system_sleep();  //wait for the interrupt

You forgot to post all of your code. I'll forget to post all of the answer.

Sorry, the first was an typo which I've corrected. The original was right but got messed up in the copy/paste
I didn't think you'd want all the code that is definitely working, but I've now posted it.

Sorry, the first was an typo which I've corrected. The original was right but got messed up in the copy/paste
I didn't think you'd want all the code that is definitely working, but I've now posted it.

I don't try to help people that go back and "fix" old posts.

Is that a bad thing to do? Sorry, I thought I was being helpful.

I am a very old dog, trying to learn new tricks. The last time I did any coding was nearly half a century ago, at school, in Algol. Since then my life has taken a different path, but it's never too late to learn, and so I am embarking again on a very steep learning curve.

Friend, it's entirely up to you whether or not you help, but cut me some slack?

robertjenkins:
The sequence I want is this:

I would do:

Put accelerometer to sleep
Enable pin change interrupts
Put ATTINY85 to sleep
Wait for the accelerometer to wake up and send the interrupt pin LOW
Disable pin change interrupts (first thing in the ISR)
Use this interrupt to wake up the ATTINY
Do stuff
repeat

The bare bones of the code I'm using is this:

Does not compile. Please include your complete code that compiles.

int asleep;

Since asleep is updated in the ISR, it needs to be declared volatile and accessed atomically in the main code.

ISR(PCINT0_vect) {
  asleep=digitalRead(intpin); //if the interrupt has gone low, then the asleep should be LOW
  SPIreadOneRegister(0x0B); //this is to clear the interrupt register on the accelerometer
}

There is no reason to read the pin in the ISR if things are set up properly, because by virtue of the fact that the ISR is invoked at all, we know the pin has changed.

Still, there are two ways to read the pin and avoid digitalRead() in the ISR:

  1. Read the port directly, something like
if ( PINB & _BV(PINB4) )
  1. Set a flag variable in the ISR that tells the main code that an interrupt has occurred. The flag variable would just be a bool type (which is a single byte). The main code can then use digitalRead().

robertjenkins:

//set up the interrupts

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

cbi and sbi are deprecated. But, if you want to use them, don't #define them yourself, just #include <avr/sfr_defs.h>

Preferred method is to use the _BV() macro.

Thanks for the tips. The reason I thought it necessary to check the status of intpin was because a pin change interrupt as I understand it will trigger if the pin changes in either direction. So I thought it would equally trigger when the accelerometer went to sleep as when it woke up. However, I have now done what you said and only enabled the interrupt after the accelerometer goes to sleep, and then disabled it in the ISR. Unfortunately there is no change. Full code below

#include <Arduino.h>
#include <SPI.h>
#include <avr/sleep.h>

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

const int slaveSelectPin = 3; //physical pin 2
const int MISOpin = 0; //physical pin 5
const int MOSIpin = 1; //physical pin 6
const int SCKpin = 2; //physical pin 7

const int intpin = 4; //physical pin 4

void setup(){
 
 pinMode(MOSIpin, OUTPUT);
 pinMode (SCKpin, OUTPUT);
 pinMode (slaveSelectPin, OUTPUT);
 pinMode (MISOpin, INPUT_PULLUP);
 pinMode (intpin, INPUT_PULLUP);
 
 begin();                   // Setup SPI protocol, issue device soft reset
 beginMeasure();

   

}

void loop(){
 
  accelsleep();
  turnoninterrupts; 
  system_sleep();
  delay(5000);

}


void turnoninterrupts(){
 sbi(GIMSK,PCIE); // Turn on Pin Change interrupt
 sbi(PCMSK,PCINT4); // Which pins are affected by the interrupt
}

void turnoffinterrupts(){
 cbi(GIMSK,PCIE); // Turn off Pin Change interrupt
 cbi(PCMSK,PCINT4); // Which pins are affected by the interrupt
}

void begin() {
  pinMode(slaveSelectPin, OUTPUT);
  SPI.begin();
  SPIwriteOneRegister(0x2C, 0x01); //ODR at 25Hz
  SPI.setDataMode(SPI_MODE0); //CPHA = CPOL = 0    MODE = 0
  delay(1000);
   
  // soft reset
  SPIwriteOneRegister(0x1F, 0x52);  // Write to SOFT RESET, "R"
  delay(10);
}

void beginMeasure() {
  byte temp = SPIreadOneRegister(0x2D); // read Reg 2D before modifying for measure mode
  // turn on measurement mode
  byte tempwrite = temp | 0x02; // turn on measurement bit in Reg 2D
  SPIwriteOneRegister(0x2D, tempwrite); // Write to POWER_CTL_REG, Measurement Mode
  delay(10);
}
 
void accelsleep(){  
    //Set the threshhold and time
    SPIwriteTwoRegisters(0x20, 300);//300 is the threshhold
    SPIwriteOneRegister(0x22, 10); //10 is the time
 
    //Set the ACT_INACT_CTL bit 1
    SPIwriteOneRegister(0x27, 0x03);
  

    // Map awake status to interrupt 1
    SPIwriteOneRegister(0x2A, 0x90);
    SPIwriteOneRegister(0x2D, 0x02);
}

 
// Basic SPI routines to simplify code
// read and write one register
 
 
  byte SPIreadOneRegister(byte regAddress){
  byte regValue = 0;
 
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0B);  // read instruction
  SPI.transfer(regAddress);
  regValue = SPI.transfer(0x00);
  digitalWrite(slaveSelectPin, HIGH);
 
 
  return regValue;
}
 
 
void SPIwriteOneRegister(byte regAddress, byte regValue){
 
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0A);  // write instruction
  SPI.transfer(regAddress);
  SPI.transfer(regValue);
  digitalWrite(slaveSelectPin, HIGH);
}
 
 
int SPIreadTwoRegisters(byte regAddress){
  int twoRegValue = 0;
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0B);  // read instruction
  SPI.transfer(regAddress); 
  twoRegValue = SPI.transfer(0x00);
  twoRegValue = twoRegValue + (SPI.transfer(0x00) << 8);
  digitalWrite(slaveSelectPin, HIGH);
 
 
  return twoRegValue;
} 
void SPIwriteTwoRegisters(byte regAddress, int twoRegValue){
  byte twoRegValueH = twoRegValue >> 8;
  byte twoRegValueL = twoRegValue;
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0x0A);  // write instruction
  SPI.transfer(regAddress); 
  SPI.transfer(twoRegValueL);
  SPI.transfer(twoRegValueH);
  digitalWrite(slaveSelectPin, HIGH);
}  



 void system_sleep() {
  cbi(ADCSRA,ADEN); // Switch Analog to Digital converter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
  sleep_mode(); // System sleeps here
  sbi(ADCSRA,ADEN);  // Switch Analog to Digital converter ON
}


ISR(PCINT0_vect) {
  turnoffinterrupts();
  SPIreadOneRegister(0x0B);
  
}

OK, that's a whole new detour :slight_smile:
I'm not convinced it's ever getting to the ISR, but it's hard to tell with no spare pins on the ATTINY for debugging. I managed to get pin change interrupt working fine when I was triggering it physically with a switch, but something is clearly going wrong now.

robertjenkins:
#include <SPI.h>

I wasn't aware that SPI.h worked with an ATtiny85, and indeed it gives me compile errors. Which core are you using?

There is a modified version which does work which I got from here

I'm using the arduino-tiny core. (I had to install this because I'm also using the Manchester encoding library which would only work with that core)
The accelerometer works fine in communication mode so SPI as such doesn't seem to be the issue.

robertjenkins:

void loop() {

accelsleep();
 turnoninterrupts;
 system_sleep();
 delay(5000);
}

Need parens: turnoninterrupts();

That's a problem, not sure whether it's the problem. (Unfortunately this is one of those situations where the compiler warning message is suppressed by the IDE unless verbose output is selected.)

When enabling and disabling the interrupts, it's sufficient to set or clear the PCIE bit GIMSK. I'd set PCMSK once and then just leave it, it has no effect if interrupts are disabled with GIMSK. Save an instruction or two :smiley: