Вы находитесь на странице: 1из 34

Guitar Tuner

Rodrigo Sierra Chavera, Electrical Engineering

Project Advisor: Dick Blandford

April 16, 2012

Evansville, Indiana

Table of Contents I. Introduction II. Background III. Design Approach


A.Hardware B. Software C. Standards

IV. Results V. Conclusion Appendix A Appendix B Appendix C Appendix D Appendix E

List of Figures Figure 1: Sketch of the Product Figure 2: Basic Block Diagram Figure 3: Pickup Representation Figure 4: Non-inverting Op-amp Diagram Figure 5: Final product Figure 6: LPC1768 ARM7 Schematic Figure 7: LM238 operational amplifier Figure 8: AC signal circuit Figure 9: Output Signal from AC Circuit Figure 10: Spectrum of a Guitar Frequency

List of Tables

Table 1: Chromatic Scale of Notes Table 2: Standard E Tuning Table 3: Program Description. Tasks and public functions

I. Introduction

Musicians need to have their instruments perfectly tuned so they can play different types of music. Anyone who is starting to learn to play any instrument knows that tuning it can be difficult and many times stressful. One of the reasons is that these novice musicians have not yet acquired what experts call musical ear or trained ear. The conventional tuning process done by ear can be useful and practical in many situations; however, this can produce many inaccuracies and if not good enough, it may require a significant amount of time. An electronic guitar tuner functions as a device used to help musicians tune instruments. In this particular case, this designed device can tune any electric guitar string to its ideal frequency. This device detects whether a string of an electric guitar is in tune or not by displaying the frequency difference (fundamental frequency current frequency). Also, a certain number of LEDs will aid the user to decide whether the tuning pegs of a guitar need to be turned clockwise or counterclockwise. When holding the device, the user will see a power on/off switch, 3 LEDs that indicate whether the string is in tune, sharp, or flat, and a push button to select any of the 6 strings.

Figure 1: Sketch of the Product

The components used to design and build the electronic guitar tuner are a microcontroller (mbed LPC1768), liquid crystal display (LCD) panel for display, light-emitting diodes (LED), user interface button, power switch, and many analog components for the guitar signal amplification. These analog components turn the incoming signal from the guitar into usable data for the microcontrollers analog to digital converter (ADC). Then, the microcontroller runs the program (see appendix E) stored in it which has two main stages. The first one reads the signal from the ADC port and runs the Goertzel algorithm (appendix D shows how this routine works) to it to find its harmonics. Next, the program does the necessary calculations to find the frequency difference and sends the data to the digital to analog converter (DAC) of the microcontroller which is displayed on an LCD panel.

! itar Si"nal D$ BIAS

Power So rce

LPC1768
A#plification

%ser interface

MCU

Figure 2: Basic Block Diagram

II. Background

A note is a name given to describe a musical frequency [1]. The chromatic scale is frequently used in Western music and it consists of the following twelve notes: A, A#, B, C#, D, D#, E, E#, F, F#, G, G#. In general, the note A is chosen as the standard frequency, and from this note, the other notes frequencies are calculated. It is possible to find the distance between the frequencies of two sounds by dividing the frequency of one by the frequency of the other. This distance is called interval. An octave is the interval between two sounds one of which has twice the frequency of the other [2]. These 12 notes mentioned before repeat for each octave. Notes are assigned by their letters, and the number after them represents the octave they reside in. For instance, B = 246.94 is in the 2nd octave and is called B3. Table 1 [1] lists the chromatic scale of notes over a three octave range.
Table 1: Chromatic Scale of Notes

Note A A# B C C# D D# E F F# G G#

Octave1 110.00 116.54 123.47 130.81 138.59 146.33 155.56 164.81 174.61 185.00 196.00 207.65

Octave2 220.00 233.08 246.94 261.63 277.18 293.66 311.13 329.63 349.23 369.99 392.00 415.30

Octave3 440.00 466.16 493.88 523.25 554.37 587.33 622.25 659.26 698.46 739.99 783.99 830.61

A guitar is a string instrument which usually has 6 strings. Because of the strings, the sounds waves that a guitar emits are harmonic in nature. When a guitar string is plucked at a random location on a guitar, the 1st harmonic or fundamental frequency, and also integer multiples of it are propagated. These locations are where notes reside are made by frets which are the raised portion on the neck of a stringed instrument. Frets divide the neck into fixed segments; on a guitar, each fret represents one semitone. An electric guitars fretboard is designed so that the 12 notes of the chromatic scale are located from the zero position (open note) to the 12th fret. After the 12th fret, all the notes repeat at the next octave. When someone tunes a guitar, what it basically happens is that the string acquires the proper tension by adjusting the tuning pegs of the guitar. Ideally, once a guitar is tuned, it should stay in tune forever. Unfortunately, due to environment (humidity, temperature, time) and other physical factors, the strings lose tension periodically and they need to be tuned again depending on how often they are used. An electric guitar is normally tuned to standard tuning (E). There are many other tunings which are specialized for certain types of music, such as Open D for folk guitarists. Table 2 lists the standard tuning for a regular electric guitar [1].
Table 2: Standard E Tuning

