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

/* Programm für ATtiny25
 * „h#s“ Henrik Haftmann, TU Chemnitz, 31. März 2009 - 8. Oktober 2015
 * tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
 */
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/fuse.h>
#include <avr/signature.h>

FUSES={
#if F_CPU >= 8000000
 0x5F,	// Taktteiler /8, kein Taktausgang, Startup=16398CK, Quarzoszillator ≥ 8 MHz
#else
 0x5D,	// wie oben jedoch Oszillator 3..8 MHz
#endif
 0xD5,	// RESET, EEPROM-Inhalt behalten, Brownout bei 2,7 V
 0xFF,	// Keine Selbstprogrammierung
};

#define FILTER	// Rufnummern-Filter aktivieren

/************
 * Hardware *
 ************/
/*
Verwendung der Ports:
PB0	(5)	PCINT0	Wählscheiben-Kontakt "nsi", 1 = unterbrochen
PB1	(6)	OC1A	PWM-Tonausgang, reichlich (250 kHz) Trägerfrequenz
PB2	(7)	PCINT2	Erdtaste (wie Shift-Taste), 0 = gedrückt
PB3	(2)	XIN	Quarz
PB4	(3)	XOUT	Quarz
PB5	(1)	!RESET	frei

Es ist viel schwieriger, einen Adapter zum Zwischenschalten
(ohne Modifikation des Telefonapparates) zu bauen.
Der Wählscheiben-Kontakt "nsa" wird nicht benötigt.

Der EEPROM des AVR ermöglicht das Abspeichern von Nummern zur Kurzwahl.
Funktion:
 1. Ein-Finger-Bedienung
 Kurzwahl: Erde (kurz) - Ziffer
 Kurzwahl speichern: Erde (lang) - Rufnummer - Erde - Ziffer
 2. Zwei-Finger-Bedienung
 Wahlwiederholung: Erde + "1"
 Speichern der zuletzt gewählten Nummer: Erde + "2" - Ziffer
 Kurzwahl speichern (wie oben): Erde + "3" - Rufnummer - Erde - Ziffer
 Sonderzeichen senden:	Erde + "4..7" -> A..D
			Erde + "8" -> *
			Erde + "9" -> #
 Letzte Rufnummer löschen: Erde + "0"

Rufnummern-Filter:
 Alles was extra Geld kostet (Handy, Sonderrufnummern, Ausland; gültig für Deutschland):
 00	Ausland
 01	Dienste (0180), Handys, Call-By-Call
 0700	Persönliche Nummern (nie gesehen)
 090	Premium-Dienste (0900)
 118	Telefonauskunft
Der Filter ist nicht beim Speichern oder Wählen einer Kurzwahl wirksam!
Daher lassen sich bestimmte Nummern im Kurzwahlspeicher ablegen.
0800 ist erst mal nicht gesperrt; kann trotzdem (bei VoIP) Geld kosten - weiß nicht.
*/

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

typedef unsigned char byte;
typedef unsigned short word;

// Sinustabelle, Mittelwert und Amplitude = 73
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};

#define FREQ(x) ((x)*256.0*65536/F_CPU+0.5)
// DDS-Inkremente = Additionswerte auf Phasenwert alle F_CPU/256
PROGMEM const word Frequencies[16] = {
		//NTSC-Trägerfrequenz (3,579545 MHz) durch:
 FREQ( 697),	//5135,6
 FREQ( 770),	//4648,8
 FREQ( 852),	//4201,3
 FREQ( 941),	//3804
 FREQ(1209),	//2960,7
 FREQ(1336),	//2679,3
 FREQ(1477),	//2423,5
 FREQ(1633),	//2192
 FREQ(1000),	//c (auf 1 kHz transponiert)
 FREQ(1122),	//d
 FREQ(1260),	//e
 FREQ(1333)};	//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 buf_w	asm("r6");	// Puffer-Schreibzeiger
volatile register byte nsi_cnt	asm("r5");	// Nummernschaltkontakt
volatile register byte nsi_len	asm("r4");	// Nummernschaltkontakt
volatile register byte ckey	asm("r3");	// Nummernschaltkontakt
volatile register byte Ton	asm("r2");	// Rest-Ton/Pausenlänge, in ~ 3 ms
#define Flags GPIOR0			// diverse Bits
#define Teiler GPIOR1			// Software-Taktteiler
#define Zeit8 GPIOR2			// Zeitzähler zum Detektieren eines Erdtastendrucks

static word Zeit16;			// Zeitzähler zum Abspeichern der letzten Rufnummer

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

/* EEPROM-Kurzwahlspeicher (256 Bytes eines ATtiny45):
11 Blöcke {
 1 Byte Nummernlänge
 30 Nibbles (15 Bytes) Ziffern (wie oben kodiert; LSN zuerst)
}
für Wahlwiederholung (Index 0) und 10 Rufnummernspeicher
5 Blöcke à 16 Bytes = 80 Bytes sind noch frei
*/

