News:

Let's find out together what makes a PIC Tick!

Main Menu

DTMF decoding in software

Started by charliecoutas, Sep 22, 2021, 10:34 AM

Previous topic - Next topic

charliecoutas

I need to decode the DTMF dial tones from an old 1960's push-button phone. I was hoping it would change the dial button presses in pulses, but it sends DTMF down the line. Has anybody ever decoded these tones in software? Otherwise I'll hack into the phone, but a soft solution would be neater.

Charlie

top204

#1
Take a look in the compiler's "C:\Users\User Name\PDS\Samples24\" folder Charlie.

"DTMF_Decoder_Mk2.bas" and "DTMF_Decoder_GLCD.bas". I think they are, essentially, the same programs, but it has been so long since I created them I do not know what differences they have to each other. :-)

They use the DSP mechanism on a dsPIC33 device as a BandPass filter and implement an FFT for the DTMF decoding, again, using the DSP mechanism for extreme speed and accuracy, and I remember it worked extremely well. I had a fascination with the DSP mechanism once, and created all sorts of programs with it so I could understand it better for the compiler.

On the old forum, I created a Wiki entry for the DTMF transmitter using a standard 18F device and outputting the sine waves from a CCP pin using PWM, and the decoder using the above codes.


John Drew

#3
Hi Charlie, sounds much more fun to use a software approach as in Les's samples folder but CPR suggestion re the MT8870 is an easy option. I use them in my latest repeater controller and they are very good.
Let us know how you get on with the software approach.

By the way you can buy DTMF modules for a pittance from eBay. As an experiment I bought a few but found them a bit iffy.
Buying the chips from a reputable retailer and putting them on a board gave me 100% success. I think the modules must use the sweepings from the factory floor.
All the best
John

PS the mt8870 outputs a tone detect which can run an interrupt so you don't miss tones. I find I can run 17ms tone and 17ms of silence between to get multiple decodes from Audacity of 30 characters with no misses.

top204

#4
If you also want to experiment with DTMF decoding using an 8-bit device, here is some code I wrote back in 2014 for the PIC18F25K20 device, using my fixed point FFT routine. If I remember correctly, it worked well, but was a bit slow if the DTMF tones were sent to it too fast, but it was fun to write at the time. I've converted the code to use Procedures.

I also created the 8-bit DTMF decoder using an interrupt and a double buffer to over-sample the ADC values in the background, and I think it worked a bit better. I'll see if I can find it on my drives.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' DTMF decoder using 8-bit FFT
' Written for the Positron8 compiler using a PIC18F25K20 device, operating at 64MHz
'
' Written by Les Johnson 23-04-2014
'
    Include "Amicus18.inc"                  ' Configure for the Amicus18 device (18F25K20) running at 64MHz
    Include "Amicus18_ADC.inc"              ' Load the Amicus18 ADC routines into the program

$define FFT_Use_Sqr                         ' Instruct the FFT routine to use more refined real and imaginary combining
$define FFT_Elements 128                    ' The amount of elements for the FFT routine to work on
    Include "FFT.inc"                       ' Load the 8-bit FFT routines into the program
'
' Create variables
'
    Dim DTMF_bIndex As Byte                      ' General purpose index variable
    Dim DTMF_bTemp As Byte
    Dim DTMF_wTimeout As Word

    Dim DTMF_bRowBin As Byte
    Dim DTMF_bColBin As Byte
    Dim DTMF_bKeyValue As Byte
    Dim DTMF_bLargest As Byte

    Dim DTMF_bOverSample As Byte
    Dim DTMF_bAccumArray[64] As Byte Heap

'-------------------------------------------------------------------------
' The main program loop starts here
'
Main:
'
' Open the ADC for samples taken on AN0
' Left justify for 8-bit result
'
    OpenADC(ADC_FOSC_32 & ADC_LEFT_JUST & ADC_2_TAD, ADC_REF_VDD_VSS, ADC_1ANA)
    Do                                          ' Create an infinite loop
        '
        ' Get a key value
        '
        DTMF_bKeyValue = DTMF_Get(10000)
        If DTMF_bKeyValue = "*" Then
            HRSOut 13
        Else
            HRSOut DTMF_bKeyValue
        EndIf
        '
        ' Look for an off period
        '
        DTMF_WaitOff(10000)
    Loop

'-------------------------------------------------------------------------
' Find the highest row peak within the FFT array
' Input     : DTMF_bAccumArray
' Output    : DTMF_bRowBin holds the bin that contains the largest value
' Notes     : Only scans the first half of the array because this is where the row frequencies are
'
Proc FindRowBin()
    DTMF_bLargest = 0
    DTMF_bRowBin = 0
    DTMF_bIndex = 0
    Repeat
        DTMF_bTemp = DTMF_bAccumArray[DTMF_bIndex]
        If DTMF_bTemp > DTMF_bLargest Then
            DTMF_bLargest = DTMF_bTemp
            DTMF_bRowBin = DTMF_bIndex
        EndIf
        Inc DTMF_bIndex
    Until DTMF_bIndex >= 36
