// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /*------------------------------------------------------------------ * * Module: dtmf.c * * Purpose: Decoder for DTMF, commonly known as "touch tones." * * Description: This uses the Goertzel Algorithm for tone detection. * * References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm * http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt * *---------------------------------------------------------------*/ #include #include #include #include #include "direwolf.h" #include "dtmf.h" // Define for unit test. //#define DTMF_TEST 1 #if DTMF_TEST #define TIMEOUT_SEC 1 /* short for unit test below. */ #define DEBUG 1 #else #define TIMEOUT_SEC 5 /* for normal operation. */ #endif #define NUM_TONES 8 static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 }; /* * Current state of the decoding. */ static struct { int sample_rate; /* Samples per sec. Typ. 44100, 8000, etc. */ int block_size; /* Number of samples to process in one block. */ float coef[NUM_TONES]; struct { /* Separate for each audio channel. */ int n; /* Samples processed in this block. */ float Q1[NUM_TONES]; float Q2[NUM_TONES]; char prev_dec; char debounced; char prev_debounced; int timeout; } C[MAX_CHANS]; } D; /*------------------------------------------------------------------ * * Name: dtmf_init * * Purpose: Initialize the DTMF decoder. * This should be called once at application start up time. * * Inputs: sample_rate - Audio sample frequency, typically * 44100, 22050, 8000, etc. * * Returns: None. * *----------------------------------------------------------------*/ void dtmf_init (int sample_rate) { int j; /* Loop over all tones frequencies. */ int c; /* Loop over all audio channels. */ /* * Processing block size. * Larger = narrower bandwidth, slower response. */ D.sample_rate = sample_rate; D.block_size = (205 * sample_rate) / 8000; #if DEBUG dw_printf (" freq k coef \n"); #endif for (j=0; j 0 && D.coef[j] < 2.0); #if DEBUG dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D.coef[j]); #endif } for (c=0; c THRESHOLD * ( output[1] + output[2] + output[3])) row = 0; else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1; else if (output[2] > THRESHOLD * (output[0] + output[1] + output[3])) row = 2; else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2] )) row = 3; else row = -1; if (output[4] > THRESHOLD * ( output[5] + output[6] + output[7])) col = 0; else if (output[5] > THRESHOLD * (output[4] + output[6] + output[7])) col = 1; else if (output[6] > THRESHOLD * (output[4] + output[5] + output[7])) col = 2; else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6] )) col = 3; else col = -1; for (i=0; i= 0 && col >= 0) { decoded = rc2char[row*4+col]; } else { decoded = '.'; } // Consider valid only if we get same twice in a row. if (decoded == D.C[c].prev_dec) { D.C[c].debounced = decoded; /* Reset timeout timer. */ if (decoded != ' ') { D.C[c].timeout = ((TIMEOUT_SEC) * D.sample_rate) / D.block_size; } } D.C[c].prev_dec = decoded; // Return only new button pushes. // Also report timeout after period of inactivity. ret = '.'; if (D.C[c].debounced != D.C[c].prev_debounced) { if (D.C[c].debounced != ' ') { ret = D.C[c].debounced; } } if (ret == '.') { if (D.C[c].timeout > 0) { D.C[c].timeout--; if (D.C[c].timeout == 0) { ret = '$'; } } } D.C[c].prev_debounced = D.C[c].debounced; #if DEBUG dw_printf (" dec=%c, deb=%c, ret=%c \n", decoded, D.C[c].debounced, ret); #endif return (ret); } return (' '); } /*------------------------------------------------------------------ * * Name: main * * Purpose: Unit test for functions above. * *----------------------------------------------------------------*/ #if DTMF_TEST push_button (char button, int ms) { static float phasea = 0; static float phaseb = 0; float fa, fb; int i; float input; char x; static char result[100]; static int result_len = 0; switch (button) { case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break; case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break; case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break; case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break; case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break; case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break; case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break; case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break; case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break; case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break; case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break; case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break; case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break; case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break; case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break; case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break; case '?': if (strcmp(result, "123A456B789C*0#D123$789$") == 0) { dw_printf ("\nSuccess!\n"); } else { dw_printf ("\n *** TEST FAILED ***\n"); dw_printf ("\"%s\"\n", result); } break; default: fa = 0; fb = 0; } for (i = 0; i < (ms*D.sample_rate)/1000; i++) { input = sin(phasea) + sin(phaseb); phasea += 2 * M_PI * fa / D.sample_rate; phaseb += 2 * M_PI * fb / D.sample_rate; /* Make sure it is insensitive to signal amplitude. */ x = dtmf_sample (0, input); //x = dtmf_sample (0, input * 1000); //x = dtmf_sample (0, input * 0.001); if (x != ' ' && x != '.') { result[result_len] = x; result_len++; result[result_len] = '\0'; } } } main () { dtmf_init(44100); dw_printf ("\nFirst, check all button tone pairs. \n\n"); /* Max auto dialing rate is 10 per second. */ push_button ('1', 50); push_button (' ', 50); push_button ('2', 50); push_button (' ', 50); push_button ('3', 50); push_button (' ', 50); push_button ('A', 50); push_button (' ', 50); push_button ('4', 50); push_button (' ', 50); push_button ('5', 50); push_button (' ', 50); push_button ('6', 50); push_button (' ', 50); push_button ('B', 50); push_button (' ', 50); push_button ('7', 50); push_button (' ', 50); push_button ('8', 50); push_button (' ', 50); push_button ('9', 50); push_button (' ', 50); push_button ('C', 50); push_button (' ', 50); push_button ('*', 50); push_button (' ', 50); push_button ('0', 50); push_button (' ', 50); push_button ('#', 50); push_button (' ', 50); push_button ('D', 50); push_button (' ', 50); dw_printf ("\nShould reject very short pulses.\n\n"); push_button ('1', 20); push_button (' ', 50); push_button ('1', 20); push_button (' ', 50); push_button ('1', 20); push_button (' ', 50); push_button ('1', 20); push_button (' ', 50); push_button ('1', 20); push_button (' ', 50); dw_printf ("\nTest timeout after inactivity.\n\n"); /* For this test we use 1 second. */ /* In practice, it will probably more like 10 or 20. */ push_button ('1', 250); push_button (' ', 500); push_button ('2', 250); push_button (' ', 500); push_button ('3', 250); push_button (' ', 1200); push_button ('7', 250); push_button (' ', 500); push_button ('8', 250); push_button (' ', 500); push_button ('9', 250); push_button (' ', 1200); /* Check for expected results. */ push_button ('?', 0); } /* end main */ #endif /* end dtmf.c */