String # 1 2 3 4 5 6

Note E B G D A E

Frequency (Hz) 329.6 246.9 196.0 146.8 110.0 82.4

III. Design Approach A. Hardware

The way to get the electric guitar output is by getting in contact with the guitars pickups. These are a type of transducer which gets the different guitar vibrations and turns them into an electric signal [3]. Different guitars have different pickups (magnetic and piezoelectric). But in general, they are mostly the same; they have magnetic windings to induce magnetic field.

Figure 3: Pickup Representation

An audio cable was connected to the electric guitars output jack to get the signal. The problem is that typically the signal coming from the electric guitar is really low (between 5 to 100 mV) and very noisy. From the LPC1768 datasheet, this signal is not enough to be sent to the ADC. The signal had to be amplified to reach the 3.3 V peak-to-peak requirement of the ADC port. To do so, the LM358 operational amplifier was used. Its schematic can be found in appendix A. This specific op-amp was chosen because it works well in this specific frequency range (0 to 1000 Hz). Moreover, they were available in the stockroom. The design of the circuit was basically divided into three stages. The first stage was designed to provide a DC offset of 1.65V to the signal. When designing the circuit, certain

values of capacitors and resistors were used to avoid filtering any undesired frequency out. In this case, a couple of 1M resistors and a 1F capacitor were used. Then, the signal goes through an amplification circuit. Two inverting op-amps that made the signal go up to 3.3V were used. The circuit was designed in a way that the overall gain is 33. Finally, a pair of diodes (1N914) with a 3.3V input were added to rectify any signal that goes beyond the 3.3V threshold. This whole circuit creates a nice waveform oscillating between 0 and 3.3V.

&
! itar Si"nal

'

AD$

Figure 4: Non-inverting Op-amp Diagram

The microcontroller chosen for this design was the LPC1768 from NXP. The reason why this microcontroller was chosen was because it is a powerful chip. The LPC1768 is based on a 32-bit ARM7 cortex-M3 core running at 96 MHz central processing unit (CPU) with real time emulation and embedded trace support that combines the microcontroller with 512 kB of embedded high-speed flash memory, 32kB of SRAM. It also has two UARTs, three 32-bit timers and a real time clock. The MCU operates at +5V; in this case a 5V regulator was used to bring the input voltage from the fours AA batteries to 5V. Moreover, the ADC is included in the chip as an analog block. There are two channels with up to 12-bit resolution each. The input for the

ADC is specified in the schematic of the MCU found in appendix A [4].A sampling rate of 20000 samples/second was used, and it satisfies the Nyquist sampling theorem. The electronic guitar tuner is powered by a set of four AA batteries (6V). It also has a power switch which turns the device on and off. Then, the device has a switching voltage regulator that drops the voltage down to +5V respectively. I chose this type of regulator due to its efficiency and heat dissipation. Everything runs at +5V (MCU, op-amps) but the inputs for rectifier, LCD and DC bias configuration (powered by the 3.3V output of the LPC1768). B. Software

The program design for this project has been written in C using Vision 4. The incoming AC electric signal is converted into digital to be used by digital signal processing (DSP). The use of the FFT and Goertzel algorithm was critical in the software design part of the project. First, the fast Fourier transform (FFT) was used to determine the spectrum of the signals. Appendix C shows how this technique works. In DSP, the user deals with discreet signal; that is why the discrete Fourier transform (DFT) formula was invented. Due to many unnecessary operations, the FFT algorithm came to life. As mentioned before, the FFT task (algorithm) is very important because it controls the sharp/flat/in-tune LEDs and the display, based on the program results. The sample frequency used in the design is 20000 Hz with 4096 samples. Unfortunately, this algorithm requires mid to high end microcontrollers that can handle many calculations. That is why, the Goertzel algorithm came in in very handy. It basically gives the same result of one frequency bin of the FFT and it is more efficient. Plus, it takes less than half of the time that the FFT takes. Thus, this algorithm was picked to transform the time domain signal to a frequency domain one.

