Source file: /~heha/basteln/Haus/Telefon/CLIP-Anzeige/clip.zip/ra.cpp

/* Rufnummern-Anzeige:
   CLIP-Dekoder (Anzeige Rufnummer des Anrufenden) und Telefonbuch

Hardware: (1) ATtiny2313/4313A			1,60€ (2017)
256 Byte EEPROM (≈10 Telefonbucheinträge) oder im Flash
Hat serielle Schnittstelle und mehr I/O aber keinen ADC; elend wenig EEPROM
5	PA0	XTAL1	Quarz 10..20 MHz
4	PA1	XTAL2	Quarz 10..20 MHz
1	PA2	RESET
12	PB0	AIN0	a-Ader vom Amt (kapazitiv)
13	PB1	AIN1	b-Ader vom Amt (kapazitiv)
14	PB2		LCD RS
15	PB3	OC1A	Tonwahl
16	PB4		LCD D4
17	PB5		LCD D5	Taste?
18	PB6		LCD D6	Taste-
19	PB7		LCD D7	Taste+
2	PD0	RxD	PC-Kommunikation?
3	PD1	TxD	PC-Kommunikation?
6	PD2	INT0	Schleifenstrom
7	PD3		nsi
8	PD4		nsa
9	PD5		Erde
11	PD6		LCD E
10	GND
20	Ucc

Hardware: (2) ATtiny261/461/861A		1,75€ (2017)
512 Byte EEPROM (≈ 20 Telefonbucheinträge) oder im Flash
Hat 64-MHz-Timer und A/D-Wandler
20	PA0		LCD D4		nsi
19	PA1		LCD D5		nsa
18	PA2		LCD D6		Erde
17	PA3		LCD D7		Taste
14	PA4		LCD E
13	PA5		LCD RS (nicht mit Telefon zusammenlegen, Display nimmt irgendwie Schaden: Plötzlich Pulldown-Widerstand)
12	PA6	AIN0	a vom Amt (kapazitiv)
11	PA7	AIN1	b = Ucc (kapazitiv)
1	PB0				Debug
2	PB1	OC1A	PWM für Inverter
3	PB2		USB D-
4	PB3	OC1B	Tonwahl		Debug
7	PB4	XTAL1	Quarz 16 MHz
8	PB5	XTAL2	Quarz 16 MHz
9	PB6	INT0	a vom Amt (resistiv 200 kΩ)
10	PB7	(RESET)	Taste?		Debug
6,16	GND
5,15	Ucc

Hardware: (3) ATtiny24/44/84
Zu wenig Pins, kein High-Speed-Timer
1	Ucc
2	PB0	XTAL1	Quarz 16 MHz
3	PB1	XTAL2	Quarz 16 MHz
4	PB3	RESET			
5	PB2		LCD E		
6	PA7		LCD D7		Taste
7	PA6	OC1A	Tonwahl
8	PA5		LCD D6		Erde
9	PA4		LCD D5		nsa
10	PA3		LCD D4		nsi
11	PA2	AIN1	b vom Amt (kapazitiv)
12	PA1	AIN0	a vom Amt (kapazitiv)
13	PA0	ADC0	LCD RS, a vom Amt (resistiv)
14	GND

Hardware: (4) ATmega32U4 auf Pro Micro		< 5 US$ (2017)
1 KByte EEPROM (≈ 40 Telefonbucheinträge) oder im Flash
Hat 64-MHz-Timer, A/D-Wandler, MUL-Befehl und USB
Pin	Port	Funk.	Proµ	Ard.µ	Leon.
8	PB0	TxLed	-	ja	-
9	PB1	(SCLK)	15	ja	ICSP	LCD E
10	PB2	(MOSI)	16	ja	ICSP	LCD RS	nsa
11	PB3	(MISO)	14	ja	ICSP
28	PB4		8	8	8
29	PB5		9	9	#9
30	PB6	OC4B	10	10	#10	Tonwahl
12	PB7		-	11	#11
31	PC6		5	5	#5
32	PC7		-	13	#13
18	PD0		3	3	#3+SCL	LCD D4	Taste+
19	PD1		2	2	2+SDA	LCD D5	Taste-
20	PD2	(RxD)	Rx1	Rx	0RX	LCD D6	Taste?
21	PD3	(TxD)	Tx0	Tx	1TX	LCD D7	Taste?
25	PD4		4	4	4	LCD RS
22	PD5	RxLed	-	-	-
26	PD6		-	12	12
27	PD7		6	6	#6	LCD E
33	PE2	(HWB)	-	-	-
1	PE6	AIN0	7	7	7	b-Ader vom Amt (kapazitiv)
41	PF0	ADC0	-	A5	A5	
40	PF1	ADC1	-	A4	A4	b-Ader vom Amt (kapazitiv)*
39	PF4	ADC4	A3	A3	A3	a-Ader vom Amt (kapazitiv)
38	PF5	ADC5	A2	A2	A2	a-Ader vom Amt (resistiv)
37	PF6	(ADC6)	A1	A1	A1	
36	PF7	(ADC7)	A0	A0	A0	
42	AREF		-	?	AREF
13	RESET		RST	RST	RST+ICSP
* Zur differenziellen A/D-Umsetzung muss am Arduino Micro ADC1 herausgeführt werden.
Also Drähtchen anlöten oder (größeren) Arduino Mini verwenden.
Notfallvariante: Umsetzung Single-ended an ADC4

Hardware: (5) ATmega328 auf Arduino Uno
DIP28	Port	Funk.	Uno	Anschluss
14	PB0	ICP1		USB D- (ohne Arduino)
15	PB1	(OC1A)		USB D+ (ohne Arduino)
16	PB2	OC1B		Tonwahl
17	PB3	OC2		PWM für Inverter
18	PB4			LCD RS
19	PB5			LCD E
9	PB6	XTAL1	-	Quarz 16 MHz (oder Uhrenquarz?)
10	PB7	XTAL2	-	Quarz 16 MHz
23	PC0		A0	LCD D4
24	PC1		A1	LCD D5
25	PC2		A2	LCD D6
26	PC3		A3	LCD D7
27	PC4	ADC4	A4	a vom Amt (resistiv)
28	PC5	ADC5	A5	a vom Amt (kapazitiv)
1	PC6	!RESET	RES	-
2	PD0	RxD	Rx	(USB-Interface)
3	PD1	TxD	Tx	(USB-Interface)
4	PD2	(INT0)		nsi
5	PD3	(INT1)		nsa
6	PD4			Erde
11	PD5			Taste
12	PD6	AIN0		a vom Amt (kapazitiv)
13	PD7	AIN1		b vom Amt (kapazitiv)
8,22	GND
7,20	Ucc			b vom Amt
21	Aref

Vergleich der Mikrocontroller bei Analogvergleich und A/D-Wandler
(Alle Mikrocontroller mit Quarzoszillator mindestens 16 MHz)
	AC+	AC-	ADC+	ADC+2	ADC-	PWM	Bits	eep(k)
tn2313	0	1	-	-	-	OC1A	16	⅛¼	Kein ADC!
tn84	0=ADC1	1=ADC2	ADC2	ADC3	ADC1	OC1A	16	⅛¼ ½
tn841	00=ADC5	01=ADC6	ADC6	ADC4	ADC5	OC1A	16	¼  ½
tn85	0	1,ADC1	ADC1	ADC2	ADC0	OC1A	8 HS	⅛¼ ½	zu wenig Pins!
tn861	0=ADC5	1=ADC6	ADC6	ADC4	ADC5	OC1A	10 HS	⅛¼ ½
m8	0	1,ADC0	ADC0	ADC1	-	OC1A	16	½
m328	0	1,ADC0	ADC0	ADC1	-	OC1A	16	1
m32u4	0	1,ADC11	ADC11	ADC12	ADC0	OC4B	10 HS	1

EEPROM-Haushalt: Zumeist stehen 512 Byte zur Verfügung:
- 10 Kurzwahl-Nummern („Favoriten“)	110
- 10 zuletzt gerufene Nummern		110
- 10 entgegengenommene Anrufe		140	mit 3-Byte-Uhrzeit
- 10 entgangene Anrufe			140	mit 3-Byte-Uhrzeit
- 3 rollende Indizes, aktuelles Jahr	2
- 5 Indizes für die Navigation		3
				Summe	505
- Telefonbuch?
	Im Flash! Aufbau: pstring(4) Nummer, pstring(8) Name
	Namenskodierung (Zeichensatz) = HD44780-ASCII
*/
#include "ra.h"
#include "LCD_44780.h"
#include <util/delay.h>
#include <avr/fuse.h>
#include <avr/signature.h>	// gepatcht: "const" weg!
#include <string.h>	// memset
#include <stdio.h>
#include <stdlib.h>	// utoa

