News:

;) This forum is the property of Proton software developers

Main Menu

Program for decoding DTMF signal

Started by Giuseppe, Mar 03, 2022, 06:49 PM

Previous topic - Next topic

Giuseppe

Thanks Gabi for sharing the scheme. In place of the 2 resistors R1-R2 I put a trimmer but with the program adapted to 18f25k22 Ad always supplies a value in output even if I move the trimmer

'****************************************************************
'*  Name    : UNTITLED.BAS                                      *
'*  Author  : [select VIEW...EDITOR OPTIONS]                    *
'*  Notice  : Copyright (c) 2010 Gabi Mihaila                   *
'*          : All Rights Reserved                               *
'*  Date    : 7/9/2010                                          *
'*  Version : 1.0                                               *
'*  Notes   :  Based on AN218 Silicon Laboratories              *
'*          :  DTMF DECODER REFERENCE DESIGN                    *
'****************************************************************


;-------------------------------------------------------------------------------
;**** Added by Fuse Configurator ****
; Use the Fuse Configurator plug-in to change these settings

Device = 18F25K22

Config_Start
  FOSC = HSMP ;HS oscillator (medio power 4Mhz a 16 MHz)
  PLLCFG = On ;Oscillator multiplied by 4
  PRICLKEN = On ;Primary clock enabled
  FCMEN = OFF ;Fail-Safe Clock Monitor disabled
  IESO = OFF ;Oscillator Switchover mode disabled
  PWRTEN = OFF ;Power up timer disabled
  BOREN = SBORDIS ;Brown-out Reset enabled in hardware only (SBOREN is disabled)
  BORV = 190 ;VBOR set to 1.90 V nominal
  WDTEN = OFF ;Watch dog timer is always disabled. SWDTEN has no effect.
  WDTPS = 32768 ;1:32768
  CCP2MX = PORTC1 ;CCP2 input/output is multiplexed with RC1
  PBADEN = OFF ;PORTB<5:0> pins are configured as digital I/O on Reset
  CCP3MX = PORTB5 ;P3A/CCP3 input/output is multiplexed with RB5
  HFOFST = On ;HFINTOSC output and ready status are not delayed by the oscillator stable status
  T3CMX = PORTC0 ;T3CKI is on RC0
  P2BMX = PORTB5 ;P2B is on RB5
  MCLRE = INTMCLR ;MCLR OFF
  STVREN = OFF ;Stack full/underflow will not cause Reset
  LVP = OFF ;Single-Supply ICSP disabled
  XINST = OFF ;Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
  Debug = OFF ;Disabled
  Cp0 = OFF ;Block 0 (000800-001FFFh) not code-protected
  CP1 = OFF ;Block 1 (002000-003FFFh) not code-protected
  CP2 = OFF ;Block 2 (004000-005FFFh) not code-protected
  CP3 = OFF ;Block 3 (006000-007FFFh) not code-protected
  CPB = OFF ;Boot block (000000-0007FFh) not code-protected
  CPD = OFF ;Data EEPROM not code-protected
  WRT0 = OFF ;Block 0 (000800-001FFFh) not write-protected
  WRT1 = OFF ;Block 1 (002000-003FFFh) not write-protected
  WRT2 = OFF ;Block 2 (004000-005FFFh) not write-protected
  WRT3 = OFF ;Block 3 (006000-007FFFh) not write-protected
  WRTC = OFF ;Configuration registers (300000-3000FFh) not write-protected
  WRTB = OFF ;Boot Block (000000-0007FFh) not write-protected
  WRTD = OFF ;Data EEPROM not write-protected
  EBTR0 = OFF ;Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
  EBTR1 = OFF ;Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
  EBTR2 = OFF ;Block 2 (004000-005FFFh) not protected from table reads executed in other blocks
  EBTR3 = OFF ;Block 3 (006000-007FFFh) not protected from table reads executed in other blocks
  EBTRB = OFF ;Boot Block (000000-0007FFh) not protected from table reads executed in other blocks
Config_End

;**** End of Fuse Configurator Settings ****
;-------------------------------------------------------------------------------

    Declare Xtal = 64

    On_Hardware_Interrupt GoTo _INTERRUPT_HANDLER

    Declare Hserial_Baud = 9600
    Declare Adin_Res 10