The whole program has four main tasks. The first one is the initialize function; it starts the system and only runs once. The second task sets the parameters of each string and also sets the baud rate of the LCD for serial communication. Then, the Goertzel task does the necessary calculations to turn the data stored in the buffer into the frequency domain. And, the last task displays all the important information on a LCD and controls the LEDs.
Table 3: Program Description. Tasks and public functions

File Main.c

Tasks Initialize variables Set parameters for each string Set LCD baud rate Goertzel algorithm Display messages

Public Functions

ADC.c

ADC()

C. Standards

My project conformed to the ARM7TDMI power consumption standard. This is about portable and handheld products require processors that consume less power than those in desktop and other powered applications. It states that designers must analyze power use in the early stage of design both at the circuit and system levels. RISC processors, such as our ARM7TDMI, have both strengths and weaknesses as far as power consumption is concerned [5].

IV. Results

The designed guitar tuner worked as expected. The incoming signal from an electric guitar was successfully amplified and centered at 1.65 V. Since only one power source was used to power the operational amplifier, the signal didnt go all the way down to ground. However, this wasnt needed since it met the basic requirements of the ADC of the mbed microcontroller. Also, the Goertzel algorithm worked as expected. It did a good job in calculating the different frequencies. After some tests, it was found that the FFT had a finite frequency resolution and it was hard to detect small frequency changes without increasing the time window which led to a sluggish program. As a result, the Goertzel algorithm performed exceptionally well and took less time than the FFT. To make it even more accurate, the designed program ran this algorithm six times with six different frequency targets, and compared them to find out whether the string was in tune or not. When comparing this device with others found in the current market, it is found that it has many customizable features. For instance, tuning types can be changed at any time by just changing the frequency target value son the program. Also, it can be built with less than 20 dollars since the Goertzel algorithm doesnt require a powerful processor.

V. Conclusion

An accurate, real-time tuner was designed using the guitar output signal, operational amplifiers, NXP LPC1768 processor, LCD panel and LEDs. It has been found that even though there is noise in the signal, the fundamental frequencies will still be the same. Also that other FFT algorithms and approaches gave more accurate results when implemented in the right way.

In the real world, users look for cheap and accurate devices and this designed device could easily fit in this area. Moreover, a better signal-to-noise ratio of the signal could have been gotten by probably using a higher sampling rate or maybe more points. Less noise in the signal might have given a cleaner FFT spectrum to be compared with the Goertzel algorithm. The FFT is not only way and best way to design a guitar tuner and this device is a perfect example. It behaves really well and does the calculations fast enough to make the tuning process enjoyable and not sluggish at all.

Figure 5: Final product

Appendix A

LPC1768 Schematic [6]:

Figure 6: LPC1768 ARM7 Schematic

Figure 7: LM238 operational amplifier

Appendix B

Simulation results:

Figure 8: AC signal circuit

Figure 9: Output Signal from AC Circuit

Appendix C

DFT and FFT algorithm While the DFT transform above can be applied to any complex valued series, in practice for large series it can take considerable time to compute, the time taken being proportional to the square of the number on points in the series. A much faster algorithm has been developed by Cooley and Tukey around 1965 called the FFT (Fast Fourier Transform). The only requirement of the the most popular implementation of this algorithm (Radix-2 Cooley-Tukey) is that the number of points in the series be a power of 2. The computing time for the radix-2 FFT is proportional to .So for example a transform on 1024 points using the DFT takes about 100 times longer than using the FFT, a significant speed increase. Note that in reality comparing speeds of various FFT routines is problematic, many of the reported timings have more to do with specific coding methods and their relationship to the hardware and operating system. Sample transform pairs and relationships

The Fourier transform is linear, that is a f(t) + b g(t) ---> a F(f) + b G(f) a xk + b yk ---> a Xk + b Yk

Scaling relationship f(t / a) ---> a F(a f) f(a t) ---> F(f / a) / a

Shifting f(t + a) ---> F(f) e-j 2 pi a f

Modulation f(t) ej 2 pi a t ---> F(t - a)

Duality Xk ---> (1/N) xN-k

Sampling theorem The sampling theorem (often called "Shannons Sampling Theorem") states that a continuous signal must be discretely sampled at least twice the frequency of the highest frequency in the signal. More precisely, a continuous function f(t) is completely defined by samples every 1/fs (fs is the sample frequency) if the frequency spectrum F(f) is zero for f > fs/2. fs/2 is called the Nyquist frequency and places the limit on the minimum sampling frequency when digitising a continuous sugnal.

Figure 10: Spectrum of a Guitar Frequency

Appendix D

