/* 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();
}
}
Vorgefundene Kodierung: UTF-8 | 0
|