TRISA.0 = 1                             ' Setup bit-0 of PortA as an input
TRISC.6 = 1
TRISC.7 = 1

Dim Q0             As Float
Dim Q1             As Float
Dim Q2             As Float

Dim FLOAT_TEMP     As Float
Dim real           As Float
Dim imag           As Float

Dim MAGNITUDE      As Word
Dim MAGNITUDE_0    As Word
Dim MAGNITUDE_1    As Word
Dim MAGNITUDE_2    As Word
Dim MAGNITUDE_3    As Word
Dim MAGNITUDE_4    As Word
Dim MAGNITUDE_5    As Word
Dim MAGNITUDE_6    As Word
Dim MAGNITUDE_7    As Word

Dim MAX_MAG_ROW    As Word
Dim MAX_MAG_COLUMN As Word
Dim ROW            As Byte
Dim COLUMN         As Byte

Dim DTMF_CODE_DETECTED As String * 1

Dim RAW_ADC_READING       As ADRESL.Word                        ' raw AD values read
Dim TIMER1REG             As TMR1L.Word                         '
Dim TIMER0REG             As TMR0L.Word

Dim SAMPLE_NUMBER         As Byte
Dim TMR_8KHZ_ENABLED      As Byte ' BIT
Dim SAMPLE_BANK_COMPLETE  As Byte ' BIT
Dim PROC_SAMP  As Byte
Dim CHECK_FREQ As Byte

Dim delta_x         As Word
Dim x_result        As Word

Dim x_old           As Word
Symbol XMIN         =  200     ' noise level difference: dc / valid signal (0 to 1023)
                               ' adjust this for best results
Dim new_tone        As Byte    ' or bit
Dim start_goertzel  As Byte    ' or bit


Dim gain_cnt            As Byte ' word
Dim gain_calc           As Byte ' bit
Dim high_x              As Word ' initializat ca 0
Dim low_x               As Word ' initializat ca 1023
Dim gain                As Word


Symbol _LED_1 = PORTB.4
Output _LED_1

Symbol _LED_2 = PORTB.7
Output _LED_2

Symbol PEIE    INTCON.6                                 ' Peripheral Interrupt Enable
Symbol GIE     INTCON.7                                 ' Global Interrupt Enable

' declares TMR0
Symbol TMR0ON  T0CON.7
Symbol TMR0IF  INTCON.2
Symbol TMR0IE  INTCON.5
Symbol T08BIT  T0CON.6   ' pentru 25K20 utilizare 8bit sau 16bit Timer

' declares TMR1
Symbol TMR1ON  T1CON.0                                  ' Timer1 ON
Symbol TMR1IF  PIR1.0                                   ' TMR1 interrupt flag
Symbol TMR1IE  PIE1.0                                   ' TMR1 interrupt enable

Symbol FUDGE_FACTOR = 3                                 ' calibrare software pentru timere

' DECLARATII TIMER0  (sint dependente de valoarea Xtal ! )
Symbol _20mS_TICK = 25536 + FUDGE_FACTOR               ' @64 Mhz Xtal :: Calcul valori offset pentru intreruperi de 20mS

' DECLARATII TIMER 1 (sint dependente de valoarea XTAL ! )
Symbol _125uS_TICK = 57536 + FUDGE_FACTOR               ' @64 Mhz Xtal :: Calcul valori offset pentru intreruperi de 125uS
Symbol TMR0_VAL = _20mS_TICK                            '
Symbol _N = 105                                         ' number of samples to collect in a detection bank
'
' Create the actual seriel buffer in high memory
'
Dim SAMPLES_BANK[_N] As Word
'
Symbol PI = 3.14159265358979323                         ' PI constant
Symbol PIx2 = 6.28318530717958                          ' PI * 2


$define INIT_SAMPLING_BANK()    '
    Clear SAMPLE_NUMBER         '
    Clear SAMPLE_BANK_COMPLETE

' The next coeficient values are calculated in the excel file
' for the eight DTMF frequencies needed
' COEFF, SINE and COSINE