// Puffer füllen
static void PutBuf(char c) {
 asm volatile(
"	add	r30,r6	\n"
"	st	Z,%0	\n"
"	inc	r6	\n"
"	sbrc	r6,5	\n"
"	 dec	r6	\n"::"r"(c),"z"(numbers));
//numbers[buf_w++] = c;
}

// Puffer lesen, nicht aufrufen wenn leer!
// Aufruf mit freigegeben Interrupts (aus Hauptschleife) OK
static char GetBuf(void) {
 char c;
 asm volatile(
"	cli		\n"
"	add	r30,r7	\n"
"	ld	%0,Z	\n"
"	inc	r7	\n"
"	sei		\n":"=r" (c):"z" (numbers));
//c=numbers[buf_r++];
 return c;
}

static void eewait(void) {
 while (EECR&2);
}

static void eewrite(void) {
 cli();
 EECR=4;
 EECR|=2;
 sei();
}

// 12 Bytes Puffer pro Nummer (22 Ziffern)
// Beim ATtiny25 bei der Kurzwahl "0" nur 8 Bytes (14 Ziffern)
// idx zwischen 0 (letzte Nummer) und 10 (Kurzwahl 0)
static void KurzSpeichern(byte idx) {
 eewait();
 EEARL=(idx<<3)+(idx<<2);	// max. 22 Stellen (24 Nibbles = 12 Bytes)
 EEDR=buf_w;
 eewrite();
 byte i;
 const char*z=numbers;
 for (i=0;i<buf_w;i+=2) {
  eewait();
  ++EEARL;
  EEDR=*z++<<4;	// direkt lesbar beim Lesen des EEPROMs
  EEDR|=*z++;
  eewrite();
 }
}

static void KurzLaden(byte idx) {
 eewait();
 EEARL=(idx<<3)+(idx<<2);
 EECR|=1;
 buf_w=EEDR;
 if (buf_w>22) buf_w=0;
 byte i;
 char*z=numbers;
 for (i=0;i<buf_w;i+=2) {
  ++EEARL;
  EECR|=1;
  *z++=EEDR>>4;
  *z++=EEDR&0x0F;
 }
 buf_r=0;	// mit dem Abspielen beginnen
}

static void StartTon(char z);

#ifdef FILTER
static void __attribute__((noreturn)) block(void) {
 StartTon(19);		// Ton des Todes
 for(;;) {
  Teiler=Ton=255;	// immer tönen, kein Weiterwählen
  sleep_cpu();		// keine Hauptschleife mehr
 }
}
#else
static void block(void) {}	// sollte der Compiler rauswerfen
#endif

// Nummernschaltkontakt-Auswertung, Aufruf mit F_CPU/65536 = 61 .. 305 Hz
static void NSK(void) {
 byte key = ~PINB;
 if (key&4) {			// Erdtaste extra entprellen mit 4-Bit-Zähler
  if ((Flags&0xF0)!=0xF0) {Flags+=0x10; key&=~4;}	// Zähler noch nicht am Anschlag: Tastendruck zurücknehmen
 }else{
  if (Flags&0xF0) {Flags-=0x10; key|=4;}		// Zähler noch nicht unten: Tastendruck wiederherstellen
 }
 ckey ^= key;			// gesetztes Bit bei Änderung
 if (ckey&4) {
  if (key&4) {
   if (Flags&8) {		// Umschalten auf's Einschreiben (wie Shift+2)
    Flags&=~8;			// Wählunterdrückung weg
    Flags|=4;			// ... auch eine Wählunterdrückung
    StartTon(19);
   }
  }else{  // Erdtaste losgelassen?
   if (Flags&2) Flags&=~2;	// Hatte Shift-Funktion? Shift weg!
   else if (!(Flags&0x0C)) {
    Flags|=1;			// Sonst danach Kurzwahl
    StartTon(17);		// tönen lassen
   }
   Zeit8=0;
  }
 }
 if (key&4 && !(Flags&2)) {
  if (!++Zeit8) {
   Flags|=8;			// Kurzwahlnummer ohne zu wählen als nächstes eingeben (= Wählunterdrückung!)
   StartTon(16);
  }
 }
 if (ckey&1) {			// nsi (Nummernschalter)
  if (key&4) Flags|=2;		// Shift-Funktion entdeckt
  if (key&1) {			// nsi geschlossen!
   nsi_len = 1;			// Zeitmessung starten
  }else{			// nsi geöffnet?
   nsi_cnt++;			// Anzahl der Öffnungen zählen
  }
 }else if (key&1 && nsi_len) {	// Zeitmessung?
  nsi_len++;
  if (nsi_len>=(byte)(0.2*F_CPU/65536)) {	// 2 Schrittzeiten?
   if (key&4) switch (nsi_cnt) {
    case 10: buf_r=buf_w=0; KurzSpeichern(0); goto raus;	// Wahlwiederholung verhindern
    case 1: KurzLaden(0); goto raus;
    case 2: Flags|=4; StartTon(19); goto raus;
    case 3: Flags|=8; StartTon(16); goto raus;	// wie langes Drücken auf Erde
    default: nsi_cnt+=10-4;	// *,#,A,B,C,D wählen
   }else if (Flags&4) {
    KurzSpeichern(nsi_cnt);
    Flags&=~4;
    StartTon(16);
    goto raus;			// nicht wählen
   }else if (Flags&1) {
    KurzLaden(nsi_cnt);		// wählen
    Flags&=~1;
    goto raus;
   }else if (nsi_cnt==10) nsi_cnt=0;
   if (buf_w==1 && numbers[0]==0 && (nsi_cnt==0 || nsi_cnt==1)) block();	// 00 (Ausland), 01 (Sonderrufnummern, Handys)
   if (buf_w==2 && numbers[0]==0 && numbers[1]==9 && nsi_cnt==0) block();	// 090 (Premium-Dienste)
   if (buf_w==2 && numbers[0]==1 && numbers[1]==1 && nsi_cnt==8) block();	// 118 (Auskunft)
   if (buf_w==3 && numbers[0]==0 && numbers[1]==7 && numbers[2]==0 && nsi_cnt==0) block();	// 0700 (Rufumleitung)
   PutBuf(nsi_cnt);		// Zählergebnis abspeichern
   if (Flags&8) {
    buf_r=buf_w;		// Nicht wählen
    StartTon(18);
   }
   else Zeit16=5*F_CPU/65536;	// Wahlwiederholungs-Abspeicher-TimeOut setzen (5 Sekunden)
raus:
   nsi_len = nsi_cnt = 0;
  }
 }
 ckey = key;
}

