Attiny85 two pin change interrupts

Hello,

I am currently revisiting an old project and trying to port it from an Atmega328P-PU to an Attiny85.

Part of the project on the 328 (which is completely finished, with working code) was to monitor two square waves on the two external interrupt pins and count the milliseconds between rising edges of each wave. I then used that information to let the Atmega do certain things.

Now I want to simplify the circuit and move down to an Attiny85 from the Atmega, because the Attiny should really be well enough for that sort of purpose.

The question now is: how do I go about counting milliseconds between "rising" pin changes on two separate pins with the Attiny85?

Here's part (!!) of my code:

#include <avr/interrupt.h>

#define reverse 0
#define speaker 1
#define mute 2
#define GALA 3            // square wave #1
#define volume 4          // square wave #2

volatile unsigned long risingTimestampGALA;
volatile unsigned long risingTimestampVolume;

void setup() {

  // Setting up pin modes
  pinMode(reverse, INPUT);
  pinMode(speaker, OUTPUT);
  pinMode(mute, OUTPUT);
  pinMode(GALA, INPUT);
  pinMode(volume, INPUT);

  // Setting Interrupt Registers
  GIMSK = 0b00100000;    // turns on pin change interrupts
  PCMSK = 0b00011000;    // turn on interrupt on pins PB3 and PB4
  sei();                 // enables interrupts
}

Now when defining the ISR routine at the bottom of the sketch, is this the right way to go?

ISR(PCINT0_vect) {

 if ((PINB & 3 << GALA) != 0)  risingTimestampGALA = millis();
 if ((PINB & 4 << volume) != 0) risingTimestampVolume = millis();
 }

The timestamps assigned within the ISR for the rising-edge pin changes are used elsewhere in the code to do time difference calculations and then make the Attiny do things depending on the results.

The code porting is still in its early stages, so I haven't had time to actually upload and test it on an Attiny85 (also because I don't have an unused Attiny here at the moment :slight_smile: ). The code does compile though. Haven't done much with interrupts on the Attiny...

anyone?

:frowning:

sei() call is unnecessary unless you've disabled interrupts elsewhere.

You need to keep track of what the current pin state is, so you can compare it to the new pin state in the ISR and determine which pin changed - I would read the contents of the PINB register directly - it also helps keep things faster. You're not doing this correctly - also, you're not even reading the pins correctly here. Review your bit-math.

erm yes, I will admit that I'm still sketchy on bit math here and there.

Sorry that I have to ask, but what do you mean by reading the PINB register directly?

Shouldn't this:

ISR(PCINT0_vect) {

 if ((PINB & 3 << GALA) != 0)  risingTimestampGALA = millis();
 if ((PINB & 4 << volume) != 0) risingTimestampVolume = millis();
 }

Be:

ISR(PCINT0_vect) {

 if ((PINB & 1 << GALA) != 0)  risingTimestampGALA = millis();
 if ((PINB & 1 << volume) != 0) risingTimestampVolume = millis();
 }

?

@ 756E6C :

You're right... I just had a look at another Attiny sketch I have here that reads pins within an ISR, and " if ((PINB & 1 << GALA) != 0)" is of course right...

Still not really 100 percent sure what the "PINB &1" operator really means there... as I said, bit operations are still a bit sketchy for me... :frowning:

DrAzzy:
You need to keep track of what the current pin state is, so you can compare it to the new pin state in the ISR and determine which pin changed

Right now, as you can see, what I am trying to do is that I set a timestamp in the ISR if the pin change is a rising edge either on pin 3 or pin 4. Within loop(), I then continuously compare that timestamp to the last known timestamp, and if the timestamp is greater than the last known one (because a new rising edge has been detected and a new timestamp generated), I calculate the time difference.

Hello,

I have now ported all of the code to the Attiny45. It turns out that the 45 offers just about the right amount of memory resources and an 85 isn't necessary.

The pin change interrupts are now working without fail.

I've cooked the code down from the Atmega328 by about 50 percent and thrown out any and all redundant parts. The code is generally working, but I've still got one or two bugs that I haven't been able to iron out.

First, here's my latest schematic... I'm still open to ideas on how to improve it:

(click on image for larger view)

