Guitar
Transcript of Guitar
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 microcontroller’s 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.
LPC1768
MCU
Power
Source
Guitar
Signal
Amplification
DC
BIAS User
interface
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 Octave1 Octave2 Octave3
A 110.00 220.00 440.00
A# 116.54 233.08 466.16
B 123.47 246.94 493.88
C 130.81 261.63 523.25
C# 138.59 277.18 554.37
D 146.33 293.66 587.33
D# 155.56 311.13 622.25
E 164.81 329.63 659.26
F 174.61 349.23 698.46
F# 185.00 369.99 739.99
G 196.00 392.00 783.99
G# 207.65 415.30 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 guitar’s 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 # Note Frequency (Hz)
1 E 329.6
2 B 246.9
3 G 196.0
4 D 146.8
5 A 110.0
6 E 82.4
III. Design Approach
A. Hardware
The way to get the electric guitar output is by getting in contact with the guitar’s 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 guitar’s 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 1µF 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.
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 +
_ Guitar
Signal
Figure 4: Non-inverting Op-amp Diagram
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 Tasks Public Functions
Main.c Initialize variables
Set parameters for each string
Set LCD baud rate
Goertzel algorithm
Display messages
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 didn’t go all the way down to ground. However,
this wasn’t 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 doesn’t 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! ", (6-string)); else if (highLED) device.printf("Tuning string: %iToo High ", (6-string)); else if (lowLED) device.printf("Tuning string: %iToo Low ", (6-string)); else device.printf("Try again"); Counter = 0; //reset counter parameter
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>.