#ifdef __AVR_ATtiny861__
FUSES={
 0x7F,		// Quarzoszillator, lange Hochlaufzeit
 0x57,		// EEPROM-Inhalt behalten, kein RESET, Brownout bei 1,8 V
 0xFE		// Selbstprogrammierung aktivieren (langes Telefonbuch)
};
#endif

/*****************************
 * Hauptteil: CLIP-Erkennung *
 *****************************/

CLIP clip;

ISR(PCINT_vect,ISR_NAKED) {
 GPIOR0|=0x04;
 reti();
}

ISR(TIMER0_OVF_vect,ISR_NAKED) {
 GPIOR0|=0x02;
 reti();
}

/**************************
 * Bonbon: DTMF-Erkennung *
 **************************/
// Das wesentliche, die ISR, steckt in „dtmf.S“

#define SLOTS 8	// Suchfrequenzen

struct DTMF{
 struct{
  byte iwnd;	// Index für Fenster
  int s;	// Summe (Gleichspannung, Offset)
  byte sq[3];	// Summe der Quadrate
 }rms[WINDOWS];	// Effektivwert-Quadrat = Leistung
 struct g{
  word add;	// Addierwert zur Vergleichsfrequenzsynthese
  word pha;	// Aktueller Phasenwinkel, High-Teil = Index in Sinustabelle
  struct{
   byte re[3];	// Realteil der Aufsummierung (Kosinus, hier: Sinus)
   byte im[3];	// Imaginärteil der Aufsummierung (Sinus, hier: -Kosinus)
  }sum[WINDOWS];// mit MUL-Befehl: 2 verschachtelte Fenster
 }f[SLOTS];	// Suchfrequenzen
 void init();
 static void done();
 static void show();
}kdata;

