Login | Cart (0)

Mini Cart Cart Loading

Programming dsPIC MCU in C

Chapter14: DSP Examples

14.1 Useful functions

At the beginning of this chapter the functions used in this chapter will be described together with some other useful functions. Table 14-1 presents a list of the functions including a description of their parameters, results (functions only), and eventual restrictions on the parameter values.

FIR_Radix

Prototype unsigned FIR_Radix(unsigned FilterOrder, const unsigned *ptrCoeffs, unsigned BuffLength, unsigned *ptrInput, unsigned Index);
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..N-1)(coef[k]*input[N-k]) - Current sample of processed signal(B[n])
N - buffer length
k - Current index

IIR_Radix

Prototype unsigned IIR_Radix (const int BScale, const int AScale, const signed *ptrB, const signed *ptrA, unsigned FilterOrder, unsigned *ptrInput, unsigned Input_Len, unsigned *ptrOutput, unsigned Index)
Description This function applies IIR filter to ptrInput. Input and output samples must be in Y data space.
AScale A Scale factor
BScale B Scale factor
ptrB 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[n-k]) - sum(k=1..M)(Bcoef[k]*y[n-k])

FFT

Prototype void Fft(unsigned log2N, const unsigned *TwiddleFactorsAddress, unsigned *Samples);
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,... , N-1}, and k in {0, 1,... , N-1}, 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.

BitReverseComplex

Prototype void BitReverseComplex(unsigned log2N, unsigned *ReIm)
Description This function does Complex (in-place) Bit Reverse re-organization.
N - buffer length (must be the power of 2).
ReIm - Output Sample(from FFT).
Note Input samples must be in Y data space.

Vector_Set

Prototype void Vector_Set(unsigned *input, unsigned size, unsigned value);
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

VectorPower

Prototype unsigned VectorPower(unsigned N, unsigned *Vector);
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,... , numElems-1}
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

Vector_Subtract

