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,