Spisu treści:

Detektor DTMF: 4 kroki
Detektor DTMF: 4 kroki

Wideo: Detektor DTMF: 4 kroki

Wideo: Detektor DTMF: 4 kroki
Wideo: Управление устройствами с сотового телефона. DTMF decoder. 2024, Lipiec
Anonim
Image
Image

Przegląd

Do zbudowania tego urządzenia zainspirowało mnie zadanie domowe na kursie on-line Digital Signal Processing. Jest to dekoder DTMF zaimplementowany w Arduino UNO, wykrywa cyfrę naciśniętą na klawiaturze telefonu w trybie tonowym na podstawie wytwarzanego dźwięku.

Krok 1: Zrozumienie algorytmu

Kod
Kod

W DTMF każdy symbol kodowany jest dwiema częstotliwościami zgodnie z tabelą na rysunku.

Urządzenie przechwytuje sygnał wejściowy z mikrofonu i oblicza amplitudy ośmiu częstotliwości. Dwie częstotliwości o maksymalnych amplitudach dają rząd i kolumnę zakodowanego symbolu.

Pozyskiwanie danych

W celu przeprowadzenia analizy widma próbki powinny być zbierane z określoną przewidywalną częstotliwością. Aby to osiągnąć wykorzystałem tryb free-run ADC z maksymalną precyzją (preskaler 128) dający częstotliwość próbkowania 9615Hz. Poniższy kod pokazuje, jak skonfigurować ADC Arduino.

nieważne initADC() {

// Rozpocznij ADC; f = (16 MHz/preskaler) / 13 cykli/konwersja ADMUX = 0; // Wybór kanału, prawy-adj, użyj pinu AREF ADCSRA = _BV(ADEN) | // ADC włącz _BV(ADSC) | // ADC start _BV(ADATE) | // Automatyczne wyzwalanie _BV(ADIE) | // Przerwanie włączone _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz ADCSRB = 0; // Tryb swobodnej pracy DIDR0 = _BV(0); // Wyłącz wejście cyfrowe dla ADC pin TIMSK0 = 0; // Timer0 off } Obsługa przerwań wygląda tak ISR(ADC_vect) { uint16_t sample = ADC;samples[samplePos++] = sample - 400; if(PrzykładPoz >= N) { ADCSRA &= ~_BV(ADIE); // Bufor pełny, przerwanie } }

Analiza widma

Po zebraniu próbek obliczam amplitudy symboli kodowania 8 częstotliwości. Nie muszę do tego uruchamiać pełnego FFT, więc użyłem algorytmu Goertzela.

void goertzel(uint8_t *samples, float *spectrum) {

pływak v_0, v_1, v_2; pływać re, im, amp; 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])); pływak a = 2. * c; v_0 = v_1 = v_2 = 0; dla (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (zmiennoprzecinkowa)(próbki) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); widmo[k] = amp; } }

Krok 2: Kodeks

Powyższy rysunek pokazuje przykład kodowania cyfry 3, gdzie maksymalna amplituda odpowiada częstotliwościom 697Hz i 1477Hz.

Cały szkic wygląda następująco

/** * Połączenia: * [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 */ #zawiera #zawiera

#włączać

#zdefiniuj CS_PIN 9

#zdefiniuj N 256

#define IX_LEN 8 #define PRÓG 20

Sterownik LEDMatrix lmd(1, CS_PIN);

próbki uint8_t[N];

lotny uint16_t samplePos = 0;

widmo zmiennoprzecinkowe[IX_LEN];

// Częstotliwości [697.0, 770.0, 852.0, 941.0, 1209.0, 1336,0, 1477,0, 1633.0]

// Obliczono dla 9615Hz 256 próbek const float cos_t[IX_LEN] PROGMEM = { 0.8932243011955153, 0.8700869911087115, 0.8448535652497071, 0.8032075314806449, 0.6895405447370669, 0.6343932841636456, 0.555.05709930130823 } const float sin_t[IX_LEN] PROGMEM = { 0.44961132965460654, 0.49289819222978404, 0.5349976198870972, 0.5956993044924334, 0.7242470829514669, 0.773010453627369, 0.8314696123025451, 0.8819212643483549

struktura typedef {

cyfra znaku; indeks uint8_t; } cyfra_t;

digit_t wykryta_cyfra;

const char table[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} };

czcionka bajtowa[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} // * };

