/* 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
210206 Bei Binärgleichheit Kommentierung erweitert
220317 Todos als Kommentar
*/
#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
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
AIN0 TODO: Empfindlichere Clip-Dekodierung via 4,7 nF (Fritzbox)
und internem Pullup, f_HP = 1/(2πRC) = 850 Hz
PB1 (6) OC1A PWM-Tonausgang, reichlich (125 kHz) Trägerfrequenz
AIN1 TODO: Gegenpol (als 5V-Ausgang) für den Analogvergleicher
PB2 (7) ADC1 Amtsader La = Telefonader a, über 680 kΩ zur
Abhebe- und Klingel-Detektierung,
CLIP-Dekodierung sowie Minimalstromversorgung
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 b
Ucc (8) Amtsader Lb; Z-Diode 5,6 V und Elko 470 µF nach GND
TODO: Normalerweise wäre zu erwarten, dass der Analogvergleicher
auch mit Spannungen von 0,3 V jenseits der Betriebsspannungsgrenzen
zurecht kommt und korrekt arbeitet. Was zu testen wäre.
Ansonsten helfen ein Pulldown an AIN1 von 680 kΩ, der mit dem internen Pullup
(40 kΩ) 4,7 V bereitstellt, und ein Koppelwiderstand von 1 MΩ von AIN1 nach AIN0.
Die CLIP-Wechselspannung beträgt (-13,5±1,5)dBm an "BT3 termination" (600 Ω),
macht P = U*I = U*U/R = -13,5 dBm = 10^-1,35 mW = 45 µW; U = sqrt(P*R) = 160 mV = 450 mVss.
Für den A/D-Wandler (8 Bit, 5 V) mickrige 20 Count.
Die Terminierung ergibt sich durch das parallel geschaltete Läutewerk (Telefonklingel).
Bei Reflexionen (bei diesen hohen Frequenzen über 1,2 kHz) muss eine gesonderte
Wechselspannungs-Terminierung (600 Ω + 330 nF für f_HP = 850 Hz) herhalten.
Die Offsetspannung des Analogvergleichers ist nicht angegeben.
Nebenbei: CLIP-Dekodierung mit Analogverleicher ist Energie sparender:
I(ADC) = 170 µA; I(AC) = 40 µA; also nur ein Viertel.
Auch die CPU hat weniger zu tun, hält sie mit 1 mA/MHz den Löwenanteil.
Keiner der Timer hat eine Capture-Funktion; die Zeitmessung muss
von der CPU (am besten via Energie sparenden Timer0) ausgeführt werden.
# Es ist schwieriger, einen Adapter zum Zwischenschalten
# (ohne Modifikation des Telefonapparates) zu bauen.
# Ein möglicher Trick dabei besteht darin, das Telefon zwischen W2 und b
# zu betreiben und den nsi-Kontakt an a abzugreifen.
# Das erspart das Unterdrücken der Wählimpulse,
# muss aber nicht mit jedem Telefon funktionieren, je nach Innenschaltung.
Die Erdtaste ist optional und ermöglicht einen schnellen Weg zum Rückruf.
(Sonst ist es die 6 in der dritten Shift-Ebene, also kurz 6³.)
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 K1..K9, K0 Kurzwahl K0..9
LANG×2 am Finger-Anschlag halten (bis zum zweiten Piep):
1..8 = Kurzwahl M1..M8 Kurzwahl M1..8
9 = '*' wählen Ortsvorwahl
0 = '#' wählen Autowahl
LANG×3 am Finger-Anschlag halten (bis zum dritten Piep):
1..4 = 3. Kurzwahl A..D mit 'A' bis 'D' vorbelegt Kurzwahl A..D
5 = Wahlwiederholung 'A'
6 = Rückruf 'B'
7 = Speichern der zuletzt gewählten Rufnummer (Ziffer folgt) 'C'
8 = Speichern einer neuen Rufnummer (Nummer folgt, dann LANG + Ziffer) 'D'
9 = Letzte Rufnummer löschen (= Wahlwiederholung verhindern) '*'
0 = Rückrufliste löschen '#'
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
Die Autowahl ist für den Einsatz als Oma-Telefon gedacht.
Andererseits kommen Omas gut mit Wählscheiben zurecht. Gerade weil's so zeitraubend ist.
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 und CLIP
Analogvergleicher: TODO: CLIP-Dekodierung
Interrupts: Zählerüberlauf Timer 0, Watchdog-Timer, ADC fertig (nur für CLIP)
EEPROM: Nummernspeicher für Wahlwiederholung, 22 Kurzwahlen, Autowahl, Ortsvorwahl
feste Adressen
Beim ATtiny85 im Debug-Modus wird die gesamte CLIP-Nachricht im EEPROM abgelegt.
Da die Fritzbox auch den Namen aus dem Telefonbuchspeicher übermittelt,
steht dann hier der Name des letzten Anrufers im Klartext drin.
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.
Da der Vorläufer mfv2b auch mit anderen Frequenzen gut lief, wird's auch
funktionieren, nur bei der CLIP- und Rückruf-Funktion bin ich mir nicht sicher.
*/
// 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" + "nsa" in Reihe, low-aktiv
// 2 abgehoben: A/D-Wandler an PB2 liefert mehr als 75 %
// 5 PB5 Erdtaste, low-aktiv
// Dabei ist PINB-Digitaleingabe an den Bits 1..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
// 4 Erdtaste gedrückt
// 3 8³ = wahlfreie Nummer speichern
// 2 7³ = letzte Nummer speichern
// 1:0 Ebenenzähler 0..3
#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 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) {
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 5³
//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 10 K9 9 9¹
//x64 10 K0 10 0¹
//x6E 10 M1 11 1²
//x78 10 M2 12 2²
//x82 10 M3 13 3²
//x8C 10 M4 14 4²
//x96 10 M5 15 5²
//xA0 10 M6 16 6²
//xAA 10 M7 17 7²
//xB4 10 M8 18 8²
//xBE 10 A 19 1³
//xC8 10 B 20 2³
//xD2 10 C 21 3³
//xDC 10 D 22 4³
//xE6 10 Vorwahl 23
//xF0 10 Autowahl 24
//xFA 2 frei
//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 25 Anrufe.
static byte lea[128]; // Liste entgangener Anrufe
static byte ovw[5]; // Ortsvorwahl (max. 8-stellig, 1. Byte = Länge)
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);
}
*z=j;
// Testen ob die Rufnummer mit Ortsvorwahl beginnt
byte k=ovw[0];
if (!k) return;
if (j<=k) return;
for (i=0;i<k;i++) if (GetNib(z+1,i)!=GetNib(ovw+1,i)) return;
// Ortsvorwahl wegnehmen
for (i=k;i<j;i++) PutNib(z+1,i-k,GetNib(z+1,i));
*z=j-k;
}else{ // keine Rückrufnummer
if (len!=1) return; // Fehler
byte reason=*clipbuf&0x1F;
switch (reason) {
case 'O'&0x1F: j|=0x20; // "unavailable"
case 'P'&0x1F: 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..22), 0 für keinen Treffer
static byte FindInKurzwahl(const byte*z) {
byte idx,i;
for (idx=22;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 24 (Autowahl)
static void KurzSpeichern(byte idx) {
EEARL=x10(idx); // × 10
numSave(wahl);
if (idx==23) memcpy(ovw,wahl,5);
}
static void KurzLaden(byte idx) {
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) ((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
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;
if (pinb&1 && !(opinb&1)) { // Steigende Flanke: nsa losgelassen oder nsi unterbrochen
++nsi; // Lo-Hi-Flanken zählen
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)
&& --nsi) { // Abzüglich letzter Lo-Hi-Flanke durch nsa-Kontakt
// Hier muss nsi für dänische oder schwedische Telefone angepasst werden
// Eigentliche Aktion
if (Flags&0x10) {
Flags&=~0x10;
goto action3;
}
if (Flags&8 && !(Flags&1) && !(Flags&2)) goto save;
if (Flags&8 || Flags&4) { // nsi = Ziffer zum Kurzwahl-Speichern
if (Flags&2) {
if (Flags&1) {
if (nsi<5) nsi+=18; // 19..22
else {nsi+=5; goto save1;} // Möglichkeit des Abspeicherns von A,B,C,D,* und #
}else{
nsi+=10; // 11..18
if (nsi>18) nsi+=4; // 23..24
}
}
KurzSpeichern(nsi);
StartTon(5);
Flags&=~0x1F;
goto raus; // nicht wählen
}else if (Flags&2) {
if (Flags&1) switch (nsi) { // LANG×3 = Halten am Fingeranschlag
action3:
case 5: KurzLaden(0); goto raus; // Wahlwiederholung
case 6: buf_r=buf_w=0; KurzSpeichern(0); goto raus; // Wahlwiederholung verhindern
case 7: Flags|=4; StartTon(5); 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: Rueckruf(); goto raus;
case 10: anrufe=0; memset(lea,0,sizeof lea); goto raus;
default: KurzLaden(18+nsi); goto raus; // 19..22 (A,B,C,D) wählen
}else switch (nsi) { // LANG×2
case 9:
case 10: nsi+=14-9; break; // '*' oder '#'
default: KurzLaden(10+nsi); goto raus;
}
}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(4);
}else Zeit16=WDT_SEC(2); // Wahlwiederholungs-Abspeicher-TimeOut setzen (4 Sekunden)
raus:
nsi=0;
}else if (!(pinb&1)) { // Vor nsi-Impulsen (Lo-Pegel)
if (!(Flags&1 && Flags&2)) ++Flags; // Zu lange gehalten: ignorieren, weiterpiepen
StartTon(1+(Flags&3)); // Ton c/d/e
Zeit8=WDT_SEC(1);
}
}
}
if (!T_on && !(pinb&0x20) && opinb&0x20) { // Erdtaste gedrückt (simple Entprellung)
Flags|= 0x10; // Ebene 3 ansteuern
}else if (pinb&0x20 && !(opinb&0x20) && Flags&0x10) {
Flags&=~0x1F;
if (anrufe) Rueckruf();
else KurzLaden(0);
}
}
// 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=0xC0; // High bei 75 %
// Da einige Zeit vergangen ist, seit die Pullups aktiviert wurden,
// wird zunächst PINB eingelesen und der Analogwert an PB2 ausgewertet.
// Dann wird in Schritten von 48 µs (max. 12 ms)
// oder per Watchdog fest 16 ms gewartet.
// Die kürzere Zeit wird für die Klingel-Detektion
// = Wechselspannung 25 oder 50 Hz an PB2 benötigt; Flanken alle 20 oder 10 ms.
// In beiden Fällen mit abgeschaltetem A/D-Wandler und minimalem Stromverbrauch:
// Bei us48!=0 durch Heruntersetzen des Oszillatortakts und einer Busy-Loop
// bei us48==0 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.
static void waitforwatchdog(byte us48) {
opinb = pinb; // PB0 einlesen, nachdem sich die Pegel eingeschwungen haben
pinb = PINB;
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 = 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; // Pullup für A/D-Wandler voreilend vorbereiten
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 wirkt als Tiefpass.
clock_1MHz(); // (10 × 16 µs)
DDRB = 0;
PORTB = 0x25; // Restliche Pullups aktivieren
ADCSRA= 0xD1; // Start mit Teiler 2: 500 kHz, 2µs, ×25 = 50 µs, einmalig, ohne Interrupt
if (Flags&0x80) 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");
}
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 = 0xB4;
MCUCR = 0xB0; // Sleep-Modus: PowerDown, kein BrownOut
// 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(0); // je 16 ms
if (pinb&4) return; // Zweites 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 = 0x0E; // Watchdog mit Reset, 1 s (war am ATmega85 erforderlich)
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;
for (i=0;; i++) {
byte c=acByte();
#ifdef __AVR_ATtiny85__
histo[30]++;
#endif
sum+=c;
if (i<sizeof clip-1) clip[i]=c;
if (i && i==clip[1]+2) break;
}
clip[sizeof clip-1]=sum;
acDone();
WDTCR = 0x1D;
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 Schwingungen.
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)
if (!(T_on&0x08)) { // Wenn kein Watchdog-Reset (kein CLIP) …
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: 255×16ms=4s
}while (!(pinb&4)); // Bit 2 = Schaltschwelle 75%
// Das Ansteigen der Spannung am PB2 kann 3 Gründe haben:
// * Klingeln, * Hörer abheben, * Paralleles Telefon nimmt Hörer ab
// 16 ms und 75% sind zur Klingeldetektion möglicherweise knapp!
// In der Konstellation mit PB1 an 'a'-Ader statt am Mikrofon ist es besser,
// dort (via Pullup und kapazitiver Kopplung) Pegelwechsel zu detektieren.
byte j=wait_ringend(); // kehrt erst nach 160 ms Ruhe zurück
if (j>=4) { // 4 Pegelwechsel gezählt: Klingeln
if (!(Flags&0x40)) { // Erstes Klingeln
Flags|=0x40; // bemerken
clipRead(); // CLIP einlesen
}
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 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 adPin7 NOINIT;
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 = 0x25; // Eingänge zu Ausgängen machen und festnageln: Gateschutzdioden entlasten
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: { // maß PB1 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 PB1
}break;
default: ADMUX = 0x21;
}
}
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;
if (Zeit8) return;
if (buf_w) return;
switch (--pause) { // zunächst 256 Aufrufe nichtstun, zwischen den Pieptönen weniger
case (byte)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 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
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 no_dial=WDT_SEC(1); // besser: auf Freizeichen warten
//Bei Freizeichen müsste <anrufe> weitergezählt werden, wenn Flags.6 gesetzt!!
byte a=anrufe;
byte b=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>
b=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);
handleADC(); // Auch den A/D-Wandler per Watchdog-Timer abfragen.
// Sonst funktioniert der Vergleich mit der Referenzspannung nicht!!
// BUGBUG: DDRB hat kein Bit 6!!
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 (T_on) {
if (--T_on==(byte)WDT_SEC(PAUSE)-1) tonAus(); // Tonlänge reduzieren
}
if (no_dial && !--no_dial && !buf_w && !(Flags&0x40) && !anrufe) {
#ifndef DEBUG
KurzLaden(24); // Automatische Wahl beim Abheben
#endif
}
NSK(); // Nummernschaltkontakt-Auswertung
if (Zeit16 && !--Zeit16) KurzSpeichern(0);
piepAnrufe();
opinb = pinb;
}
if (T_on) continue; // Ton- oder Pausenausgabe läuft
if (!no_dial) {
if (b) {StartTon(b); b=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 *
*************************************/
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;
WDTCR|= 0x18;
WDTCR = 0x40; // Watchdog auf Interrupt, 16 ms = 62 Hz (einzige Interruptquelle)
PORTB = 0x25; // Pullup für 3 Eingänge
}
static void hardwareInit(void) {
eewait();
EEARL = T_on&0x08?254:255;
#ifdef __AVR_ATtiny85__
EEARH = 0;
#endif
eechange(eeread()+1); // Reset- oder Watchdog-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 = 0; // hilft das zum Strom sparen? Nee.
buf_r = 0; // alle übrigen Register nullsetzen
Flags = T_on&0x08 ? 0x40 : 0;
nsi = 0;
Zeit8 = 0;
wend = 0;
#ifndef DEBUG
EEARL = 230; // 23×10
numLoad(ovw,8);
#endif
opinb = pinb = PINB;
}
int __attribute__((noreturn)) main(void) {
hardwareInit();
for(;;) {
onhook();
offhook();
}
}
Detected encoding: UTF-8 | 0
|