EndProc

'-------------------------------------------------------------------------
' Find the highest column peak within the FFT array
' Input     : DTMF_bAccumArray
' Output    : DTMF_bColBin holds the bin that contains the largest value
' Notes     : Only scans the second half of the array because this is where the column frequencies are
'
Proc FindColBin()
    DTMF_bLargest = 0
    DTMF_bColBin = 0
    DTMF_bIndex = 36
    Repeat
        DTMF_bTemp = DTMF_bAccumArray[DTMF_bIndex]
        If DTMF_bTemp > DTMF_bLargest Then
            DTMF_bLargest = DTMF_bTemp
            DTMF_bColBin = DTMF_bIndex
        EndIf
        Inc DTMF_bIndex
    Until DTMF_bIndex >= cFFT_NumberOfRealElements
EndProc

'-------------------------------------------------------------------------
' Decode the bin values into key values
' Input     : DTMF_bRowBin holds the row bin from the FFT array
'           : DTMF_bColBin holds the column bin from the FFT array
' Output    : DTMF_bKeyValue holds the key value (in ASCII)
' Notes     :
'
Proc DTMF_Decode()
    FindRowBin()
    FindColBin()
    'HRSOut "Row ", Dec DTMF_bRowBin, ", Col ", Dec DTMF_bColBin, 13
    DTMF_bKeyValue = 0                                  ' Default to an unknown key
    Select DTMF_bRowBin
        Case 22 To 24                                   ' Is the row frequency 697Hz?
            Select DTMF_bColBin
                Case 40 To 42                           ' Is the column frequency 1209KHz?
                    DTMF_bKeyValue = "1"
                Case 44 To 46                           ' Is the column frequency 1336KHz?
                    DTMF_bKeyValue = "2"
                Case 49 To 51                           ' Is the column frequency 1477KHz?
                    DTMF_bKeyValue = "3"
                Case 54 To 56                           ' Is the column frequency 1633KHz?
                    DTMF_bKeyValue = "A"
            EndSelect
        Case 25 To 27                                   ' Is the row frequency 770Hz?
            Select DTMF_bColBin
                Case 40 To 42                           ' Is the column frequency 1209KHz?
                    DTMF_bKeyValue = "4"
                Case 44 To 46                           ' Is the column frequency 1336KHz?
                    DTMF_bKeyValue = "5"
                Case 49 To 51                           ' Is the column frequency 1477KHz?
                    DTMF_bKeyValue = "6"
                Case 54 To 56                           ' Is the column frequency 1633KHz?
                    DTMF_bKeyValue = "B"
            EndSelect
        Case 28 To 30                                   ' Is the row frequency 853Hz?
            Select DTMF_bColBin
                Case 40 To 42                           ' Is the column frequency 1209KHz?
                    DTMF_bKeyValue = "7"
                Case 44 To 46                           ' Is the column frequency 1336KHz?
                    DTMF_bKeyValue = "8"
                Case 49 To 51                           ' Is the column frequency 1477KHz?
                    DTMF_bKeyValue = "9"
                Case 54 To 56                           ' Is the column frequency 1633KHz?
                    DTMF_bKeyValue = "C"
            EndSelect
        Case 31 To 33                                   ' Is the row frequency 941Hz?
            Select DTMF_bColBin
                Case 40 To 42                           ' Is the column frequency 1209KHz?
                    DTMF_bKeyValue = "*"
                Case 44 To 46                           ' Is the column frequency 1336KHz?
                    DTMF_bKeyValue = "0"
                Case 49 To 51                           ' Is the column frequency 1477KHz?
                    DTMF_bKeyValue = "#"
                Case 54 To 56                           ' Is the column frequency 1633KHz?
                    DTMF_bKeyValue = "D"
            EndSelect
    EndSelect
EndProc