void DTMF::init() {
#define FREQ(x) x*65536/(F_ADC)
 static const PROGMEM word tab[]={
  FREQ(697),
  FREQ(770),
  FREQ(852),
  FREQ(941),
  FREQ(1209),
  FREQ(1336),
  FREQ(1477),
  FREQ(1633),
 };
#undef FREQ
 for (int i=0;i<8;i++) f[i].add=pgm_read_word(tab+i);
 extern const void*wintab;
 rms[0].iwnd=(byte)(word)wintab;
#if WINDOWS==2
 rms[1].iwnd=(iwnd[0]+256)>>1;
#endif
 ADMUX = 0xB1;	// ADC6-ADC5, links ausgerichtet
 ADCSRB= 0x88;	// differenziell, 1,1 V Referenz, nicht an Aref
 ADCSRA= 0xFF;	// Start mit Interrupts
}

void DTMF::done() {
 ADCSRA=0;	// A/D-Wandler aus
}

word kresult[10];	// 8×Spektralwert, Summe der Quadrate, Summe

//========================================== BASIC ROUTINES ================================
const char PROGMEM charmap[8*8]={
 0b10101,0b01010,0b10001,0b10001,0b11111,0b10001,0b10001,0b00000,	// Ä
 0b10001,0b00000,0b10001,0b10001,0b10001,0b10001,0b01110,0b00000,	// Ü
 0b01110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110,0b00000,	// ß
 0b00000,0b00000,0b01111,0b10001,0b10001,0b01111,0b00001,0b01110,	// g
 0b00010,0b00000,0b00110,0b00010,0b00010,0b00010,0b10010,0b01100,	// j
 0b00000,0b00000,0b10001,0b10001,0b10001,0b01111,0b00001,0b01110,	// y
};

byte CLIP::sum() {
 byte l=clip.data[1]+3;
 if (cnt<l) return 0xFF;	// Zu wenig Datenbytes (zu viel gibt's nicht)
 byte sum=0;
 for (byte i=0;i<l;i++) sum+=clip.data[i];
 return sum;
}

char*CLIP::findChunk(char chunk) {
 char*p=clip.data;
 if (*p++!=-128) return 0;
 char l=*p++;
 while (l) {
  char ll=p[1]+2;	// Lokal-Länge
  if (ll>l) return 0;	// Innerer Block zu groß
  if (*p==chunk) return p;	// Zeiger auf <chunk>
  l-=ll;
  p+=ll;
 }
 return 0;
}

