Source file: /~heha/basteln/Haus/Telefon/Impulswahl→DTMF/mfv.zip/mfv2b/mfv2b.c

/* Programm für ATtiny25, „h#s“ Henrik Haftmann, TU Chemnitz
 * tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
 090331	Erster Anfang als mfv2.c
+1905xx	Wählen bei aufgelegtem Telefonhörer
+1905xx	Anrufzähler
*190605	Angleichung an mfv2c.c
*190613	Nochmals Angleichung an mfv2c.c, neue Funktionszuordnung
+190614	phoneParallel() — Tiefentladeschutz für Elko
-220421	Kurzen letzten nsi-Impuls detektieren (Österreicher Wählscheibe)
 */
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h> 
#include <util/delay.h>
#include <avr/fuse.h>
#include <avr/signature.h>

FUSES={
 0x5C,	// Taktteiler /8, kein Taktausgang, schnellster Startup, Resonator 3..8 MHz
 0x57,	// kein RESET, EEPROM-Inhalt behalten, kein Brownout
 0xFF,	// Keine Selbstprogrammierung
};	// {Startup: Reset = 272 CK + 64 ms (64068 µs), PowerDown = 258 CK (64 µs)}

/************
 * Hardware *
 ************/
/*
Anschlüsse:
PB0	(5)	-	Wählscheiben-Kontakte "nsi"+"nsa", 1 = unterbrochen
PB1	(6)	OC1A	PWM-Tonausgang, reichlich (125 kHz) Trägerfrequenz
PB2	(7)	ADC1	Amtsader La über Widerstand 680 kΩ
PB3	(2)	XIN	Resonator 4 MHz
PB4	(3)	XOUT	Resonator 4 MHz
PB5	(1)	!RESET	Erdtaste (optional) / Debug-Ausgang
GND	(4)		Telefonader b
Ucc	(8)		Amtsader Lb;Z-Diode 5,6 V und Elko ≥ 100 µF nach GND

Es ist schwieriger, einen Adapter zum Zwischenschalten
(ohne Modifikation des Telefonapparates) zu bauen.
Die Erdtaste ist optional.

Der EEPROM des AVR ermöglicht das Abspeichern von Nummern zur Kurzwahl.
Sonderfunktionen:
 LANG am Finger-Anschlag halten (bis zum ersten Piep):		  Speichermodus:
  1..9,0 = Kurzwahl							Kurzwahl
 LANG×2 am Finger-Anschlag halten (bis zum zweiten Piep):
  1..4 = "A".."D" wählen
  5 = Wahlwiederholung							Autowahl
  6 = Anrufliste / Letzte Rufnummer löschen (= Wahlwiederholung verhindern)
  7 = Speichern der zuletzt gewählten Rufnummer (Ziffer folgt)
  8 = Speichern einer neuen Rufnummer (Nummer folgt, dann LANG + Ziffer)
  9 = "*" wählen
  0 = "#" wählen

Die Autowahl = Wahl beim Abnehmen des Telefonhörers ist mit Vorsicht zu nutzen.
Sie kann nur unterbunden werden durch:
 - Wahl bei aufgelegtem Telefonhörer
 - Eingehender Telefonanruf (Klingeln)
 - Mindestens 1 entgangener Anruf

Wahlen aus dem Kurzwahlspeicher gehen nicht in die Wahlwiederholung ein.
D.h. der Wahlwiederholungsspeicher merkt sich nur explizit gewählte Nummern.

Folgende interne Peripherie wird verwendet:
Ports: 2 Eingabe mit Pull-Up, 1 Ausgabe via Hardware-PWM
Timer 0: Frequenzsynthese = Stetige PWM-Änderung für Timer 1
Timer 1: High-Speed-Timer mit 32-MHz-PLL: Hardware-PWM-Ausgabe, mono
Totzeitgenerator: ungenutzt
Taktgenerator: Keramikoszillator mit bei Bedarf zugeschalteter High-Speed-PLL
Power-Management: Power-Save (onhook), Idle (offhook), CPU-Taktdrosselung
Analog-Digital-Wandler: Für onhook/offhook/Klingel-Detektion
Analogvergleicher: ungenutzt
Interrupts: Zählerüberlauf Timer 0, Watchdog-Timer
EEPROM: Nummernspeicher für 10 Kurzwahlen und 1 Wahlwiederholung, 1 Autowahl,
feste Adressen
Flash-Selbstprogrammierung: ungenutzt
*/