'-------------------------------------------------------------------------
' Fill the real array with 8-bit ADC values
' And perform an FFT on the result
' Oversampled into array DTMF_bAccumArray
'
Proc DTMF_GetTones()
    Repeat                                                      ' Create an over sample loop
        DTMF_bIndex = 0                                         ' \
        Repeat                                                  ' / Create aloop to gather ADC samples
            ADCON0bits_GO_DONE = 1                              ' \ Get an ADC reading
            While ADCON0bits_GO_DONE = 1 : Wend                 ' /
            FFT_bRealData[DTMF_bIndex] = ADRESH                 ' Place the ADC value into the array
            DelayUS 250                                         ' Delay for a sample rate of 4KHz (FFT range 0 to 2KHz)
            Inc DTMF_bIndex
        Until DTMF_bIndex >= cFFT_NumberOfSamples               ' Repeat for all the samples
        '
        ' Display the sine wave on a serial terminal for debugging
        '
        'HRSOut 1
        'DTMF_bIndex = 0
        'Repeat
        '    DTMF_bTemp = FFT_bRealData[DTMF_bIndex]
        '    HRSOut Dec2 DTMF_bTemp, "|", Rep "*"\DTMF_bTemp, 13
        '    Inc DTMF_bIndex
        'Until DTMF_bIndex >= cFFT_NumberOfSamples              ' Until all the elements are read

        Fix_FFT()                                               ' Perform the FFT (Result in array FFT_bRealData)
        '
        ' Accumulate the FFT results in array DTMF_bAccumArray
        '
        DTMF_bIndex = 0                                         ' \
        Repeat                                                  ' / Create a loop to transfer the FFT result to the accumulation array
            DTMF_bTemp = FFT_bRealData[DTMF_bIndex]             ' Get a byte from the FFT array
            DTMF_bAccumArray[DTMF_bIndex] = DTMF_bAccumArray[DTMF_bIndex] + DTMF_bTemp ' Add it to the accumulator array
            Inc DTMF_bIndex
        Until DTMF_bIndex >= cFFT_NumberOfRealElements          ' Until all the elements are read
        Dec DTMF_bOverSample                                    ' Decrement the over sa,ple counter
    Until DTMF_bOverSample = 0                                  ' Until all the oversampling is done
    '
    ' Display the sine wave on a serial terminal for debugging
    '
    'HRSOut 1
    'DTMF_bIndex = 0
    'Repeat
    '    DTMF_bTemp = DTMF_bAccumArray[DTMF_bIndex]
    '    HRSOut Dec2 DTMF_bIndex, " |", Rep "*"\DTMF_bTemp, 13
    '    Inc DTMF_bIndex
    'Until DTMF_bIndex >= cFFT_NumberOfRealElements           ' Until all the elements of the FFT are read
EndProc

'-------------------------------------------------------------------------
' Wait for a tone
' Input     : pTimeout holds a counter value for a timeout loop
' Output    : Carry flag set if timed out
' Notes     : Each iteration of the loop takes approx 73ms,
'           : so the timeout time must be multiplied by this value for the actual timeout time
'
Proc DTMF_Wait(pTimeout As DTMF_wTimeout), STATUSbits_C
    Clear DTMF_bAccumArray                      ' Clear the over sample array
    Repeat                                      ' Create a loop
        DTMF_bOverSample = 2                    ' Indicate that we require 2 over samples
        DTMF_GetTones()                         ' Get the tone
        DTMF_Decode()                           ' Decode the tone
        Dec pTimeout                            ' Decrement the timeout value
        If pTimeout = 0 Then                    ' Has the timeout reached 0?
            Set Result                          ' Yes. So set the carry flag to indicate timed out
            ExitProc                            ' Exit the procedure
        EndIf
    Until DTMF_bKeyValue <> 0                   ' Keep looking until a key is detected
    Clear Result                                ' Clear the carry flag to indicate no timeout
EndProc

'-------------------------------------------------------------------------
' Detect and decode a tone
' Input     : pTimeout holds a counter value for a timeout loop
' Output    : DTMF_bKeyValue holds the ASCII key value
'           : Carry flag Set if timed out
' Notes     : Each iteration of the loop takes approx 73ms,
'           : so the timeout time must be multiplied by this value for the actual timeout time
'
Proc DTMF_Get(pTimeout As DTMF_wTimeout), DTMF_bKeyValue
    DTMF_Wait(pTimeout)
    If STATUSbits_C = 0 Then
        Repeat                                  ' Create a loop
            DTMF_bOverSample = 2                ' Indicate that we require 2 over samples
            DTMF_GetTones()                     ' Get the tone
            DTMF_Decode()                       ' Decode the tone
            Dec pTimeout                        ' Decrement the timeout value
            If pTimeout = 0 Then                ' Has the timeout reached 0?
                Set STATUSbits_C                ' Yes. So set the carry flag to indicate timed out
                ExitProc                        ' Exit the procedure
            EndIf
        Until DTMF_bKeyValue <> 0               ' Keep looking until a key is detected
        Clear STATUSbits_C                      ' Clear the carry flag to indicate no timeout
    Else
        Set STATUSbits_C                        ' Set the carry flag to indicate timed out
    EndIf
EndProc