/*
static void hexout(byte b) {
 outnibble(b>>4);
 outnibble(b&15);
}
*/
void CLIP::show() {
 lcd::clear();
 static char s[20];
 char*u=clip.findChunk(1);
 if (u) {
  char*sp=s;
  *sp++=u[4];
  *sp++=u[5];
  *sp++='.';
  *sp++=u[2];
  *sp++=u[3];
  *sp++='.';
  *sp++=' ';
  *sp++=u[6];
  *sp++=u[7];
  *sp++=':';
  *sp++=u[8];
  *sp++=u[9];
  *sp=0;
 }else strcpy_P(s,PSTR("ohne Uhrzeit"));
 lcd::gotoxy(4,0);
 lcd::write(s);
 lcd::gotoxy(0,1);
 if (u=clip.findChunk(2)) {
  byte l=(byte)u[1];
  memcpy(s,u+2,l);	// TODO: Im Telefonbuch nachschlagen
  s[l]=0;
 }else if (u=clip.findChunk(4)) {
  switch (u[2]) {
   case 'P': strcpy_P(s,PSTR("unterdr"ue"ckt")); break;	//80 - unlisted number (caller is protecting his privacy)
   case 'O': strcpy_P(s,PSTR("unvermittelt")); break;	//79 - unapproachable number (caller uses analogue switchboard)
  }
 }else strcpy_P(s,PSTR("ohne Nummer"));
 lcd::write(s);
 if (clip.sum()) lcd::write('?');
#if 0
 if (clip.sum()) {
  for(byte i=0;i<8;i++)hexout(clip.data[i]);
  lcd::gotoxy(0,1);
  for(byte i=8;i<15;i++)hexout(clip.data[i]);
  hexout(cnt);
 }
#endif
}

#define OFFHOOK (PINB&0x40)	// abgehoben oder Rufwechselspannung

// Flanken an PB6 innerhalb 100 ms zählen
// 0 = Ruhe, 2 = Flash / Hook-Flash oder Wählimpuls, ≥4 = Klingeln
static byte edges() { 
 TCNT0L = 0/*256-F_CPU/8/1024/10*/;	// = 60 @ 16 MHz
 TCCR0B = 0x05;		// Vorteiler 1024: Messumfang 100 ms ≙ 10 Hz
 GPIOR0&=~0x02;
 byte e = 0;		// Flankenzähler
 byte k = OFFHOOK;
 do{			// max. 100 ms
  sleep_cpu();		// Pegelwechsel und Timer weckt auf
  byte x=k^OFFHOOK;
  if (x) ++e;
  k^=x;
 }while (!(GPIOR0&0x02));
 return e;
}

// Wartet auf das Ende der Klingel-Wechselspannung, mindestens 50 ms
// Der Timer0 läuft noch von edges()
static void wait_ring() {
 byte k=OFFHOOK;
vorn:
 TCNT0L = 256-F_CPU/8/1024/10;	// Wartedauer 50 ms
 GPIOR0&=~0x02;
 do{			// max. 50 ms
  sleep_cpu();
  byte x=k^OFFHOOK;
  k^=x;
  if (x) goto vorn;
 }while (!(GPIOR0&0x02));
}

static void outnibble(byte n) {
 n+='0';
 if (n>'9') n+=7;
 lcd::write((char)n);
}

static void outhex(word v) {
 byte nz=0;
 for (byte d=0;d<4;d++) {
  byte n=v>>12;
  if (n||nz||d==3) {
   ++nz;
   outnibble(n);
  }else lcd::write(' ');
  v<<=4;
 }
}
void DTMF::show() {
 static byte i;
 static word mem[SLOTS];
// mem[0]+=gresult[0];	// Summe
// mem[1]+=gresult[1];	// Summe der Quadrate
 for (byte j=0; j<SLOTS; j++) mem[j]+=kresult[j];
 GPIOR0&=~0x80;		// Schreiben in gresult erlaubt
 if (++i==1) {
  lcd::home();
  for (byte j=0; j<SLOTS; j++) {
   if (j==4) lcd::gotoxy(0,1);
   outhex(mem[j]);
  }
  memset(mem,i=0,sizeof mem);
 }
}

void progress(byte ist,byte soll,char c1='.',char c2=' ') {
 if (soll>ist) {
  do lcd::write(c1); while (++ist!=soll);
 }else if (soll<ist) {
  lcd::send0(0x04);		// Cursor dekrementieren
  do lcd::write(c2); while (--ist!=soll);
  lcd::send0(0x06);		// Cursor normal (inkrementieren)
 }
}

#define NUMAX 20// max. Länge von Rufnummern, in 512 Byte passen nur 22, muss gerade sein

