AN218 DTMF D E C O D E R R E F E R E N C E D E S I G N Relevant Devices This application note applies to the following devices: C8051F300 1. Introduction Using the 25 MIPS CPU and on-chip ADC, the C8051F300 can perform DTMF tone generation and decoding. The combination of high performance and small size (3x3 mm) makes the C8051F300 an ideal choice for applications requiring DTMF decoding or other CPU intensive tasks. A reference design kit is available for this reference design. The kit includes the following: DTMF Decoder Evaluation Board containing a DTMF generator and decoder, keypad, and 7-segment LED. DTMF Decoder Kit User's Guide with step-by-step instructions for running the DTMF Demo on the DTMF Decoder Evaluation Board. Reference Design Kit CD containing the Silicon Laboratories Integrated Development Environment (IDE) and an evaluation version of Keil C8051 Development Tools (assembler, 2 kB object code limited compiler, and linker). This reference design includes the following: Background and theory of DTMF decoding using the Goertzel Algorithm. Description of a software implementation on a C8051F300 MCU. Full C source code for the DTMF decoder and generator. The software is included at the end of the application note and in a separate Zip file (AN218SW.zip) containing the firmware in C and HEX file format. The Zip file is available on the Reference Design Kit CD and on the Silicon Laboratories website. 2. Background Dual tone multi frequency (DTMF) is a method of representing digits with tones for transmission over an analog communication channel. DTMF tones are used by all touch tone phones to represent the digits on a touch tone keypad. DTMF technology provides a robust alternative to rotary telephone systems and allows user-input during a phone call. This feature has enabled interactive, automated response systems such as the ones used for telephone banking, routing customer support calls, voicemail, and similar applications. A DTMF tone consists of two superimposed sinusoidal signals selected from two frequency groups. The frequency groups represent rows and columns on a touch tone keypad as shown in Figure 1. Each DTMF tone must contain one sinusoid from the high-frequency group (1209, 1336, 1477 and 1633 Hz) and one sinusoid from the lowfrequency group (697, 770, 852 and 941 Hz). This allows a touch tone keypad to have up to 16 unique keys. Col 3 Col 4 Col 1 Col 2 1209Hz 1366Hz 1477Hz 1633Hz Row 1 697 Hz 1 2 3 A Row 2 770 Hz 4 5 6 B Row 3 852 Hz 7 8 9 C Row 4 941 Hz * 0 # D Figure 1. Touch Tone Keypad Rev. 0.11 12/05 Copyright © 2005 by Silicon Laboratories AN218 AN218 The frequencies selected for DTMF tones have some distinguishing characteristics and unique properties. All tones are in the audible frequency range allowing humans to detect when a key has been pressed. No frequency is a multiple of another. The sum or difference of any two frequencies does not equal another selected frequency. The second and third properties simplify DTMF decoding and reduce the number of falsly detected DTMF tones. The unique properties allow DTMF receivers to detect when a user has pressed multiple keys simultaneously and reject any tones that have harmonic energy. Harmonic energy can only be generated by speech/noise and not by the sum of two valid DTMF frequencies. To maintain compatibility between DTMF generators and decoders used worldwide, DTMF tones should always be generated and decoded according to the International Telecommunication Union (ITU) Recommendations Q.23 and Q.24. The ITU website is http://www.itu.int. 3. DTMF Detection and Decoding Detecting DTMF tones requires the capability to detect and differentiate between the 8 DTMF frequencies. It is also important to have a technique of detecting and rejecting false tones caused by noise (e.g., speech). One of the performance metrics used to evaluate DTMF decoders is talk-off error. Talk-off error is defined as the improper detection of a DTMF tone due to speech or other forms of noise in the communication channel. One method for distinguishing between DTMF tones and speech is checking for the presence of a second harmonic. If the second harmonic is detected, then the signal can be rejected because true DTMF tones only contain fundamental tones. Frequency detection is typically accomplished by applying the discrete fourier transform (DFT) to a time-domain signal to extract frequency information. The DFT, and other transforms such as the fast fourier transform (FFT), generate frequency information for the entire range of frequencies from dc to the sampling rate divided by two. Performing a full DFT or FFT in real-time is complex, requires large amounts of memory, and is not suitable for an 8-bit microcontroller. Instead, the Goertzel Algorithm can be used to extract specific “bins” of a DFT from the input signal without performing an entire DFT or FFT. The Goertzel Algorithm is very suitable for DTMF decoding since the decoder is only concerned with detecting energy at the 8 DTMF frequencies and their second harmonics. The Goertzel algorithm works on blocks of ADC samples. Samples are processed as they arrive and a result is produced every N samples, where N is the number of samples in each block. If the sampling rate is fixed, the block size determines the frequency resolution or “bin width” of the resulting power measurement. The example below shows how to calculate the frequency resolution and time required to capture N samples: Sampling rate: 8 kHz Block size (N): 200 samples Frequency Resolution: sampling rate/block size = 40 Hz Time required to capture N samples: block size/sampling rate = 25 ms The tradeoff in choosing an appropriate block size is between frequency resolution and the time required to capture a block of samples. Increasing the output word rate (decreasing block size) increases the “bin width” of the resulting power measurement. The bin width should be chosen to provide enough frequency resolution to differentiate between the DTMF frequencies and be able to detect DTMF tones with reasonable delay. See [2] for additional information about choosing a block size. 2 Rev. 0.11 AN218 The processing requirements for the Goertzel algorithm are equivalent to a two-pole IIR filter. Three variables Q0, Q1, and Q2 are required to hold intermediate results. For each ADC sample, the value of Q0 is computed and the previous values of Q0 and Q1 become the new Q1 and Q2, respectively. The initial values of Q1 and Q2 are zero. The computation required for each ADC sample is shown below: Q 0 = ( coef k × Q 1 [ n ] ) – Q 2 [ n ] + x [ n ] Q1 = Q0 [ n – 1 ] Q2 = Q1 [ n – 1 ] where: N × DTMF_Target_Frequency k = ( int ) ⎛⎝ 0.5 + -------------------------------------------------------------------------------⎞⎠ Sampling_Rate 2π × k coefk = 2 cos ⎛⎝ ---------------⎞⎠ N x [ n ] = ADC Sample After N samples have been received and Q0, Q1, and Q2 have been updated N times, the signal power at the target frequency can be computed using the following equation: 2 2 2 power = magnitude = Q 1 [ N ] + Q 2 [ N ] – ( coef k × Q 1 [ N ] × Q 2 [ N ] ) Notice that the value of coef k is constant throughout the calculations and can be pre-computed at compile time for the 8 possible DTMF frequencies and their second harmonics. Once signal power is determined for each of the DTMF frequencies, the power levels can be compared to a threshold value or with each other to determine the row and column of the key pressed. If no keys or multiple keys were pressed, or if energy is detected at the second harmonic, the controller can take no action. If a valid row and column are determined, the controller can report the DTMF tone to the user. Rev. 0.11 3 AN218 4. Software Implementation The general implementation of this DTMF detector is modeled by the signal flow diagram shown in Figure 2. Each of the following sections corresponds to a block in the diagram. The blocks also describe the labeled sections of code. The flowcharts in Figures 3 and 4 show the software flow of the ADC0 Interrupt Service Routine and the DTMF_Detect() function. Following along in the code while reading this section will be helpful in understanding the operation of this software. C8051F300 Block 1 Block 3 Block 2 Input (ADC0) Signal Detection Gain Correction/ DC Bias Removal Block 4 16 Goertzel Filters L = Low Frequency H= High Frequency S = Second Harmonic L1 L2 L3 L4 H1 H2 H3 H4 S1 S2 S3 S4 S5 S6 S7 S8 Block 5 Block 6 Output (UART and 7-Segment LED) Character Decoder Figure 2. Software Signal Flow Diagram 4 Rev. 0.11 AN218 4.1. Block 1—Input ADC0 handles the input signal sampling. The sampling rate is set at 8 kHz, with conversions starting every Timer 2 overflow. Even though the highest DTMF frequency is 1633 Hz, an 8 kHz sampling rate is necessary to correctly sample the second harmonics and reduce talk-off error. This allows 125 µs of CPU time per sample. 4.2. Block 2—Signal Detection The signal detection block determines whether or not a signal is present at the ADC input. After a sample has been acquired, its magnitude is compared to the magnitude of the previous sample. If the difference between the two is greater than a predefined threshold value, the DTMF decoding process is started. If the magnitude does not meet the valid signal condition and there is no DTMF detection currently in progress, then the sample is ignored. Separate DTMF tones are required to have a 10 ms to 40 ms gap between them. In this implementation, DTMF detection is disabled for the 20 ms period following the end of a DTMF tone. During this 20 ms period, any signal detected at the ADC input, such as a new DTMF tone, will restart the timeout and will not be interpreted as a valid DTMF tone. DTMF detection can only be re-enabled by the Timer 0 Interrupt Service Routine (ISR) after 20 ms of silence at the ADC input. The 20 ms timeout can be varied by changing the Timer 0 reload value. 4.3. Block 3—Gain Control/DC Bias Removal The gain control block applies a dynamic gain to ADC input to offset variations present in a typical telephone line and ensure that the input signal utilizes the full dynamic range of the ADC (0 V to VREF). The dynamic gain is determined by finding the peak amplitude of the first 70 samples then scaling the rest of the samples such that the peak value is equal to VREF. The gain is recalculated at the beginning of every tone. The gain control block also removes any dc bias from the input signal by recording the minimum value of the first 70 samples. Knowing the maximum and minimum values of the input signal, the mean value is computed and subtracted from future samples. Removing the dc bias from the input signal provides consistent power measurements and minimizes errors. The dc bias is recalculated at the beginning of every tone. 4.4. Block 4A—Goertzel Filters The Goertzel filter block contains 16 filters to determine the energy present at the 8 DTMF frequencies and their second harmonics. At 125 µs of CPU time per sample, it is not possible to execute all 16 filters concurrently in realtime. Instead, the filters are divided into two groups of 8. The first group is executed on the first N samples received and determine the presence of energy at the low and high fundamental frequencies. The second group is executed on a second set of N samples and are used to determine the presence of energy at the second harmonics. All samples are processed as they arrive and the value of N may vary between the two filter groups. 4.5. Block 4B—Goertzel Filter Output (Signal Power Calculation) The Goertzel filter output stage computes the signal power for each of the 16 tested frequencies and stores them for use by the next block. 4.6. Block 5—DTMF Decode The DTMF Decode block determines which of the 16 frequencies have enough energy to contain a signal. The signal power of each is compared to the remaining signals in the frequency group (low or high). If the signal power is K times (K = 8 in this implementation) greater than the sum of signal power from the other three signals, the input signal is determined to contain a sinusoid at the tested frequency. The results are stored in memory and used by the next block. 4.7. Block 6—DTMF Output The DTMF Output block uses the result of the previous block to determine if a valid DTMF Tone corresponding to a unique character has been received. If multiple frequencies from the same group are detected, or the combined energy from the second harmonics exceeds a threshold, the input signal is rejected. The rest of the software in this block is application dependant. In this implementation, the detected character is printed to the UART and a 7segment LED display is updated to display the decoded character. Rev. 0.11 5 AN218 ADC0 End-ofConversion ISR Get new sample Signal level variation check Valid pause check Valid signal? YES First 70 samples? YES NO NO Compute gain Compute mean value Next 200 samples? NO YES Remove DC bias Compute Goertzel feedback stage for base frequencies STOP Figure 3. ADC0 End-of-Conversion ISR Flowchart 6 Rev. 0.11 Remove DC bias Compute Goertzel feedback stage for 2 nd harmonics AN218 DTMF_Detect() Compute feedforward stage for 8 Goertzel filters YES Base frequencies? NO Check row and column frequencies Single row and column detect? YES Check 2 nd harmonics NO 2 nd harmonics present? YES NO Valid DTMF detected False tone STOP Figure 4. DTMF_Detect() Function Flowchart Rev. 0.11 7 AN218 5. Testing The software implementation of the Goertzel algorithm has been tested for talk-off errors using the Bellcore™ digit simulation test tape. For a 4 hour talk-off test with a calibration tone voltage of 72.5 mVRMS, no false hits were detected. In another 4 hour test with a calibration tone voltage of 135.5 mVRMS, only 11 false hits were detected. The software implementation in this reference design, running on the DTMF Decoder Evaluation Board, performs very well with respect to the limits specified by the Bellcore™ test tapes. 6. References [1] K. Banks, “The Goertzel Algorithm,” Embedded.com, August 28, 2002, <http://www.embedded.com/showArticle.jhtml?articleID=9900722> [2] Oppenheim, Schafer, Discrete Time Signal Processing, Prentice-Hall, 1989 8 Rev. 0.11 AN218 7. Software Example //----------------------------------------------------------------------------// Copyright 2004 Silicon Laboratories, Inc. // // FILE NAME : dtmf.c // TARGET DEVICE : C8051F300 // CREATED ON : 30.04.2004 // CREATED BY : SYRO // // Revision 1.0 // This file contains the source code of the DTMF decoder //----------------------------------------------------------------------------// Includes //----------------------------------------------------------------------------#include <stdio.h> #include “c8051f300.h” // SFR declarations #include “dtmf.h” sbit DATA = P0^5; sbit CLK = P0^7; // SIPO buffer data line // SIPO buffer clock line //----------------------------------------------------------------------------// MAIN Routine //----------------------------------------------------------------------------// void main(void) { EA = 0; // All interrupts disabled PCA0MD &= ~0x40; // Clear watchdog timer SYSCLK_Init(); PORT_Init(); Timer0_Init(SYSCLK/12/50); Timer2_Init(SYSCLK/12/SAMPLE_RATE); ADC0_Init(); CP0_Init(); UART0_Init(); PCA0_Init(); Interrupt_Init(); DTMF_Init(); while(1) { Idle(); while(!done); // // // // // // // Configure Condigure Configure Configure Configure Configure Configure system clock I/O port Timer0 Timer2 ADC0 Comparator0 UART0 // Initialize interrupts // Variable initializations // Switch to Idle Mode // Wait the feedback stage of // Goertzel filters for 2nd // harmonics to complete Rev. 0.11 9 AN218 DTMF_Detect(); // Compute feedforward stage of // Goertzel filters and decode // the DTMF character } } //----------------------------------------------------------------------------// SYSCLK_Init //----------------------------------------------------------------------------// // This routine initializes the system clock to use the internal oscillator // as its clock source. Also enables missing clock detector reset. // void SYSCLK_Init (void) { unsigned int i; OSCICN = 0x07; // configure internal oscillator for // its highest frequency for(i=0;i<255;i++); while(!(OSCXCN | 0x80)); OSCXCN = 0x61; RSTSRC = 0x06; // Enable Missing Clock Detector // and VDD Monitor } //----------------------------------------------------------------------------// PORT_Init //----------------------------------------------------------------------------// // Configure the Crossbar and GPIO ports. // P0.0 - ADC0 input // P0.1 - CP0 Negative Input // P0.2 - External Oscillator // P0.3 - External Oscillator // P0.4 - TX // P0.5 - RX // P0.6 - CP0 Positive Input // P0.7 - C2D // void PORT_Init (void) { XBR0 = 0x4F; // skip P0.0, P0.1, P0.2, P0.3 // and P0.6 10 XBR1 = 0x01; // UART TX selected XBR2 = 0xC0; // Enable crossbar and disable // weak pull-ups Rev. 0.11 AN218 P0MDIN &= ~0x4F; // P0.0, P0.1, P0.2, P03 and P0.6 // configured as analog inputs P0MDOUT = 0xFF; // All output pins are push-pull // except for P0.6 } //----------------------------------------------------------------------------// Timer0_Init //----------------------------------------------------------------------------// // This routine configures Timer0 // void Timer0_Init(int counts) { CKCON &= ~0x0B; // Timer0 clock source = SYSCLK/12 TMOD &= ~0x0E; TMOD |= 0x01; // Timer0 in 16 bit mode TH0 = -counts >> 8; TL0 = -counts & 0xFF; TMR0RLH = TH0; TMR0RLL = TL0; // Load Timer0 registers // Save values of Timer0 registers // for reload } //----------------------------------------------------------------------------// Timer2_Init //----------------------------------------------------------------------------// // Timer2 is configured in 16-bit autoreload mode. Its overflows will trigger // the ADC0 conversions. // void Timer2_Init (int counts) { TMR2CN = 0x00; // Timer2 configured for 16-bit // auto-reload, low-byte interrupt // disabled CKCON &= ~0x20; // Timer2 clock source = SYSCLK/12 TMR2 = -counts; TMR2RL = -counts; // Load Timer2 registers } //----------------------------------------------------------------------------// ADC0_Init //----------------------------------------------------------------------------// // ADC0 is configured in Single-Ended mode. Conversions are triggered by // Timer2 overflows. Rev. 0.11 11 AN218 // void ADC0_Init(void) { ADC0CN = 0x02; // ADC0 disabled; ADC0 conversions // are initiated on overflow of // Timer2 AMX0SL = 0xF0; // Select P0.0 as ADC0 input // ADC0 in Single-Ended Mode ADC0CF = (SYSCLK/5000000) << 3; ADC0CF |= 0x01; // ADC conversion clock < 5.0MHz // PGA gain = 1 REF0CN = 0x0A; // VREF = VDD, // Bias generator is on. AD0EN = 1; // Enable ADC0 } //----------------------------------------------------------------------------// CP0_Init //----------------------------------------------------------------------------// // Configure Comparator0 // void CP0_Init(void) { CPT0CN = 0x8C; // Comparator0 enabled // 20 mV positive hysteresis CPT0MX = 0x03; // P0.1 negative input // P0.6 positive input CPT0MD = 0x03; // CP0 response time 1050ns } //----------------------------------------------------------------------------// UART_Init //----------------------------------------------------------------------------// // Configure the UART0 using Timer1 for <BAUDRATE> and 8-N-1. // void UART0_Init(void) { SCON0 = 0x10; // SCON0: 8-bit variable bit rate // level of STOP bit ignored // RX enabled // ninth bits are zeros // clear RI0 and TI0 bits if (SYSCLK/BAUDRATE/2/256 < 1) { TH1 = -(SYSCLK/BAUDRATE/2); 12 Rev. 0.11 AN218 CKCON |= 0x10; } else if (SYSCLK/BAUDRATE/2/256 < 4) { TH1 = -(SYSCLK/BAUDRATE/2/4); CKCON &= ~0x13; CKCON |= 0x01; } else if (SYSCLK/BAUDRATE/2/256 < 12) { TH1 = -(SYSCLK/BAUDRATE/2/12); CKCON &= ~0x13; } else { TH1 = -(SYSCLK/BAUDRATE/2/48); CKCON &= ~0x13; CKCON |= 0x02; } // T1M = 1; SCA1:0 = xx TL1 = TH1; TMOD &= ~0xF0; TMOD |= 0x20; TR1 = 1; TI0 = 1; // Initialize Timer1 // TMOD: Timer1 in 8-bit autoreload // T1M = 0; SCA1:0 = 01 // T1M = 0; SCA1:0 = 00 // T1M = 0; SCA1:0 = 10 // START Timer1 // Indicate TX0 ready } //----------------------------------------------------------------------------// PCA0_Init //----------------------------------------------------------------------------// // Configure PCA0 // void PCA0_Init(void) { PCA0MD = 0x0B; // PCA0 timer uses external clock // divided by 8 // PCA0 timer overflow interrupt // enabled CR = 1; // Start PCA0 timer } //----------------------------------------------------------------------------// Interrupt_Init //----------------------------------------------------------------------------// // Enables the interrupts and sets their priorities // void Interrupt_Init(void) { IE = 0; // All interrupts disabled Rev. 0.11 13 AN218 EIE1 = 0; PT0 = 0; ET0 = 1; // Timer0 interrupt low priority // Timer0 interrupt enable EIP1 |= 0x04; EIE1 |= 0x04; // ADC0 interrupt high priority // ADC0 interrupt enable EIP1 &= ~0x08; EIE1 |= 0x08; // PCA0 interrupt low priority // PCA0 interrupt enable EA = 1; // Enable interrupts } //----------------------------------------------------------------------------// ADC0 Conversion Complete Interrupt Service Routine (ISR) //----------------------------------------------------------------------------// // ADC0 ISR implements the signal detection, automatic gain control, // DC bias removal and the feedback phase of the Goertzel filters // // void ADC0_ISR(void) interrupt 8 using 1 { AD0INT = 0; // Clear the interrupt flag x = ADC0; // Get a new sample //---------------------------------------------------------------------------// // // // BLOCK 2 // // Signal Detection // // // //---------------------------------------------------------------------------// delta_x = x - x_old; // The signal variation is big enough if( ( delta_x > XMIN ) || ( delta_x < -XMIN ) ) { if(new_tone) // The required pause between two { // tones has passed ET0 = 0; new_tone = 0; // // // // // Disable Timer0 interrupt Reset new_tone, only way for this code to execute again is if there is a 20ms gap in the signal and Timer0 overflows. start_goertzel = 1; // A new tone has been detected, // so start the DTMF detect. } 14 Rev. 0.11 AN218 TH0 = TMR0RLH; TL0 = TMR0RLL; // Reload Timer0 registers } x_old = ADC0; //---------------------------------------------------------------------------// // // // BLOCK 3 // // Automatic Gain Control // // // //---------------------------------------------------------------------------// // // // // // // // Initially, the signal input is passed through the following gain calculation routine. The gain is produced by dividing the maximum possible signal magnitude by the greatest input magnitude of the current tone. This produces a gain that when multiplied with the incoming signal will increase the signal amplitude to nearly full scale. Also the routine finds the lowest input magnitude, which will be used to compute the average value of the signal for DC bias removal. if(start_goertzel) { if(gain_calc) { if(x > high_x) high_x = x; else if(x < low_x) low_x = x; gain_cnt++; if(gain_cnt >= 70) { gain_cnt = 0; gain_calc = 0; // New tone detected // Gain calculation phase // Find maximum value // Find minimum value // Reset gain counter // Reset gain calculation flag // compute gain gain = ( 256 / (high_x - low_x) ); // low_x will contain the average value low_x += (high_x - low_x)>>1; } } else { // Gain calculation completed x = (x - low_x) * gain; // Scale input to nearly full scale // of ADC0, and remove DC bias //---------------------------------------------------------------------------// // // // BLOCK 4A // // Feedback Phase of Goertzel Filters // // // //---------------------------------------------------------------------------// Rev. 0.11 15 AN218 //Filter 1 Sample and Feedback Qhold.l = (long)coef1[base_freq]*(long)Q1[1].i; Q1[0].i = x; Q1[0].i += Qhold.hold.intval; Q1[0].i -= Q1[2].i; Q1[2].i = Q1[1].i; Q1[1].i = Q1[0].i; //End Filter 1 //Filter 2 Sample and Feedback Qhold.l = (long)coef2[base_freq]*(long)Q2[1].i; Q2[0].i = x; Q2[0].i += Qhold.hold.intval; Q2[0].i -= Q2[2].i; Q2[2].i = Q2[1].i; // Feedback part of filter Q2[1].i = Q2[0].i; //End Filter 2 //Filter 3 Sample and Feedback Qhold.l = (long)coef3[base_freq]*(long)Q3[1].i; Q3[0].i = x; Q3[0].i += Qhold.hold.intval; Q3[0].i -= Q3[2].i; Q3[2].i = Q3[1].i; // Feedback part of filter Q3[1].i = Q3[0].i; //End Filter 3 //Filter 4 Sample and Feedback Qhold.l = (long)coef4[base_freq]*(long)Q4[1].i; Q4[0].i = x; Q4[0].i += Qhold.hold.intval; Q4[0].i -= Q4[2].i; Q4[2].i = Q4[1].i; // Feedback part of filter Q4[1].i = Q4[0].i; //End Filter 4 //Filter 5 Sample and Feedback Qhold.l = (long)coef5[base_freq]*(long)Q5[1].i; Q5[0].i = x; Q5[0].i += Qhold.hold.intval; Q5[0].i -= Q5[2].i; Q5[2].i = Q5[1].i; // Feedback part of filter Q5[1].i = Q5[0].i; //End Filter 5 //Filter 6 Sample and Feedback Qhold.l = (long)coef6[base_freq]*(long)Q6[1].i; Q6[0].i = x; Q6[0].i += Qhold.hold.intval; Q6[0].i -= Q6[2].i; Q6[2].i = Q6[1].i; // Feedback part of filter Q6[1].i = Q6[0].i; //End Filter 6 16 Rev. 0.11 AN218 //Filter 7 Sample and Feedback Qhold.l = (long)coef7[base_freq]*(long)Q7[1].i; Q7[0].i = x; Q7[0].i += Qhold.hold.intval; Q7[0].i -= Q7[2].i; Q7[2].i = Q7[1].i; // Feedback part of filter Q7[1].i = Q7[0].i; //End Filter 7 //Filter 8 Sample and Feedback Qhold.l = (long)coef8[base_freq]*(long)Q8[1].i; Q8[0].i = x; Q8[0].i += Qhold.hold.intval; Q8[0].i -= Q8[2].i; Q8[2].i = Q8[1].i; // Feedback part of filter Q8[1].i = Q8[0].i; //End Filter 8 if(sample_no == max_sample) { sample_no = 0; done = 1; // Feed forward and output... // Reset sample counter // Set done flag so magnitude calculation // can run // The following code essentially scales each element down by 8 bits. // Filter1 Element Update Q1[1].b[1] = Q1[1].b[0]; // shift 8 bits right Q1[2].b[1] = Q1[2].b[0]; // sign extension if(Q1[1].b[0]<0) Q1[1].b[0] = 0xFF; else Q1[1].b[0] = 0x00; if(Q1[2].b[0]<0) Q1[2].b[0] = 0xFF; else Q1[2].b[0] = 0x00; // Filter2 Element Update Q2[1].b[1] = Q2[1].b[0]; Q2[2].b[1] = Q2[2].b[0]; // sign extension if(Q2[1].b[0]<0) Q2[1].b[0] = 0xFF; else Q2[1].b[0] = 0x00; if(Q2[2].b[0]<0) Q2[2].b[0] = 0xFF; else Q2[2].b[0] = 0x00; // Filter3 Element Update Q3[1].b[1] = Q3[1].b[0]; Q3[2].b[1] = Q3[2].b[0]; // sign extension Rev. 0.11 17 AN218 if(Q3[1].b[0]<0) Q3[1].b[0] = 0xFF; else Q3[1].b[0] = 0x00; if(Q3[2].b[0]<0) Q3[2].b[0] = 0xFF; else Q3[2].b[0] = 0x00; // Filter4 Element Update Q4[1].b[1] = Q4[1].b[0]; Q4[2].b[1] = Q4[2].b[0]; // sign extension if(Q4[1].b[0]<0) Q4[1].b[0] = 0xFF; else Q4[1].b[0] = 0x00; if(Q4[2].b[0]<0) Q4[2].b[0] = 0xFF; else Q4[2].b[0] = 0x00; // Filter5 Element Update Q5[1].b[1] = Q5[1].b[0]; Q5[2].b[1] = Q5[2].b[0]; // sign extension if(Q5[1].b[0]<0) Q5[1].b[0] = 0xFF; else Q5[1].b[0] = 0x00; if(Q5[2].b[0]<0) Q5[2].b[0] = 0xFF; else Q5[2].b[0] = 0x00; // Filter6 Element Update Q6[1].b[1] = Q6[1].b[0]; Q6[2].b[1] = Q6[2].b[0]; // sign extension if(Q6[1].b[0]<0) Q6[1].b[0] = 0xFF; else Q6[1].b[0] = 0x00; if(Q6[2].b[0]<0) Q6[2].b[0] = 0xFF; else Q6[2].b[0] = 0x00; // Filter7 Element Update Q7[1].b[1] = Q7[1].b[0]; Q7[2].b[1] = Q7[2].b[0]; // sign extension if(Q7[1].b[0]<0) Q7[1].b[0] = 0xFF; else Q7[1].b[0] = 0x00; if(Q7[2].b[0]<0) Q7[2].b[0] = 0xFF; else Q7[2].b[0] = 0x00; // Filter8 Element Update Q8[1].b[1] = Q8[1].b[0]; Q8[2].b[1] = Q8[2].b[0]; 18 Rev. 0.11 AN218 // sign extension if(Q8[1].b[0]<0) Q8[1].b[0] = 0xFF; else Q8[1].b[0] = 0x00; if(Q8[2].b[0]<0) Q8[2].b[0] = 0xFF; else Q8[2].b[0] = 0x00; // Save the bottom two filter elements for magnitude calculation Qt1[0].i = Q1[1].i; Qt1[1].i = Q1[2].i; Qt2[0].i = Q2[1].i; Qt2[1].i = Q2[2].i; Qt3[0].i = Q3[1].i; Qt3[1].i = Q3[2].i; Qt4[0].i = Q4[1].i; Qt4[1].i = Q4[2].i; Qt5[0].i = Q5[1].i; Qt5[1].i = Q5[2].i; Qt6[0].i = Q6[1].i; Qt6[1].i = Q6[2].i; Qt7[0].i = Q7[1].i; Qt7[1].i = Q7[2].i; Qt8[0].i = Q8[1].i; Qt8[1].i = Q8[2].i; Q1[1].i Q1[2].i Q2[1].i Q2[2].i Q3[1].i Q3[2].i Q4[1].i Q4[2].i Q5[1].i Q5[2].i Q6[1].i Q6[2].i Q7[1].i Q7[2].i Q8[1].i Q8[2].i = = = = = = = = = = = = = = = = 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; // Each window has 8 filters detecting 8 different frequencies. // If the second window has completed, then the flag to run // Goertzel is reset, the flag to calculate gain is reset, // Timer0 interrupt is reenabled, and the Timer0 high and low // bytes are reloaded with a 20ms delay. if(base_freq == 0) // 2nd harmonics filters completed { ET0 = 1; // Enable Timer0 interrupt TH0 = TMR0RLH; // Reload Timer0 TL0 = TMR0RLL; start_goertzel = 0; // Clear start of decoding flag Rev. 0.11 19 AN218 gain_calc = 1; low_x = 255; high_x = 0; max_sample = 200; } else { // // // // // Set gain calculation flag Initialize minimum and maximum signal values Number of samples for base frequencies // Base frequencies filters completed max_sample = 250; // Number of samples for 2nd harmonics } // Each time a window completes, this flag is set or reset to // indicate which window is next. base_freq^=1; } // Increment the number of iterations and move on until there // have been max_sample iterations. else sample_no++; } } } //----------------------------------------------------------------------------// Timer0 Interrupt Service Routine (ISR) //----------------------------------------------------------------------------// // Timer0 is set to overflow after 20ms with no signal. If Timer 0 overflows // then the next tone received is a new tone. // void Timer0_ISR (void) interrupt 1 using 2 { // If Timer0 overflows, then there has been at least 20ms // with no signal, that means the next tone will be a new one. new_tone = 1; done = 1; } //----------------------------------------------------------------------------// Comparator0 Rising Edge Interrupt Service Routine (ISR) //----------------------------------------------------------------------------// // Comparator0 interrupts are used to wake-up the system from Idle Mode. // This routine switches the system clock to internal 24.5 MHz oscillator // and re-enables the peripherals disabled by Idle() routine. // void CP0RE_ISR (void) interrupt 11 using 3 { OSC_Change(INT_OSC); // Switch to internal oscillator CPT0CN &= ~0x20; 20 // Clear CP0 rising edge interrupt Rev. 0.11 AN218 // flag EIE1 &= ~0x20; // Disable CPO rising edge interrupt TR0 = 1; TR1 = 1; TR2 = 1; // Start Timer0 // Start Timer1 // Start Timer2 AD0EN = 1; // Enable ADC0 } //----------------------------------------------------------------------------// PCA0 Interrupt Service Routine (ISR) //----------------------------------------------------------------------------// // PCA0 Timer is used to measure the elapsed time since last reset. The // interrupt is triggered once every 16 seconds. // void PCA0_ISR (void) interrupt 9 { CF = 0; second += 16; if(second >= 60) { second -= 60; minute++; if(minute >= 60) { minute = 0; hour++; if(hour >= 5) { hour = 0; } } } } //----------------------------------------------------------------------------// DTMF_Detect //----------------------------------------------------------------------------// // This routine implements the feedforward stage of the Goertzel filters // (the calculation of the squared magnitude) and the decoding of the // received DTMF character. If a valid tone has been detected, the // character is sent on UART0. // void DTMF_Detect(void) { Rev. 0.11 21 AN218 char temp_sig; int temp_dtmf_index; unsigned char temp_set1 = 0; unsigned char temp_set2 = 0; // // // // // All of these temp variables are used to store the values generated by the first set of magnitude calculations when the second set of calculations occurs. unsigned int pca_val; unsigned int pca_temp; char coef_index; // The index for coefficients array unsigned char i; coef_index = base_freq^1; // Reset the done flag so that the next time Timer4 overflows, // it spins until the sample and feedback stage is complete. done = 0; //---------------------------------------------------------------------------// // // // BLOCK 4B // // Feedforward Phase / Output Calculation // // // //---------------------------------------------------------------------------// // // // // Here the feedforward and magnitude stages are calculated. Notice that the feedforward phase is completed first and stored in Qthold.l. Then the magnitude is calculated using that result and the final two filter elements. //Filter 1 Output Qthold.l = (long)Qt1[0].i * (long)Qt1[1].i * (long)coef1[coef_index]; mag_squared1 = ((Qt1[0].i*Qt1[0].i) + (Qt1[1].i*Qt1[1].i) - (Qthold.hold.intval)); //Filter 2 Output Qthold.l = (long)Qt2[0].i* (long)Qt2[1].i* (long)coef2[coef_index]; mag_squared2 = ((Qt2[0].i*Qt2[0].i) + (Qt2[1].i*Qt2[1].i) - (Qthold.hold.intval)); //Filter 3 Output Qthold.l = (long)Qt3[0].i * (long)Qt3[1].i * (long)coef3[coef_index]; mag_squared3 = ((Qt3[0].i*Qt3[0].i) + (Qt3[1].i*Qt3[1].i) - (Qthold.hold.intval)); //Filter 4 Output Qthold.l = (long)Qt4[0].i * (long)Qt4[1].i * (long)coef4[coef_index]; mag_squared4 = ((Qt4[0].i*Qt4[0].i) + (Qt4[1].i*Qt4[1].i) 22 Rev. 0.11 AN218 - (Qthold.hold.intval)); //Filter 5 Output Qthold.l = (long)Qt5[0].i * (long)Qt5[1].i * (long)coef5[coef_index]; mag_squared5 = ((Qt5[0].i*Qt5[0].i) + (Qt5[1].i*Qt5[1].i) - (Qthold.hold.intval)); //Filter 6 Output Qthold.l = (long)Qt6[0].i * (long)Qt6[1].i * (long)coef6[coef_index]; mag_squared6 = ((Qt6[0].i*Qt6[0].i) + (Qt6[1].i*Qt6[1].i) - (Qthold.hold.intval)); //Filter 7 Output Qthold.l = (long)Qt7[0].i * (long)Qt7[1].i * (long)coef7[coef_index]; mag_squared7 = ((Qt7[0].i*Qt7[0].i) + (Qt7[1].i*Qt7[1].i) - (Qthold.hold.intval)); //Filter 8 Output Qthold.l = (long)Qt8[0].i * (long)Qt8[1].i * (long)coef8[coef_index]; mag_squared8 = ((Qt8[0].i*Qt8[0].i) + (Qt8[1].i*Qt8[1].i) - (Qthold.hold.intval)); //---------------------------------------------------------------------------// // // // BLOCK 5 // // DTMF Decode // // // //---------------------------------------------------------------------------// if(base_freq) temp_sig=sig_present; // All of the values calculated in the previous function call // are saved here into temporary variables. temp_sig = sig_present; temp_dtmf_index = dtmf_index; temp_set1 = set1; temp_set2 = set2; // Reset these guys for the decoding process. dtmf_index = 0; sig_present = 0; set1 = 0; set2 = 0; if(!base_freq) { // Base frequencies calculation // complete Rev. 0.11 23 AN218 // If the energy of a given frequency is eight times greater than // the sum of the other frequencies from the same group, then it is // safe to assume that the signal contains that frequency. // If that frequency is present then a unique bit in sig_present // is set, dtmf_index is modified so that the correct character // in the dtmfchar array will be accessed, and the set1 or set2 // flag is incremented to indicate that a low group or high // group frequency has been detected. if(mag_squared1 > (mag_squared2+mag_squared3+mag_squared4+8)<<3) { sig_present |= 0x01; dtmf_index += 0; set1 += 1; } else { sig_present &= ~0x01; // These elses are unnecessary } if(mag_squared2 > (mag_squared1+mag_squared3+mag_squared4+2)<<3) { sig_present |= 0x02; dtmf_index += 4; set1 += 1; } else sig_present &= ~0x02; if(mag_squared3 > (mag_squared1+mag_squared2+mag_squared4+2)<<3) { sig_present |= 0x04; dtmf_index += 8; set1 += 1; } else sig_present &= ~0x04; if(mag_squared4 > (mag_squared1+mag_squared2+mag_squared3+2)<<3) { sig_present |= 0x08; dtmf_index += 12; set1 += 1; } else sig_present &= ~0x08; if(mag_squared5 > (mag_squared6+mag_squared7+mag_squared8+1)<<3) { sig_present |= 0x10; dtmf_index += 0; set2 += 1; } else sig_present &= ~0x10; 24 Rev. 0.11 AN218 if(mag_squared6 > (mag_squared5+mag_squared7+mag_squared8+1)<<3) { sig_present |= 0x20; dtmf_index += 1; set2 += 1; } else sig_present &= ~0x20; if(mag_squared7 > (mag_squared5+mag_squared6+mag_squared8+1)<<3) { sig_present |= 0x40; dtmf_index += 2; set2 += 1; } else sig_present &= ~0x40; if(mag_squared8 > (mag_squared5+mag_squared6+mag_squared7+1)<<3) { sig_present |= 0x80; dtmf_index += 3; set2 += 1; } else sig_present &= ~0x80; } else { // 2nd harmonics calculation complete // If the sum of all 2nd harmonics energy is greater than a threshold // value, we assume that the signal wasn’t a pure DTMF. if(mag_squared1 + mag_squared2 + mag_squared3 + mag_squared4 + mag_squared5 + mag_squared6 + mag_squared7 + mag_squared8 > 125) { sig_present = 1; } } //---------------------------------------------------------------------------// // // // BLOCK 6 // // DTMF Output // // // //---------------------------------------------------------------------------// // If both windows have completed, there is 1 and only one of // the row tones and 1 and only one of the column tones, // and there are no harmonic frequencies present, then print // the proper character to the terminal. if((sig_present == 0)&&(base_freq)&&(temp_set1 == 1)&&(temp_set2 == 1)) { CR = 0; // Stop PCA0 timer pca_val = PCA0L; // Read PCA0 value Rev. 0.11 25 AN218 pca_val += PCA0H*256; CR = 1; // Start PCA0 Timer // Compute the timpe elapsed since last reset temp_sec = second; temp_min = minute; temp_hour = hour; pca_temp = pca_val / 4096; temp_sec += pca_temp; pca_temp = pca_val - pca_temp*4096; hundredth = ((unsigned long)pca_temp*100)/4096; if(temp_sec >= 60) { temp_sec -= 60; temp_min++; if(temp_min >= 60) { temp_min = 0; temp_hour++; if(temp_hour >= 5) { temp_hour = 0; } } } // Send the decoded character and the time elapsed since laste reset // on UART UART_display(dtmfchar[temp_dtmf_index]); for(i=0; i<100; i++) Delay(); // Display the decoded character on the 7-segment cell Display(display_codes[temp_dtmf_index]); } } //----------------------------------------------------------------------------// DTMF_Init //----------------------------------------------------------------------------// // Various variable initializations // void DTMF_Init(void) { 26 Rev. 0.11 AN218 new_tone = 1; done = 0; base_freq = 1; gain_cnt = 0; gain_calc = 1; sample_no = 0; set1 = 0; set2 = 0; low_x = 255; high_x = 0; max_sample = 200; TR0 = 1; TR2 = 1; // Start Timer0 // Start Timer2 Display(0x01); // Display the decimal point } //----------------------------------------------------------------------------// OSC_Change //----------------------------------------------------------------------------// // Switches system clock to internal or external oscillator // void OSC_Change(char n) { unsigned int i; RSTSRC &= ~0x04; // Disable Missing Clock Detector if(n==EXT_OSC) { for(i=0;i<255;i++); while(!(OSCXCN | 0x80)); OSCICN |= 0x08; OSCICN &= ~0x04; } else { OSCICN |= 0x04; while(!(OSCICN & 0x10)); OSCICN &= ~0x08; } // Switch to external oscillator RSTSRC |= 0x04; // Enable Missing Clock Detector // Wait for external osc. to stabilize // System clock = external oscillator // Turn off internal osc. // Switch to internal oscillator // Turn on internal osc. // System clock = internal oscillator } //----------------------------------------------------------------------------// Idle //----------------------------------------------------------------------------// // This routine turns off all active digital and analog peripherals (except for Rev. 0.11 27 AN218 // Comparator0, used to wake-up the system), switches the system clock to external // crystal and puts the microcontroller in Idle Mode. // void Idle() { EA = 0; // Disable all interrupts TR0 = 0; TR1 = 0; TR2 = 0; // Stop Timer0 // Stop Timer1 // Stop Timer2 AD0EN = 0; // Disable ADC0 EIE1 |= 0x20; // Enable CPO rising edge interrupt OSC_Change(EXT_OSC); // Switch to external oscillator EA = 1; // Enable all interrupts PCON |= 0x01; // Switch to idle mode } //----------------------------------------------------------------------------// Display //----------------------------------------------------------------------------// // Send a character to the Serial Input Parallel Output buffer that drives the // 7-segment LED display // void Display(unsigned char char_code) { unsigned char i; for(i=0x01; i!=0x00; i=i<<1) { CLK = 0; if( (char_code & i) != 0 ) DATA = 1; else DATA = 0; Delay(); CLK = 1; Delay(); // Hold clock low // Send a 1 to data line // Send a 0 to data line // Wait 250us // Clock high // Wait 250us } DATA = 1; CLK = 1; // After the character is sent, pull // clock and data high } //----------------------------------------------------------------------------- 28 Rev. 0.11 AN218 // UART_display //----------------------------------------------------------------------------// // Sends on UART the decoded character and the time elapsed since last reset. // void UART_display(unsigned char character) { unsigned char temp; putchar(character); putchar(‘ ‘); // Send the DTMF character putchar(temp_hour+’0’); putchar(‘:’); // Send hours temp = temp_min/10; putchar(temp + ‘0’); temp = temp_min - temp*10; putchar(temp + ‘0’); putchar(‘:’); // Send minutes temp = temp_sec/10; putchar(temp + ‘0’); temp = temp_sec - temp*10; putchar(temp + ‘0’); putchar(‘.’); // Send seconds temp = hundredth/10; putchar(temp + ‘0’); temp = hundredth - temp*10; putchar(temp + ‘0’); // Send hundredths of seconds putchar(‘ ‘); putchar(‘ ‘); } //----------------------------------------------------------------------------// Delay //----------------------------------------------------------------------------// // Wait for about 250us // void Delay(void) { TR0 = 0; // Stop Timer0 TL0 = 0; // Reset Timer0 TH0 = 0; TR0 = 1; // Start Timer0 while(TH0 < 2); // Wait 250us TR0 = 0; TL0 = TMR0RLL; TH0 = TMR0RLH; // Stop Timer0 // Reload Timer0 Rev. 0.11 29 AN218 TR0 = 1; // Start Timer0 } //----------------------------------------------------------------------------// end of dtmf.c //----------------------------------------------------------------------------//----------------------------------------------------------------------------// Copyright 2004 Silicon Laboratories, Inc. // // FILE NAME : dtmf.h // TARGET DEVICE : C8051F300 // CREATED ON : 30.04.2004 // CREATED BY : SYRO //----------------------------------------------------------------------------- //----------------------------------------------------------------------------// Global CONSTANTS //----------------------------------------------------------------------------#define SYSCLK 24500000 // SYSCLK frequency in Hz #define XTALCLK 32768 // XTALCLK frequency in Hz #define BAUDRATE 9600 // UART0 baud rate #define SAMPLE_RATE 8000 // Sample rate for ADC0 conversions #define XMIN 20 // Threshold value for the difference // between two succesive samples #define EXT_OSC #define INT_OSC 0 1 // Constants used by OSC_Change // routine //----------------------------------------------------------------------------//Structures and Types //----------------------------------------------------------------------------struct ltype // In order to avoid truncation in the { // filter feedback stage, a long char space; // multiply and a divide are necessary. int intval; // This structure is used to avoid the char trunc; // long division. }; typedef union ULONG { long l; struct ltype hold; } ULONG; // // // // // When the filter terms are calculated, the desired result ends up in the two middle bytes of the long variable. Rather than divide by 256, the code accesses the middle two bytes directly. typedef union UINT { int i; char b[2]; } UINT; // // // // // This type is used in much the same way as the previous one. Rather than dividing the value to scale it down, the code accesses the high byte, which is equivalent to divide by 256. 30 Rev. 0.11 AN218 typedef union LNG { long l; int i[2]; } LNG; // Same as UINT but modified to access // the high 16 bits of a long result. //----------------------------------------------------------------------------// 16-bit SFR Definitions for ‘F30x //----------------------------------------------------------------------------sfr16 TMR2 = 0xcc; // Timer2 counter sfr16 TMR2RL = 0xca; // Timer2 reload value //----------------------------------------------------------------------------// Global VARIABLES //----------------------------------------------------------------------------char code dtmfchar[16] = // DTMF characters { ‘1’,’2’,’3’,’A’, ‘4’,’5’,’6’,’B’, ‘7’,’8’,’9’,’C’, ‘*’,’0’,’#’,’D’ }; char code { 0x60, 0x66, 0xE0, 0x6E, }; code code code code code code code code int int int int int int int int display_codes[16] = 0xDA, 0xB6, 0xFE, 0xFC, 0xF2, 0xBE, 0xF6, 0x3A, coef1[2] coef2[2] coef3[2] coef4[2] coef5[2] coef6[2] coef7[2] coef8[2] = = = = = = = = // Character codes for the 7-segment // LED display 0xEE, 0x3E, 0x9C, 0x7A { 235, { 181, { 118, { 47, {-165, {-258, {-349, {-429, 437}; 421}; 402}; 378}; 298}; 255}; 204}; 146}; // Goertzel filter coefficients ULONG Qhold; // Temporary storage for fiter // calculations ULONG Qthold; // Saves previous value for magnitude // calculations UINT Q1[3]; // These are the elements of the 8 Rev. 0.11 31 AN218 UINT UINT UINT UINT UINT UINT UINT Q2[3]; Q3[3]; Q4[3]; Q5[3]; Q6[3]; Q7[3]; Q8[3]; idata idata idata idata idata idata idata idata UINT UINT UINT UINT UINT UINT UINT UINT idata idata idata idata int int int int // Goertzel filters. The filters are // 2 pole IIR filters and so require // 3 terms each. Qt1[2]; Qt2[2]; Qt3[2]; Qt4[2]; Qt5[2]; Qt6[2]; Qt7[2]; Qt8[2]; mag_squared1, mag_squared3, mag_squared5, mag_squared7, // // // // mag_squared2; mag_squared4; mag_squared6; mag_squared8; These 2 element arrays are used for storing the low two elements of the filter elements after N filter iterations. // Store output of the Goertzel filters. unsigned char TMR0RLH, TMR0RLL; // Timer0 reload value bit new_tone; // Flag for valid pause between tones bit start_goertzel; // Flag for start of the decoding // process bit gain_calc; // Flag for gain computing bit done; // Flag for starting character decoding char base_freq; // Flag for base frequencies / // 2nd harmonics int x; // Current signal sample int x_old; // Former signal sample int high_x, low_x; // Minimum and maximum value of the // signal int delta_x; int gain; // Gain computed in AGC block unsigned char gain_cnt; // Gain stage sample counter unsigned char sample_no; // Sample counter unsigned char max_sample; // Total no. of samples for base freqs // and 2nd harmonics unsigned char sig_present; // For every frequency detected, a bit 32 Rev. 0.11 AN218 // in sig_present is set unsigned char dtmf_index; // Index for dtmfchar array unsigned char set1; unsigned char set2; // Hold the no. of freqs. detected unsigned unsigned unsigned unsigned char char char char hour; minute; second; hundredth; unsigned char temp_hour; unsigned char temp_min; unsigned char temp_sec; //----------------------------------------------------------------------------// Function PROTOTYPES //----------------------------------------------------------------------------void SYSCLK_Init(void); // System clock configuration void PORT_Init(void); // I/O port configuration void Timer0_Init(int counts); // Timer0 configuration void Timer2_Init(int counts); // Timer2 configuration void ADC0_Init(void); // ADC0 configuration void CP0_Init(void); // Comparator0 configuration void UART0_Init(void); // UART0 configuration void PCA0_Init(void); // PCA0 configuration void Interrupt_Init(void); // Interrupt configuration void DTMF_Detect(void); // Compute signal energy and // decode DTMF characters void DTMF_Init(void); // Variable initializations void Idle(void); // Switches to Idle Mode void OSC_Change(char n); // Changes the system clock void Display(unsigned char char_code); // Displays a character on the // 7-segment LED display void UART_display(unsigned char character); void Delay(void); // Waits for 250us Rev. 0.11 33 AN218 //----------------------------------------------------------------------------// end of dtmf.h //----------------------------------------------------------------------------- 34 Rev. 0.11 AN218 NOTES: Rev. 0.11 35 AN218 CONTACT INFORMATION Silicon Laboratories Inc. 4635 Boston Lane Austin, TX 78735 Email: [email protected] Internet: www.silabs.com The information in this document is believed to be accurate in all respects at the time of publication but is subject to change without notice. Silicon Laboratories assumes no responsibility for errors and omissions, and disclaims responsibility for any consequences resulting from the use of information included herein. Additionally, Silicon Laboratories assumes no responsibility for the functioning of undescribed features or parameters. Silicon Laboratories reserves the right to make changes without further notice. Silicon Laboratories makes no warranty, representation or guarantee regarding the suitability of its products for any particular purpose, nor does Silicon Laboratories assume any liability arising out of the application or use of any product or circuit, and specifically disclaims any and all liability, including without limitation consequential or incidental damages. Silicon Laboratories products are not designed, intended, or authorized for use in applications intended to support or sustain life, or for any other application in which the failure of the Silicon Laboratories product could create a situation where personal injury or death may occur. Should Buyer purchase or use Silicon Laboratories products for any such unintended or unauthorized application, Buyer shall indemnify and hold Silicon Laboratories harmless against all claims and damages. Silicon Laboratories and Silicon Labs are trademarks of Silicon Laboratories Inc. Other products or brandnames mentioned herein are trademarks or registered trademarks of their respective holders. 36 Rev. 0.11