// Signal-Zeiten in Sekunden
#define TON 0.14
#define PAUSE 0.06

typedef unsigned char byte;
typedef unsigned short word;
#define NOINIT __attribute__ ((section (".noinit")))

// Sinustabelle, Mittelwert und Amplitude = 73
static PROGMEM const byte SinTab[256]={
 73, 75, 77, 78, 80, 82, 84, 85, 87, 89, 91, 92, 94, 96, 98, 99,
101,103,104,106,107,109,111,112,114,115,116,118,119,121,122,123,
125,126,127,128,129,131,132,133,134,135,136,137,137,138,139,140,
140,141,142,142,143,143,144,144,145,145,145,145,146,146,146,146,
146,146,146,146,146,145,145,145,145,144,144,143,143,142,142,141,
140,140,139,138,137,137,136,135,134,133,132,131,129,128,127,126,
125,123,122,121,119,118,116,115,114,112,111,109,107,106,104,103,
101, 99, 98, 96, 94, 92, 91, 89, 87, 85, 84, 82, 80, 78, 77, 75,
 73, 71, 69, 68, 66, 64, 62, 61, 59, 57, 55, 54, 52, 50, 48, 47,
 45, 43, 42, 40, 39, 37, 35, 34, 32, 31, 30, 28, 27, 25, 24, 23,
 21, 20, 19, 18, 17, 15, 14, 13, 12, 11, 10,  9,  9,  8,  7,  6,
  6,  5,  4,  4,  3,  3,  2,  2,  1,  1,  1,  1,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  1,  1,  1,  1,  2,  2,  3,  3,  4,  4,  5,
  6,  6,  7,  8,  9,  9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20,
 21, 23, 24, 25, 27, 28, 30, 31, 32, 34, 35, 37, 39, 40, 42, 43,
 45, 47, 48, 50, 52, 54, 55, 57, 59, 61, 62, 64, 66, 68, 69, 71};

#if F_CPU < 6000000
# define DIVSH 7	// 128 Takte pro Timer-Interrupt
// Heruntergeteilter CPU-Takt = 24..48 kHz (Resonator 3..6 MHz)
#else
# define DIVSH 8	// 256 Takte pro Timer-Interrupt
// Heruntergeteilter CPU-Takt = 24..48 kHz (Resonator 6..12 MHz)
#endif

#define FREQ(x) ((x)*65536.0*(1<<DIVSH)/F_CPU+0.5)
// DDS-Inkremente = Additionswerte auf Phasenwert alle F_CPU/(1<<DIVSH)
static PROGMEM const word Frequencies[12] = {
 FREQ( 697),
 FREQ( 770),
 FREQ( 852),
 FREQ( 941),
 FREQ(1209),
 FREQ(1336),
 FREQ(1477),
 FREQ(1633),
 FREQ(1046),	//c³
 FREQ(1175),	//d³
 FREQ(1319),	//e³
 FREQ(1397),	//f³ (Halbtonschritt)
};

// Funktionsprinzip: DDS
volatile register word addA	asm("r8");
volatile register word phaA	asm("r10");	// hoher Ton
volatile register word addB	asm("r12");
volatile register word phaB	asm("r14");	// tiefer Ton

volatile register byte buf_r	asm("r7");	// Puffer-Lesezeiger
volatile register byte anrufe	asm("r6");	// Anrufzähler
volatile register byte nsi	asm("r5");	// Nummernschaltkontakt
volatile register byte pinb	asm("r4");
volatile register byte opinb	asm("r3");
volatile register byte Ton	asm("r2");	// Rest-Ton/Pausenlänge, in 16 ms
#define Flags GPIOR0		// Bits für NSK()
//	7	Watchdog-Interrupt: 16 ms vergangen
//	6	Anruf beginnt: Wenn's klingelt oder gewählt wird
//	4	Erdtaste gedrückt
//	3	8² = wahlfreie Nummer speichern
//	2	7² = letzte Nummer speichern
//	1:0	Ebenenzähler 0..2
#define wend  GPIOR1		// Wähl-Timeout (onhook)
#define Zeit8 GPIOR2		// Zeitzähler für nsi+nsa