struct NN{	// Nummer (11 Bytes für max. 22 Stellen)
 byte n;
 byte z[NUMAX/2]; // Nibbles mit Ziffern, E=*, F=#
 byte toString(char*) const;
 void fromClip();
 void fromString(const char*);
 void emit() const;
};
struct ZZ{	// Uhrzeit (3 Bytes)
 byte m,d,h;	// Jahr(4),Monat(4),Tag(5),Stunde(5),Minute(6)
 void fromClip();
 void toString(char*) const;	// nur das Datum!
 void emit() const;		// nur das Datum!
};
struct ZN{
 ZZ z;
 NN n;
};

// Huch! Der ATtiny861 hat genausoviel EEPROM wie RAM.
// Damit kann man natürlich nicht den gesamten EEPROM im RAM halten
// wie beim ATmega32U4.
struct EECFG{
 byte verpasst;	// Anzahl verpasste Anrufe (Klingeln vor Abheben)
 byte io,ii,im;	// Index letzter ausgehender/eingehender/verpasster Anruf
 byte vk,vo,vi,vm; // Index angezeigte Rufnummer
 byte vv;	// Index angezeigte Kategorie
}ee;


struct EEDATA {
 NN kurz[10];	// Kurzwahlnummern
 NN outg[10];	// Ausgehende Anrufe (Zeit nicht ermittelbar)
 ZN ingo[10];	// Angenommene Anrufe
 ZN miss[10];	// Verpasste Anrufe
 EECFG cfg;	// Persistente Daten RAM<=>EEPROM
}ee2 EEMEM = {
 12,{0x03,0x51,0x41,0x72,0x16,0x42},	// Gerald
  7,{0x31,0x79,0x00,0x50},		// Jochen
  8,{0x91,0x28,0x54,0x25},		// Tobias
  8,{0x56,0x07,0x96,0x75},		// Pizza
  8,{0x27,0x55,0x55,0x08},		// nebenan
  7,{0x31,0x42,0x90,0x70},		// Yue
  7,{0x40,0x12,0x26,0x40},		// Natalia
 12,{0x03,0x30,0x28,0x60,0x90,0x31},	// Matthias
};

ISR(TIMER1_OVF_vect,ISR_NAKED) {
 GPIOR0|=0x08;
 reti();
}