'-------------------------------------------------------------------------
' Detect an off time
' Input     : pTimeout holds a counter value for a timeout loop
' Output    : Carry flag Set if timed out
' Notes     : Each iteration of the loop takes approx 73ms,
'           : so the timeout time must be multiplied by this value for the actual timeout time
'
Proc DTMF_WaitOff(pTimeout As DTMF_wTimeout), STATUSbits_C
    Clear DTMF_bAccumArray                      ' Clear the over sample array
    Repeat                                      ' Create a loop
        DTMF_bOverSample = 2                    ' Indicate that we require 2 over samples
        Clear DTMF_bAccumArray                  ' Clear the over sample array
        DTMF_GetTones()                         ' Get the tone
        DTMF_Decode()                           ' Decode the tone
        Dec pTimeout                            ' Decrement the timeout value
        If pTimeout = 0 Then                    ' Has the timeout reached 0?
            Set STATUSbits_C                    ' Yes. So set the carry flag to indicate timed out
            ExitProc                            ' Exit the procedure
        EndIf
    Until DTMF_bKeyValue = 0                    ' Keep looking until no key is detected
    Clear STATUSbits_C                          ' Clear the carry flag to indicate no timeout
EndProc

The FFT.inc code is listed below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Positron8 BASIC compiler 8-bit fixed point FFT routine
' For use with 18F devices only.
'
' Written by Les Johnson 02-02-2012
'
$ifndef FFT_Elements
    $define FFT_Elements 64                         ' Default elements if no define is used in the main program
$endif
'
' Trap the element value
'
$if (FFT_Elements <> 128) And (FFT_Elements <> 64) And (FFT_Elements <> 32) And (FFT_Elements <> 16)
    $error "Incorrect amount of elements. 128, 64, 32 or 16 required"
$endif

    $define FFT_NWave $eval FFT_Elements * 2        ' Calculate the size of the Sin and Cos waves required
'
' Create variables used for the merging of imaginary and real data
'
    Dim FFT_wReal As Word Access
    Dim FFT_wImaginary As Word Access
    Dim FFT_bImaginary As FFT_wImaginary.Byte0
    Dim FFT_bReal As FFT_wReal.Byte0
'
' Create variables used by Fix_FFT
' Made mostly Access types so they stay is bankless RAM for speed
'
    Dim FFT_bTemp1 As Byte Access
    Dim FFT_bTemp2 As Byte Access
    Dim FFT_bTemp3 As Byte Access
    Dim FFT_bTemp4 As Byte Access

    Dim FFT_bM As Byte Access
    Dim FFT_bMR As Byte Access
    Dim FFT_bJ As Byte Access
    Dim FFT_bL As Byte Access
    Dim FFT_bK As Byte Access

    Dim FFT_bIstep As Byte Access
    Dim FFT_bIndex As Byte Access

    Dim FFT_bQR As Byte Access
    Dim FFT_bQI As Byte Access
    Dim FFT_bTempReal As Byte Access
    Dim FFT_bTempImaginary As Byte Access
    Dim FFT_bWR As Byte Access
    Dim FFT_bWI As Byte Access

    Dim FFT_bRealData[FFT_Elements] As Byte Heap        ' Holds the real data
    Dim FFT_bImaginaryData[FFT_Elements] As Byte Heap   ' Holds the imaginary data

    Dim FXPM_wPROD As PRODL.Word                        ' Make a 16-bit variable from PRODL\H

    $define cFFT_NumberOfSamples FFT_Elements          ' The amount of elements in the FFT
    $define cFFT_NumberOfRealElements $eval FFT_Elements / 2 ' The amount of real elements after the FFT has performed its task
    $define cNWave_Div4 $eval FFT_NWave / 4                  ' Length of SineWave divided by 4

$if FFT_NWave = 256
    $define cLog2_Nwave 8                               ' Log2(256)
$elseif FFT_NWave = 128
    $define cLog2_Nwave 7                               ' Log2(128)
$elseif FFT_NWave = 64
    $define cLog2_Nwave 6                               ' Log2(64)
$elseif FFT_NWave = 32
    $define cLog2_Nwave 5                               ' Log2(32)
$elseif FFT_NWave = 16
    $define cLog2_Nwave 4                               ' Log2(16)
$endif

'--------------------------------------------------------------------------------
' 8-bit Signed Fixed-Point multiplication & scaling.
' Input     : pMultiplier holds the 8-bit value to multiply
'           : pMultiplicand holds the 8-Bit value to multiply with
' Output    ' pResult holds the resulting product of the multiplication
' Notes     : Scaling ensures that result remains within 16-bits.
'           : The code is rolled out for speed instead of using loops
'
$define Fixed_Multiply(pMultiplier, pMultiplicand, pResult) '
    If pMultiplicand = 0 Then       '
        pResult = 0                 '
    ElseIf pMultiplier = 0 Then     '
        pResult = 0                 '
    Else                            '
        Movf pMultiplier,w,0        '
        Mulwf pMultiplicand,0       '
        Btfsc pMultiplicand,7,0     '
        Subwf PRODH,f,0             '
        Btfss pMultiplier,7,0       '
        Bra $ + 6                   '
        Movf pMultiplicand,w,0      '
        Subwf  PRODH,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Movlw  3                    '
        Andwf  PRODH,f,0            '
        WREG = PRODL & 1            '
        Bcf    STATUS,C,0           '
        Rrcf   PRODH,f,0            '
        Rrcf   PRODL,f,0            '
        Btfsc  PRODH,6,0            '
        Bsf    PRODH,7,0            '
        pResult = FXPM_wPROD + WREG '
    EndIf

