Das gleiche Problem haben doch alle! Und jeder bastelt „seine“ Lösung.
Das Mittel der Wahl ist üblicherweise der Goertzel-Algorithmus. Ich habe den Goertzel-Algorithmus nicht verstanden (hm), insbesondere nicht, wo dieser gegenüber der DFT effizienter ist.
Die Koeffizienten k1 und k2 sind für jede Frequenz fix.
Deren Berechnung ist nichtlinear (mit Kosinus)
und muss je nach Abtastrate extern vorberechnet werden;
ein #define
geht da nicht.
Die Multiplikation muss, um Grenzzyklen zu minimieren, 16 Bit breit sein.
Das hier beschreibt die Diskrete Fouriertransformation (DFT). Gegenüber dem Goertzel-Algorithmus ergeben sich folgende Vorteile:
Und folgende Nachteile:
Pro zu suchender Spektralkomponente (Frequenz) und Abtastwert sind 2 Multiplikationen und Additionen (MULADD) erforderlich. Zusätzlich sind zur Fensterung und Summenleistungsbildung 2 Multiplikationen und 1 Addition pro Abtastwert nötig. Zum Abschluss der Summation, hier nach 20 ms = 178 Abtastwerten, sind pro Kanal das Betragsquadrat = Spektralleistung zu bilden und mit der Summenleistung auszuwerten.
Um überhaupt mit der knappen Rechenleistung zu Rande zu kommen, wurden folgende Bitbreiten festgelegt:
Vom zu untersuchenden Frequenzgemisch wird sowohl die Summenleistung ermittelt (unterer Signalpfad) als auch mit Sinus und Kosinus der beiden Suchfrequenzen korrelliert (übrige vier Signalpfade), die aus einem DDS-Generator wie oben stammen. Die Bandbreite entspricht der Länge des Suchfensters und damit der Anzahl der Abtastwerte im linken, schnellen Teil.
Während die Summenleistung bereits als Quadrat vorliegt, werden die Sinus- und Kosinus-Komponenten (wegen unbekannter Phasenlage des Eingangssignals) quadriert und addiert, es ergibt sich eine Spektralleistung. Die Division relativiert die Spektralleistung zur Summenleistung. Sie ist stets ≤1.
Beispiel: Besteht das Eingangssignal exakt aus dem direkten Ausgangssignal des zweiten DDS-Generators (dritter Pfad), enthält Akku 3 (Mitte) und Akku 5 (unten) den gleichen Wert, die anderen Akkus den Wert 0.
Die Fensterung, üblicherweise cos², wird von einer gesonderten Tabelle gespeist, deren Länge abhängig von der Quarzfrequenz ist, genau wie auch die Werte von f1 und f2 von der Quarzfrequenz und dem Teiler des A/D-Wandlers abhängen. Die Fensterung beseitigt Abschneidefehler nahezu vollkommen. Bei 16 MHz Quarzfrequenz „purzeln“ alle 89 Abtastwerte = 10 ms neue Ergebnisse heraus, und das Fenster ist genauso lang.
Da der Algorithmus beim Übergang von Block zu Block „blind“ ist (durch die cos²-Fensterung), bietet es sich an, den gesamten Algorithmus mit zwei Datensätzen doppelt auszuführen, wobei deren Fensterung mit 180° phasenverschoben ist. Nur bei ATmegas mit MUL-Befehl ist für diese zweite Berechnung genügend Reserve vorhanden. Das Fenster wäre dann bei 16 MHz Quarzfrequenz 2×89 = 178 Abtastwerte = 20 ms lang, und trotzdem „purzeln“ alle 10 ms neue Ergebnisse heraus.
Mit einem Windows-Programm habe ich die Prinzipfunktion verifiziert. Dabei kommt das Programm auch probeweise mit 4-Bit und sogar 3-Bit-Input zurecht, es ist also sehr robust gegenüber kleiner Signalamplitude. Mit 256 Abtastwerten pro Block und telefon-typischen 8 kHz Abtastrate ergibt sich eine günstige Blocklänge von 31 ms. Dass sich dabei zwischen jedem Block wegen der Hann-Fensterung ein blinder Fleck ergibt ist für die DTMF-Detektion praktisch unerheblich. Alle Multiplikation sind 8×8 = 16 bit, sehr günstig für ATmega und ATtiny.
Angezeigt wird unten in magenta die Summenleistung (logarithmisch), in grün und türkis die doppelte Spektralleistung (logarithmisch), für jeden Block von links nach rechts, von oben nach unten. Darüber der gefundene Kode. Das Erkennen von Lücken ist dann eine andere Aufgabe.
Die übliche schlampige Implementierung beachtet die Summenleistung nicht und führt zu Fehldetektionen.
Das geht ohne Wurzelziehen hinreichend genau, wie die folgende Grafik zeigt.
r = √x²+y²
und damit einer Wurzelfunktion (ergibt einen Kreis)
r = |x|>|y| ? |x|+|y|>>1 : |y|+|x|>>1
,
also ohne eine Wurzel zu ziehen (ergibt ein kreisähnliches Kleeblatt)
r = |x|>|y| ? |x|+|y|>>2 : |y|+|x|>>2
,
schneller aber mehr maximale Abweichung
r = |x|>|y| ? |x|+|y|*78>>8 : |y|+|x|*78>>8
,
Hardware-Multiplizierer vorteilhaft (ATmega)
Ader v.l.n.r. | Kennung | Bedeutung |
---|---|---|
rot | 1 | (ÖT): (Tür)Öffner-Taste gegen 0 |
blau | M | Mikrofon, ca. 11 kΩ gegen 0 |
weiß | T | Hörkapsel, 45 Ω gegen 0 |
weiß | S | Summer, „schnarrende Klingel“, ca. 15-50 Ω gegen 0 |
grün | 0 | Masse, Bezugspotenzial |
braun | Z | (ZT): Zusatztaster (z.B. für Treppenlicht, derzeit funktionslos) gehen 0 |
T,M,0 | Sprechleitungen | |
GS | Gabelschalter (geschlossen, wenn Hörer abgenommen) | |
ET | Etagenruftaster (= Klingelknopf vor der Wohnungstür) | |
RTL | Ruftonlautsprecher (= Lautsprecher für den ET) |
Es wurde Wert auf saubere Potenzialverhältnisse gelegt. Der Einfachheit halber „springt“ das Telefon zwischen Amt- und Türpotenzial mittels Relais K1 hin und her. Dadurch ersparen sich breitbandige Analogsignal-Übertrager. Nur der Hilfsübertrager T1 ist unverzichtbar, um die Anklopf- und Türwählfunktion zu ermöglichen. Interessant ist, dass der Teilnehmer während des Türsprechens für das Amt nicht als „besetzt“ gilt sondern angeklopft werden kann. Das (und nur das) erfordert eine Hilfsspannungsquelle.