int main() {
head:
#ifdef __AVR_ATmega32U4__
#else
// Zunächst Speisespannung bei minimalem Eigenbedarf hochlaufen lassen:
// Dazu muss OCR0A solange Pulse ausgeben, bis 5 V erreicht sind.
// CPU läuft mit 2 MHz
 DIDR0 = 0xFF;
 DIDR1 = 0xF0;
 ACSRA = 0x80;	// Analogvergleicher aus
 PRR   = 0x07;	// alles aus außer Timer1 für PWM zum Inverter
 DDRB  = 0x0F;	// Pin 2 = Ausgang
 TCCR1A= 0x82;	// OC1A = Pin 2 aktivieren, Fast-PWM
 TCCR1B= 0x01;	// volle Taktfrequenz
 OCR1C = 125-1;	// 16 kHz Wiederholfrequenz
 OCR1A = 2;	// Nadeln
 MCUCR = 0x20;
 TIMSK = 0x04;	// Überlauf-Interrupts vom Timer1
 sei();
 for (word k=8000;k>0;--k) sleep_cpu();	// 0,5 s warten
 PRR   = 0x03;	// Timer0 aktivieren
 DDRA  = 0x10;
 PORTA = 0x2F;	// Pullup an ADC4 und Tasten
 PORTB = 0x70;
 PCMSK0= 0x0F;	// Bit 5: Abheben oder Klingeln
 PCMSK1= 0x4D;	// Weitere Tasten
 GIMSK = 0x30;
 TIMSK = 0x02;	// ständig Überlauf-Interrupts vom Timer0
 DIDR0 = 0xD0;	// Keine digitalen Eingänge für AIN0, AIN1 und E
 DIDR1 = 0;
#endif
 sei();
 lcd::init();
 lcd::userchars(charmap);
 lcd::clear();
 byte rings=0;	// Anzahl der Klingeltöne
// ACSRA = 0x80;	// Analogvergleicher aus
// PRR   = 0x0F;	// alles aus
 GIFR  = 0x20;
 if (!OFFHOOK) {
// Warte auf Klingeln oder Abheben; Interruptquelle = Pegelwechsel-Interrupt
  //MCUCR = 0x30;	// PowerDown: Quarz anhalten
  if (ee.verpasst) {
   lcd::gotoxy(0,0);	// links oben
   char s[4];
   utoa(ee.verpasst,s,10);
   lcd::write(s);
   lcd::write_rom(PSTR(" Anruf"));
   lcd::write(ee.verpasst>1?'e':' ');
   lcd::clear(2);	// Datum komplett überschreiben
  }else lcd::write_rom(PSTR("Auf"_g"ele"_g"t"));
  do sleep_cpu();	// Nur das Display schluckt 1,2 mA
  while (!OFFHOOK);
 }
// MCUCR = 0x20;	// Idle: Quarz weiterlaufen lassen
checkring:	// Nur mit OFFHOOK (Schleifenstrom) anspringen!!
 lcd::gotoxy(0,0);
// Feststellen ob Klingeln oder abgehoben:
// Klingeln ist für das Einganspin wie Abheben und Auflegen mit 25 oder 50 Hz.
// Eine positive Übersteuerung gibt's nur am Thomson-Modem
// oder mit einem Brückenkondensator: Zusatzaufwand vermeiden!
// Wählimpulse sind mit 10 Hz langsamer.
// nsa (Vollkurzschluss des Telefons) kann durch leichtes Anheben der Spannung 
// per A/D-Wandler detektiert werden.
// (Geht erst mal nicht mit DTMF-Erkennung zusammen, wäre zu überdenken.)
 OCR1A  = 7;		// Mehr "Gas"
 //PRR    = 0x02;		// ADC und Timer0 aktivieren
 if (edges()>=4) goto ring;
#if 1
offhook:	// Hörer abgenommen, Schleifenstrom
 ee.verpasst=0;
 lcd::write_rom(PSTR("Ab"_g"ehoben"));
// DTMF oder Wählimpulse mitschneiden
 cli();
 CLKPR=0x80;
 CLKPR=0;	// Volle Taktfrequnz
 sei();
 kdata.init();
// Wählimpulse und Hook-Flash ignorieren
cont:
 while (OFFHOOK) {
  sleep_cpu();
  if (GPIOR0&0x80) 	// Daten aus der Filterbank?
  kdata.show();
 }
 for (byte i=0;i<10;i++) {
  _delay_ms(1);
  if (OFFHOOK) goto cont;	// Ein Wählimpuls
 }
// Hörer aufgelegt
 kdata.done();
 cli();
 CLKPR=0x80;
 CLKPR=0x03;	// Taktfrequnz herabsetzen (÷8)
 sei();
 lcd::gotoxy(0,0);
 goto head;
#endif
ring:
 if (!rings) lcd::clear();	// Beginnender Anruf
 lcd::write_rom(PSTR("Klin"_g"eln "));
 lcd::write(++rings>9?'\363':rings+'0');
 wait_ring();	// Warten auf Ende der Wechselspannung
// Am Ende des Klingelns gibt es nur 2 Fälle:
// 1: Schleifenstrom weil Hörer abgenommen (kein CLIP)
// 2: Kein Schleifenstrom
 if (OFFHOOK) goto offhook;
// Timer0 ist Strom sparender
 lcd::gotoxy(0,0);
 lcd::write_rom(PSTR("Anruf"));
 lcd::clear(5);
 if (rings==1) {
  lcd::gotoxy(0,1);
  lcd::write_rom(PSTR("Erwarte CLIP"));
  stat=0;
  TCCR1A=0;
  clip.init();		// (verschiedene Implementierungen, hier: Analogvergleicher)
  word i=2000;		// Wartezeit auf CLIP oder nächstes Klingeln: 2 s
  byte p=0;
  do{
   GPIOR0&=~0x02;
   do{
    sleep_cpu();
    byte q=stat; if (q>4) q=4;	// Der Zustand 4 bedeutet: Startbit gefunden, also Daten kommen
    progress(p,q);	// Wackelnde Punkte anzeigen
    p=q;
   }while(!(GPIOR0&0x02));
   if (stat&0x80 || OFFHOOK) break;
  }while(--i);	// insgesamt 2 Sekunden warten
  clip.done();
  TCCR1A=0x82;
  if (stat&0x80) clip.show();
  else lcd::clear();
 }
 for (byte i=0; i<40; i++) {	// 3 Sekunden auf das nächste Klingeln warten
  GPIOR0 &=~0x02;
  do{
   sleep_cpu();
   if (OFFHOOK) goto checkring;	// = Klingeln oder Abheben
  }while (!(GPIOR0&0x02));
 }
// Verpasster Anruf: Irgendwie abspeichern
 ++ee.verpasst;
 goto head;
}
Detected encoding: UTF-80