Prototype void Vector_Subtract(unsigned *dest, unsigned *v1, unsigned *v2, unsigned numElems);
Description This procedure does substraction of two vectors. numElems must be less or equal to minimum size of two vectors.
v1 - First Vector
v2 - Second Vector
dest - Result Vector
Operation dstV[n] = srcV1[n] - srcV2[n]
with n in {0, 1,... , numElems-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

VectorScale

Prototype void VectorScale(unsigned N, int ScaleValue, unsigned *SrcVector, unsigned *DestVector);
Description This procedure does vector scaling with scale value.
N - Buffer length
SrcVector - original vector
DestVector - scaled vector
ScaleValue - Scale Value
Operation dstV[n] = sclVal * srcV[n], with n in {0, 1,... , numElems-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

Vector_Negate

Prototype void Vector_Negate(unsigned *srcVector, unsigned *DestVector, unsigned numElems);
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.

Vector_Multiply

Prototype void Vector_Multiply(unsigned *v1, unsigned *v2, unsigned *dest, unsigned numElems);
Description This procedure does multiplication of two vectors.
numElems must be less or equal to minimum size of two vectors.
v1 - First Vector
v2 - Second Vector
dest - Result Vector
Operation dstV[n] = srcV1[n] * srcV2[n]
with n in {0, 1,... , numElems-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

Vector_Min

Prototype unsigned Vector_Min(unsigned *Vector, unsigned numElems, unsigned *MinIndex);
Description This function find min. value in vector.
Vector - Original vector.
numElems - Number of elements
MinIndex - Index of minimum value
Operation minVal = min {srcV[n], n in {0, 1,...numElems-1}
if srcV[i] = srcV[j] = minVal, and i < j, then minIndex = j
Returns minimum value (minVal)

Vector_Max

Prototype unsigned Vector_Max(unsigned *Vector, unsigned numElems, unsigned *MaxIndex);
Description This function find max. value in vector.
Vector - Original vector.
numElems - Number of elements
MaxIndex - Index of maximum value
Operation maxVal = max {srcV[n], n in {0, 1,...numElems-1} }
if srcV[i] = srcV[j] = maxVal, and i < j, then maxIndex = j
Returns maximum value (maxVal)

Vector_Dot

Prototype unsigned Vector_Dot(unsigned *v1, unsigned *v2, unsigned numElems);
Description Procedure calculates vector dot product.
v1 - First vector.
v2 - Second vector
numElems - Number of elements
Operation dotVal = sum (srcV1[n] * srcV2[n]),
with n in {0, 1,... , numElems-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

Vector_Correlate

Prototype void Vector_Correlate(unsigned *v1, unsigned *v2, unsigned *dest, unsigned numElemsV1, unsigned numElemsV2);
Description Procedure calculates Vector correlation (using convolution).
v1 - First vector.
v2 - Second vector
numElemsV1 - Number of first vector elements
numElemsV2 - Number of second vector elements
dest - Result vector
Operation r[n] = sum_(k=0:N-1){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+M-1.

Vector_Convolve

Prototype void Vector_Convolve(unsigned *v1, unsigned *v2, unsigned *dest, unsigned numElemsV1, unsigned numElemsV2);
Description Procedure calculates Vector using convolution.
v1 - First vector.
v2 - Second vector
numElemsV1 - Number of first vector elements
numElemsV2 - Number of second vector elements
dest - Result vector
Operation y[n] = sum_(k=0:n){x[k]*h[n-k]}, 0 <= n < M
y[n] = sum_(k=n-M+1:n){x[k]*h[n-k]}, M <= n < N
y[n] = sum_(k=n-M+1:N-1){x[k]*h[n-k]}, N <= n < N+M-1
Note AccuA used, not restored.
CORCON saved, used, restored.

Vector_Add

Prototype void Vector_Add(unsigned *dest, unsigned *v1, unsigned *v2, unsigned numElems);
Description Procedure calculates vector addition.
v1 - First vector.
v2 - Second vector
numElems - Number of vector elements
dest - Result vector
Operation dstV[n] = srcV1[n] + srcV2[n],
with n in {0, 1,... , numElems-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

Matrix_Transponse

Prototype void Matrix_Transpose(unsigned * src, unsigned * dest, unsigned num_rows, unsigned num_cols);
Description Procedure does matrix transposition.
src - Original matrix.
dest - Result matrix
numRows - Number of matrix rows
numCols - Number of matrix columns
Operation dstM[i][j] = srcM[j][i]

Matrix_Subtract

Prototype void Matrix_Subtract(unsigned * src1, unsigned * src2, unsigned * dest, unsigned num_rows, unsigned num_cols);
Description Procedure does matrix substraction.
src1 - First matrix.
src2 - Second matrix
dest - Result matrix
num_rows - Number of matrix rows
num_cols - 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.

Matrix_Scale

Prototype void Matrix_Scale(unsigned scale_value, unsigned *src1, unsigned *dest, unsigned num_rows, unsigned num_cols);
Description Procedure does matrix scale.
ScaleValue - Scale Value
src1 - Original matrix
dest - Result matrix
num_rows - Number of matrix rows
num_cols - Number of matrix columns
Operation dstM[i][j] = sclVal * srcM[i][j]
Note AccuA used, not restored.
CORCON saved, used, restored.

Matrix_Multiply

Prototype void Matrix_Multiply(unsigned * src1, unsigned * src2, unsigned * dest, unsigned numRows1, unsigned numCols2, unsigned numCols1Rows2);
Description Procedure does matrix multiply.
src1 - First Matrix
src2 - Second Matrix
dest - Result Matrix
numRows1 - Number of first matrix rows
numCols2 - Number of second matrix columns
numCols1Rows2 - 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, ..., numRows1-1}
j in {0, 1, ..., numCols2-1}
k in {0, 1, ..., numCols1Rows2-1}
Note AccuA used, not restored.
CORCON saved, used, restored.

Matrix_Add

Prototype void Matrix_Add(unsigned * src1, unsigned * src2, unsigned * dest, unsigned numRows, unsigned numCols);
Description Procedure does matrix addition.
src1 - First Matrix
src2 - Second Matrix
dest - Result Matrix
numRows - Number of first matrix rows
numCols - Number of second matrix columns
Operation dstM[i][j] = srcM1[i][j] + srcM2[i][j]
Note AccuA used, not restored.
CORCON saved, used, restored.

14.2.1 Example 1 – Menu

The example shows a method of formation of a menu with options on an LCD in 4-bit 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 */

signed short menu_index;
char menu[5][7] = {"First" ,                   // Menu items
                   "Second",
                   "Third" ,
                   "Fourth",
                   "Fifth"   } absolute 0x1880;
                   // Directive absolute specifies the starting address
                   // in RAM for a variable.

void main() {
  ADPCFG = 0xFFFF;                             // Configure PORTB as digital
  TRISF  = 0xFFFF;                             // Configure PORTF as input (menu control)

  menu_index = 0;                              // Init menu_item[0]

  // Init LCD in 4-bit mode for dsPICPRO3 board
  Lcd_Custom_Config(&PORTD, 7,6,5,4, &PORTB, 4,0,6);
  Lcd_Custom_Cmd(LCD_CURSOR_OFF);
  Lcd_Custom_Cmd(LCD_FIRST_ROW);
  Lcd_Custom_Out(1,1,"Menu :");

  Lcd_Custom_Out(1,8,menu[menu_index]);        // Show menu element on LCD

  while (1) {                                  // endless loop
    if (PORTF.F1 == 1) {                       // Detect logical one on RF1 pin => MENU UP
      menu_index = menu_index + 1;             // Next index in menu
      if (menu_index > 4)
        menu_index = 0;                        // Circular menu
      Lcd_Custom_Out(1,8, "      ");           // Clear text
      Lcd_Custom_Out(1,8,menu[menu_index]);    // Show menu element on LCD
      Delay_ms(250);                           // No more than 4 changes per sec
    }

    if (PORTF.F0 == 1) {                       // Detect logical one on RF0 pin => MENU DOWN
      menu_index = menu_index - 1;             // Previous index in menu
      if (menu_index < 0)
        menu_index = 4;                        // Circular menu
      Lcd_Custom_Out(1,8, "      ");           // Clear text
      Lcd_Custom_Out(1,8,menu[menu_index]);    // Show menu element on LCD
      Delay_ms(250);                           // No more than 4 changes per sec
    }
  }
}

14.2.2 Example 2 – DTMFout

The example shows a method of generation of DTMF signal. The following assumptions have been adopted:

  • the level of the signal of the higher frequency is 0 to 3dB higher compared to the signal of the lower frequency,
  • transmission of one character lasts 90ms, the pause between two characters is 200ms,
  • the sampling frequency is 20kHz,
  • input data are sent to the microcontroller via the UART module at the rate of 9600 bps,

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:

  • frequency deviation is less than 1.5%,
  • the level of all harmonics is 20dB below the useful signal.

The algorithm is shown in Fig. 14-1.

Algorithm for generation of DTMF signal

Fig. 14-1 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 697-941Hz filter attenuation is in -3dB to -2.8dB range and low frequencies 1209-1633Hz 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 1.

Filter Designer Tool Window 2

Filter Designer Tool Window 2.

Fig. 14-2 shows the generated signal. The signals of the lower and higher frequencies are added, with the higher frequency signal 3dB above the lower frequency signal.

Generated signal

Fig. 14-2 Generated signal.

Spectrum of the signal

Fig. 14-3 Spectrum of the signal of Fig. 14-2

Filtered signal

Fig. 14-4 Filtered signal

Spectrum of the signal after filtering

Fig. 14-5 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 mikroC 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

   On-board DAC module
   Enable SPI connection to DAC on SW4 and DAC's Load(LD) and Chip Select(CS) pins on SW3.
 */

#include <spi_const.h>                       // to be used with spi_init_advanced routine

// *** Filter Designer Tool outputs *** //
const unsigned int BUFFER_SIZE  = 8;
const unsigned int FILTER_ORDER = 3;
const signed int   COEFF_B[FILTER_ORDER+1] = {0x21F3, 0x65DA, 0x65DA, 0x21F3};
const signed int   COEFF_A[FILTER_ORDER+1] = {0x2000, 0xB06D, 0x47EC, 0xE8B6};
const unsigned int SCALE_B =  6;
const unsigned int SCALE_A = -2;

// *** DAC pinout *** //
const char CS_PIN   = 1;                     // DAC CS pin
const char LOAD_PIN = 2;                     // DAC LOAD pin

unsigned int THalf_Low, THalf_High;          // half-periods of low and high-frequency 
                                             // square signals
char         char2send;                      // char recived from UART
unsigned int sample, sending_ch_cnt;         // digital signal sample, sending char counter
unsigned int us_cntL, us_cntH;               // low and high-frequency square signal 
                                             // microseconds counters
signed int   input[BUFFER_SIZE];             // filter input signal (two square signals)
signed int   output[BUFFER_SIZE];            // filtered signal
unsigned int sample_index;                   // index of current sample
signed int   voltageL, voltageH;             // square signals amplitudes

void 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
}

void DAC_Output(unsigned int valueDAC) {
  
  LATC.CS_PIN = 0;                           // CS enable for DAC
  // filter output range is 16-bit number; DAC input range is 12-bit number
  valueDAC = valueDAC >> 4;
  // now both numbers are 12-bit, 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 | valueDAC;               // write valueDAC to DAC (0x3 is required by DAC)
  while (SPI2STAT.F1)                        // wait for SPI module to finish sending
    asm nop;
  LATC.CS_PIN = 1;                           // CS disable for DAC
}

void SetPeriods(char ch) {

/* 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 half-periods in microseconds
  //   example: 1/697Hz = 0.001435 seconds = 1435 microseconds
  //            1435/2  = 717
  
  switch (ch) {
    case 49: THalf_Low = 717; THalf_High = 414; break;  //'1'
    case 50: THalf_Low = 717; THalf_High = 374; break;  //'2'
    case 51: THalf_Low = 717; THalf_High = 339; break;  //'3'
    case 65: THalf_Low = 717; THalf_High = 306; break;  //'A'
    
    case 52: THalf_Low = 649; THalf_High = 414; break;  //'4'
    case 53: THalf_Low = 649; THalf_High = 374; break;  //'5'
    case 54: THalf_Low = 649; THalf_High = 339; break;  //'6'
    case 66: THalf_Low = 649; THalf_High = 306; break;  //'B'
    
    case 55: THalf_Low = 587; THalf_High = 414; break;  //'7'
    case 56: THalf_Low = 587; THalf_High = 374; break;  //'8'
    case 57: THalf_Low = 587; THalf_High = 339; break;  //'9'
    case 67: THalf_Low = 587; THalf_High = 306; break;  //'C'
    
    case 42: THalf_Low = 531; THalf_High = 414; break;  //'*'
    case 48: THalf_Low = 531; THalf_High = 374; break;  //'0'
    case 35: THalf_Low = 531; THalf_High = 339; break;  //'#'
    case 68: THalf_Low = 531; THalf_High = 306; break;  //'D'
  }
}

void ClearBufs() {
  //Clear buffers
  Vector_Set(input,  BUFFER_SIZE, 0);
  Vector_Set(output, BUFFER_SIZE, 0);
}

void Timer1Int() org 0x1A {            // interrupt frequency is 20kHz

  // calculate sample
  sample = voltageL + voltageH;        // add voltages
  input[sample_index] = sample;        // write sample to input buffer
  
  // update low-frequency 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) {           // half-period exceeded, change sign
    voltageL = -voltageL;
    us_cntL  = us_cntL - THalf_Low;    // subtract half-period
  }
    
  // update high-frequency square signal microseconds counter
  us_cntH = us_cntH + 50;
  if (us_cntH > THalf_High) {
    voltageH = -voltageH;
    us_cntH  = us_cntH - THalf_High;
  }
    
  //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 digital-to-analog converter

  output[sample_index] = sample;       // write filtered sample in output buffer
  
  sample_index++;                      // increment sample index, prepare for next sample
  if (sample_index >= BUFFER_SIZE)
    sample_index = 0;

  sending_ch_cnt--;                    // decrement char sending counter
                                       //   (character transmition lasts 90ms = 1800 samples)
  if (sending_ch_cnt == 0) {           // if character transmition is over
    T1CON=0;                           // turn off Timer1
    Delay_ms(200);                     // pause between two characters is 200ms
  }
    
  IFS0.F3 = 0;                         // clear Timer1 interrupt flag
}


// --- main --- //
void 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 = 0x8000;                    // disable nested interrupts
  IEC0    = 0x0008;                    // 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() / 80;
  // Note: interrupt routine execution takes ~10us

  while (1) {                          // endless loop
    if ((sending_ch_cnt == 0) &&       // check if sending of previous character is over
        (Uart1_Data_Ready() > 0)) {    // check if character arrived via UART1

      char2send = Uart1_Read_Char();   // read data from UART and store it
      SetPeriods(char2send);           // set periods for low and high-frequency 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 = 0x7FFF / 2;           // high-frequency square signal amplitude
      voltageL = 0x7FFF / 2;           // low-frequency square signal amplitude
      us_cntL = 0;                     // low-frequency square signal microseconds counter
      us_cntH = 0;                     // high-frequency square signal microseconds counter
      
      // start Timer T1
      sending_ch_cnt =   1800;         // character tansmition lasts 90ms = 1800 samples * 50us
      T1CON          = 0x8000;         // enable Timer1 (TimerOn, prescaler 1:1)
    }
  }
}

14.2.3 Example 3 – DTMFin

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. 14-6.

DTMF signal before filtering

Fig. 14-6 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. 14-7.

DTMF signal after filtering

Fig. 14-7 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 mikroC for dsPIC. Settings of Filter Designer Tool for LowPass and HighPass filter are given below:

IIR lowpass filter settings

IIR lowpass filter settings

IIR lowpass frequency window

IIR lowpass frequency window

IIR highpass filter settings

IIR highpass filter settings

IIR highpass frequency window

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
*/

#include <Spi_Const.h>

// *** DAC pinout *** //
const  LOAD_PIN = 2;                         // DAC load pin
const  CS_PIN   = 1;                         // DAC CS pin



// filter setup:
//     filter kind: IIR
//     filter type: lowpass filter
//     filter order: 4
//     design method: Chebyshev type II
const unsigned int  BUFFER_SIZE   = 8;
const unsigned int  FILTER_ORDER  = 4;
const unsigned int  BPF1_COEFF_B[FILTER_ORDER+1] = {0x1BD7, 0xAB5D, 0x753A, 0xAB5D, 0x1BD7};
const unsigned int  BPF1_COEFF_A[FILTER_ORDER+1] = {0x2000, 0xA1C7, 0x6C59, 0xC6EA, 0x0BDE};
const unsigned int  BPF1_SCALE_B       =  0;
const unsigned int  BPF1_SCALE_A       = -2;
// filter setup:
//     filter kind: IIR
//     filter type: Highpass filter
//     filter order: 4
//     design method: Chebyshev type II

const unsigned int  BPF2_COEFF_B[FILTER_ORDER+1] = {0x0BF7, 0xD133, 0x45AF, 0xD133, 0x0BF7};
const unsigned int  BPF2_COEFF_A[FILTER_ORDER+1] = {0x1000, 0xCA8B, 0x44B5, 0xD7E5, 0x08F3};
const unsigned int  BPF2_SCALE_B       = -3;
const unsigned int  BPF2_SCALE_A       = -3;
  
// min voltage offset level on ADC that can be detected as DTMF
const unsigned int  MinLevel = 18;


char SignalActive;                    // indicator (if input signal exists)
int  sample;                          // temp variable used for reading from ADC
char  Key;                            // detected character
long int  f;                          // detected frequency
unsigned  SampleCounter;              // indicates the number of samples in circular buffer
unsigned  sample_index;               // index of next sample
int  input[8];                        // circular buffer - raw samples (directly after ADC)
int  output_f1[8];                    // circular buffer - samples after IIR BP filter
int  output_f2[8];                    // circular buffer - samples after IIR BP filter

unsigned  TransitLow, TransitHigh;    // counts of transitions (low, high freq)
int  sgnLow, sgnHigh;                 // current signs of low and high freq signal
int  KeyCnt;                          // number of recived DTFM and displayed on LCD


void Estimate(){
unsigned fd;
/* 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*20000l;  // 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)
    fd = 1;   //Index of Low_freq = 1
  else if (f < 811)
    fd = 2;   //Index of Low_freq = 2
  else if (f < 896)
    fd = 3;   //Index of Low_freq = 3
  else
    fd = 4;  //Index of Low_freq = 4

  // calculating index of higher freq
  f = TransitHigh*20000l;  // f = No_Of_Transitions*Sampling_Freq
  f = f >> 11;             // f = f/2048 = f/2/1024 (2 transitions in each period)

  if (f<1272)
    fd = fd + 10;  // encode Index of higher freq as 10
  else if (f<1406)
    fd = fd + 20;  // encode Index of higher freq as 20
  else if (f<1555)
    fd = fd + 30;  // encode Index of higher freq as 30
  else
    fd = fd + 40; // encode Index of higher freq as 40

  switch (fd){    // reading of input char from DTMF matrix
   case 11: Key = '1'; break;
   case 12: Key = '4'; break;
   case 13: Key = '7'; break;
   case 14: Key = '*'; break;
   case 21: Key = '2'; break;
   case 22: Key = '5'; break;
   case 23: Key = '8'; break;
   case 24: Key = '0'; break;
   case 31: Key = '3'; break;
   case 32: Key = '6'; break;
   case 33: Key = '9'; break;
   case 34: Key = '#'; break;
   case 41: Key = 'A'; break;
   case 42: Key = 'B'; break;
   case 43: Key = 'C'; break;
   case 44: Key = 'D'; break;
   }
  
  // diplay recived char on second row of LCD
  if(KeyCnt >= 16)
    { // if second row is full erase it and postion cursor at first column
      Lcd_Custom_Cmd(LCD_SECOND_ROW);
      Lcd_Custom_Out_CP("                ");
      Lcd_Custom_Cmd(LCD_SECOND_ROW);
      KeyCnt = 0; // reset recived DTFM signals counter
    }
  Lcd_Custom_Chr_CP(Key); // output recived on LCD
  KeyCnt++;               // increment counter

}

void DAC_Output(unsigned valueDAC){
  LATC.CS_PIN = 0;  // CS enable for DAC
  // filter output range is 16-bit number; DAC input range is 12-bit number
  valueDAC = valueDAC >> 4;
  // now both numbers are 12-bit 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 | valueDAC;         // write valueDAC to DAC (0x3 is required by DAC)
  while (SPI2STAT.f1)                  // wait for SPI module to finish sending
    asm nop;
  LATC.CS_PIN = 1;                     // CS disable for DAC
}

void InitDec(){
                                     // estimate on 1024 samples for fast DIV
  SampleCounter = 1024;              // init low-freq transitions counter
  TransitLow = 0;                    // init high-freq transitions counter
  TransitHigh = 0;                   // init input circular buffer (zero-filled)
  Vector_Set(input, 8, 0);           // init filtered circular buffer (zero-filled)
  Vector_Set(output_f1, 8, 0);       // init filtered circular buffer (zero-filled)
  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);
}

void ADC1Int() org 0x2A {
  sample = ADCBUF0;                                // read input ADC signal

  if ((sample > 2048+MinLevel) &&  !SignalActive)  // detecting signal
    {
      SignalActive = 1;                            // activate estimation algorithm
      InitDec();                                   // initialize variables
    }

  // since ADC is configured to get samples as intgers
  //   mean value of input 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)
    {
      input[sample_index] = sample;  // write sample in circular buffer

      // filter input signal (for low-freq estimation)
      sample  = IIR_Radix(BPF1_SCALE_B, BPF1_SCALE_A, BPF1_COEFF_B, BPF1_COEFF_A,
                          FILTER_ORDER+1, input, 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 & 0x8000) != sgnLow)     // if transition trough 0
        {
          sgnLow = (sample & 0x8000);      // save current sign
          ++TransitLow;                    // increment transition counter
        }

      // filter input signal (for high-freq estimation)
      sample  = IIR_Radix(BPF2_SCALE_B, BPF2_SCALE_A, BPF2_COEFF_B, BPF2_COEFF_A,
                          FILTER_ORDER+1, input, BUFFER_SIZE, output_f2, sample_index);

      output_f2[sample_index] = sample;      // write filtered signal in buffer
      // transition_High?
      if ((sample & 0x8000) != sgnHigh)      // if transition
        {
          sgnHigh = (sample & 0x8000);
          ++TransitHigh;                     // increment transition counter
        }
        
      sample_index = (sample_index+1) & 7;   // move pointer on next element
      --SampleCounter;                       // decrement sample counter
      if (SampleCounter == 0)                // if all of 1024 samples are readed
        {
          SignalActive = 0;                  // deactivate estimation algorithm
          Estimate();                        // read estimated character
          DAC_Output(0);                     // set DAC output to 0
          Delay_ms(80);                      // wait for next char
        }
    }
  IFS0.f11 = 0;                              // clear ADC complete IF
}

void Timer1Int() org 0x1A{

  ADCON1.f1  = 1; // ASAM=0 and SAMP=1 begin sampling
  ADCON1.f15 = 1; // start ADC
  IFS0.f3    = 0; // clear Timer1 IF
}

void main(){
  KeyCnt = 0;                                               // set to 0
  SignalActive = 0;                                         // no signal is present
  ADPCFG = 0xFFFF;                                          // configure pins as digital
  Lcd_Custom_Config(&PORTD, 7, 6, 5, 4, &PORTB, 4, 2, 6);   // initialize LCD
  Lcd_Custom_Out(1,1,"tone is:");                           // print message at first row
  Lcd_Custom_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.f10 = 1;           // configure RB10 pin as input
  ADPCFG    = 0xFBFF;      // configure RB10 pin as analog
  ADCON1    = 0x00E0;      // auto-convert, auto-conversion
  ADCON2    = 0x0000;
  ADCON3    = 0x021A;      // sampling time=2*Tad, minimum Tad selected
  ADCHS     = 0x000A;      // sample input on RB10
  ADCSSL    = 0;           // no input scan

  // clear interrupt flags
  IFS0    = 0;
  IFS1    = 0;
  IFS2    = 0;
  
  INTCON1 = 0x8000;      // disable nested interrupts
  INTCON2 = 0;
  IEC0    = 0x0808;      // enable Timer1 and ADC interrupts
  IPC0.f12 = 1;          // Timer1 interrupt priority level = 1
  IPC2.f13 = 1;          // ADC interrupt priority level = 2

  // 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() / 80;
  T1CON = 0x8000;    // Enable Timer1

  while(1)           // Infinite loop
    asm nop;
}

14.2.4 Example 4 – Acceleration sensor

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 function. 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. Pull-down PORTC and
    put button jumper in Vcc position. */


// --- GLCD Messages ---
const char msg1[] = "Put board to pos  ";
const char msg2[] = "and press RC1";

// Global variables
signed int zeroG_x, zeroG_y; // zero gravity values
signed int oneG_x,  oneG_y;  // 1G values
signed int meas_x,  meas_y;  // measured values
signed int box_x,   box_y;   // variables for drawing box on GLCD
char positionNo;             // variable used in text messages
char text[21];               // variable used for text messages

void Init() {
  ADPCFG   = 0xFCFF;         // configure AN8(RB8) and AN9(RB9) as analog pins
  TRISB.F8 = 1;              // configure RB8 and RB9 as input pins
  TRISB.F9 = 1;
  Glcd_Init_DsPicPro3();     // init GLCD 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.
  Glcd_Set_Font(FontSystem5x8, 5, 8, 32); // set GLCD font
  Glcd_Fill(0);              // clear GLCD
  TRISC = 0x02;              // pin PORTC.1 is input for calibration
  positionNo = 1;            // variable used in text messages
}

void DoMeasureXY() {
  meas_x = Adc_Read(8);      // measure X axis acceleration
  meas_y = Adc_Read(9);      // measure Y axis acceleration
}

void DrawPointerBox() {
  float x_real, y_real;

  x_real = (float)(meas_x-zeroG_x)/(oneG_x-zeroG_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 = (float)(meas_y-zeroG_y)/(oneG_y-zeroG_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 = (int)x_real;
  box_y = (int)y_real;
  
  // force x and y to range [0..124] and [0..60] because of Glcd_Box parameters range
  if (box_x>124) box_x=124;
  if (box_x<0)   box_x=0;
  if (box_y>60)  box_y=60;
  if (box_y<0)   box_y=0;
  
  Glcd_Box(box_x, box_y, box_x+3, box_y+3, 2);  // draw box pointer, color=2(invert ecah dot)
}

void ErasePointerBox() {
  Glcd_Box(box_x, box_y, box_x+3, box_y+3, 2);  // draw inverted box at the same position
                                                // (erase box)
}

// --- Calibration procedure determines zeroG and 1G values for X and Y axes ---//
void DoCalibrate() {
//  1) Put the Accel board in the position 1 : PARALLEL TO EARTH'S SURFACE
//  to measure Zero Gravity values for X and Y
  strcpy(text, msg1);
  text[17] = positionNo + 48;
  Glcd_Write_Text(text,5,1,1);
  positionNo++;
  strcpy(text, msg2);
  Glcd_Write_Text(text,5,20,1);
  while (PORTC.F1 == 0) ;          // wait for user to press RC1 button
  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
  strcpy(text, msg1);
  text[17] = positionNo + 48;
  Glcd_Write_Text(text,5,1,1);
  positionNo++;
  strcpy(text, msg2);
  Glcd_Write_Text(text,5,20,1);
  while (PORTC.F1 == 0) ;          // wait for user to press RC1 button
  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
  strcpy(text, msg1);
  text[17] = positionNo + 48;
  Glcd_Write_Text(text,5,1,1);
  positionNo++;
  strcpy(text, msg2);
  Glcd_Write_Text(text,5,20,1);
  while (PORTC.F1 == 0) ;          // wait for user to press RC1 button
  DoMeasureXY();
  oneG_y = meas_y;                 // save Y axis 1G value
  Delay_ms(1000);
}


void 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 (1) {                      // endless loop
    DoMeasureXY();                 // measure X and Y values
    DrawPointerBox();              // draw box on GLCD
    Delay_ms(250);                 // pause
    ErasePointerBox();             // erase box
  }
}

previous chapter | table of contents | next chapter

LiveZilla Live Chat Software