'  PRECOMPUTED VALUES ' for Sample rate = 8Khz, bins N=105
'
$define _coeff_0  1.716897587      ' Excel cell: E10
$define _coeff_1  1.652477549      ' Excel cell: F10
$define _coeff_2  1.582142069      ' etc
$define _coeff_3  1.506142932
$define _coeff_4  1.151234131
$define _coeff_5  0.947737325
$define _coeff_6  0.840714457
$define _coeff_7  0.618033989
Dim COEFF          As Float        ' where x_result = 0 to 7
'
$define _sin_0    0.512899277
$define _sin_1    0.563320058
$define _sin_2    0.611724299
$define _sin_3    0.657938726
$define _sin_4    0.817719386
$define _sin_5    0.880595532
$define _sin_6    0.907358695
$define _sin_7    0.951056516
Dim SINE           As Float         ' where x_result = 0 to 7
'
$define _cos_0    0.858448794
$define _cos_1    0.826238774
$define _cos_2    0.791071035
$define _cos_3    0.753071466
$define _cos_4    0.575617066
$define _cos_5    0.473868662
$define _cos_6    0.420357228
$define _cos_7    0.309016994
Dim COSINE         As Float         ' where x_result = 0 to 7
'

    GoTo _OVER_TIMER_HANDLER                               ' Jump over the subroutines
'Small_Micro_Model = On

_INTERRUPT_HANDLER:
    ' Verificare intrerupere overflow Timer1
    If TMR1IF = 1 Then                                 '
        TIMER1REG = _125uS_TICK                        ' reload timer1
        If TMR_8KHZ_ENABLED = 1 Then                   '
            '=====================================================================
            ' Block 1
            '=====================================================================
            'SAMPLES_BANK[SAMPLE_NUMBER] = ADIn 0       ' RAW_ADC_READING    ' read the result
            ' sau citire adc alternativa
            ADCON0.1 = 1                               ' start conversie ADC  (GO/DONE)
            While ADCON0.1 = 1 : Wend                  ' Asteptam citirea valorii ADC
            x_result = RAW_ADC_READING                 ' update var
             
            '===============================================================================
            '                               BLOCK 2
            '                          Signal Detection
            '===============================================================================
            delta_x = x_result - x_old
            x_old = x_result                      ' salvare il valore letto
            ' Verifichiamo se il segnale è al di sopra della soglia di rumore (XMIN )
            If delta_x > XMIN Then                ' **** or ( delta_x < -XMIN ) then
                If new_tone = 1 Then              ' La pausa richiesta tra due toni è trascorsa
                   new_tone = 0                  ' Reimposta new_tone, l'unico modo per eseguire di nuovo questo codice è Se è presente
                                                 ' è uno spazio di 20 ms nel segnale e il timer0 va in overflow.
                   start_goertzel = 1            ' È stato rilevato un nuovo tono, quindi avviare il rilevamento DTMF
                   '
                   high_x = 0                    ' preload initial values for gain calculations
                   low_x  = 1023
                   gain_cnt  = 0                 ' Reset gain counter
                   gain_calc = 1                 ' Posso iniziare un calcolo del guadagno?
                   '
                EndIf
            EndIf
            '===============================================================================
            '                               BLOCK 3
            '                        Automatic Gain Control
            '===============================================================================
            ' Inizialmente, il segnale in ingresso passa attraverso il guadagno successivo
            ' routine di calcolo. Il guadagno si ottiene dividendo il massimo
            ' possibile ampiezza del segnale dalla massima ampiezza di ingresso del
            ' tono attuale. Questo produce un guadagno che moltiplicato per il
            ' il segnale in ingresso aumenterà l'ampiezza del segnale fino a raggiungere il limite massimo
            ' scala. Inoltre la routine trova la grandezza di input più bassa, che lo farà
            ' essere utilizzato per calcolare il valore medio del segnale per la rimozione della polarizzazione CC.
           
            '===============================================================================
            If start_goertzel = 1 Then                          ' New tone detected
                 If gain_calc = 1 Then                          ' Gain calculation phase
                    '
                    If x_result > high_x Then
                        high_x = x_result                       ' Find maximum value
                    Else
                       If x_result < low_x Then
                           low_x = x_result                     ' Find minimum value
                       EndIf
                    EndIf
                    '
                    Inc gain_cnt '

                    If  gain_cnt >= 70 Then
                        gain_cnt  = 0                           ' Reset gain counter
                        gain_calc = 0                           ' Reset gain calculation flag
                        ' compute gain
                        gain =  1024 / (high_x - low_x)         ' 1024 daca e 10 biti A/D
                        ' low_x will contain the average value
                        low_x = low_x + ((high_x - low_x) >> 1) ' low_x += (high_x - low_x)>>1'

                    EndIf
                Else                                            ' Gain calculation completed
                    ' !!! debug
                    If x_result < low_x Then                    ' *** for 10 bit A/D
                       x_result = 0
                    Else
                       x_result = (x_result - low_x) * gain     ' Scale input to nearly full scale of ADC0, And remove DC bias
                    EndIf
                EndIf
                '==========================================================================
                ' Block 4
                '==========================================================================
                SAMPLES_BANK[SAMPLE_NUMBER] = x_result     ' RAW_ADC_READING
                '
                Inc SAMPLE_NUMBER
                If SAMPLE_NUMBER = _N Then
                    SAMPLE_BANK_COMPLETE = 1               ' notificam terminarea unei transe de sampling
                    TMR_8KHZ_ENABLED = 0                   ' disable 8khz timer
                    TMR1IE   = 0                           ' and disable interrupts pe TMR1
                    start_goertzel = 0                     ' oprim starea de analiza semnal
                    TIMER0REG = TMR0_VAL                   ' reload TMR0 registers (TMR0L and TMR0H)
                    TMR0IE = 1                             ' enable Timer de pauza intre semnale
                    TMR0ON = 1                             ' start timer 0
                EndIf
           EndIf
           '===============================================================================
        EndIf
        Clear TMR1IF                                   ' clear timer1 int flag so we wait for new interrup
    EndIf
    '
    ' Verificare intrerupere overflow Timer0
    If TMR0IF = 1 Then                                 ' TMR0IF
       TMR0ON = 0                                      ' stop the timer
       'TIMER0REG = TMR0_VAL              ' reload TMR0 registers (TMR0L and TMR0H)
       TMR0IE = 0                                      ' si facem si disable la intreruperi pe TMR0
       new_tone = 1                                    ' anuntam ca se poate face procesare Goertzel
       TMR_8KHZ_ENABLED = 1                   ' enable 8khz timer
       TMR1IE = 1  ' re-enable timer 1
       Clear TMR0IF                                    ' resetam steagul de intrerupere pe TMR0
    EndIf
    '
    Retfie Fast                                        ' Exit from the interrupt, restoring the WREG, STATUS, and BSR registers