Goerzel algorithm [7]: There are various ways to detect the presence of a special known frequency in a monitored signal. A simplistic way is to take an FFT (Fast Fourier Transform) of the signal and check whether the desired frequency is present. That is not very efficient, however, because most of the computed results are ignored. A discrete Fourier transform (DFT) produces the same numerical result for a single frequency of interest, making it a better choice for tone detection. The Goertzel Algorithm is a DFT in disguise, with some numerical tricks to eliminate complex number arithmetic, roughly doubling the efficiency. This note presents the Goertzel Algorithm [1,2], and in particular, ways to improve its ability to isolate frequencies of interest. The Goertzel Algorithm has received a lot of attention recently for mobile telephone applications, but there are certainly many other ways it can be used. This section summarizes very briefly how an ordinary DFT is transformed into Goertzel filter form. If you don't care about these details, you can skip this section.We start from the DFT equation in its natural form.

The frequency of interest is located at term k. f = k/N fsample The weighting factors in the DFT equation are complex values obtained by sampling the cosine and sine functions. These complex numbers have the property that indexing them in the reverse direction generates the complex conjugates; the negative powers also produce complex conjugates. Doing both results in the original weighting factors, only with a different way of thinking about how they are generated.

In something of a role reversal, we can treat the powers of W in the DFT summation as the power terms in a polynomial, with the x terms as the multiplier coefficients. An efficient way to evaluate this polynomial is the nested form.

As the nested form is evaluated term by term, it produces a sequence of intermediate results y: y-1 = 0 y0 = W-k y-1 + x0 y1 = W-k y0 + x1 ... yN-1 = W-k yN-2 + xN-1 yN = W-k yN-1 The final result yN equals the desired DFT sum. The N evaluation steps can be summarized by iteration yn = W-k yn-1 + xn n = 0, ... , N-1 While simple, this is still no more efficient than the original DFT.The iteration equation has an equivalent transfer function in terms of discrete domain variable z.

Multiplying top and bottom by the conjugate of the denominator terms, and incorporating the multiply operation that follows the final step of the nested sequence, we get the final Goertzel filter equation.

Let us review the minor miracles. 1. The filtering is split into two simple cascaded parts. 2. The part of the filtering applied directly to the input sequence uses only real numbers. 3. One of the filter multipliers has reduced to a constant value of 1.0, eliminating half of the multiply operations. 4. The other multiplier does not change. It is not necessary to use a table of pre-computed coefficients as in an FFT or DFT. 5. The only value of y we really care about is the final one, yN. The second filter stage is a two-term FIR filter evaluated just once.

Appendix E Source code:


///////////////////////Electric Guitar Tuner////////////////////// /////////////////Rodrigo Sierra Chavera////////////////////////// #include "mbed.h" #include "adc.h" #include <math.h> #define PI 3.1416 #define fs 20000

InterruptIn selection(p12); Serial pc(USBTX, USBRX); // tx, rx //to communicate with the putty //for debugging Serial device(p13, p14); // tx, rx //LCD serial communication //DebounceIn pb(p12); DigitalOut lowLED(p28); ///leds ports DigitalOut tuneLED(p29); DigitalOut highLED(p30); //Initialize variables int Counter = 0; int string = 0; int DataBuffer[5000]; float low, lowok; float intune, intuneA, intuneB, intuneC, note_freq; float high, highok; char* note_played;

void selection_pressed() // push button routine { string++; if (string > 5) string = 0; } //////////////////////////////////////////////////////////////////// //////////////////////GOERTZEL ALGORITHM//////////////////////////// //////////////////////////////////////////////////////////////////// float Goertzel(int samples[], float freq, int N) { float s_prev = 0.0; float s_prev2 = 0.0; float coeff,normalizedfreq,power,s; int i; normalizedfreq = freq / fs; coeff = 2*cos(2*PI*normalizedfreq); for (i=0; i<N; i++) { s = samples[i] + coeff * s_prev - s_prev2; ///////////////////// ///Formula: Q0=s=coeff*Q1-Q2+buffer//////////////////////////////

s_prev2 = s_prev; s_prev = s; } power = s_prev2*s_prev2+s_prev*s_prev-coeff*s_prev*s_prev2; ////Optimized Goertzel/////////////////////////////////////////////// ////magnitude2 = Q12 + Q22-Q1*Q2*coeff/////////////////////////////// return power; } ////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////

ADC adc(fs, 1);

//set ADC using .h file