'-------------------------------------------------------------------------
' Create 8-bit sine wave data
'
$if FFT_NWave = 256
    Dim SineWave As Flash8 = {000, -002, -003, -005, -006, -008, -009, -011,
                              -012, -014, -016, -017, -019, -020, -022, -023,
                              -024, -026, -027, -029, -030, -032, -033, -034,
                              -036, -037, -038, -039, -041, -042, -043, -044,
                              -045, -046, -047, -048, -049, -050, -051, -052,
                              -053, -054, -055, -056, -056, -057, -058, -059,
                              -059, -060, -060, -061, -061, -062, -062, -062,
                              -063, -063, -063, -064, -064, -064, -064, -064,
                              -064, -064, -064, -064, -064, -064, -063, -063,
                              -063, -062, -062, -062, -061, -061, -060, -060,
                              -059, -059, -058, -057, -056, -056, -055, -054,
                              -053, -052, -051, -050, -049, -048, -047, -046,
                              -045, -044, -043, -042, -041, -039, -038, -037,
                              -036, -034, -033, -032, -030, -029, -027, -026,
                              -024, -023, -022, -020, -019, -017, -016, -014,
                              -012, -011, -009, -008, -006, -005, -003, -002,
                              000, 002, 003, 005, 006, 008, 009, 011,
                              012, 014, 016, 017, 019, 020, 022, 023,
                              024, 026, 027, 029, 030, 032, 033, 034,
                              036, 037, 038, 039, 041, 042, 043, 044,
                              045, 046, 047, 048, 049, 050, 051, 052,
                              053, 054, 055, 056, 056, 057, 058, 059,
                              059, 060, 060, 061, 061, 062, 062, 062,
                              063, 063, 063, 064, 064, 064, 064, 064,
                              064, 064, 064, 064, 064, 064, 063, 063,
                              063, 062, 062, 062, 061, 061, 060, 060,
                              059, 059, 058, 057, 056, 056, 055, 054,
                              053, 052, 051, 050, 049, 048, 047, 046,
                              045, 044, 043, 042, 041, 039, 038, 037,
                              036, 034, 033, 032, 030, 029, 027, 026,
                              025, 023, 022, 020, 019, 017, 016, 014,
                              012, 011, 009, 008, 006, 005, 003, 002}
$elseif FFT_NWave = 128
    Dim SineWave As Flash8 = {000, -003, -006, -009, -012, -016, -019, -022,
                              -024, -027, -030, -033, -036, -038, -041, -043,
                              -045, -047, -049, -051, -053, -055, -056, -058,
                              -059, -060, -061, -062, -063, -063, -064, -064,
                              -064, -064, -064, -063, -063, -062, -061, -060,
                              -059, -058, -056, -055, -053, -051, -049, -047,
                              -045, -043, -041, -038, -036, -033, -030, -027,
                              -024, -022, -019, -016, -012, -009, -006, -003,
                              000, 003, 006, 009, 012, 016, 019, 022,
                              024, 027, 030, 033, 036, 038, 041, 043,
                              045, 047, 049, 051, 053, 055, 056, 058,
                              059, 060, 061, 062, 063, 063, 064, 064,
                              064, 064, 064, 063, 063, 062, 061, 060,
                              059, 058, 056, 055, 053, 051, 049, 047,
                              045, 043, 041, 038, 036, 033, 030, 027,
                              025, 022, 019, 016, 012, 009, 006, 003}
$elseif FFT_NWave = 64
    Dim SineWave As Flash8 = {000, -006, -012, -019, -024, -030, -036, -041,
                              -045, -049, -053, -056, -059, -061, -063, -064,
                              -064, -064, -063, -061, -059, -056, -053, -049,
                              -045, -041, -036, -030, -024, -019, -012, -006,
                              000, 006, 012, 019, 024, 030, 036, 041,
                              045, 049, 053, 056, 059, 061, 063, 064,
                              064, 064, 063, 061, 059, 056, 053, 049,
                              045, 041, 036, 030, 025, 019, 012, 006}
$elseif FFT_NWave = 32
    Dim SineWave As Flash8 = {000, -012, -024, -036, -045, -053, -059, -063,
                              -064, -063, -059, -053, -045, -036, -024, -012,
                              000, 012, 024, 036, 045, 053, 059, 063,
                              064, 063, 059, 053, 045, 036, 025, 012}
$elseif FFT_NWave = 16
    Dim SineWave As Flash8 = {000, -024, -045, -059, -064, -059, -045, -024,
                              000, 024, 045, 059, 064, 059, 045, 025}
