/* Rufnummern-Anzeige:
CLIP-Dekoder (Anzeige Rufnummer des Anrufenden) und Telefonbuch
Hardware: (1) ATtiny2313/4313A 1,60€ (2017)
256 Byte EEPROM (≈10 Telefonbucheinträge) oder im Flash
Hat serielle Schnittstelle und mehr I/O aber keinen ADC; elend wenig EEPROM
5 PA0 XTAL1 Quarz 10..20 MHz
4 PA1 XTAL2 Quarz 10..20 MHz
1 PA2 RESET
12 PB0 AIN0 a-Ader vom Amt (kapazitiv)
13 PB1 AIN1 b-Ader vom Amt (kapazitiv)
14 PB2 LCD RS
15 PB3 OC1A Tonwahl
16 PB4 LCD D4
17 PB5 LCD D5 Taste?
18 PB6 LCD D6 Taste-
19 PB7 LCD D7 Taste+
2 PD0 RxD PC-Kommunikation?
3 PD1 TxD PC-Kommunikation?
6 PD2 INT0 Schleifenstrom
7 PD3 nsi
8 PD4 nsa
9 PD5 Erde
11 PD6 LCD E
10 GND
20 Ucc
Hardware: (2) ATtiny261/461/861A 1,75€ (2017)
512 Byte EEPROM (≈ 20 Telefonbucheinträge) oder im Flash
Hat 64-MHz-Timer und A/D-Wandler
20 PA0 LCD D4 nsi
19 PA1 LCD D5 nsa
18 PA2 LCD D6 Erde
17 PA3 LCD D7 Taste
14 PA4 LCD E
13 PA5 LCD RS (nicht mit Telefon zusammenlegen, Display nimmt irgendwie Schaden: Plötzlich Pulldown-Widerstand)
12 PA6 AIN0 a vom Amt (kapazitiv)
11 PA7 AIN1 b = Ucc (kapazitiv)
1 PB0 Debug
2 PB1 OC1A PWM für Inverter
3 PB2 USB D-
4 PB3 OC1B Tonwahl Debug
7 PB4 XTAL1 Quarz 16 MHz
8 PB5 XTAL2 Quarz 16 MHz
9 PB6 INT0 a vom Amt (resistiv 200 kΩ)
10 PB7 (RESET) Taste? Debug
6,16 GND
5,15 Ucc
Hardware: (3) ATtiny24/44/84
Zu wenig Pins, kein High-Speed-Timer
1 Ucc
2 PB0 XTAL1 Quarz 16 MHz
3 PB1 XTAL2 Quarz 16 MHz
4 PB3 RESET
5 PB2 LCD E
6 PA7 LCD D7 Taste
7 PA6 OC1A Tonwahl
8 PA5 LCD D6 Erde
9 PA4 LCD D5 nsa
10 PA3 LCD D4 nsi
11 PA2 AIN1 b vom Amt (kapazitiv)
12 PA1 AIN0 a vom Amt (kapazitiv)
13 PA0 ADC0 LCD RS, a vom Amt (resistiv)
14 GND
Hardware: (4) ATmega32U4 auf Pro Micro < 5 US$ (2017)
1 KByte EEPROM (≈ 40 Telefonbucheinträge) oder im Flash
Hat 64-MHz-Timer, A/D-Wandler, MUL-Befehl und USB
Pin Port Funk. Proµ Ard.µ Leon.
8 PB0 TxLed - ja -
9 PB1 (SCLK) 15 ja ICSP LCD E
10 PB2 (MOSI) 16 ja ICSP LCD RS nsa
11 PB3 (MISO) 14 ja ICSP
28 PB4 8 8 8
29 PB5 9 9 #9
30 PB6 OC4B 10 10 #10 Tonwahl
12 PB7 - 11 #11
31 PC6 5 5 #5
32 PC7 - 13 #13
18 PD0 3 3 #3+SCL LCD D4 Taste+
19 PD1 2 2 2+SDA LCD D5 Taste-
20 PD2 (RxD) Rx1 Rx 0RX LCD D6 Taste?
21 PD3 (TxD) Tx0 Tx 1TX LCD D7 Taste?
25 PD4 4 4 4 LCD RS
22 PD5 RxLed - - -
26 PD6 - 12 12
27 PD7 6 6 #6 LCD E
33 PE2 (HWB) - - -
1 PE6 AIN0 7 7 7 b-Ader vom Amt (kapazitiv)
41 PF0 ADC0 - A5 A5
40 PF1 ADC1 - A4 A4 b-Ader vom Amt (kapazitiv)*
39 PF4 ADC4 A3 A3 A3 a-Ader vom Amt (kapazitiv)
38 PF5 ADC5 A2 A2 A2 a-Ader vom Amt (resistiv)
37 PF6 (ADC6) A1 A1 A1
36 PF7 (ADC7) A0 A0 A0
42 AREF - ? AREF
13 RESET RST RST RST+ICSP
* Zur differenziellen A/D-Umsetzung muss am Arduino Micro ADC1 herausgeführt werden.
Also Drähtchen anlöten oder (größeren) Arduino Mini verwenden.
Notfallvariante: Umsetzung Single-ended an ADC4
Hardware: (5) ATmega328 auf Arduino Uno
DIP28 Port Funk. Uno Anschluss
14 PB0 ICP1 USB D- (ohne Arduino)
15 PB1 (OC1A) USB D+ (ohne Arduino)
16 PB2 OC1B Tonwahl
17 PB3 OC2 PWM für Inverter
18 PB4 LCD RS
19 PB5 LCD E
9 PB6 XTAL1 - Quarz 16 MHz (oder Uhrenquarz?)
10 PB7 XTAL2 - Quarz 16 MHz
23 PC0 A0 LCD D4
24 PC1 A1 LCD D5
25 PC2 A2 LCD D6
26 PC3 A3 LCD D7
27 PC4 ADC4 A4 a vom Amt (resistiv)
28 PC5 ADC5 A5 a vom Amt (kapazitiv)
1 PC6 !RESET RES -
2 PD0 RxD Rx (USB-Interface)
3 PD1 TxD Tx (USB-Interface)
4 PD2 (INT0) nsi
5 PD3 (INT1) nsa
6 PD4 Erde
11 PD5 Taste
12 PD6 AIN0 a vom Amt (kapazitiv)
13 PD7 AIN1 b vom Amt (kapazitiv)
8,22 GND
7,20 Ucc b vom Amt
21 Aref
Vergleich der Mikrocontroller bei Analogvergleich und A/D-Wandler
(Alle Mikrocontroller mit Quarzoszillator mindestens 16 MHz)
AC+ AC- ADC+ ADC+2 ADC- PWM Bits eep(k)
tn2313 0 1 - - - OC1A 16 ⅛¼ Kein ADC!
tn84 0=ADC1 1=ADC2 ADC2 ADC3 ADC1 OC1A 16 ⅛¼ ½
tn841 00=ADC5 01=ADC6 ADC6 ADC4 ADC5 OC1A 16 ¼ ½
tn85 0 1,ADC1 ADC1 ADC2 ADC0 OC1A 8 HS ⅛¼ ½ zu wenig Pins!
tn861 0=ADC5 1=ADC6 ADC6 ADC4 ADC5 OC1A 10 HS ⅛¼ ½
m8 0 1,ADC0 ADC0 ADC1 - OC1A 16 ½
m328 0 1,ADC0 ADC0 ADC1 - OC1A 16 1
m32u4 0 1,ADC11 ADC11 ADC12 ADC0 OC4B 10 HS 1
EEPROM-Haushalt: Zumeist stehen 512 Byte zur Verfügung:
- 10 Kurzwahl-Nummern („Favoriten“) 110
- 10 zuletzt gerufene Nummern 110
- 10 entgegengenommene Anrufe 140 mit 3-Byte-Uhrzeit
- 10 entgangene Anrufe 140 mit 3-Byte-Uhrzeit
- 3 rollende Indizes, aktuelles Jahr 2
- 5 Indizes für die Navigation 3
Summe 505
- Telefonbuch?
Im Flash! Aufbau: pstring(4) Nummer, pstring(8) Name
Namenskodierung (Zeichensatz) = HD44780-ASCII
*/
#include "ra.h"
#include "LCD_44780.h"
#include <util/delay.h>
#include <avr/fuse.h>
#include <avr/signature.h> // gepatcht: "const" weg!
#include <string.h> // memset
#include <stdio.h>
#include <stdlib.h> // utoa
#ifdef __AVR_ATtiny861__
FUSES={
0x7F, // Quarzoszillator, lange Hochlaufzeit
0x57, // EEPROM-Inhalt behalten, kein RESET, Brownout bei 1,8 V
0xFE // Selbstprogrammierung aktivieren (langes Telefonbuch)
};
#endif
/*****************************
* Hauptteil: CLIP-Erkennung *
*****************************/
CLIP clip;
ISR(PCINT_vect,ISR_NAKED) {
GPIOR0|=0x04;
reti();
}
ISR(TIMER0_OVF_vect,ISR_NAKED) {
GPIOR0|=0x02;
reti();
}
/**************************
* Bonbon: DTMF-Erkennung *
**************************/
// Das wesentliche, die ISR, steckt in „dtmf.S“
#define SLOTS 8 // Suchfrequenzen
struct DTMF{
struct{
byte iwnd; // Index für Fenster
int s; // Summe (Gleichspannung, Offset)
byte sq[3]; // Summe der Quadrate
}rms[WINDOWS]; // Effektivwert-Quadrat = Leistung
struct g{
word add; // Addierwert zur Vergleichsfrequenzsynthese
word pha; // Aktueller Phasenwinkel, High-Teil = Index in Sinustabelle
struct{
byte re[3]; // Realteil der Aufsummierung (Kosinus, hier: Sinus)
byte im[3]; // Imaginärteil der Aufsummierung (Sinus, hier: -Kosinus)
}sum[WINDOWS];// mit MUL-Befehl: 2 verschachtelte Fenster
}f[SLOTS]; // Suchfrequenzen
void init();
static void done();
static void show();
}kdata;
void DTMF::init() {
#define FREQ(x) x*65536/(F_ADC)
static const PROGMEM word tab[]={
FREQ(697),
FREQ(770),
FREQ(852),
FREQ(941),
FREQ(1209),
FREQ(1336),
FREQ(1477),
FREQ(1633),
};
#undef FREQ
for (int i=0;i<8;i++) f[i].add=pgm_read_word(tab+i);
extern const void*wintab;
rms[0].iwnd=(byte)(word)wintab;
#if WINDOWS==2
rms[1].iwnd=(iwnd[0]+256)>>1;
#endif
ADMUX = 0xB1; // ADC6-ADC5, links ausgerichtet
ADCSRB= 0x88; // differenziell, 1,1 V Referenz, nicht an Aref
ADCSRA= 0xFF; // Start mit Interrupts
}
void DTMF::done() {
ADCSRA=0; // A/D-Wandler aus
}
word kresult[10]; // 8×Spektralwert, Summe der Quadrate, Summe
//========================================== BASIC ROUTINES ================================
const char PROGMEM charmap[8*8]={
0b10101,0b01010,0b10001,0b10001,0b11111,0b10001,0b10001,0b00000, // Ä
0b10001,0b00000,0b10001,0b10001,0b10001,0b10001,0b01110,0b00000, // Ü
0b01110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110,0b00000, // ß
0b00000,0b00000,0b01111,0b10001,0b10001,0b01111,0b00001,0b01110, // g
0b00010,0b00000,0b00110,0b00010,0b00010,0b00010,0b10010,0b01100, // j
0b00000,0b00000,0b10001,0b10001,0b10001,0b01111,0b00001,0b01110, // y
};
byte CLIP::sum() {
byte l=clip.data[1]+3;
if (cnt<l) return 0xFF; // Zu wenig Datenbytes (zu viel gibt's nicht)
byte sum=0;
for (byte i=0;i<l;i++) sum+=clip.data[i];
return sum;
}
char*CLIP::findChunk(char chunk) {
char*p=clip.data;
if (*p++!=-128) return 0;
char l=*p++;
while (l) {
char ll=p[1]+2; // Lokal-Länge
if (ll>l) return 0; // Innerer Block zu groß
if (*p==chunk) return p; // Zeiger auf <chunk>
l-=ll;
p+=ll;
}
return 0;
}
/*
static void hexout(byte b) {
outnibble(b>>4);
outnibble(b&15);
}
*/
void CLIP::show() {
lcd::clear();
static char s[20];
char*u=clip.findChunk(1);
if (u) {
char*sp=s;
*sp++=u[4];
*sp++=u[5];
*sp++='.';
*sp++=u[2];
*sp++=u[3];
*sp++='.';
*sp++=' ';
*sp++=u[6];
*sp++=u[7];
*sp++=':';
*sp++=u[8];
*sp++=u[9];
*sp=0;
}else strcpy_P(s,PSTR("ohne Uhrzeit"));
lcd::gotoxy(4,0);
lcd::write(s);
lcd::gotoxy(0,1);
if (u=clip.findChunk(2)) {
byte l=(byte)u[1];
memcpy(s,u+2,l); // TODO: Im Telefonbuch nachschlagen
s[l]=0;
}else if (u=clip.findChunk(4)) {
switch (u[2]) {
case 'P': strcpy_P(s,PSTR("unterdr"ue"ckt")); break; //80 - unlisted number (caller is protecting his privacy)
case 'O': strcpy_P(s,PSTR("unvermittelt")); break; //79 - unapproachable number (caller uses analogue switchboard)
}
}else strcpy_P(s,PSTR("ohne Nummer"));
lcd::write(s);
if (clip.sum()) lcd::write('?');
#if 0
if (clip.sum()) {
for(byte i=0;i<8;i++)hexout(clip.data[i]);
lcd::gotoxy(0,1);
for(byte i=8;i<15;i++)hexout(clip.data[i]);
hexout(cnt);
}
#endif
}
#define OFFHOOK (PINB&0x40) // abgehoben oder Rufwechselspannung
// Flanken an PB6 innerhalb 100 ms zählen
// 0 = Ruhe, 2 = Flash / Hook-Flash oder Wählimpuls, ≥4 = Klingeln
static byte edges() {
TCNT0L = 0/*256-F_CPU/8/1024/10*/; // = 60 @ 16 MHz
TCCR0B = 0x05; // Vorteiler 1024: Messumfang 100 ms ≙ 10 Hz
GPIOR0&=~0x02;
byte e = 0; // Flankenzähler
byte k = OFFHOOK;
do{ // max. 100 ms
sleep_cpu(); // Pegelwechsel und Timer weckt auf
byte x=k^OFFHOOK;
if (x) ++e;
k^=x;
}while (!(GPIOR0&0x02));
return e;
}
// Wartet auf das Ende der Klingel-Wechselspannung, mindestens 50 ms
// Der Timer0 läuft noch von edges()
static void wait_ring() {
byte k=OFFHOOK;
vorn:
TCNT0L = 256-F_CPU/8/1024/10; // Wartedauer 50 ms
GPIOR0&=~0x02;
do{ // max. 50 ms
sleep_cpu();
byte x=k^OFFHOOK;
k^=x;
if (x) goto vorn;
}while (!(GPIOR0&0x02));
}
static void outnibble(byte n) {
n+='0';
if (n>'9') n+=7;
lcd::write((char)n);
}
static void outhex(word v) {
byte nz=0;
for (byte d=0;d<4;d++) {
byte n=v>>12;
if (n||nz||d==3) {
++nz;
outnibble(n);
}else lcd::write(' ');
v<<=4;
}
}
void DTMF::show() {
static byte i;
static word mem[SLOTS];
// mem[0]+=gresult[0]; // Summe
// mem[1]+=gresult[1]; // Summe der Quadrate
for (byte j=0; j<SLOTS; j++) mem[j]+=kresult[j];
GPIOR0&=~0x80; // Schreiben in gresult erlaubt
if (++i==1) {
lcd::home();
for (byte j=0; j<SLOTS; j++) {
if (j==4) lcd::gotoxy(0,1);
outhex(mem[j]);
}
memset(mem,i=0,sizeof mem);
}
}
void progress(byte ist,byte soll,char c1='.',char c2=' ') {
if (soll>ist) {
do lcd::write(c1); while (++ist!=soll);
}else if (soll<ist) {
lcd::send0(0x04); // Cursor dekrementieren
do lcd::write(c2); while (--ist!=soll);
lcd::send0(0x06); // Cursor normal (inkrementieren)
}
}
#define NUMAX 20// max. Länge von Rufnummern, in 512 Byte passen nur 22, muss gerade sein
struct NN{ // Nummer (11 Bytes für max. 22 Stellen)
byte n;
byte z[NUMAX/2]; // Nibbles mit Ziffern, E=*, F=#
byte toString(char*) const;
void fromClip();
void fromString(const char*);
void emit() const;
};
struct ZZ{ // Uhrzeit (3 Bytes)
byte m,d,h; // Jahr(4),Monat(4),Tag(5),Stunde(5),Minute(6)
void fromClip();
void toString(char*) const; // nur das Datum!
void emit() const; // nur das Datum!
};
struct ZN{
ZZ z;
NN n;
};
// Huch! Der ATtiny861 hat genausoviel EEPROM wie RAM.
// Damit kann man natürlich nicht den gesamten EEPROM im RAM halten
// wie beim ATmega32U4.
struct EECFG{
byte verpasst; // Anzahl verpasste Anrufe (Klingeln vor Abheben)
byte io,ii,im; // Index letzter ausgehender/eingehender/verpasster Anruf
byte vk,vo,vi,vm; // Index angezeigte Rufnummer
byte vv; // Index angezeigte Kategorie
}ee;
struct EEDATA {
NN kurz[10]; // Kurzwahlnummern
NN outg[10]; // Ausgehende Anrufe (Zeit nicht ermittelbar)
ZN ingo[10]; // Angenommene Anrufe
ZN miss[10]; // Verpasste Anrufe
EECFG cfg; // Persistente Daten RAM<=>EEPROM
}ee2 EEMEM = {
12,{0x03,0x51,0x41,0x72,0x16,0x42}, // Gerald
7,{0x31,0x79,0x00,0x50}, // Jochen
8,{0x91,0x28,0x54,0x25}, // Tobias
8,{0x56,0x07,0x96,0x75}, // Pizza
8,{0x27,0x55,0x55,0x08}, // nebenan
7,{0x31,0x42,0x90,0x70}, // Yue
7,{0x40,0x12,0x26,0x40}, // Natalia
12,{0x03,0x30,0x28,0x60,0x90,0x31}, // Matthias
};
ISR(TIMER1_OVF_vect,ISR_NAKED) {
GPIOR0|=0x08;
reti();
}
int main() {
head:
#ifdef __AVR_ATmega32U4__
#else
// Zunächst Speisespannung bei minimalem Eigenbedarf hochlaufen lassen:
// Dazu muss OCR0A solange Pulse ausgeben, bis 5 V erreicht sind.
// CPU läuft mit 2 MHz
DIDR0 = 0xFF;
DIDR1 = 0xF0;
ACSRA = 0x80; // Analogvergleicher aus
PRR = 0x07; // alles aus außer Timer1 für PWM zum Inverter
DDRB = 0x0F; // Pin 2 = Ausgang
TCCR1A= 0x82; // OC1A = Pin 2 aktivieren, Fast-PWM
TCCR1B= 0x01; // volle Taktfrequenz
OCR1C = 125-1; // 16 kHz Wiederholfrequenz
OCR1A = 2; // Nadeln
MCUCR = 0x20;
TIMSK = 0x04; // Überlauf-Interrupts vom Timer1
sei();
for (word k=8000;k>0;--k) sleep_cpu(); // 0,5 s warten
PRR = 0x03; // Timer0 aktivieren
DDRA = 0x10;
PORTA = 0x2F; // Pullup an ADC4 und Tasten
PORTB = 0x70;
PCMSK0= 0x0F; // Bit 5: Abheben oder Klingeln
PCMSK1= 0x4D; // Weitere Tasten
GIMSK = 0x30;
TIMSK = 0x02; // ständig Überlauf-Interrupts vom Timer0
DIDR0 = 0xD0; // Keine digitalen Eingänge für AIN0, AIN1 und E
DIDR1 = 0;
#endif
sei();
lcd::init();
lcd::userchars(charmap);
lcd::clear();
byte rings=0; // Anzahl der Klingeltöne
// ACSRA = 0x80; // Analogvergleicher aus
// PRR = 0x0F; // alles aus
GIFR = 0x20;
if (!OFFHOOK) {
// Warte auf Klingeln oder Abheben; Interruptquelle = Pegelwechsel-Interrupt
//MCUCR = 0x30; // PowerDown: Quarz anhalten
if (ee.verpasst) {
lcd::gotoxy(0,0); // links oben
char s[4];
utoa(ee.verpasst,s,10);
lcd::write(s);
lcd::write_rom(PSTR(" Anruf"));
lcd::write(ee.verpasst>1?'e':' ');
lcd::clear(2); // Datum komplett überschreiben
}else lcd::write_rom(PSTR("Auf"_g"ele"_g"t"));
do sleep_cpu(); // Nur das Display schluckt 1,2 mA
while (!OFFHOOK);
}
// MCUCR = 0x20; // Idle: Quarz weiterlaufen lassen
checkring: // Nur mit OFFHOOK (Schleifenstrom) anspringen!!
lcd::gotoxy(0,0);
// Feststellen ob Klingeln oder abgehoben:
// Klingeln ist für das Einganspin wie Abheben und Auflegen mit 25 oder 50 Hz.
// Eine positive Übersteuerung gibt's nur am Thomson-Modem
// oder mit einem Brückenkondensator: Zusatzaufwand vermeiden!
// Wählimpulse sind mit 10 Hz langsamer.
// nsa (Vollkurzschluss des Telefons) kann durch leichtes Anheben der Spannung
// per A/D-Wandler detektiert werden.
// (Geht erst mal nicht mit DTMF-Erkennung zusammen, wäre zu überdenken.)
OCR1A = 7; // Mehr "Gas"
//PRR = 0x02; // ADC und Timer0 aktivieren
if (edges()>=4) goto ring;
#if 1
offhook: // Hörer abgenommen, Schleifenstrom
ee.verpasst=0;
lcd::write_rom(PSTR("Ab"_g"ehoben"));
// DTMF oder Wählimpulse mitschneiden
cli();
CLKPR=0x80;
CLKPR=0; // Volle Taktfrequnz
sei();
kdata.init();
// Wählimpulse und Hook-Flash ignorieren
cont:
while (OFFHOOK) {
sleep_cpu();
if (GPIOR0&0x80) // Daten aus der Filterbank?
kdata.show();
}
for (byte i=0;i<10;i++) {
_delay_ms(1);
if (OFFHOOK) goto cont; // Ein Wählimpuls
}
// Hörer aufgelegt
kdata.done();
cli();
CLKPR=0x80;
CLKPR=0x03; // Taktfrequnz herabsetzen (÷8)
sei();
lcd::gotoxy(0,0);
goto head;
#endif
ring:
if (!rings) lcd::clear(); // Beginnender Anruf
lcd::write_rom(PSTR("Klin"_g"eln "));
lcd::write(++rings>9?'\363':rings+'0');
wait_ring(); // Warten auf Ende der Wechselspannung
// Am Ende des Klingelns gibt es nur 2 Fälle:
// 1: Schleifenstrom weil Hörer abgenommen (kein CLIP)
// 2: Kein Schleifenstrom
if (OFFHOOK) goto offhook;
// Timer0 ist Strom sparender
lcd::gotoxy(0,0);
lcd::write_rom(PSTR("Anruf"));
lcd::clear(5);
if (rings==1) {
lcd::gotoxy(0,1);
lcd::write_rom(PSTR("Erwarte CLIP"));
stat=0;
TCCR1A=0;
clip.init(); // (verschiedene Implementierungen, hier: Analogvergleicher)
word i=2000; // Wartezeit auf CLIP oder nächstes Klingeln: 2 s
byte p=0;
do{
GPIOR0&=~0x02;
do{
sleep_cpu();
byte q=stat; if (q>4) q=4; // Der Zustand 4 bedeutet: Startbit gefunden, also Daten kommen
progress(p,q); // Wackelnde Punkte anzeigen
p=q;
}while(!(GPIOR0&0x02));
if (stat&0x80 || OFFHOOK) break;
}while(--i); // insgesamt 2 Sekunden warten
clip.done();
TCCR1A=0x82;
if (stat&0x80) clip.show();
else lcd::clear();
}
for (byte i=0; i<40; i++) { // 3 Sekunden auf das nächste Klingeln warten
GPIOR0 &=~0x02;
do{
sleep_cpu();
if (OFFHOOK) goto checkring; // = Klingeln oder Abheben
}while (!(GPIOR0&0x02));
}
// Verpasster Anruf: Irgendwie abspeichern
++ee.verpasst;
goto head;
}
| Detected encoding: UTF-8 | 0
|