Распознавание короткой последовательности тонов через микрофон.

Тема в разделе "ESP8266, ESP32", создана пользователем Gomez, 31 янв 2025.

  1. Gomez

    Gomez Гик

    Код (C++):
    /*
      DTMF Decoding

      11.09.2019 V0_08P  Author 6V6GT https://forum.arduino.cc/index.php?topic=121540.msg4305445#msg4305445

     
      This code is a basic implementation of a DTMF decoder for detecting the 16 character
      DTMF code from the analog pin A0 and gives the decoded output by checking for all the
      Upper and lower tones in the DTMF matrix and gives us the corresponding number by turning
      on the corresponding digital bit for the numbers 0-9 and by Serially printing the rest of
      the characters.

      Wire as follows:
      vcc -->10k-->100nf(lead1)--> 10k --> gnd ;  100nf(lead2) --> A0 ;

      Good results  8bit /  9600 / 64 samples / ADC prescaler  8
                    8bit / 19200 / 128 samples  / ADC prescaler  8 (but needs validationRepeats = 0 for 50mS burst length)

      ToDo
        1. better stop and start of data collection suspending adc trigger
        2. replace floats in calculations.

    */


    // bits and ideas from and many thanks to:
    // https://create.arduino.cc/projecthub/MM_Shoaib/dtmf-decoder-using-only-arduino-872502 (Mian Mohammad Shoaib)
    // https://github.com/jacobrosenthal/Goertzel (Jacob Rosenthal)
    // http://www.embedded.com/design/embedded/4024443/The-Goertzel-Algorithm  (Kevin Banks)
    // https://forum.arduino.cc/index.php?topic=121540.0 (el_supremo and others)


    #include "Goertzel.h"

    uint8_t led = 13 ;

    const uint16_t N = 64;                   // sample size. Dependency to sample frequency. Say 64 for 9600 samples per second. 128 for 19200 sps.
    const float threshold = 500;             // minimum tone amplitude. Say 500 for 8bit resolution, 2000 for 10 bit.
    const uint16_t sampling_freq = 9600;     // Sample frequency must be > twice signal frequency. (Timer driven ADC limit 76.9kSPS - see data sheet)
    //                                            higher frequency needs more samples. Best is 9600
    const uint8_t validationRepeats = 1 ;    // number of consecutive repeats of a code required to confirm it is valid. Can be 0 or more.
    //                                            e.g. 2 repeats = 3 consecutive identical codes. Higher is more accurate but slower.
    //                                            use 1 (or even 0 ) for 50mS spacing between tones. 2 for 100mS. 2 or 3 for longer spacing

    uint8_t sampleStore[ N ] ;               // sample store. Passed as pointer to library by static init() method



    //row objects
    Goertzel x_goertzel[4] = {
      Goertzel( 1209.0 ),
      Goertzel( 1336.0 ),
      Goertzel( 1477.0 ),
      Goertzel( 1633.0 )
    } ;

    //column objects
    Goertzel y_goertzel[4] = {
      Goertzel( 697.0 ),
      Goertzel( 770.0 ),
      Goertzel( 852.0 ),
      Goertzel( 941.0 )
    } ;

    const char decoder[4][4] = {
      { '1' , '4', '7', '*' } ,
      { '2' , '5', '8', '0' } ,
      { '3' , '6', '9', '#' } ,
      { 'A' , 'B', 'C', 'D' }
    } ;



    void setup() {
      Serial.begin(115200);
      Serial.println(F("DTMF Decoder V0_08P")) ;
      pinMode(led, OUTPUT) ;

      // initialise class static storage
      Goertzel::init( sampleStore, N , sampling_freq ) ;

      // calculate coefficients for Goertzel objects
      //   must be done after init()
      for ( int i = 0 ; i < 4 ; i++ ) {
        x_goertzel[ i ].getCoefficient(  ) ;
        y_goertzel[ i ].getCoefficient(  ) ;
      }
    }



    void loop() {

      static uint8_t repeats = 0 ;
      static int8_t x_last = -1 ;         // -1 marker indicates not set
      static int8_t y_last = -1 ;         // -1 marker indicates not set
      static uint8_t failedMatches = 0 ;  // Quality statistic. Both x and y are > threshold, but have been rejected.
      // At least validationRepeat rejects will be counted.

      static uint32_t ledOnAtMs = 0 ;
      static bool ledOn = false ;

      if ( ledOn ) {
        if ( millis() - ledOnAtMs > 250 ) {
          digitalWrite( led, LOW ) ;  //expire led
          ledOn = false ;
        }
      }


      if ( Goertzel::testDataReady ) {
        // if sample buffer full

        float x_magnitude_best = 0 ;
        int8_t x = -1 ;  // x is row index , -1 marker indicates not set

        float y_magnitude_best = 0 ;
        int8_t y = -1 ;  // y is column index , -1 marker indicates not set


        for ( int i = 0 ; i < 4 ; i++ ) {

          // pick the best match (x : rows)
          float x_magnitude = x_goertzel[ i ].detect();
          if ( x_magnitude > x_magnitude_best ) {
            x_magnitude_best = x_magnitude ;
            x = i ;
          }

          // pick the best match (y : columns )
          float y_magnitude = y_goertzel[ i ].detect();
          if ( y_magnitude > y_magnitude_best ) {
            y_magnitude_best = y_magnitude ;
            y = i ;
          }

          /*
                // uncomment for detailed debug
                Serial.print( "i= " ) ;
                Serial.print( i ) ;
                Serial.print( "; Magnitude x= " ) ;
                Serial.print( x_magnitude ) ;
                Serial.print( "; Magnitude y= " ) ;
                Serial.print( y_magnitude ) ;

                Serial.print( "; x= " ) ;
                Serial.print( x ) ;
                Serial.print( "; y= " ) ;
                Serial.print( y ) ;

                Serial.print( "; Repeats= " ) ;
                Serial.println( repeats ) ;
                delay(100) ;
          */


        }  // for i

        // test if the sample was identified
        if ( x_magnitude_best > threshold && y_magnitude_best > threshold ) {

          // test if we have i consecutive matches to filter spurious results
          if ( x_last == x && y_last == y ) {
            if ( repeats < 255 ) repeats ++ ;
          }
          else {
            repeats = 0 ;
          }

          x_last = x ;
          y_last = y ;

          if ( repeats == validationRepeats  ) {
            // print out once if we have exactly the required number of consecutive matches
            // irrespecive of the length of the tone burst, we want only one.
            ledOnAtMs = millis() ;
            digitalWrite( led, HIGH ) ;
            ledOn = true ;
            Serial.print( decoder[x][y] ) ;
            Serial.print( "   (" ) ;
            Serial.print( x ) ;
            Serial.print( "," ) ;
            Serial.print( y ) ;
            Serial.print( ")   failed/rejected matches=" ) ;
            Serial.println( failedMatches ) ;
            failedMatches = 0 ;
          }
          else if ( repeats < validationRepeats ) {
            if ( failedMatches < 255 ) failedMatches ++ ;
          }

        } // if ( x_magnitude_best . . .
        else {
          x_last = -1 ;  //clean
          y_last = -1 ;
          repeats = 0 ;
        }

        // allow ISR to refill the sample buffer.
        Goertzel::testDataReady = false ;

      }
    }


     
     

    Вложения:

    • goertzel.zip
      Размер файла:
      3,6 КБ
      Просмотров:
      12
    • dtmf_v0_08P.ino
      Размер файла:
      6,2 КБ
      Просмотров:
      10
  2. Ariadna-on-Line

    Ariadna-on-Line Гуру

    Как ожидал - ваш код не компилируется с моими библами. Убрал свои. Вставил вашу - код скомпилировался и запустился. Однако ничего не опознаёт. Такое же было и у меня. Проблема в библиотеке. Отправляю нормально-работающую библу, но, сами понимаете - ДТМФ придется рисовать самому ))))
    ,
     

    Вложения:

    • Облом.png
      Облом.png
      Размер файла:
      39,1 КБ
      Просмотров:
      11
    • Goertzel.zip
      Размер файла:
      20,9 КБ
      Просмотров:
      8
    Последнее редактирование: 16 фев 2025 в 18:18
    Gomez нравится это.
  3. Ariadna-on-Line

    Ariadna-on-Line Гуру

    Начёркал код для ДТМФ-а на библе что у меня. Из спортивного интереса, чтоб сравнить с БПФ. Ну да, сравнимый код - короче БПФ-овского вдвое. В Протеусе работает. Могу выложить, если актуально. Проверять в реале пока нет нужды.
     
    Последнее редактирование: 18 фев 2025 в 23:35
    Gomez нравится это.
  4. Gomez

    Gomez Гик

    Ну как я уже сказал - в моих условиях прекрасно работает и тот, уже только через свою "вумную розетку" теперь телефон и заряжаю, но микрофон где-то ещё был, nano закончились пока, но несколько штук Uno валяется, так что как-нибудь холодными зимними вечерами могу попробовать сравнить. Выложите, плиз, да и вдруг кому-то ещё когда-то пригодится.
     
  5. Ariadna-on-Line

    Ariadna-on-Line Гуру

    Вот.
     

    Вложения:

    Последнее редактирование: 19 фев 2025 в 11:29
    Gomez нравится это.