'------------------------------------------------------------------------------
ENABLE_TMR1_INTERRUPT Macro                            ' activare intrerupere TMR1
    GoSub _ENABLE_TMR1
    Endm
#ifdef ENABLE_TMR1_INTERRUPT#REQ
_ENABLE_TMR1:
    INTCON = 0
    INTCON.7 = 1                                       ' Enable General Interrupts
    INTCON.6 = 1                                       ' Enable Peripherals Interrupts
    TMR1IF   = 0                                       ' Clear TMR1 Interrupt Flag
    TMR1IE   = 1                                       ' TMR1 Interrupt Enable
    Return
#endif
'
DISABLE_TMR1_INTERRUPT Macro                           ' dezactivare intrerupere TMR1
    GoSub _DISABLE_TMR1
    Endm
#ifdef DISABLE_TMR1_INTERRUPT#REQ
_DISABLE_TMR1:
    'INTCON.7 = 0                                       ' Disable General Interrupts
    'INTCON.6 = 0                                       ' Disable Peripherals Interrupts
    'TMR1IF   = 0
    TMR1IE   = 0                                        ' TMR1 Interrupt Disable
    Return
#endif

'------------------------------------------------------------------------------
_OVER_TIMER_HANDLER:

' INITIALIZARE
'
SETARE_TIMER:
    ' setare TMR0
    T0CON  = %00000100             ' TMR0 cu 1:32 prescaler
    T08BIT = 0                     ' TMR0 declarat de tip 16bit
    TMR0ON = 0                     ' TMR0 oprit
    TMR0IF = 0                     ' clear TMR0 int flag
    TMR0IE = 0                     ' TMR0 Int Disabled

    ' setare TMR1
    T1CON   = %10000000            ' SET UP TMR1 TO HAVE 1:1 PRESCALER AND ACT AS A TIMER
    TMR1IF  = 0                    ' CLEAR TMR1 INTERRUPT FLAG
    TMR1IE  = 1                    ' ENABLE TMR1 AS PERIPHERAL INTERRUPT SOURCE
    TMR1ON  = 0                    ' TMR1 not running

