At the beginning of this chapter the functions and procedures used in this chapter will be described together with some other useful functions and procedures. Table 141 presents a list of the functions and procedures including a description of their parameters, results (functions only), and eventual restrictions on the parameter values.
Prototype  sub function FIR_Radix(dim FilterOrder as Word, dim ptrCoeffs as LongInt, dim BuffLength as Word, dim ptrInput as Word, dim Index as Word) as Word 
Description 
This function applies FIR filter to ptrInput. Input samples must be in Y data space.FilterOrder is order of the filter + 1.ptrCoeffs is address of filter coeffitients in program memory.BuffLength represents number of samples ptrInput points to.ptrInput is address of input samples. Index index of current sample.

Returns 
sum(k=0..N1)(coef[k]*input[Nk])  Current sample of processed signal(B[n]) N  buffer length k  Current index 
Prototype  sub function IIR_Radix(dim BScale as Integer, dim AScale as Integer, dim ptrB as Word, dim ptrA as Word, dim FilterOrder as Word, dim ptrInput as Word, dim Input_Len as Word, dim ptrOutput as Word, dim Index as Word) as Word 
Description 
This function applies IIR filter to ptrInput. Input and output samples must be in Y data space.AScale A Scale factorBScale B Scale factorptrB Address of B coefficients (In program memory)ptrA Address of A coefficients (In program memory)FilterOrder is order of the filter + 1.ptrInput is address of input samples. Input_Len represents number of samples ptrInput points to.ptrOutput is address of output samples. Output length is equal to Input length. Index index of current sample.