static byte Zeit16;	// Zeitzähler zum Abspeichern der letzten Rufnummer (für Wahlwiederholung)

static char numbers[21];		// Puffer für Wählziffern
#define buf_w numbers[0]
// '*'=14, '#'=15, wie im EEPROM-Kurzwahlspeicher

// Puffer füllen
static void PutBuf(char c) {
 if (buf_w<20) numbers[1+buf_w++]=c;
}

// Puffer lesen, nicht aufrufen wenn leer!
// Aufruf mit freigegeben Interrupts (aus Hauptschleife) OK
static char GetBuf(void) {
 return numbers[1+buf_r++];
}

static byte x10(byte i) {
 i<<=1;		// × 2
 i+=i<<2;	// + × 4 = × 5
 return i;
}

/******************
 * EEPROM-Zugriff *
 ******************/

//Belegung des EEPROM:
//x00	12×10	Rufnummernspeicher, Index 0 = Letzte Rufnummer, 11 = Autowahl
//x78	4	frei
//x7C	1	Anzahl entgangener Anrufe
//x7D	1	Abhebezähler
//x7E	1	frei
//x7F	1	Reset-Zähler


static void eewait(void) {
 while (EECR&2);
}
static byte eeread(void) {
 EECR|=1;
 return EEDR;
}
static void eewrite(void) {
 cli();
 EECR|=4;
 EECR|=2;
 sei();
}
static void eechange(byte b) {
 byte k=eeread();
 if (k!=b) {
  if (b==0xFF) EECR|=0x10;	// erase only
  if (k==0xFF) EECR|=0x20;	// write only
  EEDR=b;
  eewrite();
  eewait();
  EECR=0;
 }
}

// 10 Bytes Puffer pro Nummer (20 Ziffern)
// idx zwischen 0 (letzte Nummer) und 11 (Autowahl)
static void KurzSpeichern(byte idx) {
 EEARL=x10(idx);	// × 10
// 1. Ziffernpuffer in ganzer Länge mit 0x0F auffüllen,
// mit dem Sonderfall '#' am Ende, dann 0x0D als 'falsches' Ende
 byte i=buf_w;
 char*z=numbers+1+i;
 if (i && i!=20 && z[-1]==0x0F) {*z++=0x0D; i++;}
 while (i!=20) {*z++=0x0F; i++;}
// 2. Komplette Nummer speichern (nur Änderungen)
 z=numbers+1;
 for (i=0;i<10;i++) {
  byte b=*z++<<4;	// direkt lesbar beim Lesen des EEPROMs
  b|=*z++;
  eechange(b);
  EEARL++;
 }
}

static void KurzLaden(byte idx) {
// 1. 20 Nibbles vom EEPROM laden
 EEARL=x10(idx);	// × 10
 byte i;
 char*z=numbers+1;
 for (i=0;i<20;i+=2) {
  byte b=eeread();
  *z++=b>>4;
  *z++=b&0x0F;
  EEARL++;
 }
// 2. <buf_w> ermitteln = tatsächliche Rufnummern-Länge
 for (;;) {
  byte b=*--z;
  if (b!=0x0F) {
   if (b==0x0D && i>=2 && *--z==0x0F) --i;
   break;
  }
  if (!--i) break;
 }
 buf_w=i;	// 0..20 möglich
 buf_r=0;	// mit dem Abspielen beginnen
}

/**************************************
 * Frequenzsynthese = Sinustonausgabe *
 **************************************/
// CLD2 = Clock Logarithmus Dualis
#if F_CPU>=8000000
# define CLD2 3	// ÷ 8 = 1..1,99 MHz
#elif F_CPU>=4000000
# define CLD2 2	// ÷ 4 = 1..1,99 MHz
#else
# define CLD2 1	// ÷ 2 = 1..1,99 MHz
#endif