nieważne initADC() {

// Rozpocznij ADC; f = (16 MHz/preskaler) / 13 cykli/konwersja ADMUX = 0; // Wybór kanału, prawy-adj, użyj pinu AREF ADCSRA = _BV(ADEN) | // ADC włącz _BV(ADSC) | // ADC start _BV(ADATE) | // Automatyczne wyzwalanie _BV(ADIE) | // Przerwanie włączone _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz ADCSRB = 0; // Tryb swobodnej pracy DIDR0 = _BV(0); // Wyłącz wejście cyfrowe dla ADC pin TIMSK0 = 0; // Timer0 wyłączony }

void goertzel(uint8_t *samples, float *spectrum) {

pływak v_0, v_1, v_2; pływać re, im, amp; 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])); pływak a = 2. * c; v_0 = v_1 = v_2 = 0; dla (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (zmiennoprzecinkowa)(próbki) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); widmo[k] = amp; } }

float avg(float *a, uint16_t len) {

wynik zmiennoprzecinkowy = 0,0; for (uint16_t i = 0; i < len; i++) { wynik += a; } zwraca wynik / len; }

int8_t get_single_index_above_threshold(float *a, uint16_t len, float próg) {

if (próg < PRÓG) { return -1; } int8_t ix = -1; for (uint16_t i = 0; i próg) { if (ix == -1) { ix = i; } else { return -1; } } } return ix; }

void detect_digit(zmiennoprzecinkowa *widmo) {

float avg_row = avg(widmo, 4); float śr_kol = śr(&widmo[4], 4); int8_t wiersz = get_single_index_above_threshold(widmo, 4, avg_row); int8_t col = get_single_index_above_threshold(&spectrum[4], 4, avg_col); if (wiersz != -1 && col != -1 && avg_col > 200) { wykryta_cyfra.cyfra = pgm_read_byte(&(tabela[wiersz][kol])); wykryty_cyfra.index = pgm_read_byte(&(char_indexes[wiersz][kol])); } else { wykryta_cyfra.cyfra = 0; } }

void drawSprite(bajt* sprite) {

// Maska służy do pobrania bitu kolumny z maski bajtowej wiersza sprite = B10000000; for(int iy = 0; iy < 8; iy++) { for(int ix = 0; ix < 8; ix++) { lmd.setPixel(7 - iy, ix, (bool)(sprite[iy] i maska));

// przesuń maskę o jeden piksel w prawo

maska = maska >> 1; }

// zresetuj maskę kolumny

maska = B10000000; } }

pusta konfiguracja () {

cli(); initADC(); sei();

Serial.początek(115200);

lmd.setEnabled(prawda); lmd.setIntensity(2); lmd.wyczyść(); lmd.wyświetlacz();

wykryta_cyfra.cyfra = 0;

}

bez znaku długie z = 0;

pusta pętla () {

while(ADCSRA i _BV(ADIE)); // Czekaj na zakończenie próbkowania dźwięku goertzel(samples, spectrum); wykryj_cyfra(widmo);

if (wykryta_cyfra.cyfra != 0) {

drawSprite(font[wykryta_cyfra.indeks]); lmd.wyświetlacz(); } if (z % 5 == 0) { for (int i = 0; i < IX_LEN; i++) { Serial.print(spectrum); Serial.print("\t"); } Serial.println(); Serial.println((int)detected_digit.digit); } z++;

próbkaPos = 0;

ADCSRA |= _BV(ADIE); // Wznów przerwanie próbkowania

}

ISR(ADC_wektor) {

próbka uint16_t = ADC;

próbki[przykładPoz++] = próbka - 400;

if(PrzykładPoz >= N) { ADCSRA &= ~_BV(ADIE); // Bufor pełny, przerwanie } }

Krok 3: Schematy

Schematy
Schematy

Należy wykonać następujące połączenia:

Mikrofon do Arduino

Out -> A0

Vcc -> 3.3V Gnd -> Gnd

Ważne jest, aby podłączyć AREF do 3,3V

Wyświetlacz do Arduino

Vcc -> 5V

Masa -> Masa DIN -> D11 CLK -> D13 CS -> D9

Krok 4: Wniosek

Co można tutaj poprawić? Użyłem N=256 próbek przy częstotliwości 9615Hz, która ma pewien wyciek widma, jeśli N=205 i częstotliwość 8000Hz to żądane częstotliwości pokrywają się z siatką dyskretyzacji. W tym celu ADC powinien być używany w trybie przepełnienia timera.

Zalecana: