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

/* Programm für ATtiny25
 * „h#s“ Henrik Haftmann, TU Chemnitz, 31. März 2009 - 6. Dezember 2017
 * 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
};

/************
 * 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	Wählscheiben-Kontakt "nsa", 0 = betätigt
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.
Es wird keine Erdtaste benötigt.

Der EEPROM des AVR ermöglicht das Abspeichern von Nummern zur Kurzwahl.
Sonderfunktionen:
 LANG am Finger-Anschlag halten (bis zum ersten Piep):
  1..9,0 = Kurzwahl
 LANG×2 am Finger-Anschlag halten (bis zum zweiten Piep):
  1 = Wahlwiederholung
  2 = Speichern der zuletzt gewählten Rufnummer (Ziffer folgt)
  3 = Speichern einer neuen Rufnummer (Nummer folgt, dann LANG + Ziffer)
  4..7 = "A".."D" wählen
  8 = "*" wählen
  9 = "#" wählen
  0 = Letzte Rufnummer löschen (= Wahlwiederholung verhindern)

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 64-MHz-PLL: Hardware-PWM-Ausgabe, mono
Totzeitgenerator: ungenutzt
Taktgenerator: Als Quarzoszillator mit bei Bedarf zugeschalteter High-Speed-PLL
Power-Management: Einfacher Sleep-Modus, CPU-Taktdrosselung ÷256 wenn keine PWM läuft
Analog-Digital-Wandler, Analogvergleicher: ungenutzt
Interrupts: Nur einer: Zählerüberlauf Timer 0
EEPROM: Als Nummernspeicher für Kurzwahl und Wahlwiederholung
Flash-Selbstprogrammierung: ungenutzt
*/

// 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	asm("r5");	// 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 für nsa

static word Zeit16;	// Zeitzähler zum Abspeichern der letzten Rufnummer (für Wahlwiederholung)

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);

// Nummernschaltkontakt-Auswertung, Aufruf mit F_CPU/65536 = 264 Hz
// (Wenn Ton läuft dann häufiger, mit 977 Hz)
static void NSK(void) {
 byte key = PINB;
 ckey ^= key;		// gesetztes Bit bei Änderung
 if (ckey&4) {	
  if (!(key&4)) {	// nsa betätigt? (Start Nummernscheibe aufziehen)
   nsi=0;
   Zeit8=0;
   Flags&=~3;		// Etappenzähler für „nsa halten“ rücksetzen
  }else if (nsi) {	// nsa losgelassen? (Nummernscheibe abgelaufen)
// Eigentliche Aktion
   if (Flags&4) {	// nsi = Ziffer zum Kurzwahl-Speichern
    KurzSpeichern(nsi);
    Flags=0;
    StartTon(19);
    goto raus;		// nicht wählen
   }else if (Flags&2) switch (nsi) {	// LANG×2 = Ganz langes Halten am Fingeranschlag
    case 10: buf_r=buf_w=0; KurzSpeichern(0); goto raus;	// Wahlwiederholung verhindern
    case 1: KurzLaden(0); goto raus;		// Wahlwiederholung
    case 2: Flags=4; StartTon(19); goto raus;	// Letzte Nummer speichern: Ziffer folgt
    case 3: Flags=8; buf_r=buf_w=0; StartTon(16); goto raus;	// Nummer einspeichern: Nummer + Ziffer folgen
    default: nsi+=10-4;		// 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 if (nsi==10) nsi=0;
   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(18);
   }else Zeit16=5*F_CPU/65536;	// Wahlwiederholungs-Abspeicher-TimeOut setzen (5 Sekunden)
raus:;
  }
 }else if (!(key&4)) {
  if (!++Zeit8) {
   if (!(Flags&2)) ++Flags;	// Zu lange gehalten: ignorieren, weiterpiepen
   if (Flags&8) Flags=14;	// Einspeicher-Modus? Umschalten zum Abspeichern wie LANG×2 + 2
   StartTon(15+(Flags&3));	// Ton 16 oder 17 
  }
 }
 if (ckey&1 && key&1) {	// nsi (Nummernschalter) gerade geöffnet?
  nsi++;			// Anzahl der Öffnungen zählen
  Zeit8=0;			// Zeitmessung für nsa verhindern
 }
 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
// 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,0x88,0x99,0xAA,0xBB};
// 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, Hinweistöne nur 1 Sinus)
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
 phaA=phaB;
 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  = 0x04;		// nsi geschlossen, nsa geöffnet annehmen
 buf_w = buf_r = 0;	// alle übrigen Register nullsetzen
 Ton = 0;
 Flags = 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