// Kernroutine der Frequenzsynthese
// Aufruf mit F_CPU/256 = 15625 .. 78125 Hz
ISR(TIM0_OVF_vect) {
// Tonlänge nominell 70 ms, Pause 30 ms
// Bei 20 MHz sind das 5469 bzw. 2344 ISR-Aufrufe.
// Durch 256 geteilt sind's handliche 21 bzw. 9.
// Ton>=0 = Tonausgabe, sonst Pause
 if (Ton>=(byte)(PAUSE*F_CPU/65536)) {
#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
 }
 if (CLKPR || !--Teiler) {
  asm volatile("rcall __vector_1");	// kein ((void(*)(void))__vector)(), denn das führt zum weiträumigen Register-Push
 }
}

ISR(_VECTOR(1),ISR_NOBLOCK) {
 if (Ton) --Ton;
 NSK();		// Nummernschaltkontakt-Auswertung
 if (Zeit16 && !--Zeit16) KurzSpeichern(0);
}

PROGMEM const byte Nr2HiLo[20]={
//  0    1    2    3    4    5    6    7    8    9    A    B    C    D    *    #   ..Hinweistöne..
 0x53,0x40,0x50,0x60,0x41,0x51,0x61,0x42,0x52,0x62,0x70,0x71,0x72,0x73,0x43,0x63,0x8F,0x9F,0xAF,0xBF};
// 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

// Startet Ton für Ziffer z ("*"=14, "#"=15)
static void StartTon(char z) {
 if ((byte)z>=20) return;	// sollte nie vorkommen
 if (CLKPR) {
  cli();
  CLKPR=0x80;
  CLKPR=0;
  sei();
 }
 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
 if (!PLLCSR) {
  PLLCSR|=0x02;
  _delay_us(100);
  while (!(PLLCSR&1));
  PLLCSR|=0x04;
  TCCR1=0x61;
 }
 Ton=(TON+PAUSE)*F_CPU/65536;			// Tonausgabe in ISR starten
 DDRB|=0x02;					// Ausgang aktivieren
}

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

static void hardwareInit(void) {
 CLKPR = 0x80;
 CLKPR = 0x08;		// Taktteiler /256 (15 .. 78 kHz)
 ACSR |= 0x80;		// Analogvergleicher ausschalten
 MCUCR = 0x20;		// Sleep-Modus: Idle (Timer läuft)
 PORTB = 0x05;		// Pullups für 2 Eingänge
 DIDR0 = 0x3A;		// diese digitalen Eingänge nicht nutzen
 TCCR0B= 0x01;		// Timer0: Vorteiler 1
 TIMSK = 0x02;		// Überlauf-Interrupt
 ckey = ~PINB;		// einlesen
 nsi_len = nsi_cnt = 0;	// alle übrigen Register nullsetzen
 buf_w = buf_r = 0;
 Ton = 0;
 Flags = Zeit8 = 0;
}

int __attribute__((noreturn)) main(void) {
 hardwareInit();
slow:
 DDRB&=~0x02;			// Ausgang hochohmig
 TCCR1=0;			// Timer1 aus
 PLLCSR=0;			// PLL aus
 cli();
 CLKPR=0x80;
 CLKPR=0x08;			// Taktteiler /256 (15 .. 78 kHz)
 sei();
 for(;;) {		// Hauptschleife
  sleep_cpu();
  if (Ton) continue;		// Ton- oder Pausenausgabe läuft
  if (buf_r==buf_w) goto slow;	// Puffer leer, nichts zu tun
  StartTon(GetBuf());		// Nächsten Ton + Pause ausgeben
 }
}
Detected encoding: UTF-80