void __attribute__((noinline)) clock_set(byte div) {
 cli();
 CLKPR = 0x80;
 CLKPR = div;
 sei();
}

static void clock_max(void) {clock_set(0);}
static void clock_1MHz(void) {clock_set(CLD2);}
static void clock_16us(void) {clock_set(CLD2+4);}

#define WDT_SEC(s) ((s)/0.016)

static void tonEin(void) {
 clock_max();
 PLLCSR|= 0x82;		// Low-Speed-Modus
 _delay_us(100);
 while (!(PLLCSR&1));
 PLLCSR|= 0x04;
 TCCR1  = 0x61;
 TCCR0B = 0x01;		// Timer0 mit Vorteiler 1 starten
 Ton=WDT_SEC(TON+PAUSE);// Länge setzen
 DDRB  |= 0x02;		// Ausgang aktivieren
}

static void tonAus(void) {
 DDRB  &=~0x02;		// Ausgang hochohmig
 TCCR0B = 0;		// Timer0 anhalten
 TCCR1  = 0;		// Timer1 aus
 PLLCSR = 0;		// PLL aus
 clock_1MHz();
}

// Startet Wählton für Ziffer z ("*"=14, "#"=15)
static void StartDTMF(char z) {
// z&=15;		// sollte nie vorkommen
 if (MCUCR&0x10) return;	// niemals im aufgelegten Zustand
 if (!(Flags&0x40)) {
  Flags|=0x40;
  if (anrufe) --anrufe;	// Anrufliste abbauen
 }
 static PROGMEM const byte Nr2HiLo[16]={
//   0    1    2    3    4    5    6    7    8    9    A    B    C    D    *    # 
  0x53,0x40,0x50,0x60,0x41,0x51,0x61,0x42,0x52,0x62,0x70,0x71,0x72,0x73,0x43,0x63};
// High-Nibble  4  5  6  7
// Low-Nibble ┌───────────
//	    0 │ 1  2  3  A
//	    1 │ 4  5  6  B
//	    2 │ 7  8  9  C
//	    3 │ *  0  #  D
 byte i=pgm_read_byte(Nr2HiLo+z);		// umrechnen in die 2 Frequenzen
 addA=pgm_read_word(Frequencies+(i>>4));	// hoher Ton im High-Nibble
 addB=pgm_read_word(Frequencies+(i&15));	// tiefer Ton im Low-Nibble
 tonEin();
}

// Startet Hinweiston, z=0 für c³
static void StartTon(char z) {
 z&=3;
 if (MCUCR&0x10) return;	// niemals im aufgelegten Zustand
 addB=addA=pgm_read_word(Frequencies+8+z);
 phaB=phaA;			// addieren lassen
 tonEin();
}

// Kernroutine der Frequenzsynthese
// Aufruf mit F_CPU/256 oder F_CPU/128, 20..40 kHz, reicht für HiFi-Ton
// Mit F_CPU/64 kommt der Controller nicht mehr hinterher,
// obwohl noch Rechenleistung frei sein müsste.
ISR(TIM0_COMPA_vect) {
// Tonlänge nominell 70 ms, Pause 30 ms
// Ton>=0 = Tonausgabe, sonst Pause
// if (Ton>=(byte)WDT_SEC(PAUSE)) {
#if 0
  byte a=pgm_read_byte(SinTab+((phaA+=addA)>>8));
  byte b=pgm_read_byte(SinTab+((phaB+=addB)>>8));
  OCR1A=a+b-(b>>2);		// hier ohne Runden
#else
  asm(
"	add	r10,r8	\n"
"	adc	r11,r9	\n"
"	add	%A1,r11	\n"
"	adc	%B1,r1	\n"
"	lpm		\n"
"	sub	%A1,r11	\n"	// ZH:ZL wiederherstellen
"	sbc	%B1,r1	\n"
"	add	r14,r12	\n"
"	adc	r15,r13	\n"
"	add	%A1,r15	\n"
"	adc	%B1,r1	\n"
"	lpm	r1,z	\n"	// gcc compiliert fäschlicherweise "lpm r1,Z+"
"	add	r0,r1	\n"
"	lsr	r1	\n"	// (kompensiert Kabeldämpfung für höhere Frequenzen)
"	lsr	r1	\n"
"	sbc	r0,r1	\n"	// "sbc" ist der Trick fürs Abrunden
"	clr	r1	\n"	// __zero_reg__ wiederherstellen
"	out	%0,r0	\n"
  ::"I" (_SFR_IO_ADDR(OCR1A)),"z"(SinTab):"1");
#endif
// }
}

