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

/* Programm für ATtiny25
 * „h#s“ Henrik Haftmann, TU Chemnitz, 24. März 2019
 * 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>
#include <avr/eeprom.h>

// Auf diese Weise gespeichert (byteweise) gibt's beim ATtiny25 nur Platz für 8 Nummern
// Zulässig sind auch '*' und '#'.
EEMEM const char eewahl[2][16]={
 "03713396018",
 "037127555500"
};

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 (15 µA)
 0xFF,	// Keine Selbstprogrammierung
};

/************
 * Hardware *
 ************/
/*
Verwendung der Ports:
PB0	(5)	PCINT0	Taste 1, 0 = gedrückt, wählt erste Nummer
PB1	(6)	OC1A	PWM-Tonausgang, reichlich (125 kHz) Trägerfrequenz
PB2	(7)	PCINT2	Taste 2, 0 = gedrückt, wählt zweite Nummer
PB3	(2)	XIN	Quarz oder Resonator
PB4	(3)	XOUT	Quarz oder Resonator
PB5	(1)	!RESET	frei
Der PWM-Ausgang geht über 100 nF ..1 µF und 1 kΩ an La,
während der Mikrocontroller in Reihe zum Telefon in der Leitung Lb „hängt“.
Parallel zum µC eine Z-Diode, ein Elko und ein keramischer Kondensator.
Das ist bereits alles!

Im EEPROM des AVR liegen die Ziffernfolgen.

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
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) (word)((x)*256.0*65536/F_CPU+0.5)
// DDS-Inkremente = Additionswerte auf Phasenwert alle F_CPU/256
PROGMEM const word Frequencies[7] = {
 FREQ( 697),	// Zeile oben
 FREQ( 770),
 FREQ( 852),
 FREQ( 941),	// Zeile unten
 FREQ(1209),	// Spalte links
 FREQ(1336),
 FREQ(1477)};	// Spalte rechts
#undef FREQ

// 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");	// wahl-Lesezeiger
volatile register byte Teiler	asm("r4");	// Software-Taktteiler
volatile register byte ckey	asm("r3");	// Vorherige Tasten
volatile register byte T_on	asm("r2");	// Rest-Ton/Pausenlänge, in ~ 3 ms

static char wahl[16];	// Puffer für Wählziffern, nullterminiert

// 16 Bytes Puffer pro Nummer (15 Ziffern)
static void KurzLaden(byte idx) {
 eeprom_read_block(wahl,eewahl+idx,16);
}

// Tasten-Auswertung, Aufruf mit F_CPU/65536
static void Keys() {
 byte key = ~PINB;
 ckey ^= key;		// gesetztes Bit bei Änderung
 if (!buf_r) {
  if (ckey&key&4) KurzLaden(1);	// Taste 2 betätigt?
  else if (ckey&key&1) KurzLaden(0);	// Taste 1 betätigt?
 }
 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
// T_on>=0 = Tonausgabe, sonst Pause
 if (T_on>(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älschlicherweise "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 (!--Teiler) --T_on;	// Zeit abzählen
 }else{
  if (DDRB&0x02) {
   DDRB &=~0x02;	// Ausgang hochohmig
   TCCR1 = 0;		// Timer1 (Stromfresser) aus
   PLLCSR= 0;		// PLL (Stromfresser) aus
   CLKPR = 0x80;
   CLKPR = 0x08;	// Quarzfrequenz /256
  }
  if (T_on) --T_on;
 }
}

// Startet Ton für Zeichen z, z ∈ [0-9*#]
static void StartTon(char z) {
 static PROGMEM const byte Nr2HiLo[10]={
//  0    1    2    3    4    5    6    7    8    9
 0x53,0x40,0x50,0x60,0x41,0x51,0x61,0x42,0x52,0x62};
// High-Nibble  4  5  6
// Low-Nibble ┌────────
//	    0 │ 1  2  3
//	    1 │ 4  5  6
//	    2 │ 7  8  9
//	    3 │ *  0  #
 byte i;
 if (z=='*') i=0x43;
 else if (z=='#') i=0x63;
 else if (z<'0') return;	// Alle anderen Zeichen ignorieren
 else if (z>'9') return;
 else i=pgm_read_byte(Nr2HiLo+z-'0');	// umrechnen in die 2 Frequenzen
 cli();
 CLKPR=0x80;
 CLKPR=0;				// Volle CPU-Frequenz (Stromfresser)
 PLLCSR=0x82;
 addA=pgm_read_word(Frequencies+(i>>4));	// hoher Ton im High-Nibble
 addB=pgm_read_word(Frequencies+(i&15));	// tiefer Ton im Low-Nibble
 _delay_us(100);
 while (!(PLLCSR&1));
 PLLCSR|=0x04;				// 32 MHz an Timer1 (Stromfresser)
 TCCR1=0x61;				// Timer1 PWM aktivieren
 T_on=(TON+PAUSE)*F_CPU/65536;		// Tonausgabe in ISR starten
 DDRB|=0x02;				// Ausgang aktivieren
 sei();
}

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

static void hardwareInit() {
 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  = 0;		// alle übrigen Register nullsetzen
 T_on  = 0;
}

int main() {
 hardwareInit();
 sei();
slow:
 buf_r = 0;
 wahl[0]=0;		// Keine Nummer abspielen
 for(;;) {		// Hauptschleife
  sleep_cpu();
  if (T_on) continue;	// Ton- oder Pausenausgabe läuft
  Keys();
  char z=wahl[buf_r];
  if (!z) goto slow;	// Pufferende erreicht, nichts zu tun
  StartTon(z);		// Nächsten Ton + Pause ausgeben
  buf_r++;
 }
}
Detected encoding: UTF-80