Inhaltsverzeichnis:
Video: DTMF-Detektor - Gunook
2024 Autor: John Day | [email protected]. Zuletzt bearbeitet: 2024-01-30 07:19
Überblick
Ich wurde inspiriert, dieses Gerät durch eine Hausaufgabe im Online-Kurs Digitale Signalverarbeitung zu bauen. Dies ist ein mit Arduino UNO implementierter DTMF-Decoder, der eine auf einer Telefontastatur gedrückte Ziffer im Tonmodus anhand des erzeugten Tons erkennt.
Schritt 1: Den Algorithmus verstehen
Bei DTMF wird jedes Symbol mit zwei Frequenzen entsprechend der Tabelle auf dem Bild codiert.
Das Gerät erfasst Eingaben vom Mikrofon und berechnet Amplituden von acht Frequenzen. Zwei Frequenzen mit maximalen Amplituden ergeben eine Zeile und eine Spalte des codierten Symbols.
Datenerfassung
Um eine Spektralanalyse durchzuführen, sollten Proben mit einer bestimmten vorhersagbaren Frequenz erfasst werden. Um dies zu erreichen, habe ich den Freilauf-ADC-Modus mit maximaler Präzision (Prescaler 128) verwendet, der eine Abtastrate von 9615 Hz ergibt. Der folgende Code zeigt, wie Sie den ADC von Arduino konfigurieren.
void initADC() {
// ADC initiieren; f = (16MHz/Prescaler) / 13 Zyklen/Wandlung ADMUX = 0; // Kanalauswahl, rechts-adj, AREF-Pin verwenden ADCSRA = _BV(ADEN) | // ADC-Freigabe _BV(ADSC) | // ADC-Start _BV(ADATE) | // Autotrigger _BV(ADIE) | // Unterbrechungsfreigabe _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128: 1 / 13 = 9615 Hz ADCSRB = 0; // Freilaufmodus DIDR0 = _BV(0); // Digitaleingang für ADC-Pin ausschalten TIMSK0 = 0; // Timer0 off } Und der Interrupt-Handler sieht so aus ISR(ADC_vect) { uint16_t sample = ADC;samples[samplePos++] = sample - 400; if(samplePos >= N) { ADCSRA &= ~_BV(ADIE); // Puffer voll, Unterbrechung aus } }
Spektrumanalyse
Nach dem Sammeln von Samples berechne ich Amplituden von 8 Frequenzen, die Symbole kodieren. Ich muss dafür keine vollständige FFT ausführen, also habe ich den Algorithmus von Goertzel verwendet.
void goertzel(uint8_t *samples, float *spektrum) {
schweben v_0, v_1, v_2; Schwimmer re, im, Ampere; for (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); Schwimmer a = 2. * c; v_0 = v_1 = v_2 = 0; für (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (float)(samples) + a * v_1 – v_0; } re = c * v_2 – v_1; im = s * v_2; amp = sqrt (re * re + im * im); Spektrum[k] = Ampere; } }
Schritt 2: Der Code
Das obige Bild zeigt das Beispiel der Kodierung von Ziffer 3, wobei die maximale Amplitude den Frequenzen 697 Hz und 1477 Hz entspricht.
Die komplette Skizze sieht wie folgt aus
/** * Anschlüsse: * [Mic to Arduino] * - Out -> A0 * - Vcc -> 3.3V * - Gnd -> Gnd * - Arduino: AREF -> 3.3V * [Display to Arduino] * - Vcc - > 5V * - Gnd -> Gnd * - DIN -> D11 * - CLK -> D13 * - CS -> D9 */ #include #include
#enthalten
#define CS_PIN 9
#define N 256
#define IX_LEN 8 #define SCHWELLENWERT 20
LEDMatrixDriver lmd(1, CS_PIN);
uint8_t Proben[N];
flüchtig uint16_t samplePos = 0;
Float-Spektrum[IX_LEN];
// Frequenzen [697.0, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0]
// Berechnet für 9615Hz 256 Samples const float cos_t[IX_LEN] PROGMEM = { 0.8932243011955153, 0.8700869911087115, 0.8448535652497071, 0.8032075314806449, 0.6895405447370669, 0.6343932841636456, 0.5555702330196023, 0.25994778 const float sin_t[IX_LEN] PROGMEM = { 0.44961132965460654, 0.49289819222978404, 0.5349976198870972, 0.5956993044924334, 0.7242470829514669, 0.7730104533627369, 0.8314696123025451, 0.88192126;43483549}
typedef-Struktur {
Zeichenziffer; uint8_t-Index; } Ziffer_t;
Stelle_t erkannte_Stelle;
const char-Tabelle[4][4] PROGMEM = {
{'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', ' C'}, {'*', '0', '#', 'D'} };
const uint8_t char_indexes[4][4] PROGMEM = {
{1, 2, 3, 10}, {4, 5, 6, 11}, {7, 8, 9, 12}, {15, 0, 14, 13} };
Byte-Schriftart[16][8] = {
{0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38}, // 0 {0x04, 0x0c, 0x14, 0x24, 0x04, 0x04, 0x04, 0x04}, // 1 {0x00, 0x30, 0x48, 0x04, 0x04, 0x38, 0x40, 0x7c}, // 2 {0x00, 0x38, 0x04, 0x04, 0x18, 0x04, 0x44, 0x38}, // 3 {0x00, 0x04, 0x0c, 0x14, 0x24, 0x7e, 0x04, 0x04 }, // 4 {0x00, 0x7c, 0x40, 0x40, 0x78, 0x04, 0x04, 0x38}, // 5 {0x00, 0x38, 0x40, 0x40, 0x78, 0x44, 0x44, 0x38}, // 6 {0x00, 0x7c, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10}, // 7 {0x00, 0x3c, 0x44, 0x44, 0x38, 0x44, 0x44, 0x78}, // 8 {0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x04, 0x78}, // 9 {0x00, 0x1c, 0x22, 0x42, 0x42, 0x7e, 0x42, 0x42}, // A {0x00, 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x7c}, / / B {0x00, 0x3c, 0x44, 0x40, 0x40, 0x40, 0x44, 0x7c}, // C {0x00, 0x7c, 0x42, 0x42, 0x42, 0x42, 0x44, 0x78}, // D {0x00, 0x0a, 0x7f, 0x14, 0x28, 0xfe, 0x50, 0x00}, // # {0x00, 0x10, 0x54, 0x38, 0x10, 0x38, 0x54, 0x10} // * };
void initADC() {
// ADC initiieren; f = (16MHz/Prescaler) / 13 Zyklen/Wandlung ADMUX = 0; // Kanalauswahl, rechts-adj, AREF-Pin verwenden ADCSRA = _BV(ADEN) | // ADC-Freigabe _BV(ADSC) | // ADC-Start _BV(ADATE) | // Autotrigger _BV(ADIE) | // Unterbrechungsfreigabe _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128: 1 / 13 = 9615 Hz ADCSRB = 0; // Freilaufmodus DIDR0 = _BV(0); // Digitaleingang für ADC-Pin ausschalten TIMSK0 = 0; // Timer0 aus }
void goertzel(uint8_t *samples, float *spektrum) {
schweben v_0, v_1, v_2; Schwimmer re, im, Ampere; for (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); Schwimmer a = 2. * c; v_0 = v_1 = v_2 = 0; für (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (float)(samples) + a * v_1 – v_0; } re = c * v_2 – v_1; im = s * v_2; amp = sqrt (re * re + im * im); Spektrum[k] = Ampere; } }
float avg(float *a, uint16_t len) {
Float-Ergebnis =.0; for (uint16_t i = 0; i < len; i++) { Ergebnis += a; } Ergebnis zurückgeben / len; }
int8_t get_single_index_above_threshold(float *a, uint16_t len, float Schwellenwert) {
if (threshold < THRESHOLD) { return -1; } int8_tix = -1; für (uint16_t i = 0; i Schwellenwert) { if (ix == -1) { ix = i; aufrechtzuerhalten. Sonst { return -1; } } } return ix; }
void Detect_digit (Float * Spektrum) {
float avg_row = avg(Spektrum, 4); float avg_col = avg(&spectrum[4], 4); int8_t Zeile = get_single_index_above_threshold (Spektrum, 4, avg_row); int8_t col = get_single_index_above_threshold(&spectrum[4], 4, avg_col); if (row != -1 && col != -1 && avg_col > 200) {Detected_digit.digit = pgm_read_byte(&(table[row][col])); erkannte_ziffer.index = pgm_read_byte(&(char_indexes[row][col])); aufrechtzuerhalten. Else {Detected_digit.digit = 0; } }
void drawSprite(byte* sprite) {
// Die Maske wird verwendet, um das Spaltenbit aus der Spritezeile zu erhalten byte mask = B10000000; for(int iy = 0; iy < 8; iy++) { for(int ix = 0; ix < 8; ix++) { lmd.setPixel(7 - iy, ix, (bool)(sprite[iy] & mask));
// die Maske um ein Pixel nach rechts verschieben
Maske = Maske >> 1; }
// Spaltenmaske zurücksetzen
Maske = B10000000; } }
Leere Einrichtung () {
cli(); initADC(); sei();
Serial.begin(115200);
lmd.setEnabled(true); lmd.setIntensity(2); lmd.clear(); lmd.display();
erkannte_ziffer.ziffer = 0;
}
vorzeichenloses langes z = 0;
Leere Schleife () {
while(ADCSRA & _BV(ADIE)); // Warten Sie, bis das Audio-Sampling abgeschlossen ist goertzel (samples, Spectrum); Detect_digit (Spektrum);
if (erkannte_ziffer.ziffer != 0) {
drawSprite(font[detected_digit.index]); lmd.display(); aufrechtzuerhalten. Wenn (z % 5 == 0) { für (int i = 0; i < IX_LEN; i ++) { Serial.print (spectrum ); Serial.print("\t"); } Serial.println(); Serial.println ((int)detected_digit.digit); } z++;
SamplePos = 0;
ADCSRA |= _BV(ADIE); // Abtastunterbrechung fortsetzen
}
ESR(ADC_vect) {
uint16_t Probe = ADC;
Proben[samplePos++] = Probe - 400;
if(samplePos >= N) { ADCSRA &= ~_BV(ADIE); // Puffer voll, Unterbrechung aus } }
Schritt 3: Schaltpläne
Folgende Verbindungen sollten hergestellt werden:
Mikrofon zu Arduino
Aus -> A0
Vcc -> 3.3V Masse -> Masse
Es ist wichtig, AREF an 3.3V anzuschließen
Anzeige auf Arduino
Vcc -> 5V
Masse -> Masse DIN -> D11 CLK -> D13 CS -> D9
Schritt 4: Fazit
Was könnte hier verbessert werden? Ich habe N = 256 Samples mit einer Rate von 9615 Hz verwendet, was eine gewisse Spektrumsleckage aufweist. Wenn N = 205 und die Rate 8000 Hz beträgt, stimmen die gewünschten Frequenzen mit dem Diskretisierungsraster überein. Dazu sollte der ADC im Timer-Überlaufmodus verwendet werden.