$else
    $error "Unknown FFT_NWave length"
$endif
'-------------------------------------------------------------------------
' Create 8-bit cosine wave data
'
$if FFT_NWave = 256
    Dim CosineWave As Flash8 = {064, 064, 064, 064, 064, 064, 063, 063,
                                063, 062, 062, 062, 061, 061, 060, 060,
                                059, 059, 058, 057, 056, 056, 055, 054,
                                053, 052, 051, 050, 049, 048, 047, 046,
                                045, 044, 043, 042, 041, 039, 038, 037,
                                036, 034, 033, 032, 030, 029, 027, 026,
                                024, 023, 022, 020, 019, 017, 016, 014,
                                012, 011, 009, 008, 006, 005, 003, 002,
                                000, -002, -003, -005, -006, -008, -009, -011,
                                -012, -014, -016, -017, -019, -020, -022, -023,
                                -024, -026, -027, -029, -030, -032, -033, -034,
                                -036, -037, -038, -039, -041, -042, -043, -044,
                                -045, -046, -047, -048, -049, -050, -051, -052,
                                -053, -054, -055, -056, -056, -057, -058, -059,
                                -059, -060, -060, -061, -061, -062, -062, -062,
                                -063, -063, -063, -064, -064, -064, -064, -064,
                                -064, -064, -064, -064, -064, -064, -063, -063,
                                -063, -062, -062, -062, -061, -061, -060, -060,
                                -059, -059, -058, -057, -056, -056, -055, -054,
                                -053, -052, -051, -050, -049, -048, -047, -046,
                                -045, -044, -043, -042, -041, -039, -038, -037,
                                -036, -034, -033, -032, -030, -029, -027, -026,
                                -024, -023, -022, -020, -019, -017, -016, -014,
                                -012, -011, -009, -008, -006, -005, -003, -002,
                                000, 002, 003, 005, 006, 008, 009, 011,
                                012, 014, 016, 017, 019, 020, 022, 023,
                                024, 026, 027, 029, 030, 032, 033, 034,
                                036, 037, 038, 039, 041, 042, 043, 044,
                                045, 046, 047, 048, 049, 050, 051, 052,
                                053, 054, 055, 056, 056, 057, 058, 059,
                                059, 060, 060, 061, 061, 062, 062, 062,
                                063, 063, 063, 064, 064, 064, 064, 064}
$elseif FFT_NWave = 128
    Dim CosineWave As Flash8 = {064, 064, 064, 063, 063, 062, 061, 060,
                                059, 058, 056, 055, 053, 051, 049, 047,
                                045, 043, 041, 038, 036, 033, 030, 027,
                                024, 022, 019, 016, 012, 009, 006, 003,
                                000, -003, -006, -009, -012, -016, -019, -022,
                                -024, -027, -030, -033, -036, -038, -041, -043,
                                -045, -047, -049, -051, -053, -055, -056, -058,
                                -059, -060, -061, -062, -063, -063, -064, -064,
                                -064, -064, -064, -063, -063, -062, -061, -060,
                                -059, -058, -056, -055, -053, -051, -049, -047,
                                -045, -043, -041, -038, -036, -033, -030, -027,
                                -024, -022, -019, -016, -012, -009, -006, -003,
                                000, 003, 006, 009, 012, 016, 019, 022,
                                024, 027, 030, 033, 036, 038, 041, 043,
                                045, 047, 049, 051, 053, 055, 056, 058,
                                059, 060, 061, 062, 063, 063, 064, 064}
$elseif FFT_NWave = 64
    Dim CosineWave As Flash8 = {064, 064, 063, 061, 059, 056, 053, 049,
                                045, 041, 036, 030, 024, 019, 012, 006,
                                000, -006, -012, -019, -024, -030, -036, -041,
                                -045, -049, -053, -056, -059, -061, -063, -064,
                                -064, -064, -063, -061, -059, -056, -053, -049,
                                -045, -041, -036, -030, -024, -019, -012, -006,
                                000, 006, 012, 019, 024, 030, 036, 041,
                                045, 049, 053, 056, 059, 061, 063, 064}
$elseif FFT_NWave = 32
    Dim CosineWave As Flash8 = {064, 063, 059, 053, 045, 036, 024, 012,
                                000, -012, -024, -036, -045, -053, -059, -063,
                                -064, -063, -059, -053, -045, -036, -024, -012,
                                000, 012, 024, 036, 045, 053, 059, 063}
$elseif FFT_NWave = 16
    Dim CosineWave As Flash8 = {064, 059, 045, 024, 000, -024, -045, -059,
                                -064, -059, -045, -024, 000, 024, 045, 059}
$else
    $error "Unknown FFT_NWave length"
$endif