SETARE_ADC:
     ADCON2 = %10101111       ' Right justify, FRC and 12 TAD ACQ time
     ADCON1 = 0               ' Vref = VDD/VSS
     ADCON0.0 = 1             ' Turn ON the AD module
     ANSELA.0 = 1             ' set RA.0 to Analog Input
'-------------------------------------------------------------------------------
    ' Initializare variabile

    new_tone = 1                       '  siamo nel periodo di analisi (= 1, cioè stiamo aspettando un nuovo segnale)
    x_result = 0
    x_old = 0
    delta_x = 0
    start_goertzel = 0
'===============================================================================
    TMR1ON = 0                                  ' TMR1 is off
    INIT_SAMPLING_BANK()                        ' prepare sampling related flags, counters....
    Clear TIMER1REG                             '
    TIMER1REG = _125uS_TICK                     ' incarcare TMR1 cu valoarea "tick" de 125uS
    TMR_8KHZ_ENABLED = 1                        ' enable counter timer de 8khz
    ENABLE_TMR1_INTERRUPT                       ' activam intreruperi pe TMR1 overflow
    TMR1ON = 1                                  ' start TMR1
'================================================================================

MAIN_LOOP:
    '
    While 1 = 1                                 '  in attesa del banco di campionamento Per completare
          '
          'HRSOut "ADC Value = ", Dec x_result , 13, 10
          'DelayMS 100
         
          If SAMPLE_BANK_COMPLETE = 1 Then      ' ora possiamo iniziare i calcoli per ogni frequenza

                 ' proviamo l'elaborazione per tutte e 8 le frequenze
                 For CHECK_FREQ = 0 To 7         ' number of bins - 1
                     Select CHECK_FREQ
                            Case 0
                                 COEFF  = _coeff_0
                                 SINE   = _sin_0
                                 COSINE = _cos_0
                            Case 1
                                 COEFF = _coeff_1
                                 SINE   = _sin_1
                                 COSINE = _cos_1
                            Case 2
                                 COEFF = _coeff_2
                                 SINE   = _sin_2
                                 COSINE = _cos_2
                            Case 3
                                 COEFF = _coeff_3
                                 SINE   = _sin_3
                                 COSINE = _cos_3
                            Case 4
                                 COEFF = _coeff_4
                                 SINE   = _sin_4
                                 COSINE = _cos_4
                            Case 5
                                 COEFF = _coeff_5
                                 SINE   = _sin_5
                                 COSINE = _cos_5
                            Case 6
                                 COEFF = _coeff_6
                                 SINE   = _sin_6
                                 COSINE = _cos_6
                            Case 7
                                 COEFF = _coeff_7
                                 SINE   = _sin_7
                                 COSINE = _cos_7
                     EndSelect
                     ' procesare Q0, Q1, Q2
                     Q1 = 0                                ' start with these two cleared
                     Q2 = 0
                     PROC_SAMP = 0                         ' incepem cu elementul 0
                     '
                     Repeat
                         Q0 = COEFF  * Q1
                         FLOAT_TEMP = Q2 + SAMPLES_BANK[PROC_SAMP]
                         Q0 = Q0 - FLOAT_TEMP
                         Q2 = Q1
                         Q1 = Q0
                         Inc  PROC_SAMP
                     Until PROC_SAMP = _N
                     '
                     Nop
                     ' now get the magnitude
                     ' varianta complexa cu imaginar & real
                     FLOAT_TEMP = Q2 * COSINE
                     real = Q1 - FLOAT_TEMP
                     imag = Q2 * SINE
                     real = real * real
                     imag = imag * imag
                     FLOAT_TEMP = real + imag
                     MAGNITUDE = Sqr  FLOAT_TEMP
                     '
                     Select CHECK_FREQ
                            Case 0
                                 MAGNITUDE_0 = MAGNITUDE
                                 If MAGNITUDE_0 > MAX_MAG_ROW Then
                                    MAX_MAG_ROW = MAGNITUDE
                                    ROW = 0
                                 EndIf
                            Case 1
                                 MAGNITUDE_1 = MAGNITUDE
                                 If MAGNITUDE_1 > MAX_MAG_ROW Then
                                    MAX_MAG_ROW = MAGNITUDE
                                    ROW = 1
                                 EndIf
                            Case 2
                                 MAGNITUDE_2 = MAGNITUDE
                                 If MAGNITUDE_2 > MAX_MAG_ROW Then
                                    MAX_MAG_ROW = MAGNITUDE
                                    ROW = 2
                                 EndIf
                            Case 3
                                 MAGNITUDE_3 = MAGNITUDE
                                 If MAGNITUDE_3 > MAX_MAG_ROW Then
                                    MAX_MAG_ROW = MAGNITUDE
                                    ROW = 3
                                 EndIf
                            Case 4
                                 MAGNITUDE_4 = MAGNITUDE
                                 If MAGNITUDE_4 > MAX_MAG_COLUMN Then
                                    MAX_MAG_COLUMN = MAGNITUDE
                                    COLUMN = 4
                                 EndIf
                            Case 5
                                 MAGNITUDE_5 = MAGNITUDE
                                 If MAGNITUDE_5 > MAX_MAG_COLUMN Then
                                    MAX_MAG_COLUMN = MAGNITUDE
                                    COLUMN = 5
                                 EndIf
                            Case 6
                                 MAGNITUDE_6 = MAGNITUDE
                                 If MAGNITUDE_6 > MAX_MAG_COLUMN Then
                                    MAX_MAG_COLUMN = MAGNITUDE
                                    COLUMN = 6
                                 EndIf
                            Case 7
                                 MAGNITUDE_7 = MAGNITUDE
                                 If MAGNITUDE_7 > MAX_MAG_COLUMN Then
                                    MAX_MAG_COLUMN = MAGNITUDE
                                    COLUMN = 7
                                 EndIf
                     EndSelect
                 Next
                 '
                 GoSub DECODE_DTMF_CHAR    ' decodificam caracterul DTMF receptionat
                 ' debug
                 HRSOut "DTMF CHAR = ", DTMF_CODE_DETECTED, 13, 10   'seriale pin 17
                 '
                 SAMPLE_BANK_COMPLETE = 0      ' resettare il flag di stato
          EndIf
    Wend