/***********************************
 * Nummernschaltkontakt-Auswertung *
 ***********************************/
// An PB0, Aufruf alle 16 ms (Watchdog)
static void NSK(void) {
 Flags&=~0x80;
 if (pinb&1 && !(opinb&1)) {	// Steigende Flanke: nsa losgelassen oder nsi unterbrochen
  ++nsi;		// Lo-Hi-Flanken zählen
  GIFR=0x20;		// Erkannte Pegelwechsel quittieren
  wend=0xFF;		// Globales Wähl-Timeout setzen
  Zeit8=WDT_SEC(0.2);	// zur Feststellung ob nsa oder nsi
 }else if (!(pinb&1) && opinb&1) {	// Fallende Flanke
  if (nsi) Zeit8=0;	// Kein Timeout wenn Lo nach 1. nsi
  else{
   Flags&=~1;		// Etappenzähler für „nsa halten“ rücksetzen
   Flags&=~2;
   Zeit8=WDT_SEC(1.5);
  }
 }else{			// keine Flanke
  if (Zeit8 && !--Zeit8) {	// Timeout
   if (nsi) {		// Nummernscheibe abgelaufen (nur bei Hi-Pegel)
    if (!(GIFR&0x20)	// Kein < 16 ms kurzes letzes („übersehenes“) Low?
     && !--nsi) {	// Letzte Lo-Hi-Flanke durch nsa-Kontakt abziehen
     StartTon(-2);	// Fehler: Nur 1 langes Low detektiert: Wählscheibe weniger als 30° aufgezogen = Fehlbedienung
     goto raus;		// Oder soll man das als Sonderfunktion interpretieren?
    }
    if (nsi>10) {	// Zu viele Impulse?
     StartTon(-3);	// Fehler: Mehr als 10 Impulse, Sonderwählscheibe?
     goto raus;
    }
// Hier muss nsi für dänische oder schwedische Telefone angepasst werden
// Eigentliche Aktion
    if (Flags&0x10) {
     Flags&=~0x10;
     goto action2;
    }
    if (Flags&8 && !(Flags&1) && !(Flags&2)) goto save;
    if (Flags&8 || Flags&4) {	// nsi = Ziffer zum Kurzwahl-Speichern
     if (Flags&2) {		// Zweite Ebene
      if (nsi==5) nsi=11;	// Autowahl (11) setzen
      else if (nsi<=4) nsi+=10-1;	// 'A' bis 'D' einschreiben
      else if (nsi>=9) nsi+=14-9;	// '*' oder '#' einschreiben
      else nsi=0;		// ignorieren
     }
     if (nsi) {
      KurzSpeichern(nsi);
      StartTon(3);
     }
     Flags&=~0x1F;
     goto raus;		// nicht wählen
    }else if (Flags&2) switch (nsi) {	// LANG×2 = Ganz langes Halten am Fingeranschlag
action2:
     case 5: KurzLaden(0); goto raus;		// Wahlwiederholung
     case 6: if (anrufe) anrufe=0;		// Anrufzähler löschen
     else {buf_r=buf_w=0; KurzSpeichern(0);} goto raus; // Wahlwiederholung verhindern
     case 7: Flags|=4; StartTon(3); goto raus;	// Letzte Nummer speichern: Ziffer folgt
     case 8: Flags|=8; buf_r=buf_w=0; StartTon(0); goto raus;	// Nummer einspeichern: Nummer + Ziffer folgen
     case 9:
     case 10: nsi+=14-9; goto save1;	// *,# wählen
     default: nsi+=10-1; goto save1;	// A,B,C,D wählen
    }else if (Flags&1) {		// LANG
     KurzLaden(nsi);		// wählen lassen (1..10), Hauptschleife generiert Töne
     goto raus;
    }else save: if (nsi==10) nsi=0;
save1: PutBuf(nsi);	// Zählergebnis abspeichern (Hauptschleife generiert Ton anschließend)
    if (Flags&8) {
     buf_r=buf_w;	// Nicht wählen (Hauptschleife nicht in Aktion treten lassen)
     StartTon(2);
    }else Zeit16=WDT_SEC(2);	// Wahlwiederholungs-Abspeicher-TimeOut setzen (2 Sekunden)
raus:
    nsi=0;
   }else if (!(pinb&1)) {	// Vor nsi-Impulsen (Lo-Pegel)
    if (!(Flags&2)) ++Flags;	// Zu lange gehalten: ignorieren, weiterpiepen
    StartTon((Flags&3)-1);	// Ton 0 oder 1
    Zeit8=WDT_SEC(1);
   }
  }
 }
 if (!Ton && !(pinb&0x20) && opinb&0x20) {	// Erdtaste gedrückt (simple Entprellung)
  Flags|= 0x10;			// Ebene 2 ansteuern
 }else if (pinb&0x20 && !(opinb&0x20) && Flags&0x10) {
  Flags&=~0x1F;
  KurzLaden(0);
 }
}