'-------------------------------------------------------------------------
' Perform a forward Fast Fourier Transform
' Input     : Array FFT_bRealData holds the 8-bit ADC samples to perform the FFT upon
' Output    : Array FFT_bRealData holds real data from the FFT
'           : Array FFT_bImaginaryData holds imaginary data from the FFT
' Notes     : None
'
Proc Fix_FFT()
    Clear FFT_bImaginaryData                    ' Clear the imaginary data array
'
' Decimation in time - re-order data
'
    FFT_bMR = 0
    FFT_bM = 1
    Repeat
        FFT_bL = cFFT_NumberOfSamples
        Repeat
            FFT_bL = FFT_bL >> 1
            FFT_bTemp1 = FFT_bMR + FFT_bL
        Until FFT_bTemp1 < cFFT_NumberOfSamples
        FFT_bTemp1 = FFT_bL - 1
        FFT_bMR = FFT_bMR & FFT_bTemp1
        FFT_bMR = FFT_bMR + FFT_bL
        If FFT_bMR > FFT_bM Then
            FFT_bTempReal = FFT_bRealData[FFT_bM]
            FFT_bRealData[FFT_bM] = FFT_bRealData[FFT_bMR]
            FFT_bRealData[FFT_bMR] = FFT_bTempReal
        EndIf
        Inc FFT_bM
    Until FFT_bM >= cFFT_NumberOfSamples
'
' Perform the FFT
'
    FFT_bL = 1
    FFT_bK = cLog2_Nwave - 1
    While FFT_bL < cFFT_NumberOfSamples
        FFT_bIstep = FFT_bL << 1
        FFT_bM = 0
        Repeat
            FFT_bJ = FFT_bM << FFT_bK
            FFT_bWR = CRead8 CosineWave[FFT_bJ]                     ' Cosine
            FFT_bWI = CRead8 SineWave[FFT_bJ]                       ' Sine
            FFT_bIndex = FFT_bM
            Repeat
                FFT_bJ = FFT_bIndex + FFT_bL

                FFT_bTemp3 = FFT_bRealData[FFT_bJ]
                FFT_bTemp4 = FFT_bImaginaryData[FFT_bJ]

                Fixed_Multiply(FFT_bWR, FFT_bTemp3 , FFT_bTemp1)    ' 8-bit Fixed point multiply into FFT_bTemp1
                Fixed_Multiply(FFT_bWI, FFT_bTemp4 , FFT_bTemp2)    ' 8-bit Fixed point multiply into FFT_bTemp2
                FFT_bTempReal = FFT_bTemp1 - FFT_bTemp2

                Fixed_Multiply(FFT_bWR, FFT_bTemp4 , FFT_bTemp1)    ' 8-bit Fixed point multiply into FFT_bTemp1
                Fixed_Multiply(FFT_bWI, FFT_bTemp3 , FFT_bTemp2)    ' 8-bit Fixed point multiply into FFT_bTemp2
                FFT_bTempImaginary = FFT_bTemp1 + FFT_bTemp2

                FFT_bQR = FFT_bRealData[FFT_bIndex]
                FFT_bQR = FFT_bQR.SByte / 2                         ' Reduce the chance of overflow
                FFT_bQI = FFT_bImaginaryData[FFT_bIndex]
                FFT_bQI = FFT_bQI.SByte / 2                         ' Reduce the chance of overflow
                FFT_bRealData[FFT_bJ] = FFT_bQR - FFT_bTempReal
                FFT_bImaginaryData[FFT_bJ] = FFT_bQI - FFT_bTempImaginary
                FFT_bRealData[FFT_bIndex] = FFT_bQR + FFT_bTempReal
                FFT_bImaginaryData[FFT_bIndex] = FFT_bQI + FFT_bTempImaginary
                FFT_bIndex = FFT_bIndex + FFT_bIstep
            Until FFT_bIndex >= cFFT_NumberOfSamples
            Inc FFT_bM
        Until FFT_bM >= FFT_bL
        FFT_bK = FFT_bK - 1
        FFT_bL = FFT_bIstep
    Wend
' Fall through to "ConvToReal"
'-------------------------------------------------------------
' Convert the real and imaginary arrays to a single real array
' Input     : Array FFT_bRealData holds the real data from the FFT
'           : Array FFT_bImaginaryData holds the imaginary data from the FFT
' Output    : Results are placed back into the 'FFT_bRealData' array
' Notes     : There are two ways to compute the real part:
'           : 1). Find the real value by the square root of the squares of real and imaginary values
'           : 2). Find the real value by summing the real and imaginary parts (quicker, but dirty)
'           : Also removed the first element which is the DC component
'
ConvToReal:
    FFT_bIndex = 0
    Repeat
        FFT_bReal = FFT_bRealData[FFT_bIndex]                       ' Extract a value from the real array
        FFT_bReal = Abs(FFT_bReal)                                  ' Make sure it's positive
        FFT_bImaginary = FFT_bImaginaryData[FFT_bIndex]             ' Extract a value from the imaginary array
        FFT_bImaginary = Abs(FFT_bImaginary)                        ' Make sure it's positive
        '
        ' Quick and dirty method
        '