And here's the code... the comments should be enough to get an idea of what the code is supposed to do:

#include <avr/interrupt.h>

int i;
byte j = 0;

// PIN definitions

#define reverse 0
#define speaker 1
#define mute 2
#define GALA 3
#define volume 4

/*    GALA signal calculation variables

      GALA = Geschwindigkeitsabhaengige Lautstaerkeanpassung
      (speed sensitive volume adjust)
      The GALA signal is a +12V/0V square wave which basically
      gets shorter the faster you go. The radio uses that
      information to increase volume automatically at higher speeds */

unsigned long CycleSum = 0;
unsigned long CycleAverage = 0;

unsigned long CycleLength;
unsigned long time;
unsigned long timeMuteSustain;
unsigned long timeMuteSustainNew;

volatile unsigned long risingTimestampGALA = 0;
unsigned long risingTimestampGALAold = 0;

/*    Volume knob signal calculation variables

      The volume knob is a 5V two-phase rotary encoder.
      The muting device is connected to one of its
      terminals and uses rising edges from the encoder
      as a cue to deactivate the radio mute
*/

unsigned long timeVolume;
unsigned long timeNow;

unsigned long timeCutoff;
int VolumeCounter = 0;

boolean VolumeMuteOff = false;  // Tracks if mute has been deactivated with volume knob

// Mute Switching Routine

void MuteOnRoutine() {

  /*  250 ms delay to filter out transient +12V voltage spikes on reverse signal
      (reverse signal shares GND with rear lights on the car's rear lighting loom)
  */

  delay(250);

  if (digitalRead(reverse)) {

    // Mute-on only if not already in mute

    if (!(PINB & 1 << mute)) {

      digitalWrite(mute, HIGH);

      // wait 500 ms for radio to go into mute so beep can be heard

      delay(500);

      tone(speaker, 1200);
      delay(100);
      noTone(speaker);
    }
  }
}

// Switching back out of mute

void MuteOffRoutine() {

  // Mute-off only if not already off

  if ((PINB & 1 << mute)) {

    tone(speaker, 800);
    delay(100);
    noTone(speaker);
    digitalWrite(mute, LOW);
  }
}

/*    Volume Knob Check Routine
      This function counts rising edges from the
      rotary encoder volume knob. If 20 rising edges
      are detected within five seconds, the mute
      is deactivated. If you turn the knob too slowly,
      you basically have to start over. This is meant
      to keep the muting device from alerting on
      "accidental" volume knob turns.
*/

void checkVolumeKnob() {

  if (!VolumeMuteOff) {

    if (VolumeCounter == 0) {

      timeVolume = millis();
      VolumeCounter += 1;
    }

    else {

      timeNow = millis();
      timeCutoff = timeNow - timeVolume;
      VolumeCounter += 1;

      // Volume Knob-triggered Mute Off

      if (timeCutoff < 5000 && VolumeCounter >= 20) {

        if (CycleAverage > 400 || CycleAverage == 0) {

          VolumeMuteOff = true;
          MuteOffRoutine();
        }

        else VolumeMuteOff = false;

        VolumeCounter = 0;
        timeCutoff = 0;

        return;
      }

      // If you snooze, you lose...

      else if (timeCutoff > 5000) {

        VolumeMuteOff = false;

        VolumeCounter = 0;
        timeCutoff = 0;

        return;
      }
      return;
    }
  }
}

/*    Setting cycle average to a specific value
      This is necessary because at standstill,
      speed signal intervals are infinite
*/

int  SetCycleAverage(int setValue) {

  // Checking When GALA Rising Last Occurred

  if (CycleAverage != setValue && CycleAverage > 45) {

    unsigned long GALAtimeNow = millis();

    unsigned long LastGALAtimeDifference = GALAtimeNow - risingTimestampGALA;

    if (LastGALAtimeDifference >= setValue) {

      CycleAverage = setValue;
      CycleSum = 0;
      j = 1;
    }
  }
}