Returns  y[n]=sum(k=0..N)(Acoef[k]*x[nk])  sum(k=1..M)(Bcoef[k]*y[nk]) 
Prototype  sub procedure FFT(dim log2N as word, dim TwiddleFactorsAddress as LongInt, dim byref Samples as word[1024]) 
Description 
Function applies FFT transformation to input samples, input samples must be in Y data space.N  buffer length (must be the power of 2).TwiddleFactorsAddress is address of costant array which contains complex twiddle factors.The array is expected to be in program memory.Samples array of input samples.Upon completion complex array of FFT samples is placed in the Samples parameter. 
Returns 
F(k) = 1/N*sum_n (f(n)*WN(kn)), WN(kn) = exp[(j*2*pi*k*n)/N] Fn  array of complex input samples n in {0, 1,... , N1}, and k in {0, 1,... , N1}, with N = 2^m, m element of Z. WN  TwiddleFactors The amplitude of current FFT sample is calculated as: F[k]=sqrt(Re[k]^2+ Im[k]^2) 
Note 
Complex array of FFT samples is placed in Samples parameter. Input Samples are arranged in manner Re,Im,Re,Im... (where Im is always zero). Output samples are arranged in the same manner but Im parts are different from zero. Output samples are symmetrical (First half of output samples (index from 0 to N/2) is identical to the second half of output samples(index from N/2 to N). Input data is a complex vector such that the magnitude of the real and imaginary parts of each of its elements is less than 0.5. If greater or equal to this value the results could produce saturation. Note that the output values are scaled by a factor of 1/N, with N the length of the FFT. input is expected in natural ordering, while output is produced in bit reverse ordering. 
Prototype  sub procedure IFFT(dim log2N as word, dim TwiddleFactorsAddress as LongInt, dim byref FftSamples as word[1024]) 
Description 
Function applies IFFT transformation to input samples, input samples must be in Y data space.N  buffer length (must be the power of 2).TwiddleFactorsAddress is address of costant array which contains complex twiddle factors.The array is expected to be in program memory.Samples array of input samples.Upon completion complex array of IFFT samples is placed in the Samples parameter. 
Operation 
f(k) = 1/N*sum_n (F(n)*WN(kn)), WN(kn) = exp[(j*2*pi*k*n)/N] Fn  array of complex input samples n in {0, 1,... , N1}, and k in {0, 1,... , N1}, with N = 2^m, m element of Z. WN  TwiddleFactors 
Note 
Complex array of IFFT samples is placed in Samples parameter. Input Samples are arranged in manner Re,Im,Re,Im... (where Im is always zero). Input data is a complex vector such that the magnitude of the real and imaginary parts of each of its elements is less than 0.5. If greater or equal to this value the results could produce saturation. Note that the output values are scaled by a factor of 1/N, with N the length of the IFFT. Input is expected in bit reverse ordering, while output is produced in natural ordering. 
Prototype  sub procedure BitReverseComplex(dim log2N as word, dim byref ReIm as word[1024]) 
Description 
This function does Complex (inplace) Bit Reverse reorganization.N  buffer length (must be the power of 2).ReIm  Output Sample(from FFT).

Note  Input samples must be in Y data space. 
Prototype  sub procedure Vector_Set(dim byref input as word[1024], dim size, value as word) 
Description 
Sets size elements of input to value , starting from the first element.Size must be > 0. Length of input is limited by available ram

Prototype  sub function VectorPower(dim N as word, dim byref Vector as word[1024]) as word 
Description  Function returns result of power value (powVal) in radix point 1.15 
Operation  powVal = sum (srcV[n] * srcV[n]) with n in {0, 1,... , numElems1} 
Input 
N = number of the elements in vector(s) (numElems)Vector = ptr to source vector (srcV)

Note 
AccuA used, not restored CORCON saved, used, restored 
Prototype  sub procedure Vector_Subtract(dim byref dest, v1, v2 as word[1024], dim numElems as word) 
Description 
This procedure does substraction of two vectors. numElems must be less or equal to minimum size of two vectors.v1  First Vectorv2  Second Vectordest  Result Vector

Operation 
dstV[n] = srcV1[n]  srcV2[n] with n in {0, 1,... , numElems1} 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure VectorScale(dim N as word, dim ScaleValue as integer, dim byref SrcVector, DestVector as word[1024]) 
Description 
This procedure does vector scaling with scale value.N  Buffer lengthSrcVector  original vectorDestVector  scaled vectorScaleValue  Scale Value

Operation  dstV[n] = sclVal * srcV[n], with n in {0, 1,... , numElems1} 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Vector_Negate(dim byref srcVector, DestVector as word[1024], dim numElems as word) 
Description 
This procedure does negation of vector.srcVector  Original vector destVector  Result vector numElems  Number of Elements

Operation  dstV[n] = (1)*srcV1[n] + 0, 0 <= n < numElems 
Note 
Negate of 0x8000 is 0x7FFF. AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Vector_Multiply(dim byref v1, v2, dest as word[1024], dim numElems as word) 
Description 
This procedure does multiplication of two vectors.numElems must be less or equal to minimum size of two vectors.v1  First Vectorv2  Second Vectordest  Result Vector

Operation 
dstV[n] = srcV1[n] * srcV2[n] with n in {0, 1,... , numElems1} 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub function Vector_Min(dim byref Vector as word[1024], dim numElems as word, dim byref MinIndex as word) as word 
Description 
This function find min. value in vector.Vector  Original vector.numElems  Number of elementsMinIndex  Index of minimum value

Operation 
minVal = min {srcV[n], n in {0, 1,...numElems1} if srcV[i] = srcV[j] = minVal, and i < j, then minIndex = j 
Returns  minimum value (minVal) 
Prototype  sub function Vector_Max(dim byref Vector as word[1024], dim numElems as word, dim byref MaxIndex as word) as word 
Description 
This function find max. value in vector.Vector  Original vector.numElems  Number of elementsMaxIndex  Index of maximum value

Operation 
maxVal = max {srcV[n], n in {0, 1,...numElems1} } if srcV[i] = srcV[j] = maxVal, and i < j, then maxIndex = j 
Returns  maximum value (maxVal) 
Prototype  sub function Vector_Dot(dim byref v1, v2 as word[1024], dim numElems as word) as word 
Description  Procedure calculates vector dot product.v1  First vector.v2  Second vectornumElems  Number of elements 
Operation  dotVal = sum (srcV1[n] * srcV2[n]), with n in {0, 1,... , numElems1} 
Note  AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Vector_Correlate(dim byref v1, v2, dest as word[1024], dim numElemsV1, numElemsV2 as word) 
Description 
Procedure calculates Vector correlation (using convolution).v1  First vector.v2  Second vectornumElemsV1  Number of first vector elementsnumElemsV2  Number of second vector elementsdest  Result vector

Operation 
r[n] = sum_(k=0:N1){x[k]*y[k+n]}, where: x[n] defined for 0 <= n < N, y[n] defined for 0 <= n < M, (M <= N), r[n] defined for 0 <= n < N+M1. 
Prototype  sub procedure Vector_Convolve(dim byref v1, v2, dest as word[1024], dim numElemsV1, numElemsV2 as word) 
Description 
Procedure calculates Vector using convolution.v1  First vector.v2  Second vectornumElemsV1  Number of first vector elementsnumElemsV2  Number of second vector elementsdest  Result vector

Operation 
y[n] = sum_(k=0:n){x[k]*h[nk]}, 0 <= n < M y[n] = sum_(k=nM+1:n){x[k]*h[nk]}, M <= n < N y[n] = sum_(k=nM+1:N1){x[k]*h[nk]}, N <= n < N+M1 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Vector_Add(dim byref dest, v1, v2 as word[256], dim numElems as word) 
Description 
Procedure calculates vector addition.v1  First vector.v2  Second vectornumElemsV1  Number of vector elementsdest  Result vector

Operation 
dstV[n] = srcV1[n] + srcV2[n], with n in {0, 1,... , numElems1} 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Matrix_Transponse(dim byref src, dest as word[1024], dim numRows, numCols as word) 
Description 
Procedure does matrix transposition.src  Original matrix.dest  Result matrixnumRows  Number of matrix rowsnumCols  Number of matrix columns

Operation  dstM[i][j] = srcM[j][i] 
Prototype  sub procedure Matrix_Subtract(dim byref src1, src2, dest as word[1024], dim numRows, numCols as word) 
Description 
Procedure does matrix substraction.src1  First matrix.src2  Second matrixdest  Result matrixnumRows  Number of matrix rowsnumCols  Number of matrix columns

Operation  dstM[i][j] = srcM1[i][j]  srcM2[i][j] 
Note 
AccuA used, not restored. AccuB used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Matrix_Scale(dim ScaleValue as word, dim byref src1, dest as word[1024], dim numRows, numCols as word) 
Description 
Procedure does matrix scale.ScaleValue  Scale Valuesrc1  Original matrixdest  Result matrixnumRows  Number of matrix rowsnumCols  Number of matrix columns

Operation  dstM[i][j] = sclVal * srcM[i][j] 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Matrix_Multiply(dim byref src1, src2, dest as word[256], dim numRows1, numCols2, numCols1Rows2 as word) 
Description 
Procedure does matrix multiply.src1  First Matrixsrc2  Second Matrixdest  Result MatrixnumRows1  Number of first matrix rowsnumCols2  Number of second matrix columnsnumCols1Rows2  Number of first matrix columns and second matrix rows

Operation 
dstM[i][j] = sum_k(srcM1[i][k]*srcM2[k][j]), with i in {0, 1, ..., numRows11} j in {0, 1, ..., numCols21} k in {0, 1, ..., numCols1Rows21} 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
Prototype  sub procedure Matrix_Add(dim byref src1, src2, dest as word[1024], dim numRows, numCols as word) 
Description 
Procedure does matrix addition.src1  First Matrixsrc2  Second Matrixdest  Result MatrixnumRows1  Number of first matrix rowsnumCols2  Number of second matrix columns

Operation  dstM[i][j] = srcM1[i][j] + srcM2[i][j] 
Note 
AccuA used, not restored. CORCON saved, used, restored. 
The example shows a method of formation of a menu with options on an LCD in 4bit mode. Setting of bit 1 on port F means go to the next option, whereas setting of bit 0 on port F means go to the previous option. The rate of transition to the next/previous option is limited so the maximum of 4 transitions per second is allowed.
' This project is designed to work with PIC P30F6014A. It has been tested ' on dsPICPRO3 development system with 10.0 MHz crystal and 8xPLL. ' It should work with any other crystal. ' Note: the maximum operating frequency for dsPIC is 120MHz. ' With minor adjustments, this example should work with any other dsPIC MCU program MenuTest1 dim menu_index as short menu as String[5][6] absolute $1880 ' Array[0..4] of String[6] ' Directive absolute specifies ' the starting address in RAM for a variable. main: ADPCFG = $FFFF ' Configure PORTB as digital TRISF = $FFFF ' Configure PORTF as input (menu control) menu_index = 0 ' Init menu_item[0] menu[0] = "First" ' Menu items menu[1] = "Second" menu[2] = "Third" menu[3] = "Fourth" menu[4] = "Fifth" Lcd_Init_DsPicPro3() ' Init LCD in 4bit mode for dsPICPRO3 board ' Note: GLCD/LCD Setup routines are in the setup library files located in the Uses folder ' These routines will be moved into AutoComplete in the future. Lcd_Cmd(LCD_CURSOR_OFF) Lcd_Cmd(LCD_FIRST_ROW) Lcd_Out(1,1,"Menu :") Lcd_Out(1,8,menu[menu_index]) ' Show menu element on LCD while true ' endless loop if PORTF.1 = 1 then ' Detect logical one on RF1 pin => MENU UP menu_index = menu_index+1 ' Next index in menu if menu_index>4 then menu_index = 0 ' Circular menu end if Lcd_Out(1,8, " ") ' Clear text Lcd_Out(1,8,menu[menu_index]) ' Show menu element on LCD Delay_ms(250) ' No more than 4 changes per sec end if if PORTF.0 = 1 then ' Detect logical one on RF0 pin => MENU DOWN menu_index = menu_index1 ' Previous index in menu if menu_index<0 then menu_index = 4 ' Circular menu end if Lcd_Out(1,8, " ") ' Clear text Lcd_Out(1,8,menu[menu_index]) ' Show menu element on LCD Delay_ms(250) ' No more than 4 changes per sec end if wend end.
The example shows a method of generation of DTMF signal. The following assumptions have been adopted:
The algorithm of generating the signal is the following. On the basis of the calculated frequencies the square signal is sent first. Then, this signal is filtered by a low pass IIR filter of the third order (4 coefficients). The output signal specifications are:
The algorithm is shown in Fig. 141.
Fig. 141 Algorithm for generation of DTMF signal
Digital filter is lowpass Chebyshev type 1 filter. Wp is 1700 Hz, Ap is 3dB, sampling frequency is 20kHz. Third order fiter can meet the requirements:
a) level of all harmonics is 20dB below the useful signal:
Harmonic nearest to the useful signal is 3xf1. f1 is lowest frequency of useful signal, f1=697 Hz. Fourier series of square wave signal implies that amplitude of 3xf1=2091 Hz harmonic is 1/3 of f1signal amplitude. So we already have 9.5dB. In IIR frequency window we can see that 3xf1 frequency is 12dB weaker than useful signal. The most critical harmonic 3xf1 is 9.5dB 12dB = 21.5dB weaker than useful signal – so this requirement is fullfilled.
b) the level of the signal of the higher frequency is 0 to 3dB higher compared to the signal of the lower frequency:
IIR frequency window also shows that low frequencies 697941Hz filter attenuation is in 3dB to 2.8dB range and low frequencies 12091633Hz filter attenuation is in –1.8dB to 0dB range. So any higher frequency signal is 1 to 3dB stronger than any lower frequency signal.
Filter Designer Tool Window 1.
Filter Designer Tool Window 2.
Fig. 142 shows the generated signal. The signals of the lower and higher frequencies are added, with the higher frequency signal 3dB below the lower frequency signal.
Fig. 142 Generated signal.
Fig. 143 Spectrum of the signal of Fig. 142
Fig. 144 Filtered signal
Fig. 145 Spectrum of the signal after filtering (output signal)
For generation of the filter coefficients use the Filter Designer Tool which is a constituent part of mikroBasic for dsPIC.
The program for generation of the described signal is as follows:
' This project is designed to work with PIC P30F6014A. It has been tested ' on dsPICPRO3 board with 10.0 MHz crystal and 8xPLL. It should work with any ' other crystal. Note: the maximum operating frequency for dsPIC is 120MHz. ' With minor adjustments, this example should work with any other dsPIC MCU ' ' Onboard DAC module ' Enable SPI connection to DAC on SW4 and DAC's Load(LD) and Chip Select(CS) pins on SW3. program DTMFout ' *** Filter Designer Tool outputs *** ' const BUFFER_SIZE = 8 FILTER_ORDER = 3 COEFF_B as integer[FILTER_ORDER+1] = (0x21F3, 0x65DA, 0x65DA, 0x21F3) COEFF_A as integer[FILTER_ORDER+1] = (0x2000, 0xB06D, 0x47EC, 0xE8B6) SCALE_B = 6 SCALE_A = 2 ' *** DAC pinout *** ' const LOAD_PIN = 2 ' DAC load pin const CS_PIN = 1 ' DAC CS pin dim THalf_Low, THalf_High as word ' halfperiods of low and highfrequency square signals char2send as byte ' char recived from UART sample, sending_ch_cnt as word ' digital signal sample, sending char counter us_cntL, us_cntH as word ' low and highfrequency square signal microseconds counters input_ as integer[BUFFER_SIZE] ' filter input signal (two square signals) output as integer[BUFFER_SIZE] ' filtered signal sample_index as word ' index of current sample voltageL, voltageH as integer ' square signals amplitudes sub procedure InitMain() LATC.CS_PIN = 1 ' set DAC CS to inactive LATC.LOAD_PIN = 0 ' set DAC LOAD to inactive TRISC.LOAD_PIN = 0 ' configure DAC LOAD pin as output TRISC.CS_PIN = 0 ' configure DAC CS pin as output ' Initialize SPI2 module Spi2_Init_Advanced(_SPI_MASTER, _SPI_16_BIT, _SPI_PRESCALE_SEC_1, _SPI_PRESCALE_PRI_1, _SPI_SS_DISABLE, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_ACTIVE_2_IDLE) Uart1_Init(9600) ' Initialize UART1 module end sub sub procedure DAC_Output(dim valueDAC as word) LATC.CS_PIN = 0 ' CS enable for DAC ' filter output range is 16bit number DAC input range is 12bit number valueDAC = valueDAC >> 4 ' now both numbers are 12bit but filter output is signed and DAC input is unsigned. ' Half of DAC range 4096/2=2048 is added to correct this valueDAC = valueDAC + 2048 SPI2BUF = 0x3000 or valueDAC ' write valueDAC to DAC (0x3 is required by DAC) while (SPI2STAT.1 = 1) ' wait for SPI module to finish sending nop wend LATC.CS_PIN = 1 ' CS disable for DAC end sub sub procedure SetPeriods(dim ch as word) ' DTMF frequencies: ' ' 1209 Hz 1336 Hz 1477 Hz 1633 Hz ' 697 Hz 1 2 3 A ' 770 Hz 4 5 6 B ' 852 Hz 7 8 9 C ' 941 Hz * 0 # D ' Calculate halfperiods in microseconds ' example: 1/697Hz = 0.001435 seconds = 1435 microseconds ' 1435/2 = 717 select case ch case 49 THalf_Low=717 THalf_High=414 '1' case 50 THalf_Low=717 THalf_High=374 '2' case 51 THalf_Low=717 THalf_High=339 '3' case 65 THalf_Low=717 THalf_High=306 'A' case 52 THalf_Low=649 THalf_High=414 '4' case 53 THalf_Low=649 THalf_High=374 '5' case 54 THalf_Low=649 THalf_High=339 '6' case 66 THalf_Low=649 THalf_High=306 'B' case 55 THalf_Low=587 THalf_High=414 '7' case 56 THalf_Low=587 THalf_High=374 '8' case 57 THalf_Low=587 THalf_High=339 '9' case 67 THalf_Low=587 THalf_High=306 'C' case 42 THalf_Low=531 THalf_High=414 '*' case 48 THalf_Low=531 THalf_High=374 '0' case 35 THalf_Low=531 THalf_High=339 '#' case 68 THalf_Low=531 THalf_High=306 'D' end select end sub sub procedure ClearBufs() 'Clear buffers Vector_Set(input_, BUFFER_SIZE, 0) Vector_Set(output, BUFFER_SIZE, 0) end sub sub procedure Timer1Int org $1A ' interrupt frequency is 20kHz ' calculate sample sample = voltageL + voltageH ' add voltages input_[sample_index] = sample ' write sample to input buffer ' update lowfrequency square signal microseconds counter us_cntL = us_cntL + 50 ' since us_cntL and THalf_Low are in microseconds ' and Timer1 interrupt occures every 50us ' increment us_cntL by 50 if us_cntL > THalf_Low then ' halfperiod exceeded, change sign voltageL = voltageL us_cntL = us_cntL  THalf_Low ' subtract halfperiod end if ' update highfrequency square signal microseconds counter us_cntH = us_cntH + 50 if us_cntH > THalf_High then voltageH = voltageH us_cntH = us_cntH  THalf_High end if 'IIR(amp), filtering new sample sample = IIR_Radix(SCALE_B, SCALE_A, @COEFF_B, @COEFF_A, FILTER_ORDER+1, @input_, BUFFER_SIZE, @output, sample_index) DAC_Output(sample) ' send sample to digitaltoanalog converter output[sample_index] = sample ' write filtered sample in output buffer Inc(sample_index) ' increment sample index, prepare for next sample if sample_index = BUFFER_SIZE then sample_index = 0 end if Dec(sending_ch_cnt) ' decrement char sending counter ' (character transmition lasts 90ms = 1800 samples) if sending_ch_cnt = 0 then ' if character transmition is over T1CON=0 ' turn off Timer1 Delay_ms(200) ' pause between two characters is 200ms end if IFS0.3 = 0 ' clear Timer1 interrupt flag end sub '  main  ' main: InitMain() ' perform initializations sending_ch_cnt = 0 ' reset counter sample_index = 0 ' initialize sample index ' Clear interrupt flags IFS0 = 0 IFS1 = 0 IFS2 = 0 INTCON1 = $8000 ' disable nested interrupts IEC0 = $0008 ' enable Timer1 interrupt ' Timer1 input clock is Fosc/4. Sampling frequency is 20kHz. Timer should ' raise interrupt every 50 microseconds. PR1 = (Fosc[Hz]/4) / 20000Hz = Fosc[kHz]/(4*20) PR1 = Clock_kHz() div 80 ' Note: interrupt routine execution takes ~10us while true if (sending_ch_cnt = 0) and ' check if sending of previous character is over (Uart1_Data_Ready() = 1) then ' check if character arrived via UART1 char2send = Uart1_Read_Char() ' read data from UART and store it SetPeriods(char2send) ' set periods for low and highfrequency square signals ClearBufs() ' clear input and output buffers ' digital filter computing error is smaller for signals of higher amplitudes ' so signal amplitude should as high as possible. The highest value for ' signed integer type is 0x7FFF but since we are adding 2 signals we must ' divide it by 2. voltageH = $7FFF div 2 ' highfrequency square signal amplitude voltageL = $7FFF div 2 ' lowfrequency square signal amplitude us_cntL = 0 ' lowfrequency square signal microseconds counter us_cntH = 0 ' highfrequency square signal microseconds counter ' start Timer T1 sending_ch_cnt = 1800 ' character tansmition lasts 90ms = 1800 samples * 50us T1CON = $8000 ' enable Timer1 (TimerOn, prescaler 1:1) end if wend end.
The example shows a method of detecting DTMF signal.
The following detection algorithm has been applied.
The level of input signal denoting the presence of DTMF signal is awaited. Frequency estimation over 1024 samples is performed. The sampling frequency is 20kHz. In other words, the process of estimation lasts approximately 50ms. The minimum length of one character is 65ms. The minimum pause after one character is 80ms. For this reason each estimation is followed by an 80ms pause, and then the next character is awaited.
The estimate of the signal frequency can be performed by counting zero crossings, as shown in Fig. 146.
Fig. 146 DTMF signal before filtering
Before zero crossing estimation algorithm can be performed signal must be filtred to separate low frequency from high frequency.
The signal is then put through a lowpass IIR filter of the 4th order having stopband corner frequency 1200Hz whereby all frequencies except the lower frequency are suppressed.
By counting zero crossings of the filtered signal the estimation of the frequency of the lower frequency signal is performed.
The signal is also put through a highpass IIR filter of the 4th order having stopband corner frequency 950Hz whereby all frequencies except the higher frequency are suppressed. The signal after filtering is shown in Fig. 147.
Fig. 147 DTMF signal after filtering
By counting zero crossings of the filtered signal the estimation of the frequency of the higher frequency signal is performed. After the frequencies have been obtaind, the sent character is obtained simply by comparison.
For generation of the filter coefficients one can use the Filter Designer Tool which is a constituent part of mikroBasic for dsPIC. Settings of Filter Designer Tool for LowPass and HighPass filter are given below:
IIR lowpass filter settings
IIR lowpass frequency window
IIR highpass filter settings
IIR highpass frequency window
Implementation of the described algorithm:
' This project is designed to work with PIC P30F6014A. It has been tested ' on dsPICPRO3 board with 10.0 MHz crystal and 8xPLL. It should work with any ' other crystal. Note: the maximum operating frequency for dsPIC is 120MHz. ' With minor adjustments, this example should work with any other dsPIC MCU program DTMFin ' *** DAC pinout *** ' const LOAD_PIN = 2 ' DAC load pin const CS_PIN = 1 ' DAC CS pin const ' Filter setup: ' Filter kind: IIR ' Filter type: lowpass filter ' Filter order: 4 ' Design method: Chebyshev type II BUFFER_SIZE = 8 FILTER_ORDER = 4 BPF1_COEFF_B as Integer[FILTER_ORDER+1] = ( 0x1BD7, 0xAB5D, 0x753A, 0xAB5D, 0x1BD7) BPF1_COEFF_A as Integer[FILTER_ORDER+1] = ( 0x2000, 0xA1C7, 0x6C59, 0xC6EA, 0x0BDE) BPF1_SCALE_B = 0 BPF1_SCALE_A = 2 ' Filter setup: ' Filter kind: IIR ' Filter type: Highpass filter ' Filter order: 4 ' Design method: Chebyshev type II BPF2_COEFF_B as Integer[FILTER_ORDER+1] = (0x0BF7, 0xD133, 0x45AF, 0xD133, 0x0BF7) BPF2_COEFF_A as Integer[FILTER_ORDER+1] = (0x1000, 0xCA8B, 0x44B5, 0xD7E5, 0x08F3) BPF2_SCALE_B = 3 BPF2_SCALE_A = 3 MinLevel = 18 ' Min voltage offset level on ADC that can be detected as DTMF dim SignalActive as boolean ' Indicator (if input1 signal exists) dim sample as Integer ' Temp variable used for reading from ADC dim Key as Char ' Detected character dim f as longint ' Detected frequency dim SampleCounter as word ' Indicates the number of samples in circular buffer dim sample_index as word ' Index of next sample dim input1 as Integer[8] ' Circular buffer  raw samples (directly after ADC) dim output_f1 as Integer[8] ' Circular buffer  samples after IIR BP filter dim output_f2 as Integer[8] ' Circular buffer  samples after IIR BP filter dim TransitLow as Integer TransitHigh as Word ' Counts of transitions (low, high freq) dim sgnLow as Integer sgnHigh as Integer ' Current signs of low and high freq signal dim KeyCnt as integer ' Number of recived DTFM and displayed on LCD sub procedure Estimate dim fd as Word ' DTMF frequencies: ' 1209 Hz 1336 Hz 1477 Hz 1633 Hz ' 697 Hz 1 2 3 A ' 770 Hz 4 5 6 B ' 852 Hz 7 8 9 C ' 941 Hz * 0 # D ' ' calculating index of lower freq f = TransitLow*20000 ' f = No_Of_Transitions*Sampling_Freq [Hz] f = f >> 11 ' f = f div 2048 = f/2/1024 (2 transitions in each period) if f < 733 then fd=1 ' Index of Low_freq = 1 else if f < 811 then fd=2 ' Index of Low_freq = 2 else if f < 896 then fd=3 ' Index of Low_freq = 3 else fd=4 ' Index of Low_freq = 4 end if end if end if ' calculating index of higher freq f=TransitHigh*20000 ' f = No_Of_Transitions*Sampling_Freq f = f >> 11 ' f = f/2048 = f/2/1024 (2 transitions in each period) if f<1272 then fd=fd+10 ' encode Index of higher freq as 10 else if f<1406 then fd=fd+20 ' encode Index of higher freq as 20 else if f<1555 then fd=fd+30 ' encode Index of higher freq as 30 else fd=fd+40 ' encode Index of higher freq as 40 end if end if end if select case fd ' Reading of input1 char from DTMF matrix case 11 Key= "1" case 12 Key= "4" case 13 Key= "7" case 14 Key= "*" case 21 Key= "2" case 22 Key= "5" case 23 Key= "8" case 24 Key= "0" case 31 Key="3" case 32 Key="6" case 33 Key="9" case 34 Key="#" case 41 Key="A" case 42 Key="B" case 43 Key="C" case 44 Key="D" end select ' diplay recived char on second row of LCD if(KeyCnt >= 16) then ' if second row is full erase it and postion cursor at first column Lcd_Cmd(LCD_SECOND_ROW) Lcd_Out_CP(" ") Lcd_Cmd(LCD_SECOND_ROW) KeyCnt = 0 ' reset recived DTFM signals counter end if Lcd_Chr_CP(Key) ' output recived on LCD inc(KeyCnt) ' increment counter end sub sub procedure DAC_Output(dim valueDAC as word) LATC.CS_PIN = 0 ' CS enable for DAC ' filter output range is 16bit number DAC input1 range is 12bit number valueDAC = valueDAC >> 4 ' now both numbers are 12bit but filter output is signed and DAC input1 is unsigned. ' Half of DAC range 4096/2=2048 is added to correct this valueDAC = valueDAC + 2048 SPI2BUF = 0x3000 or valueDAC ' write valueDAC to DAC (0x3 is required by DAC) while (SPI2STAT.1 = 1) ' wait for SPI module to finish sending nop wend LATC.CS_PIN = 1 ' CS disable for DAC end sub sub procedure InitDec() SampleCounter = 1024 ' Init lowfreq transitions counter TransitLow = 0 ' Init highfreq transitions counter TransitHigh = 0 ' Init input1 circular buffer (zerofilled) Vector_Set(input1, 8, 0) ' Init filtered circular buffer (zerofilled) Vector_Set(output_f1, 8, 0) ' Init filtered circular buffer (zerofilled) Vector_Set(output_f2, 8, 0) ' Points on first element of circular buffer sample_index = 0 ' Current sign is positive sgnLow=0 ' Current sign is positive sgnHigh=0 DAC_Output(0) end sub sub procedure ADC1Int org $2A sample = ADCBUF0 ' read input1 ADC signal if (sample > 2048+MinLevel) and not(SignalActive) then ' detecting signal SignalActive = true ' activate estimation algorithm InitDec() ' initialize variables end if ' since ADC is configured to get samples as intgers ' mean value of input1 signal is expected to be located at ' middle of ADC voltage range sample = sample << 4 sample = sample(2048 << 4) 'expanding signal to full scale ' now sample is ready to be filtred if SignalActive then input1[sample_index] = sample ' Write sample in circular buffer ' Filter input1 signal (for lowfreq estimation) sample = IIR_Radix(BPF1_SCALE_B, BPF1_SCALE_A, @BPF1_COEFF_B, @BPF1_COEFF_A, FILTER_ORDER+1, @input1, BUFFER_SIZE, @output_f1, sample_index) DAC_Output(sample) ' output filtred signal to DAC for Visual check output_f1[sample_index]=sample ' transition_Low? if sample.15<>sgnLow then ' If transition trough 0 sgnLow=sample.15 ' save current sign Inc(TransitLow) ' Increment transition counter end if ' Filter input1 signal (for highfreq estimation) sample = IIR_Radix(BPF2_SCALE_B, BPF2_SCALE_A, @BPF2_COEFF_B, @BPF2_COEFF_A, FILTER_ORDER+1, @input1, BUFFER_SIZE, @output_f2, sample_index) output_f2[sample_index]=sample ' Write filtered signal in buffer ' transition_High? if sample.15<>sgnHigh then ' If transition sgnHigh=sample.15 ' save current sign Inc(TransitHigh) ' Increment transition counter end if sample_index=(sample_index+1) and 7 ' Move pointer on next element dec(SampleCounter) ' Decrement sample counter if SampleCounter = 0 then ' If all of 1024 samples are readed SignalActive=false ' Deactivate estimation algorithm Estimate() ' Read estimated character DAC_Output(0) ' set DAC output to 0 Delay_ms(80) ' Wait for next char end if end if IFS0.11 = 0 ' clear ADC complete IF end sub sub procedure Timer1Int org $1A ADCON1.1 = 1 ' ASAM=0 and SAMP=1 begin sampling ADCON1.15 = 1 ' start ADC IFS0.3 = 0 ' clear Timer1 IF end sub main: KeyCnt = 0 ' set to 0 SignalActive = false ' no signal is present ADPCFG = $FFFF ' configure pins as digital Lcd_Init_DsPicPro3() ' initialize LCD Lcd_Out(1,1,"tone is:") ' print message at first row Lcd_Cmd(LCD_SECOND_ROW) ' position cursor at second row LATC.CS_PIN = 1 ' set DAC CS to inactive LATC.LOAD_PIN = 0 ' set DAC LOAD to inactive TRISC.LOAD_PIN = 0 ' configure DAC LOAD pin as output TRISC.CS_PIN = 0 ' configure DAC CS pin as output ' Initialize SPI2 module Spi2_Init_Advanced(_SPI_MASTER, _SPI_16_BIT, _SPI_PRESCALE_SEC_1, _SPI_PRESCALE_PRI_1, _SPI_SS_DISABLE, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_ACTIVE_2_IDLE) TRISB.10 = 1 ' configure RB10 pin as input1 ADPCFG = $FBFF ' configure RB10 pin as analog ADCON1 = $00E0 ' autoconvert, autoconversion ADCON2 = $0000 ADCON3 = $021A ' sampling time=2*Tad, minimum Tad selected ADCHS = $000A ' sample input1 on RB10 ADCSSL = 0 ' no input1 scan ' clear interrupt flags IFS0 = 0 IFS1 = 0 IFS2 = 0 INTCON1 = $8000 ' disable nested interrupts INTCON2 = 0 IEC0 = $0808 ' enable Timer1 and ADC interrupts IPC0.12 = 1 ' Timer1 interrupt priority level = 1 IPC2.13 = 1 ' ADC interrupt priority level = 2 ' Timer1 input1 clock is Fosc/4. Sampling frequency is 20kHz. Timer should ' raise interrupt every 50 microseconds. PR1 = (Fosc[Hz]/4) / 20000Hz = Fosc[kHz]/(4*20) PR1 = Clock_kHz() div 80 T1CON = $8000 ' Enable Timer1 while true ' Infinite loop nop wend end.
This example shows a possibility of using dsPIC30F4013 or dsPIC6014A in order to realize control of the pointer of a GLCD on the basis of the signal from an acceleration sensor. In this example the use is made of an accelerometer card containing the sensor ADXL330 by Analog Devices. Besides the sensor, the card contains an operational amplifier used as a unit amplifier increasing the output current capacity of the sensor. The sensor measures the acceleration along two axes, X and Y. The offset voltage of the sensor has to be measured during the calibration procedure. From the accelerometer card, two analogue signals, for the X and Y axes, are fed to the inputs of AN8 and AN9 AD converters (pins PORTB.8 and PORTB.9). After the sampling and conversion, the measured value is presented as the shift of the pointer from the central position on the GLCD.
This example is widely applicable for the realization of e.g. joysticks, simple gyroscopes, robot controls or movement detectors.
' An example of the use of the microcontroller dsPIC30F6014A and Accel Extra Board. ' The example shows how the signal from the sensor is sampled and how the information on the ' accelerations along the X and Y axes are used for controlling the cursor on a GLCD. ' The example also covers the calibration of the sensor (determination of zeroG ' and 1G values for X and Y axes). Pin RC1 is used as user input. Pulldown PORTC and ' put button jumper in Vcc position. program AccelerationPointer '  GLCD Messages  const msg1 = "Put board to pos " const msg2 = "and press RC1" dim ' Global variables zeroG_x, zeroG_y as integer ' zero gravity values oneG_x, oneG_y as integer ' 1G values meas_x, meas_y as integer ' measured values box_x, box_y as integer ' variables for drawing box on GLCD positionNo as byte ' variable used in text messages text as String[20] ' variable used for text messages sub procedure Init() ADPCFG = $FCFF ' configure AN8(RB8) and AN9(RB9) as analog pins TRISB.8 = 1 ' configure RB8 and RB9 as input pins TRISB.9 = 1 Glcd_Init_DsPicPro3() ' init GLCD for dsPICPRO3 board ' Noteas GLCD/LCD Setup routines are in the setup library files located in the Uses folder ' These routines will be moved into AutoComplete in the future. Glcd_Fill(0) ' clear GLCD TRISC = $02 ' pin PORTC.1 is input for calibration positionNo = 1 ' variable used in text messages end sub sub procedure DoMeasureXY() meas_x = Adc_Read(8) ' measure X axis acceleration meas_y = Adc_Read(9) ' measure Y axis acceleration end sub sub procedure DrawPointerBox() dim x_real, y_real as real x_real = (meas_xzeroG_x)/(oneG_xzeroG_x) ' scale [1G..1G] to [1..1] x_real = x_real * 64 ' scale [1..1] to [64..64] x_real = x_real + 64 ' scale [64..64] to [0..128] y_real = (meas_yzeroG_y)/(oneG_yzeroG_y) ' scale [1G..1G] to [1..1] y_real = y_real * 32 ' scale [1..1] to [32..32] y_real = y_real + 32 ' scale [32..32] to [0..64] ' convert reals to integers box_x = x_real box_y = y_real ' force x and y to range [0..124] and [0..60] because of Glcd_Box parameters range if (box_x>124) then box_x=124 end if if (box_x<0) then box_x=0 end if if (box_y>60) then box_y=60 end if if (box_y<0) then box_y=0 end if Glcd_Box(box_x, box_y, box_x+3, box_y+3, 2) ' draw box pointer, color=2(invert ecah dot) end sub sub procedure ErasePointerBox() Glcd_Box(box_x, box_y, box_x+3, box_y+3, 2) ' draw inverted box at the same position ' (erase box) end sub '  Calibration procedure determines zeroG and 1G values for X and Y axes ' sub procedure DoCalibrate() ' 1) Put the Accel board in the position 1 : PARALLEL TO EARTH'S SURFACE ' to measure Zero Gravity values for X and Y text = msg1 text[17] = positionNo + 48 Glcd_Write_Text(text,5,1,1) Inc(positionNo) text = msg2 Glcd_Write_Text(text,5,20,1) while (PORTC.1 = 0) ' wait for user to press RC1 button nop wend DoMeasureXY() zeroG_x = meas_x ' save Zero Gravity values zeroG_y = meas_y Delay_ms(1000) ' 2) Put the Accel board in the position 2 : X AXIS IS VERTICAL, WITH X LABEL UP ' to measure the 1G X value text = msg1 text[17] = positionNo + 48 Glcd_Write_Text(text,5,1,1) Inc(positionNo) text = msg2 Glcd_Write_Text(text,5,20,1) while (PORTC.1 = 0) ' wait for user to press RC1 button nop wend DoMeasureXY() oneG_x = meas_x ' save X axis 1G value Delay_ms(1000) ' 3) Put the Accel board in the position 3 : Y AXIS IS VERTICAL, WITH Y LABEL UP ' to measure the 1G Y value text = msg1 text[17] = positionNo + 48 Glcd_Write_Text(text,5,1,1) Inc(positionNo) text = msg2 Glcd_Write_Text(text,5,20,1) while (PORTC.1 = 0) ' wait for user to press RC1 button nop wend DoMeasureXY() oneG_y = meas_y ' save Y axis 1G value Delay_ms(1000) end sub main: Init() ' initialization DoCalibrate() ' calibration Glcd_Fill(0) ' clear GLCD Glcd_H_Line(0, 127, 32, 1) ' draw X and Y axes Glcd_V_Line(0, 63, 64, 1) Glcd_Write_Char("X", 122, 3, 1) Glcd_Write_Char("Y", 66, 0, 1) while TRUE ' endless loop DoMeasureXY() ' measure X and Y values DrawPointerBox() ' draw box on GLCD Delay_ms(250) ' pause ErasePointerBox() ' erase box wend end.