ISR(WDT_vect,ISR_NAKED) {
 Flags|=0x80;
 reti();
}

static byte offhooklevel=0xC0;	// High bei 75 %

// Wartet bei laufendem Oszillator in Schritten von 48 µs (max. 12 ms)
// oder per Watchdog 16 ms wenn <us48> = 0
// Die kürzere Zeit ist für sichere Klingel-Detektion erforderlich (?)
static void waitforwatchdog(byte us48) {
 opinb = pinb;		// PB0 einlesen, nachdem sich die pegel eingeschwungen haben
 pinb  = PINB;
 while (!(ADCSRA&0x10));	// warten auf Ende
 if (ADCH>=offhooklevel) pinb|=4;	// High bei 75 %
 ADCSRA= 0;		// ADC abschalten — sonst gibt's zusätzlichen Stromverbrauch.
 PORTB = 0;		// kein Pullup: Versorgungsstrom 120 µA fließt über die negative Gateschutzdiode
 DDRB  = 0x25;		// Floating-Eingänge auf Low festnageln, um Gateschutzdioden zu schonen
 if (us48) {
  clock_16us();
  _delay_loop_1(us48);
  DDRB &=~0x04;
  PORTB|= 0x04;
 }else{
  clock_max();		// kürzestmögliche Hochlaufzeit
  sleep_cpu();		// Oszillator aus bis zum nächsten Watchdog-Interrupt
  DDRB &=~0x04;		// dann umgehend Pin 7 zur Messung vorbereiten
  PORTB|= 0x04;
  clock_16us();		// 16 µs pro Takt, gaanz langsam (4 × 16 µs)
 }
// Die Unterprogrammaufrufe genügen zur Verzögerung.
// Warten mit dem A/D-Konverter, bis sich der Pegel an Pin 7 stabilisiert hat.
// Der Kondensator (der für mfv2b.c gar nicht notwendig ist) wirkt als Tiefpass.
 clock_1MHz();		// (10 × 16 µs)
 DDRB  = 0;
 PORTB = 0x25;		// Pullups aktivieren
 ADCSRA= 0xD1;		// Start mit Teiler 2: 500 kHz, 2µs, ×25 = 50 µs
 if (Flags&0x80) NSK();// Man kann noch während des Klingelns wählen :-)
}

/************************************
 * Routinen für aufgelegten Zustand *
 ************************************/