void sample_data(int chan, uint32_t value) { DataBuffer[Counter] = adc.read(p15); Counter += 1;//////////////number of samples } int main() { //LCD device.baud(4800); device.putc('/'); //clear LCD //Interupt for Switching Strings selection.mode(PullDown); selection.rise(&selection_pressed);

while (1) { //when button is pressed, the program goes through this different cases //and it sets frequency values switch (string) { case 0: note_freq = 82; note_played= "E2"; break; case 1: note_freq = 110; note_played= "A2"; break; case 2: note_freq = 147; note_played= "D3"; break; case 3: note_freq = 196; note_played= "G3"; break; case 4: note_freq = 247; note_played= "B3"; break;

case 5: note_freq = 330; note_played= "E4"; break; } ////////////////////////////////////ADC setup/////////////////////////////////////////////////////////////// //////////////////to get data from port 15////////////////////////////////////////////////////////////////// //Prepare for burst mode on all ADC pins and set up interrupt handler adc.append(sample_data); adc.startmode(0,0); //set up mbed adc.burst(1); adc.setup(p15,1); //ADC on adc.interrupt_state(p15,1); //interrupt start up wait(.2);

//delay so microcontroller can get a total of 4096 samples

adc.interrupt_state(p15,0); //Finish the interrupt adc.setup(p15,0); //turn off ADC int actualfs = adc.actual_sample_rate(); //find the actual sampling rate used for calculations /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////// high = 0; low = 0; int while1 = 2; while(while1<48) //low note scenario { lowok = Goertzel(DataBuffer, (note_freq + while1 ), Counter); if (lowok > low) low=lowok; while1+=2; } intuneA = Goertzel(DataBuffer, (note_freq+1), Counter); //Close frequencies from Target Frequency intuneB = Goertzel(DataBuffer, note_freq, Counter); //Close frequencies from Target Frequency intuneC = Goertzel(DataBuffer, (note_freq-1), Counter); //Close frequencies from Target Frequency if ((intuneA > intuneB) && (intuneA > intuneC)) intune = intuneA; else if ((intuneB > intuneA) && (intuneB > intuneC)) intune = intuneB; else intune = intuneC; int while2 = 2; while(while2<48) //high note scenario { highok = Goertzel(DataBuffer, (note_freq + while2 ), Counter); if (highok > high) high=highok; while2+=2; } ////////////////////////////////////For degubbing purposes///////////////////////////////////////

////////////////////////////////print low, in tune, high values////////////////////////////////// pc.printf("low = %.2f, in tune = %.2f, high = %.2f", low, intune, high); //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// if ((intune > high) && (intune > low)) //comparison of magnitudes to decide whether a string //is high or low or in tune { highLED = 0; tuneLED = 1; lowLED = 0; } else if (high > intune) { highLED = 1; tuneLED = 0; lowLED = 0; } else if (low > intune) { highLED = 0; tuneLED = 0; lowLED = 1; } else { highLED = 0; tuneLED = 0; lowLED = 0; } // Display on the LCD if (tuneLED) device.printf("Tuning string: %iIn Tune! else if (highLED) device.printf("Tuning string: %iToo High else if (lowLED) device.printf("Tuning string: %iToo Low else device.printf("Try again"); Counter = 0; //reset counter parameter } }

", (6-string));

", (6-string));

", (6-string));

ADC.c:
#include "mbed.h" #include "adc.h"

ADC *ADC::instance; ADC::ADC(int sample_rate, int cclk_div) { int i, adc_clk_freq, pclk, clock_div, max_div=1; //Work out CCLK adc_clk_freq=CLKS_PER_SAMPLE*sample_rate; int m = (LPC_SC->PLL0CFG & 0xFFFF) + 1; int n = (LPC_SC->PLL0CFG >> 16) + 1; int cclkdiv = LPC_SC->CCLKCFG + 1; int Fcco = (2 * m * XTAL_FREQ) / n; int cclk = Fcco / cclkdiv; //Power up the ADC LPC_SC->PCONP |= (1 << 12); //Set clock at cclk / 1. LPC_SC->PCLKSEL0 &= ~(0x3 << 24); switch (cclk_div) { case 1: LPC_SC->PCLKSEL0 |= 0x1 << 24; break; case 2: LPC_SC->PCLKSEL0 |= 0x2 << 24; break; case 4: LPC_SC->PCLKSEL0 |= 0x0 << 24; break; case 8: LPC_SC->PCLKSEL0 |= 0x3 << 24; break; default: LPC_SC->PCLKSEL0 |= 0x1 << 24; break; } pclk = cclk / cclk_div; clock_div=pclk / adc_clk_freq; if (clock_div > 0xFF) { clock_div=0xFF; } if (clock_div == 0) { fprintf(stderr, "Warning: Clock division is 0. Re-Setting to 1.\n"); clock_div=1; } _adc_clk_freq=pclk / clock_div; if (_adc_clk_freq > MAX_ADC_CLOCK) { fprintf(stderr, "Warning: Actual ADC sample rate of %u which is above %u limit\n", _adc_clk_freq / CLKS_PER_SAMPLE, MAX_ADC_CLOCK / CLKS_PER_SAMPLE); while ((pclk / max_div) > MAX_ADC_CLOCK) max_div++; fprintf(stderr, "Maximum recommended sample rate is %u\n", (pclk / max_div) / CLKS_PER_SAMPLE); }

LPC_ADC->ADCR = ((clock_div - 1 ) << 8 ) | //Clkdiv ( 1 << 21 ); //A/D operational //Default no channels enabled LPC_ADC->ADCR &= ~0xFF; //Default NULL global custom isr _adc_g_isr = NULL; //Initialize arrays for (i=7; i>=0; i--) { _adc_data[i] = 0; _adc_isr[i] = NULL; }

//* Attach IRQ instance = this; NVIC_SetVector(ADC_IRQn, (uint32_t)&_adcisr); //Disable global interrupt LPC_ADC->ADINTEN &= ~0x100; }; void ADC::_adcisr(void) { instance->adcisr(); }

void ADC::adcisr(void) { uint32_t stat; int chan; // Read status stat = LPC_ADC->ADSTAT; //Scan channels for over-run or done and update array if (stat & 0x0101) _adc_data[0] = LPC_ADC->ADDR0; if (stat & 0x0202) _adc_data[1] = LPC_ADC->ADDR1; if (stat & 0x0404) _adc_data[2] = LPC_ADC->ADDR2; if (stat & 0x0808) _adc_data[3] = LPC_ADC->ADDR3; if (stat & 0x1010) _adc_data[4] = LPC_ADC->ADDR4; if (stat & 0x2020) _adc_data[5] = LPC_ADC->ADDR5; if (stat & 0x4040) _adc_data[6] = LPC_ADC->ADDR6; if (stat & 0x8080) _adc_data[7] = LPC_ADC->ADDR7; // Channel that triggered interrupt chan = (LPC_ADC->ADGDR >> 24) & 0x07; //User defined interrupt handlers if (_adc_isr[chan] != NULL) _adc_isr[chan](_adc_data[chan]); if (_adc_g_isr != NULL) _adc_g_isr(chan, _adc_data[chan]); return;

} int ADC::_pin_to_channel(PinName pin) { int chan; switch (pin) { case p15://=p0.23 of LPC1768 default: chan=0; break; case p16://=p0.24 of LPC1768 chan=1; break; case p17://=p0.25 of LPC1768 chan=2; break; case p18://=p0.26 of LPC1768 chan=3; break; case p19://=p1.30 of LPC1768 chan=4; break; case p20://=p1.31 of LPC1768 chan=5; break; } return(chan); } PinName ADC::channel_to_pin(int chan) { const PinName pin[8]={p15, p16, p17, p18, p19, p20, p15, p15}; if ((chan < 0) || (chan > 5)) fprintf(stderr, "ADC channel %u is outside range available to MBED pins.\n", chan); return(pin[chan & 0x07]); }

int ADC::channel_to_pin_number(int chan) { const int pin[8]={15, 16, 17, 18, 19, 20, 0, 0}; if ((chan < 0) || (chan > 5)) fprintf(stderr, "ADC channel %u is outside range available to MBED pins.\n", chan); return(pin[chan & 0x07]); }

uint32_t ADC::_data_of_pin(PinName pin) { //If in burst mode and at least one interrupt enabled then //take all values from _adc_data if (burst() && (LPC_ADC->ADINTEN & 0x3F)) { return(_adc_data[_pin_to_channel(pin)]); } else { //Return current register value or last value from interrupt switch (pin) { case p15://=p0.23 of LPC1768 default:

return(LPC_ADC->ADINTEN & 0x01?_adc_data[0]:LPC_ADC->ADDR0); case p16://=p0.24 of LPC1768 return(LPC_ADC->ADINTEN & 0x02?_adc_data[1]:LPC_ADC->ADDR1); case p17://=p0.25 of LPC1768 return(LPC_ADC->ADINTEN & 0x04?_adc_data[2]:LPC_ADC->ADDR2); case p18://=p0.26 of LPC1768: return(LPC_ADC->ADINTEN & 0x08?_adc_data[3]:LPC_ADC->ADDR3); case p19://=p1.30 of LPC1768 return(LPC_ADC->ADINTEN & 0x10?_adc_data[4]:LPC_ADC->ADDR4); case p20://=p1.31 of LPC1768 return(LPC_ADC->ADINTEN & 0x20?_adc_data[5]:LPC_ADC->ADDR5); } } } //Enable or disable an ADC pin void ADC::setup(PinName pin, int state) { int chan; chan=_pin_to_channel(pin); if ((state & 1) == 1) { switch(pin) { case p15://=p0.23 of LPC1768 default: LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 14); LPC_PINCON->PINSEL1 |= (unsigned int)0x1 << 14; LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 14); LPC_PINCON->PINMODE1 |= (unsigned int)0x2 << 14; break; case p16://=p0.24 of LPC1768 LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 16); LPC_PINCON->PINSEL1 |= (unsigned int)0x1 << 16; LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 16); LPC_PINCON->PINMODE1 |= (unsigned int)0x2 << 16; break; case p17://=p0.25 of LPC1768 LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 18); LPC_PINCON->PINSEL1 |= (unsigned int)0x1 << 18; LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 18); LPC_PINCON->PINMODE1 |= (unsigned int)0x2 << 18; break; case p18://=p0.26 of LPC1768: LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 20); LPC_PINCON->PINSEL1 |= (unsigned int)0x1 << 20; LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 20); LPC_PINCON->PINMODE1 |= (unsigned int)0x2 << 20; break; case p19://=p1.30 of LPC1768 LPC_PINCON->PINSEL3 &= ~((unsigned int)0x3 << 28); LPC_PINCON->PINSEL3 |= (unsigned int)0x3 << 28; LPC_PINCON->PINMODE3 &= ~((unsigned int)0x3 << 28); LPC_PINCON->PINMODE3 |= (unsigned int)0x2 << 28; break; case p20://=p1.31 of LPC1768 LPC_PINCON->PINSEL3 &= ~((unsigned int)0x3 << 30); LPC_PINCON->PINSEL3 |= (unsigned int)0x3 << 30; LPC_PINCON->PINMODE3 &= ~((unsigned int)0x3 << 30);

LPC_PINCON->PINMODE3 |= (unsigned int)0x2 << 30; break; } //Only one channel can be selected at a time if not in burst mode if (!burst()) LPC_ADC->ADCR &= ~0xFF; //Select channel LPC_ADC->ADCR |= (1 << chan); } else { switch(pin) { case p15://=p0.23 of LPC1768 default: LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 14); LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 14); break; case p16://=p0.24 of LPC1768 LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 16); LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 16); break; case p17://=p0.25 of LPC1768 LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 18); LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 18); break; case p18://=p0.26 of LPC1768: LPC_PINCON->PINSEL1 &= ~((unsigned int)0x3 << 20); LPC_PINCON->PINMODE1 &= ~((unsigned int)0x3 << 20); break; case p19://=p1.30 of LPC1768 LPC_PINCON->PINSEL3 &= ~((unsigned int)0x3 << 28); LPC_PINCON->PINMODE3 &= ~((unsigned int)0x3 << 28); break; case p20://=p1.31 of LPC1768 LPC_PINCON->PINSEL3 &= ~((unsigned int)0x3 << 30); LPC_PINCON->PINMODE3 &= ~((unsigned int)0x3 << 30); break; } LPC_ADC->ADCR &= ~(1 << chan); } } //Return channel enabled/disabled state int ADC::setup(PinName pin) { int chan; chan = _pin_to_channel(pin); return((LPC_ADC->ADCR & (1 << chan)) >> chan); } //Select channel already setup void ADC::select(PinName pin) { int chan; //Only one channel can be selected at a time if not in burst mode if (!burst()) LPC_ADC->ADCR &= ~0xFF; //Select channel chan = _pin_to_channel(pin); LPC_ADC->ADCR |= (1 << chan);

} //Enable or disable burst mode void ADC::burst(int state) { if ((state & 1) == 1) { if (startmode(0) != 0) fprintf(stderr, "Warning. startmode is %u. Must be 0 for burst mode.\n", startmode(0)); LPC_ADC->ADCR |= (1 << 16); } else LPC_ADC->ADCR &= ~(1 << 16); } //Return burst mode state int ADC::burst(void) { return((LPC_ADC->ADCR & (1 << 16)) >> 16); } //Set startmode and edge void ADC::startmode(int mode, int edge) { int lpc_adc_temp; //Reset start mode and edge bit, lpc_adc_temp = LPC_ADC->ADCR & ~(0x0F << 24); //Write with new values lpc_adc_temp |= ((mode & 7) << 24) | ((edge & 1) << 27); LPC_ADC->ADCR = lpc_adc_temp; } //Return startmode state according to mode_edge=0: mode and mode_edge=1: edge int ADC::startmode(int mode_edge){ switch (mode_edge) { case 0: default: return((LPC_ADC->ADCR >> 24) & 0x07); case 1: return((LPC_ADC->ADCR >> 27) & 0x01); } } //Start ADC conversion void ADC::start(void) { startmode(1,0); }

//Set interrupt enable/disable for pin to state void ADC::interrupt_state(PinName pin, int state) { int chan; chan = _pin_to_channel(pin); if (state == 1) { LPC_ADC->ADINTEN &= ~0x100; LPC_ADC->ADINTEN |= 1 << chan; /* Enable the ADC Interrupt */ NVIC_EnableIRQ(ADC_IRQn); } else {

LPC_ADC->ADINTEN &= ~( 1 << chan ); //Disable interrrupt if no active pins left if ((LPC_ADC->ADINTEN & 0xFF) == 0) NVIC_DisableIRQ(ADC_IRQn); } } //Return enable/disable state of interrupt for pin int ADC::interrupt_state(PinName pin) { int chan; chan = _pin_to_channel(pin); return((LPC_ADC->ADINTEN >> chan) & 0x01); }

//Attach custom interrupt handler replacing default void ADC::attach(void(*fptr)(void)) { //* Attach IRQ NVIC_SetVector(ADC_IRQn, (uint32_t)fptr); } //Restore default interrupt handler void ADC::detach(void) { //* Attach IRQ instance = this; NVIC_SetVector(ADC_IRQn, (uint32_t)&_adcisr); }

//Append interrupt handler for pin to function isr //////////////////////////////////////////////////////////////// void ADC::append(PinName pin, void(*fptr)(uint32_t value)) { int chan; chan = _pin_to_channel(pin); _adc_isr[chan] = fptr; } //Append interrupt handler for pin to function isr void ADC::unappend(PinName pin) { int chan; chan = _pin_to_channel(pin); _adc_isr[chan] = NULL; } //Unappend global interrupt handler to function isr void ADC::append(void(*fptr)(int chan, uint32_t value)) { _adc_g_isr = fptr; } //Detach global interrupt handler to function isr void ADC::unappend() { _adc_g_isr = NULL; }

//Set ADC offset void offset(int offset) { LPC_ADC->ADTRM &= ~(0x07 << 4); LPC_ADC->ADTRM |= (offset & 0x07) << 4; } //Return current ADC offset int offset(void) { return((LPC_ADC->ADTRM >> 4) & 0x07); } //Return value of ADC on pin int ADC::read(PinName pin) { //Reset DONE and OVERRUN flags of interrupt handled ADC data _adc_data[_pin_to_channel(pin)] &= ~(((uint32_t)0x01 << 31) | ((uint32_t)0x01 << 30)); //Return value return((_data_of_pin(pin) >> 4) & 0xFFF); } //Return DONE flag of ADC on pin int ADC::done(PinName pin) { return((_data_of_pin(pin) >> 31) & 0x01); } //Return OVERRUN flag of ADC on pin int ADC::overrun(PinName pin) { return((_data_of_pin(pin) >> 30) & 0x01); } int ADC::actual_adc_clock(void) { return(_adc_clk_freq); } int ADC::actual_sample_rate(void) { return(_adc_clk_freq / CLKS_PER_SAMPLE); }

References [1] T. Yahaya, Abdullah. "Music Scales." Music Scales. Web. 10 Mar. 2012. Available: http://www.angelfire.com/in2/yala/4scales.htm. [2] "First Music Lesson - Frequency, Octave, Semitone, Sound Names, Major Scale Apronus.com." Apronus.com. TargetSoft. Web. 11 Mar. 2012. Available: http://www.apronus.com/music/lessons/unit01.htm. [3] Wagner, David. "Electric Guitar Pickups 101." Gear-Vault. Web. 09 Mar. 2012. Available: http://gear-vault.com/electric-guitar-pickups-101/. [4] NXP. LPC1768. mbed. Web. Available: http://mbed.org/media/uploads/chris/mbed-005.1.pdf [5] IEEE. "ARM7TDMI Power Consumption." ARM7TDMI Power Consumption. Aug. 1997. Web. 14 Mar. 2012. Available: http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=612178&abstractAccess=no&userType= inst. [6] mbed website. NXP LCP1768.10 Mar. 2012. Avialable: http://mbed.org/ [7] "Detecting A Single Frequency Efficiently." Frequency Detection. Web. 12 Apr. 2012. <http://www.mstarlabs.com/dsp/goertzel/goertzel.html>.

Вам также может понравиться