/* Programm für ATtiny45, „h#s“ Henrik Haftmann, TU Chemnitz
* tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
090331 Erster Anfang als mfv2.c
+190531 CLIP funktioniert
~190610 CLIP funktioniert nicht am Telekom-Anschluss
+190617 phoneParallel() — Tiefentladeschutz für Elko
?190626 Nummer des Anrufers kann immer noch nicht mit 7³ gespeichert werden
210203 Ausgehend von mfv2c Umbau auf Vorsatzgerät mit umgepoltem Telefon an b und W2
*/
#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>
#include <string.h>
FUSES={
0x5C, // Taktteiler /8, kein Taktausgang, schnellster Startup, Resonator 3..8 MHz
#ifdef USEBOD
# if (F_CPU>4000000)
0x55, // kein RESET, EEPROM-Inhalt behalten, Brownout bei 2,7 V
# else
0x56, // kein RESET, EEPROM-Inhalt behalten, Brownout bei 1,8 V
# endif
#else
0x57, // kein RESET, EEPROM-Inhalt behalten, kein Brownout
#endif
0xFF, // Keine Selbstprogrammierung
}; // {Startup: Reset = 272 CK + 64 ms (64068 µs), PowerDown = 258 CK (64 µs)}
/************
* Hardware *
************/
/*
Anschlüsse:
PB0 (5) - Wählscheiben-Kontakt "nsi" = Telefonleitung a, 1 = unterbrochen
PB1 (6) OC1A PWM-Tonausgang, reichlich (125 kHz) Trägerfrequenz
PB2 (7) ADC1 Amtsader La = Telefonader b, über 680 kΩ zur CLIP-Dekodierung,
Detektierung von "nsa" Telefon-Kurzschluss
sowie Stromversorgung im aufgelegten Zustand
inklusive RC-Hochpass 33 kΩ + 1 nF (optional)
PB3 (2) XIN Resonator 4 MHz
PB4 (3) XOUT Resonator 4 MHz
PB5 (1) !RESET Erdtaste (optional) / Debug-Ausgang
GND (4) Telefonader W2 = Anschluss für Zweitwecker
Ucc (8) Amtsader Lb; Z-Diode 5,6 V und Elko ≥ 470 µF nach GND
Die Erdtaste ist optional und ermöglicht einen schnellen Weg zum Rückruf.
(Sonst ist es die 9 in der Shift-Ebene, kurz 9¹)
Da man die Zeit zum Festhalten am Fingeranschlag nicht gut abschätzen kann,
ist nur eine Shift-Ebene vorgesehen.
Mit einer Bediengestik ähnlich zu HolgerK (und Vorsatzgerät mit ATtiny2313)
verbleiben nur 8 Kurzwahlspeicher.
Der EEPROM des AVR ermöglicht das Abspeichern von Nummern zur Kurzwahl.
Sonderfunktionen durch Wählen und >1 s am Finger-Anschlag halten:
1¹..8¹ Kurzwahl
9¹ Wahlwiederholung oder Rückruf
0¹ Kurzwahl speichern ODER '*'/'#' wählen, danach
1..8 Kurzwahl-Speicherplatz, Nummer, auflegen
9 '*' wählen
0 '#' wählen
Die Speicherfunktion bei 0¹ hängt die danach gewählten Ziffern
an vorher gewählte Ziffern an. Daher ist es auch möglich,
erst zu wählen und dann zu speichern. So können auch per CLIP
eingegangene Nummern gespeichert werden, allerdings stets mit Ortsvorwahl.
Wahlen aus dem Kurzwahlspeicher gehen nicht in die Wahlwiederholung ein.
D.h. der Wahlwiederholungsspeicher merkt sich nur explizit gewählte Nummern
oder solche vom CLIP.
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 und CLIP
Analogvergleicher: ungenutzt
Interrupts: Zählerüberlauf Timer 0, Watchdog-Timer, ADC fertig (nur für CLIP)
EEPROM: Nummernspeicher für Wahlwiederholung, 8 Kurzwahlen
feste Adressen
RAM: Nummernspeicher für CLIP, als Liste hintereinanderweg
Flash-Selbstprogrammierung: ungenutzt
In dieser Firmware ist die Resonatorfrequenz variabel gehalten;
getestet habe ich's aber nur mit 4 MHz.
*/
// 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
// Mit der gewählten Amplitude läuft die Signal-Addition
// mit 100 % (hoher Ton) + 75 % (tiefer Ton) geradeso nicht über.
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[20] = {
FREQ( 697),
FREQ( 770),
FREQ( 852),
FREQ( 941),
FREQ(1209),
FREQ(1336),
FREQ(1477),
FREQ(1633),
FREQ(1046), //c³
FREQ(1109),
FREQ(1175), //d³
FREQ(1245),
FREQ(1319), //e³
FREQ(1397), //f³ (Halbtonschritt)
FREQ(1480),
FREQ(1568), //g³
FREQ(1661),
FREQ(1760), //a³
FREQ(1865),
FREQ(1975), //h³
};
// Funktionsprinzip: DDS = Digitale Frequenzsynthese
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"); // reflektiert PINB mit diesen Bits:
// 0 PB0 Nummernschalter "nsi", low-aktiv
// 1 PB1 Klingelwechselspannung (nur aufgelegt)
// 2 (PB2) abgehoben: A/D-Wandler liefert mehr als 75 %
// 3 nsa-Telefonkurzschluss: A/D-Wandler liefert mehr als 88 % (nur abgehoben)
// 5 PB5 Erdtaste, low-aktiv, optional
// 7:6 Zähler für nsa-Telefonkurzschluss
// Dabei ist PINB-Digitaleingabe auf den Bits 2..4 gesperrt via DIDR0
volatile register byte opinb asm("r3"); // Um Veränderungen an pinb zu erkennen
volatile register byte T_on asm("r2"); // Rest-Ton/Pausenlänge, in 16 ms
#define Flags GPIOR0 // Bits für NSK() u.a.
// 7 Watchdog-Interrupt: 16 ms vergangen
// 6 Anruf beginnt: Wenn's klingelt oder gewählt wird
// 3 0¹ = Speichermodus, Folgeziffern nicht wählen
// 2 Beim Auflegen speichern
// 0 Shift-Ebene = längerer dem 1. nsi-Impuls vorausgehender Telefon-Kurzschluss
#define wend GPIOR1 // Zeitzähler für Telefonkurzschluss = nsa
#define Zeit8 GPIOR2 // Zeitzähler für nsi
static word onto; // Zeitzähler für Wahl bei aufgelegtem Telefonhörer
//static byte Zeit16; // Zeitzähler zum Abspeichern der letzten Rufnummer (für Wahlwiederholung)
static byte savepos; // Speicherplatz für Rufnummer, wird bei mfv3c der Rufnummer vorausgewählt!
static byte wahl[11]; // Puffer für Wählziffern
#define buf_w wahl[0]
// 1. Byte = Füllstand (buf_w)
// '*'=14, '#'=15, wie im EEPROM-Kurzwahlspeicher
extern void PutNib(byte*z,byte idx,char v);
// Puffer füllen, c=0..15
static void PutBuf(char c) {
Flags|=0x04; // Beim Auflegen speichern
if (buf_w<20) PutNib(wahl+1,buf_w++,c);
}
extern char GetNib(const byte*z,byte idx);
// Puffer lesen, nicht aufrufen wenn leer!
// Aufruf mit freigegeben Interrupts (aus Hauptschleife) OK
static char GetBuf(void) {
return GetNib(wahl+1,buf_r++);
}
extern byte x10(byte);
/******************
* EEPROM-Zugriff *
******************/
// Um im EEPROM möglichst viele Kurzwahlen unterbringen zu können,
// werden die Ziffern nibbleweise gespeichert, das verdoppelt die Kapazität,
// erschwert aber die Verarbeitung.
// Die Kurzwahlen liegen allesamt an festen Adressen.
//Belegung des EEPROM: Index Kurzwahl
//x00 10 Letzte Nummer 0 9¹
//x0A 10 K1 1 1¹
//x14 10 K2 2 2¹
//x1E 10 K3 3 3¹
//x28 10 K4 4 4¹
//x32 10 K5 5 5¹
//x3C 10 K6 6 6¹
//x46 10 K7 7 7¹
//x50 10 K8 8 8¹
//x5A 160 frei
//xFA 1 Schwellwert für Telefon abgehoben (typ. 0xC0)
//xFB 1 Schwellwert zur Erkennung von nsa-Kurzschluss (typ. 0xE0)
//xFC 1 Anzahl entgangener Anrufe
//xFD 1 Abhebezähler
//xFE 1 Watchdog-Timeout-Zähler (= kein CLIP)
//xFF 1 Reset-Zähler
//x1E0 32 CLIP-Daten (nur ATtiny85)
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
else if (k==0xFF) EECR|=0x20; // write only
EEDR=b;
eewrite();
eewait();
EECR=0;
}
}
// Speichern der Nummer ab EEARL im kopflosen Format (FF-terminiert)
static void numSave(byte*z) {
byte i=*z++;
// 1. Ziffernpuffer in ganzer Länge mit 0x0F auffüllen,
// mit dem Sonderfall '#' am Ende, dann 0x0D als 'falsches' Ende
if (i && i!=20 && GetNib(z,i-1)==0x0F) PutNib(z,i++,0x0D);
while (i!=20) PutNib(z,i++,0x0F);
// 2. Komplette Nummer speichern (nur Änderungen)
for (i=0;i<10;i++) {
eechange(z[i]);
EEARL++;
}
}
// <max> muss gerade sein!
static void numLoad(byte*z,byte max) {
z++;
// 1. Nibbles vom EEPROM laden
byte i;
for (i=0;i<max>>1;i++) {
z[i]=eeread();
EEARL++;
}
// 2. tatsächliche Rufnummern-Länge ermitteln
for (i=max;i;) {
char c=GetNib(z,--i);
if (c!=0x0F) {
if (c==0x0D && i && GetNib(z,i-1)==0x0F) --i;
i++;
break;
}
}
*--z=i; // 0..max möglich
}
/****************************
* Rückruf-Liste bearbeiten *
****************************/
// Organisation der Rückruf-Liste:
// Nahtlose Aneinanderreihung entgangener und noch nicht "abgearbeiteter" Anrufe
// 1 Byte Länge (5 Bit) + Grund der Null-Länge (2 Bit):
// 0x00 = kein oder fehlerhaft empfangenes CLIP (Prüfsumme wird nicht ausgewertet)
// 0x20 = Grund 'O' = keine Durchleitung
// 0x40 = Grund 'P' = unterdrückt vom Anrufer
// 0x60 = sonstiger Grund
// Darauf folgen <Länge> Hex-Nibbles Rufnummer.
// Die gespeicherte Ortsvorwahl wird bei Gleichheit abgeschnitten.
// Bei ungerader Länge folgt ein ungenutztes Nibble (0) zur Byteausrichtung
// Da der Puffer mit Null initialisiert ist, führt fehlendes CLIP
// automatisch zum passenden Grund.
// Bei drohendem Pufferüberlauf wird nicht mehr gespeichert.
// Der Puffer reicht für durchschnittlich 20 Anrufe.
static byte lea[100]; // Liste entgangener Anrufe
static byte ealen(byte l) { // Längen-Byte in Byte-Länge der Nummer
return ((l&0x1F)+1>>1)+1;
}
static byte*eaindex(byte i) { // Zum gegebenen Index „vorspulen“
byte*z=lea;
if (i) do z+=ealen(*z); while(--i);
return z;
}
static byte IstRueckruf(const byte*z) { // liefert buf_w wenn die aktuelle Nummer
if (*z++!=buf_w) return 0;
byte i;
for(i=0; i<buf_w; i++) if (GetNib(wahl+1,i)!=GetNib(z,i)) return 0;
return buf_w;
}
// <clipbuf> zeigt auf 2 (Nummer des Anrufenden) oder 4 (Grund für das Fehlen)
static void AppendClip(const byte*clipbuf) {
// <anrufe> noch 0 beim 1. Klingeln
byte*z=eaindex(anrufe);
byte type=*clipbuf++&0x1F;
byte len=*clipbuf++&0x1F; // Nicht die wackligen höheren Bits beachten
if (z+1+ealen(len)>=lea+sizeof lea) return; // Überlauf (in den Stack) verhindern
byte i,j=0;
if (type==2) { // Rückrufnummer vorhanden
if (len>20) return; // Fehler
for (i=0;i<len;i++) {
byte c=*clipbuf++&0x1F;
if (('0'&0x1F)<=c && c<=('9'&0x1F)) c&=0x0F;
else if (c==('*'&0x1F)) c=0x0E;
else if (c==('#'&0x1F)) c=0x0F;
else continue;
PutNib(z+1,j++,c);
}
}else{ // keine Rückrufnummer
if (len!=1) return; // Fehler
byte reason=*clipbuf;
switch (reason) {
case 'O': j|=0x20; // "unavailable"
case 'P': j|=0x40; // "private"
default: j|=0x60; // unknown reason
}
}
*z=j;
}
static void DelRueckruf(void) {
byte i;
byte*z=lea;
for(i=0;i<anrufe;) {
byte*e=z+ealen(*z);
if (IstRueckruf(z)) {
memcpy(z,e,lea+sizeof lea-e);
--anrufe;
}else{
z=e;
i+=2;
}
}
z=eaindex(anrufe);
memset(z,0,lea+sizeof lea-z); // „Schwanz“ des Rückrufpuffers gelöscht halten
}
// liefert Nummer der Kurzwahl (1..8), 0 für keinen Treffer
static byte FindInKurzwahl(const byte*z) {
byte idx,i;
for (idx=8;idx;--idx) {
EEARL=x10(idx);
numLoad(wahl,20);
if (buf_w=*z) for (i=0;i<*z;i++) if (GetNib(wahl+1,i)!=GetNib(z+1,i)) goto notfound;
break;
notfound:;
}
buf_w=0;
return idx;
}
/****************************
* Kurzwahl laden/speichern *
****************************/
// 10 Bytes Puffer pro Nummer (20 Ziffern)
// idx zwischen 0 (letzte Nummer) und 8 (K8)
static void KurzSpeichern(byte idx) {
EEARL=x10(idx); // × 10
numSave(wahl);
}
static void KurzLaden(byte idx) {
Flags&=~0x04; // Nicht (als Wahlwiederholung) speichern
EEARL=x10(idx); // × 10
numLoad(wahl,20); // 0..20 Ziffern
DelRueckruf();
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
extern void clock_set(byte div);
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) (int)((s)/0.016)
static void tonEin(void) {
clock_max();
PLLCSR = 0x82; // Low-Speed-Modus
_delay_us(100);
while (!(PLLCSR&1));
PLLCSR = 0x87;
TCCR1 = 0x61;
OCR0A = (1<<DIVSH)-1; // rund 32 kHz
TIMSK = 0x10; // Compare-Interrupt
TCCR0A = 0x02; // Timer0: CTC
TCCR0B = 0x01; // Timer0 mit Vorteiler 1 starten
T_on=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) {
if (MCUCR&0x10) return; // niemals im aufgelegten Zustand
char okt=0; // Oktave
while (z<0) {z+=12; --okt;}
while (z>=12) {z-=12; ++okt;} // jetzt 0<=z<12
addA=pgm_read_word(Frequencies+8+z);
if (okt<0) addA>>=-okt;
else if (okt>0) addA<<=okt;
addB=addA;
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
// T_on>=0 = Tonausgabe, sonst Pause
// if (T_on>=(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 *
***********************************/
// Abarbeitung nach dem LIFO-Prinzip
static void Rueckruf(void) {
byte *z,l,i=anrufe;
for (;;) {
if (!i) {
if (!(DDRB&2)) StartTon(-6);
return; // Nichts (mehr) rückzurufen
}
z=eaindex(--i); // Zum letzten Rückruf vorspulen
l=*z&0x1F;
if (l) break;
*z=0; // Unmögliche Rückrufe von hinten allesamt löschen
--anrufe;
if (!(DDRB&2)) StartTon(-12); // Rückruf (der letzten Anrufe) nicht möglich
}
memcpy(wahl,z,11);
DelRueckruf();
KurzSpeichern(0);
buf_r=0; // mit dem Abspielen beginnen
}
// Nummernschaltkontakt an PB0 (sowie Erdtaste an PB5 auswerten)
// Aufruf alle 16 ms per Watchdog-Interrupt aus onhook- oder offhook-Schleife
static void NSK(void) {
Flags&=~0x80;
byte n=nsi,z=Zeit8;
if (!(pinb&0x20) && opinb&0x20) Flags|=1; // Erdtaste aktiviert Shift-Ebene
if (pinb&0x20 && !(opinb&0x20) && !(pinb&8)) goto neun; // loslassen ohne Wählscheibenbetätigung
if (pinb&1 && !(opinb&1)) { // Steigende Flanke: nsi unterbrochen
++n; // Lo-Hi-Flanken zählen
onto=WDT_SEC(30); // Globales Wähl-Timeout setzen (nur bei aufgelegtem Hörer wirksam)
//TODO: A/D-Wandler-Wert aufnehmen, das ist ungefähr die richtige Schwelle für Kurzschluss
}else if (!(pinb&1) && opinb&1) { // Fallende Flanke
z=WDT_SEC(0.2);
}else if (z && !--z && n) { // keine Flanke: Timeout und Nummernscheibe abgelaufen?
if (n>10) n=10; // begrenzen
// Eigentliche Aktion
if (Flags&8) { // 0¹ vorgewählt?
if (savepos) { // Speicherplatz 1..8 angegeben?
if (Flags&1) { // Nochmal mit langem Halten?
if (n<=4) n+=9; // 'A' bis 'D' einspeichern
else if (n>=9) n+=4;// '*' oder '#' einspeichern
}else{ // Bei den Ziffern 5,6,7,8 bleibt langes Halten folgenlos
if (n==10) n=0;
}
PutBuf(n);
buf_r=buf_w; // nicht ertönen lassen
StartTon(4);
}else{ // Langes Halten der Ziffer nach 0¹ folgenlos
if (n>=9) PutBuf(n+4); // '*' oder '#' wählen
else{
savepos=n;
StartTon(2);
}
}
}else{ // kein Speichermodus
if (Flags&1) switch (n) {
case 9: neun: if (anrufe) Rueckruf(); else KurzLaden(0); break;
case 10: Flags|=8; StartTon(0); if (anrufe) Flags|=4; break; // Nummer einspeichern: Nummer + Ziffer folgen
default: KurzLaden(n);
}else{ // normaler Wählvorgang
if (n==10) n=0;
PutBuf(n);
}
}
Flags&=~1; // Shift-Ebene verlassen
DDRB &=~0x20;
PORTB|= 0x20; // Kontroll-LED ausschalten
n=0;
}
Zeit8=z; nsi=n;
}
// Bei Ablauf des Watchdog-Timers (meist 16 ms) wird die CPU geweckt
// und das Bit 7 im Flag-Register (GPIOR0) gesetzt.
ISR(WDT_vect,ISR_NAKED) {
Flags|=0x80;
reti();
}
static byte offhooklevel NOINIT; // High bei 75 %
static byte nsalevel NOINIT; // Telefonkurzschluss bei 1/(1+40/680) = 93%
// Laut Datenblatt: min. 20 kΩ → 1/(1+20/680) = 97% => 0xF8
// max. 50 kΩ → 1/(1+50/680) = 93% => 0xEE
// Gemessen; Mittelwert 35? kΩ → 1/(1+35/680) = 95% => 0xF3
// Bei nur 5 V über dem Telefon ist die Datenblatt-Streuung des Pullups zu groß,
// da sollte dynamisch angepasst werden:
// A/D-Wert während nsi-Impulse hernehmen und einige Stufen darunter ansetzen
// sollte reichen.
// Da einige Zeit vergangen ist, seit die Pullups aktiviert wurden,
// wird zunächst PINB eingelesen und der Analogwert an PB2 ausgewertet.
// Dann wird per Watchdog fest 16 ms gewartet.
// In beiden Fällen mit abgeschaltetem A/D-Wandler und minimalem Stromverbrauch:
// Durch maximale(!) Oszillatorfrequenz und kürzester Hochlaufzeit.
// Heraus kommt die Funktion mit 1 MHz CPU-Takt, aktivierten Pullups
// und mit gestartetem A/D-Wandler, immer an PB2,
// sowie ausgewertetem Nummernschalter.
// Nicht aufrufen während Tonausgabe läuft!
static void waitforwatchdog(void) {
opinb = pinb; // PB0 einlesen, nachdem sich die Pegel eingeschwungen haben
pinb = PINB;
GIFR = 0x20; // Anhängigen Pegelwechsel-Interrupt wegnehmen
while (!(ADCSRA&0x10)); // warten auf Ende, kann bis zu 50 µs dauern
if (ADCH>=offhooklevel) pinb|=4; // High bei 75 %
ADCSRA= 0; // ADC abschalten — sonst gibt's zusätzlichen Stromverbrauch.
PORTB = 0x02; // Pullups aus außer an PB1
DDRB |= 0x04; // Floating-Eingang auf Low festnageln, um Gateschutzdiode zu schonen
clock_max(); // kürzestmögliche Hochlaufzeit
#ifdef USEBOD
MCUCR = 0xB4;
MCUCR = 0xB0; // PowerDown ohne BOD
#endif
sleep_cpu(); // Oszillator aus bis zum nächsten Watchdog-Interrupt
DDRB &=~0x04; // dann umgehend PB2 = 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 wirkt als Tiefpass für den ansonsten springenden Pegel an PB2.
clock_1MHz(); // (10 × 16 µs)
DDRB = 0;
ADCSRA= 0xD1; // Start mit Teiler 2: 500 kHz, 2µs, ×25 = 50 µs, einmalig
PORTB = 0x27; // Alle Pullups aktivieren
NSK(); // Man kann noch während des Klingelns wählen :-)
}
/********************
* CLIP-Dekodierung *
********************/
// Der Interrupt dient nur zum Aufwecken; Interrupts werden/bleiben gesperrt.
// Dieser wird nur für die CLIP-Detektion benötigt, das ist Energie sparender
// als die Auswertung von ADCSRA.ADIF, denn während der CLIP-Erkennung
// steht nur die Ladung im Elko als Energiequelle zur Verfügung.
ISR(ADC_vect,ISR_NAKED) {
asm volatile("ret");
}
// CLIP = Caller Line Identification Presentation:
// Hier liefert das Amt bzw. die Fritzbox die Uhrzeit und die Nummer des Anrufers
// mit FSK (0 ~ 800 Hz und 1 ~ 1400 Hz) und 1200 Baud und ganz kleiner Wechselspannung.
// Dummerweise muss man ziemlich flott Frequenzen auswerten, es kommt nicht flankensynchron.
// Das hatte man wohl bei der Post wegen möglicher Gruppenlaufzeitdifferenzen so gemacht.
// Zusätzlich liefert die Fritzbox den Namen in ASCII (oder UTF-8?)
// wenn dieser im Fritzbox-Telefonbuch steht? Wird hier nicht ausgewertet.
// (Den Namen habe ich bei mir nur am ISDN-Telefon gesehen.)
static byte clip[64] NOINIT; // letztes Byte = Prüfsumme
// CLIP-Dekodierung verlangt vom Mikrocontroller alles ab, daher ist's
// in Assembler (extern) geschrieben.
// Steckt die aktuelle Rufnummer in „lea“
static void saveClip(void) {
byte i=2,j=clip[1]+2;
if (j>sizeof clip) j=sizeof clip;
do{
if (clip[i]==2 || clip[i]==4) {
AppendClip(clip+i);
break;
}else i+=clip[i+1]+2; // Länge des unbekannten Chunks
}while(i<j);
}
extern void acInit(void);
extern byte acMean(void);
#ifdef __AVR_ATtiny85__
static byte histo[32] NOINIT;
extern void acHisto(byte data[],byte len);
#endif
extern void acOnes(void);
extern byte acByte(void);
static void acDone(void) {
ADMUX|= 0x20; // Ergebnis links ausrichten um nur 8 Bit auswerten zu müssen
ADCSRA= 0xD1; // Start mit Ergebnis in 50 µs ohne Interrupt
MCUCR = 0x30; // Sleep-Modus: PowerDown
// clock_1MHz();
}
// mit 1 MHz CPU-Takt
static void clipRead(void) {
// STELLSCHRAUBE, die entscheidend für die nötige Kapazität von C1 ist
byte i=15; // 32 ist am Thomson-Modem erprobt.
// Laut Standard sind es 250 ms nach dem Klingeln. Daher Minimum = 5.
// Kleinere Zahlen fressen mehr Zeit und mehr Strom für die Detektierung
do{
waitforwatchdog(); // je 16 ms
if (GIFR&0x20) return;// Klingelwechselspannung
if (pinb&4) return; // Klingeln oder Abheben
}while (--i); // weitere ms energiesparend warten
#ifdef __AVR_ATtiny85__
memset(histo,0,sizeof histo);
memset(clip,0,sizeof clip);
#endif
cli();
wdt_reset();
// STELLSCHRAUBE, die entscheidend für die nötige Kapazität von C1 ist
WDTCR = 0x1E;
WDTCR = 0x0E; // Watchdog mit Reset, 1 s, bei 1200 Baud sind das 120 Bytes
acInit();
#ifdef __AVR_ATtiny85__
histo[31]=acMean();
acHisto(histo,30);
#else
acMean();
#endif
acOnes();
#ifdef __AVR_ATtiny85__
histo[30]=1;
#endif
wdt_reset(); // nach 100 langen Nulldurchgängen
byte sum=0; // Prüfsumme
for (i=0;; i++) {
byte c=acByte();
#ifdef __AVR_ATtiny85__
histo[30]++;
#endif
sum+=c; // Prüfsumme nachführen
if (i<sizeof clip-1) clip[i]=c; // Byte abspeichern
if (i && i==clip[1]+2) break; // Ende der Message
}
clip[sizeof clip-1]=sum; // Prüfsumme abspeichern (sollte 0 sein) aber erst mal nicht auswerten
acDone();
wdt_reset();
WDTCR = 0x18;
WDTCR = 0x40; // Watchdog normal (gemessen 16,4 ms)
sei();
saveClip();
}
/************************************
* Routinen für aufgelegten Zustand *
************************************/
// Handelt es sich beim Pegel an PB2 um Gleichspannung (permanent High oder auch Low) oder Klingelwechselspannung 25 Hz?
// Routine kehrt erst nach Ende des Klingelns zurück.
// Liefert Anzahl detektierter Flanken (ungefähr, in 16 ms könnten 2 Flanken bei 50 Hz sein).
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(); // 16 ms
if (GIFR&0x20) {
++j; i=0; // Beide Flanken an PB1 zählen
}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;
}
// Diese Routine wird in der Hauptschleife zuerst gerufen.
// Bei verreckter CLIP-Erkennung via Watchdog-Timeout ist Flags.6 gesetzt,
// das heißt es hat bereits 1× geklingelt
// Beim Beenden steht PORTB auf 0x27
static void onhook(void) {
DIDR0 = 0x04; // PB2 nur analog
PORTB|= 0x02; // Dort Klingeln detektieren
PCMSK = 0x02; // Klingelwechselspannung detektieren
opinb = pinb = PINB;
MCUCR = 0x30; // Sleep-Modus: PowerDown
ADMUX = 0x21; // ADLAR, ADC1 = PB2
ADCSRA= 0xD1; // einmalig (wird in waitforwatchdog erneut gestartet)
buf_w = buf_r = 0; // Rufnummer tilgen
byte rend = Flags&0x40 ? WDT_SEC(4) : 0; // Klingel-Timeout, zum Zählen verpasster Anrufe
for(;;){
do{
waitforwatchdog();
if (rend && !--rend) {
Flags&=~0x40; // Wenn jetzt abgehoben wird, ist vermutlich kein Gespräch aktiv.
EEARL=252;
eechange(++anrufe); // Anruf verpasst
}
if (onto && !--onto) buf_w=0; // Timeout für's Wählen bei aufgelegtem Telefonhörer: 255×16ms=4s
}while (!(pinb&4) && !(GIFR&0x20)); // Bit 2 = Schaltschwelle 75% ODER Klingelwechselspannung
// Das Ansteigen der Spannung am PB2 kann 3 Gründe haben:
// * Klingeln, * Hörer abheben, * Paralleles Telefon nimmt Hörer ab
byte j=wait_ringend();// kehrt erst nach 160 ms Ruhe zurück
if (pinb&4) { // Statisch > 75 % Spannung an PB2? Dann wurde abgehoben (dieses ODER paralleles Telefon!)
if (j>=4) Flags|=0x40;// mit Klingeln: CLIP nicht (mehr) möglich, aber an onhook() laufendes Gespräch mitteilen
break; // Oder ohne Klingeln: Möglicherweise nach Klingelzeichen, Flags.6 teilt das mit
}
if (j>=4) { // 4 Pegelwechsel gezählt: Klingeln
if (!(Flags&0x40)) { // Erstes Klingeln
Flags|=0x40; // bemerken
clipRead(); // CLIP einlesen, kann zum Watchdog-Reset führen (wie Exception)
}
rend=WDT_SEC(4); // 4 Sekunden nach dem letzten Klingeln sei ein (verpasster) Anruf zu Ende
}
}
}
/************************************
* Routinen für abgehobenen Zustand *
************************************/
// Testen (mit dem A/D-Wandler) ob das High an Pin 7 tatsächlich mit einer
// ausreichenden, steigenden Betriebsspannung einhergeht.
// Wenn nicht ist es ein parallel geschaltetes Telefon,
// ein gezogener Stecker oder ein totes Amt;
// dann muss mit gaaanz langer Watchdog-Spanne
// auf das Ende dieses irregulären Zustandes gewartet werden,
// um den Elko möglichst nicht zu entladen.
// Dass währenddessen keine Wahl bei aufgelegtem Telefonhörer unterstützt wird
// ist kaum tragisch und erwartungsgemäß.
// Denn wenn dieser erst mal leer ist, kann der Mikrocontroller nicht mehr
// hochlaufen und bspw. Anrufe zählen.
// Das geht dann nur durch Abnehmen des Telefonhörers.
// Im Prinzip ein Designfehler des Mikrocontrollers: Es gibt keine
// Fuse-Einstellung, mit der der Mikrocontroller ab einer bestimmten Spannung
// loslegt UND mit einer deutlich niedrigeren weiterarbeiten kann (= Hysterese)
// UND wenig Strom dafür braucht; der Brown-Out-Detektor ist zu hungrig.
// Daten etwa:
// Mikrocontroller bei 3 V im PowerDown ohne Watchdog: max. 2 µA
// ... mit Watchdog: max. 10 µA
// ... dazu BrownOut: + 15 µA
// ... arbeitend bei 1 MHz: 500 µA
// Vom 680-kΩ-Widerstand kommen bei 45 V Amtsspannung: 60 µA
static byte adRef NOINIT;
// Nachdem der Abfall der Speisespannung bei High an PB2 (vermeintlich abgehoben,
// aber eher parallel geschaltetes Telefon abgehoben, daher keine Durchstromung)
// bemerkt wurde, mit extrem geringer Stromaufnahme in langen Intervallen testen
// und verweilen, bis dieser Zustand beendet und genügend Spannung da ist.
// Andernfalls wird der Mikrocontroller abstürzen und kommt erst durch Abnehmen
// des Telefonhörers = Herstellung des Schleifenstroms wieder hoch.
static void phoneParallel(void) {
tonAus();
PORTB = 0; // Keine ohmschen Verbraucher: Keine Pullups!
DDRB |= 0x04; // PB2 zu Ausgang machen und festnageln: Gateschutzdiode entlasten
DIDR0 = 0x27;
do{
ADCSRA= 0; // ADC aus
WDTCR = 0x61; // 8 s, Maximum
#ifdef USEBOD
MCUCR = 0xB4;
MCUCR = 0xB0; // Sleep-Modus: PowerDown, kein BrownOut
#else
MCUCR = 0x30;
#endif
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)
DIDR0 = 0x06; // PB2 und PB1 nicht als Digitaleingang
DDRB = 0;
PORTB = 0x25;
}
// Misst mal PB2 und mal Speisespannung. Liefert Spannung an PB2, sonst 0
static byte handleADC(void) {
byte adPin7 = 0;
switch (ADMUX) {
case 0x21: { // maß PB2 bzgl. Speisespannung?
adPin7 = ADCH;
ADMUX = 0x2C; // umschalten auf Referenzspannung 1,1 V
}break;
case 0x2C: { // maß Referenzspannung bzgl. Speisespannung?
byte b = adRef;
byte a = ADCH;
adRef = a;
if (a>=0x40 && a>b) { // Ucc < 4,4 V (Uref > Ucc × 25%) und fallend
phoneParallel();
}
ADMUX = 0x21; // umschalten auf PB2
}break;
default: ADMUX = 0x21;
}
return adPin7;
}
static byte pause,index; // nur offhook verwendet!
// Zyklisch aufzurufen: Arbeitet alle <anrufe> ab und generiert Zweiton-Töne
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; // Nummernschalter zählt
if (pinb&8) return; // Telefonkurzschluss durch nsa: Ton wäre unhörbar
if (buf_w) return;
switch (--pause) { // zunächst 256 Aufrufe nichtstun, zwischen den Pieptönen weniger
case WDT_SEC(0.25): StartTon(0); return;
case 0: break;
default: return;
}
const byte*z=eaindex(index);
byte l=*z;
char f;
if (!(l&0x1F)) f=-1-(l>>5); // kein Rückruf möglich
else f=FindInKurzwahl(z);
StartTon(f); // CLIP hörbar machen
if (++index>=anrufe) index=0;
else pause=WDT_SEC(0.5);
}
static void nsa(void) {
byte w=wend;
if (!(opinb&8)) w=WDT_SEC(1.5); // starte Zeitgeber
else if (w && !--w) {
Flags|= 1; // Shift-Ebene startet
PORTB&=~0x20;
DDRB |= 0x20; // Kontroll-LED an Pin 1, die auch die Erdtaste anzeigt
}
wend=w;
}
static void nsalevel_detect(byte a) {
static word sum;
static byte n;
word s=sum;
s+=a; if (!++n) { // A/D-Wert des kurzgeschlossenen Telefons aufsummieren
//PORTB&=~0x20; // LED zur Kontrolle aufblitzen lassen
//DDRB |= 0x20;
nsalevel=(s>>8)-16; // Schwellwert neu festlegen
s=0;
//if (!(Flags&1)) {
// sleep_cpu(); // 16 ms
// DDRB &=~0x20;
// PORTB|= 0x20;
//}
}
sum=s;
}
static void offhook(void) {
EEARL = 253;
eechange(eeread()+1); // Abhebe-Zähler
#ifdef __AVR_ATtiny85__
EEARH = 1;
EEARL = 0;
byte i;
for (i=0; i<sizeof clip; i++) {
eechange(clip[i]);
++EEARL;
}
for (i=0; i<sizeof histo; i++) {
eechange(histo[i]);
++EEARL;
}
EEARH = 0;
#elif defined(DEBUG)
EEARL = 0xE0;
byte i;
for (i=0; i<28; i++) {
eechange(clip[i]);
++EEARL;
}
#endif
DIDR0|= 0x02; // Zusätzlich PB1 nicht nutzen
PORTB&=~0x02; // PB1 hochohmig, Gespräch nicht stören
MCUCR = 0x20; // Sleep-Modus: Idle (Timer läuft, Strom ist genug da)
ADCSRA= 0xF1; // /2 = 15 kHz / 13 = 1 kSa/s (reicht erstmal)
T_on = 0;
byte dialpause=WDT_SEC(1); // besser: auf Freizeichen warten
//Bei Freizeichen müsste <anrufe> weitergezählt werden, wenn Flags.6 gesetzt!!
byte a=anrufe;
byte k=0;
if (Flags&0x40 && a) {
// Beim Abheben während des Klingelns nachgucken, ob CLIP im Rückrufspeicher liegt.
// Wenn ja dann aus dieser Liste tilgen.
byte*z=eaindex(a);
byte l=*z&0x1F;
if (l) {
buf_r=l;
memcpy(wahl,z,11);
DelRueckruf(); // reduziert <anrufe>
k=a-anrufe; // Wenn etwas gelöscht wurde, dann piepsen.
// Dann weiß man, dass dieser Anrufer bereits vorher versucht hatte anzurufen.
// Den Ton hören beide Partner.
KurzSpeichern(0);
}
}
pause=WDT_SEC(1);
index=0;
for(;;) { // Hauptschleife
sleep_cpu(); // blockiert bis zu 16 ms — oder auch fast nicht bei laufender DDS
if (Flags&0x80) { // Watchdog-Interrupt?
EEARL=252; eechange(anrufe);
byte a=handleADC(); // Auch den A/D-Wandler per Watchdog-Timer abfragen.
byte b=pinb;
opinb=b;
b&=0xCC;
b|=PINB&0x21;
if (DDRB&0x20) b|=0x20; // Bei eingeschalteter LED „keine Erdtaste“ annehmen
if (a) { // effektiv Halbierung dieser Schleifendurchlaufrate
b&=~0x0C;
if (a>=offhooklevel) b|=4; // High bei 75 %
if (!(b&4) && !(opinb&4)) {pinb=b; break;} // 2× Low durch Auflegen (> 64 ms, kein Spike)
if (a>=nsalevel) { // nsa?
if (!(b&8)) {
b+=0x40; // viermal hintereinander
//TODO: Dieses Stückchen Kode ist zur Hölle nicht zum Laufen zu bekommen:
//b.3 ist dann immer wieder gelöscht!
/*if (!(b&0xC0))*/ b|=8; // Bei Überlauf
}
}else{
b&=~0xC8;
}
if (b&1) nsalevel_detect(a); // nsi geöffnet
}
if (T_on && --T_on==WDT_SEC(PAUSE)-1) tonAus(); // Tonlänge reduzieren
pinb=b;
if (b&8) nsa();
// if (pinb&8 && !(opinb&8)) PINB|=0x20;
NSK(); // Nummernschaltkontakt-Auswertung
piepAnrufe();
if (T_on) continue; // Ton- oder Pausenausgabe läuft
if (dialpause) --dialpause;
else{
if (k) {StartTon(k); k=0;}
else if (buf_r!=buf_w) StartDTMF(GetBuf()); // Nächsten Ton + Pause ausgeben
}
}
}
if (T_on>=(byte)WDT_SEC(PAUSE)) tonAus();
}
/*************************************
* Initialisierung und Hauptschleife *
*************************************/
// Hochlauf mit 500 kHz
void __init(void) __attribute__((naked,section (".init2")));
void __init(void) {
asm volatile("clr r1");// Stack nicht initialisieren, um gleichermaßen auf ATtiny45 und ATtiny85 lauffähig zu sein
// SPH:SPL ist bereits mit RAMEND initialisiert
T_on = MCUSR; // retten in R2
MCUSR = 0;
ACSR |= 0x80; // Analogvergleicher ausschalten
DIDR0 = 0x27; // Digitale Eingänge (Stromfresser) aus
sei();
if (!(T_on&0x08)) { // Kein RESET durch Watchdog?
WDTCR = 0x18;
WDTCR = 0x46; // Watchdog-Interrupt nach 1 s
#ifdef USEBOD
MCUCR = 0xB4; // PowerDown
MCUCR = 0xB0; // Unterspannungsdetektor aus
#else
MCUCR = 0x30; // PowerDown
#endif
sleep_cpu(); // 1 s warten, Speisespannung sollte steigen
}
WDTCR = 0x18;
WDTCR = 0x40; // Watchdog auf Interrupt, 16 ms = 62 Hz (einzige Interruptquelle)
}
static void hardwareInit(void) {
eewait();
EEARL = T_on&0x08?254:255; // Watchdog-Reset (während CLIP-Erkennung)
#ifdef __AVR_ATtiny85__
EEARH = 0;
#endif
eechange(eeread()+1); // Reset- oder Watchdog-Reset-Zähler
EEARL = 250; byte a=eeread();
offhooklevel = /*a!=0xFF ? a :*/ 0xC0;
EEARL = 251; a=eeread();
nsalevel = /*a!=0xFF ? a :*/ 0xE0;
EEARL = 252; a=eeread();
if (a>20) a=0;
anrufe= a;
buf_r = 0; // alle übrigen Register nullsetzen
Flags = T_on&0x08 ? 0x40 : 0;
nsi = 0;
Zeit8 = 0;
}
int __attribute__((noreturn)) main(void) {
hardwareInit();
for(;;) {
onhook();
offhook();
if (Flags&4) KurzSpeichern(savepos);
EEARL = 250;
eechange(offhooklevel);
EEARL = 251;
eechange(nsalevel);
Flags = 0; // Aktiven Anruf beenden
}
}
Detected encoding: UTF-8 | 0
|