// Handelt es sich beim Pegel an PB2 um Gleichspannung (permanent High oder auch Low)
// oder Klingelwechselspannung 25 Hz / 50 Hz?
// Routine kehrt erst nach Ende des Klingelns zurück.
// Liefert Anzahl detektierter Schwingungen.
// Kann sein, dass sich statisches High durch ein parallel geschaltetes und abgehobenes Telefon ergibt,
// das wird in offhook() verarbeitet.
static byte wait_ringend(void) {
 byte i=0,j=0;	// geht mit PB2 = High in die Routine, d.h. opinb.2 ist nach waitforwatchdog() = 1
 for(;;){
  waitforwatchdog(100);	// 5 ms
  if (pinb&4 && !(opinb&4)) {	// (Erneuter) Lo-Hi-Wechsel
   ++j; i=0;		// Diese Flanken zählen
  }else if (!(pinb&4) && opinb&4) {	// Hi-Lo-Wechsel
   i=0;			// sollte der Compiler wegoptimieren
  }else{		// gleich geblieben
   if (++i==10) break;	// 16 ms × 10 = 160 ms ist ungefähr die Zeit, die das Kabelmodem braucht, um das Freizeichen aufzuschalten
  }
 }
 return j;
}

static void onhook(void) {
 MCUCR = 0xB4;
 MCUCR = 0xB0;		// Sleep-Modus: PowerDown, kein BrownOut
 ADMUX = 0x21;		// ADLAR, ADC1 = PB2
 ADCSRA= 0xD1;		// einmalig (wird in waitforwatchdog erneut gestartet)
 Flags&=~0x40;		// Aktiven Anruf beenden
 buf_w = buf_r = 0;	// Rufnummer tilgen
 byte rend=0;		// Klingel-Timeout, zum Zählen verpasster Anrufe
 do{
  do{
   waitforwatchdog(0);
   if (rend && !--rend && Flags&0x40) {
    Flags&=~0x40;
    EEARL=252;
    eechange(++anrufe);	// Anruf verpasst
   }
   if (wend && !--wend) buf_w=0;	// Timeout für's Wählen bei aufgelegtem Telefonhörer
  }while (!(pinb&4));	// Bit 2 = Schaltschwelle 75%
  byte j=wait_ringend();	// kehrt erst nach 160 ms Ruhe zurück
  if (j>=4) {
   Flags|=0x40;
   rend=WDT_SEC(4);	// 4 Sekunden nach dem letzten Klingeln sei ein (verpasster) Anruf zu Ende
   continue;
  }
 }while (!(pinb&4));	// In der Schleife bleiben bis abgehoben wurde (PB2 = High)
}

/************************************
 * Routinen für abgehobenen Zustand *
 ************************************/

// Testen (mit dem A/D-Wandler) ob das High an Pin 7 mit einer
// steigenden Betriebsspannung einhergeht.
// Wenn nicht ist es ein parallel geschaltetes Telefon oder ein totes Amt;
// dann sollte mit waitforwatchdog mit ganz langer Watchdog-Spanne
// auf das Ende dieses irregulären Zustandes gewartet werden,
// um den Elko möglichst nicht zu entladen.
// Denn wenn dieser erst mal leer ist, kann der Mikrocontroller nicht mehr
// hochlaufen und bspw. Anrufe zählen.

static byte adPin7 NOINIT;
static byte adRef NOINIT;

static void phoneParallel(void) {
 tonAus();
 PORTB = 0;	// Keine ohmschen Verbraucher
 DDRB  = 0x25;
 do{
  ADCSRA= 0;	// ADC aus
  WDTCR = 0x61;	// 8 s
  MCUCR = 0xB4;
  MCUCR = 0xB0;	// Sleep-Modus: PowerDown, kein BrownOut
  sleep_cpu();	// bis zum Watchdog
  ADCSRA= 0xF4;	// Start mit Teiler 16: 16 µs, ×13 = 208 µs
  WDTCR = 0x40;	// 16 ms (für den ADC wird offenbar 1 ms benötigt!! Sonst kommt Murks weil Referenzspannung falsch.)
  MCUCR = 0x28;	// ADC-Störunterdrückung
  sleep_cpu();	// bis zum Watchdog
 }while (ADCH>=0x3E);	// 0x3E: Hysterese von 2 (gegenüber 0x40)
}

static void handleADC(void) {
 switch (ADMUX) {
  case 0x21: {
   adPin7 = ADCH;
   ADMUX  = 0x2C;
  }break;
  case 0x2C: {
   byte b = adRef;
   byte a = ADCH;
   adRef  = a;
   if (a>=0x40 && a>b) {	// Ucc < 4,4 V und fallend
    phoneParallel();
   }
   ADMUX = 0x21;
  }break;
  default: ADMUX = 0x21;
 }
}

