/* 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-8 | 0
|