void setup() {

  // Setting up pin modes

  pinMode(reverse, INPUT);
  pinMode(speaker, OUTPUT);
  pinMode(mute, OUTPUT);
  pinMode(GALA, INPUT);
  pinMode(volume, INPUT);

  // Setting Interrupt Registers

  GIMSK = 0b00100000;    // turns on pin change interrupts
  PCMSK = 0b00011000;    // turn on interrupt on pins PB3 and PB4
  
  // Start-up beep

  tone(speaker, 1100);
  delay(100);
  noTone(speaker);
}

void loop() {

  /*    Calculating simple average GALA cycle length for every five rising edges
        I know there are much more refined ways of doing this, but
        for my purposes, this is good enough.
  */

  if (risingTimestampGALA > risingTimestampGALAold) {

    CycleLength = risingTimestampGALA - risingTimestampGALAold;

    risingTimestampGALAold = risingTimestampGALA;

    if (j < 5) {

      CycleSum += CycleLength;
      j += 1;
    }

    if (j == 5) {

      CycleAverage = CycleSum / 5;
      j = 1;
      CycleSum = 0;
    }
  }

  // Setting Cycle Average to 500 at slow speed

  SetCycleAverage(500);

  // Speed-sensitive Mute Delay

  if (!digitalRead(reverse) && (PINB & 1 << mute)) {

    i = 0;
    timeMuteSustain = millis();

    // 30-second mute sustain after gearshift is back out of reverse

    while (i <= 30000) {

      SetCycleAverage(500);    // Set cycle average to 500 if car is standing still

      timeMuteSustainNew = millis();
      i = timeMuteSustainNew - timeMuteSustain;

      /*    Cycle length of 37 ms equals 20 kph / 12.5 mph; at this forward speed,
            the mute deactivates itself within the 30 seconds.
      */

      if (!digitalRead(reverse) && CycleAverage < 37 && CycleAverage >= 1 && (PINB & 1 << mute)) {

        MuteOffRoutine();
        break;
      }

      // go back into reverse mode if new reverse is detected during the 30 seconds

      else if (digitalRead(reverse)) {

        MuteOnRoutine();
        break;
      }
    }

    // Switch off mute when 30 seconds are reached and mute is still active

    if (i >= 30000) MuteOffRoutine();
  }

  // Activate or Reactivate Mute in Reverse

  if (digitalRead(reverse) && !(PINB & 1 << mute)) {

    if (!VolumeMuteOff)  {

      MuteOnRoutine();
      SetCycleAverage(500);
    }

    else if (VolumeMuteOff && CycleAverage > 0 && CycleAverage < 450) {

      VolumeMuteOff = false;
      MuteOnRoutine();
    }
  }

  // Setting VolumeMuteOff to False when moving forward

  if (!digitalRead(reverse)) VolumeMuteOff = false;
}

// Pin change ISR for GALA signal and volume knob

ISR(PCINT0_vect) {

  if ((PINB & 1 << GALA))  risingTimestampGALA = millis();
  if ((PINB & 1 << volume) && (PINB & 1 << mute)) checkVolumeKnob();
}

Now, one of the problems I still have with this code is that when MuteOffRoutine() is triggered, it goes out of mute but no beep sound is heard. This problem occurs both when I deactivate the mute with the volume knob and when the mute deactivates itself at 20 kph / 12.5 mph. If it goes out of mute on its own after 30 seconds, however, you hear the desired beep.

I also had this problem on the Atmega328, but now that I am porting it to the Attiny45, I'd like to get rid of that bug.

I am using the Dr.Azzy core, so in general, the Attiny is capable of producing sound.

Also, I want to be able to deactivate the mute with the volume knob when the car is standing still in reverse. And then when it is detected that the car is moving backwards again, I want the mute to come back on. At the moment, this only works in forward gear. Meaning when I go out of reverse and I am in neutral or forward gear, I can deactivate the mute with the volume knob. But not when it's standing still in reverse.

I figure I have an error somewhere in terms of the state certain variables are in while in reverse gear, but haven't been able to put my finger on it.

(checkVolumeKnob() may be a huge chunk of code to be called right from the ISR, but it seems to run stable, and works even if you turn the volume knob very fast. I tried doing it differently, but that often made the Attiny miss rising edges from the volume knob)

I know it's a ton of info I am posting here, but maybe you guys could give me some pointers.

Thanks,

  • carguy.