static byte pause,index;	// nur offhook verwendet!

static void piepAnrufe(void) {
 if (Flags&0x40) return;	// Anruf wurde entgegen genommen: Nichts tun
 if (!anrufe) return;	// Keine Anrufe seit letztem Auflegen: Nichts tun
 if (nsi) return;
 if (Zeit8) return;
 if (buf_w) return;
 if (--pause) return;	// zunächst 256 Aufrufe nichtstun, zwischen den Pieptönen weniger
 StartTon(1);		// Anzahl Anrufe melden
 if (++index>=anrufe>>1) index=0;
 else pause=WDT_SEC(0.5);
}

static void offhook(void) {
 EEARL = 253;
 eechange(eeread()+1);	// Abhebe-Zähler
 MCUCR = 0x20;		// Sleep-Modus: Idle (für Timer, Strom ist genug da)
 adRef = 0xFF;
 ADCSRA= 0xF4;		// /16 = 62 kHz / 25 = 2,5 kSa/s
 byte no_dial=WDT_SEC(1);	// besser: auf Freizeichen warten
 //Bei Freizeichen müsste <anrufe> weitergezählt werden, wenn Flags.6 gesetzt!!
 pause=WDT_SEC(1);
 index=0;
 for(;;) {		// Hauptschleife
  sleep_cpu();
  if (Flags&0x80) {
   EEARL=252;
   eechange(anrufe);
   handleADC();		// Auch den A/D-Wandler per Watchdog-Timer abfragen.
// Sonst funktioniert der Vergleich mit der Referenzspannung nicht!!
   if (DDRB&0x40) break;	// wenn handleADC() phoneParallel() festgestellt hatte, dann raus hier
   pinb = PINB;
   if (adPin7>=offhooklevel) pinb|=4;	// High bei 75 %
   if (!(pinb&4) && !(opinb&4)) break;	// Low durch Auflegen
   if (Ton) {
    if (--Ton==(byte)WDT_SEC(PAUSE)-1) tonAus();	// Tonlänge reduzieren
   }
   if (no_dial && !--no_dial && !buf_w && !(Flags&0x40) && !anrufe) {
    KurzLaden(11);	// Automatische Wahl beim Abheben
   }
   NSK();			// Nummernschaltkontakt-Auswertung
   if (Zeit16 && !--Zeit16) KurzSpeichern(0);
   piepAnrufe();
   opinb = pinb;
  }
  if (Ton) continue;		// Ton- oder Pausenausgabe läuft
  if (!no_dial && buf_r!=buf_w) StartDTMF(GetBuf());	// Nächsten Ton + Pause ausgeben
 }
 tonAus();
}

/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/

static void hardwareInit(void) {
 Ton   = 0;
 WDTCR = 0x40;		// Watchdog auf Interrupt, 16 ms = 62 Hz (einzige Interruptquelle)
 PORTB = 0x25;		// Pullup für 3 Eingänge
 eewait();
 EEARL = 255;
 EEARH = 0;		// Auch für ATtiny25/45 so compilieren für Portierbarkeit; EEARH ist nach Reset uninitialisiert
 eechange(eeread()+1);	// Reset-Zähler
 EEARL = 252;
 byte a=eeread();
 if (a>32) a=0;
 anrufe= a;
 ACSR |= 0x80;		// Analogvergleicher ausschalten
 DIDR0 = 0x1E;		// diese digitalen Eingänge nicht nutzen
 TCCR0A= 0x02;		// Timer0: CTC
 OCR0A = (1<<DIVSH)-1;	// rund 32 kHz
 TIMSK = 0x10;		// Compare-Interrupt
 PCMSK = 0x01;		// Nur PB0 = Wählscheibe überwachen (ohne Interrupt)
 buf_r = 0;		// alle übrigen Register nullsetzen
 Flags = 0;
 nsi   = 0;
 Zeit8 = 0;
 wend  = 0;
 opinb = pinb = PINB;
}

int __attribute__((noreturn)) main(void) {
 hardwareInit();
 for(;;) {
  onhook();
  offhook();
 }
}
Detected encoding: UTF-80