(* Stiamo cercando le due frequenze trovate con la magnitudine più alta
ROW     = FRECVENTA_1
COLUMN  = FRECVENTA_2
*)
DECODE_DTMF_CHAR:
    If ROW = 0 Then
            Select COLUMN
                Case 4
                    DTMF_CODE_DETECTED = "1"
                Case 5
                    DTMF_CODE_DETECTED = "2"
                Case 6
                    DTMF_CODE_DETECTED = "3"
                Case 7
                    DTMF_CODE_DETECTED = "A"
            EndSelect
       ElseIf ROW = 1 Then
            Select COLUMN
                Case 4
                    DTMF_CODE_DETECTED = "4"
                Case 5
                    DTMF_CODE_DETECTED = "5"
                Case 6
                    DTMF_CODE_DETECTED = "6"
                Case 7
                    DTMF_CODE_DETECTED = "B"
            EndSelect
       ElseIf ROW = 2 Then
            Select COLUMN
                Case 4
                    DTMF_CODE_DETECTED = "7"
                Case 5
                    DTMF_CODE_DETECTED = "8"
                Case 6
                    DTMF_CODE_DETECTED = "9"
                Case 7
                    DTMF_CODE_DETECTED = "C"
            EndSelect
       ElseIf ROW = 3 Then
            Select COLUMN
                Case 4
                    DTMF_CODE_DETECTED = "*"
                Case 5
                    DTMF_CODE_DETECTED = "0"
                Case 6
                    DTMF_CODE_DETECTED = "#"
                Case 7
                    DTMF_CODE_DETECTED = "D"
            EndSelect
    EndIf
    Return