$ifndef FFT_Use_Sqr
        FFT_bRealData[FFT_bIndex] = FFT_bReal + FFT_bImaginary      ' Place the summed value back into the RealData array (quick and dirty method)
$else
        '
        ' Slower but more refined method (also traps zero values for extra speed)
        '
        If FFT_bImaginary = 0 Then                                  ' Is FFT_bImaginary zero?
            If FFT_bReal = 0 Then                                   ' Yes. So is FFT_bReal zero?
                FFT_bRealData[FFT_bIndex] = 0                       ' Yes. So place a zero into the array
            Else                                                    ' Otherwise...
                FFT_wReal = FFT_bReal * FFT_bReal                   ' Square the real value
                FFT_bRealData[FFT_bIndex] = ISqr FFT_wReal          ' Find the square root of real
            EndIf
        ElseIf FFT_bReal = 0 Then                                   ' Otherwise. Is FFT_bReal zero?
            If FFT_bImaginary = 0 Then                              ' Yes. So is FFT_bImaginary zero?
                FFT_bRealData[FFT_bIndex] = 0                       ' Yes. So place a zero into the array
            Else                                                    ' Otherwise...
                FFT_wImaginary = FFT_bImaginary * FFT_bImaginary    ' Square the imaginary value
                FFT_bRealData[FFT_bIndex] = ISqr FFT_wImaginary     ' Find the square root of imaginary
            EndIf
        Else                                                        ' Otherwise. None are zero
            FFT_wImaginary = FFT_bImaginary * FFT_bImaginary        ' Square the imaginary value
            FFT_wReal = FFT_bReal * FFT_bReal                       ' Square the real value
            FFT_bRealData[FFT_bIndex] = ISqr FFT_wImaginary + FFT_wReal ' Find the square root of real and imaginary
        EndIf
$endif
        Inc FFT_bIndex
    Until FFT_bIndex >= cNWave_Div4
    FFT_bRealData#0 = 0                                             ' Remove the DC content from the first element
EndProc
'-------------------------------------------------------------------------
_FFT_Main_:

charliecoutas

Les, CPR, John: Many thanks, I'm looking over your ideas......

charliecoutas

On dismantling the phone I found a switch: DTMF or PULSE dialling! So the problem is solved. To explain what the phones are going to be used for.

We are creating some Escape Rooms at the museum. Dotted around each room are telephones from the period of the room, so the Colossus Gallery will have 1940's bakelite phones. A clue might suggest that a certain number is dialled, and the phone responds by playing an appropriate audio track. I have made a generic pcb (using an 18F26K22) which handles the pulse dialling, plays dial tones, ring tones and all the pre-recoded message we will use. My software is written so that I don't need to alter the phones, they should all work without me knowing what phone is attached. The audio is recorded on a micro-SD card, which is played by a DF Player (it took some time to make that behave itself). Great fun.

So thanks guys, and Les, I will design a MK2 board using a 33dsPIC instead of the 18F26K22 so I can use your DTMF decoding software; it is bound the happen that we get a phone that can't do pulse dialling.

If anybody wants to decode the pulse dialling of the UK phone system, let me know and I'll give you my code. Perhaps a WIKI article would be of interest?

Thanks again guys, you help is much appreciated.

Charlie

The museum is the national museum of computing, at Bletchley Park:  www.tnmoc.org

top204

#7
You are welcome Charlie.

The pulse/DTMF switches on the older phones were common, because DTMF dialing was a new thing and not all GPO exchanges had the capability of it.

John Lawton

Quote from: top204 on Sep 23, 2021, 01:05 PMYou are welcome Charlie.

The pulse/DTMF switches on the older phones were common, because DTMF dialing was a new thing and not all GPO switching buildings had the capability of it.
I have one word for you: Strowger
The noise in one of those exchanges from the uniselectors was quite something.
Now available on eBay...

top204

#9
You are absolutely correct John.

We had a GPO exchange in Shields when I was a young-un, and when you passed it, you could hear the noise inside the building. :-) I remember Dad bringing home some surplus uniselector units from a, soon to be demolished, building he was bricking up when I was a boy, and I used them for a multi-coloured disco light driver. LOL

charliecoutas

We use uni-selectors (similar but smaller to Strowger selectors) on Colossus. The electrical noise from them is frightening. How those guys in 1943 merged high current solenoids with sensitive valve circuits is amazing.

The phone works with only 8 volts across it, pulse or tone dialling.

Charlie

CPR

Quote from: charliecoutas on Sep 22, 2021, 07:26 PMThe museum is the national museum of computing, at Bletchley Park:  www.tnmoc.org


Interesting! Just had a look at the website  :)