Digital Guitar Tuner

                                                                                                            Dmitry Berenson

Galen Reeves

ECE 476

Spring 04





We created a digital guitar tuner that takes output from an electric guitar and helps the user tune it to “Standard E” tuning.


The user interacts with the device by pushing buttons to select which string he or she is tuning. The user then plucks the string and the device displays if the string is too high frequency (sharp), too low frequency (flat), or within 0.5% of the desired value. We chose this project because both of us play guitar and don’t want to pay a lot for tuners. We were also very interested in applying some of the Digital Signal Processing (DSP) and analog amplification principles we learned in some of our other classes. This project gave us the opportunity to test out these principles in a real-world design.



High Level Design


We got the idea for this project by browsing through previous years’ project websites. Both of us play guitar and we both have a hard time keeping our instruments in tune. (partly because we can’t tune by ear and partly because of Ithaca’s weather). We knew that making a guitar tuner was feasible because there are portable guitar tuners. We figured that if we amplified the signal enough, did some hardware and software filtering, and displayed how far off the frequency was to the user, we’d be able to produce a tuner that could tune all six strings to within acceptable bounds.


To accomplish the hardware amplification, we used a DC biasing element as well as a non-inverting amplifier. Below is a general circuit for such a task. Note: this is only part of the circuit we ended up using; please see Program/Hardware Design for the complete circuit.










R2, R3








Blocks low frequency noise and DC from the guitar.


Provide a DC offset (2.5V when Vcc = 5).


Set the gain of the op-amp. Av = 1 + R2/R3.


Blocks DC amplification.





Fig. 1: A general circuit that amplifies AC and introduces a DC bias with a table describing the role of each component.



A note about guitar strings (ha): Though strings are tuned to a fundamental frequency they simultaneously vibrate at other frequencies. The fundamental frequency is known as the first harmonic because it is most prominent and defines the tone of the string. The other harmonics (2nd, 3rd, etc interfere with the 1st). See the graph below


Fig. 2: This is the DFT of a 16 ms sample of the input from the B-string. Notice that all the harmonics occur at multiples of the fundamental frequency and the second harmonic is very large.


There are also some basics about sampling, period and frequency the reader must understand before continuing. Frequency (f) is the inverse of period (T). The ADC can only effectively measure inputs in terms of period using time between samples (we would need a Fourier transform to measure frequency) thus we had to take our target frequencies and convert them to periods by taking their inverse. Also, the ADC samples at a finite intervals. According to the Nyquist sampling criterion, we must sample at least at twice the highest frequency to assure complete frequency data acquisition. Though the highest frequency we were trying to tune to was 329Hz, the string had higher harmonics (i.e. 2 and 3 times the fundamental frequency), necessitating a sampling frequency of something like 5000Hz or a sampling period of 0.2ms. The amount of samples can be used as a measure of the period as shown in the table below:


Table 1: “Standard E” Tuning
String   Frequency(Hz)    Period(s)   Samples at 5kHz (0.2ms)
1         E = 82.4069      0.0121     60.675
2         A = 110.0000     0.0091     45.45
3         D = 146.8324     0.0068     34.05
4         G = 195.9978     0.0051     25.5
5         B = 246.9417     0.0040     20.248
6         e = 329.6277     0.0030     15.169

Since the distances between certain sampling values were small (for instance between string 6 and string 5) we had to measure multiple periods to get higher accuracy. This is discussed in the Program/Hardware Design section of the report.


The logical structure of the program was the following: Every 0.2 ms the device reads the input from ADC and creates a sampled signal. The sampled signal is then filtered with a low-pass filter specific to the frequency of the string being tuned. Next, the program measures the period of the filtered signal by counting ten positive-edge zero-crossings. The sum of ten periods is compared with a target value and appropriate LEDs are lit to indicate the string is either too high, too low, or in tune. There are five LEDs with the central LED indicating that the guitar is in tune. If the guitar is near the correct frequency the LEDs above and below the central LED indicate that the string is high or low respectively. If the guitar string is not close to the correct tone then highest and lowest LEDs are used instead.


We didn’t use any IEEE, ISO, or ANSI standards. Nor did we make use of copyrighted code, patents, and trademarks.



Program/Hardware Design


Hardware Design


Since the output of the guitar will be at low amplitude (about 3mV peek to peek), we must amplify the signal before running it to the Mega32’s ADC. To do this, we used the circuit shown below:



Fig. 3: This is a schematic of the analog circuitry used to amplify the guitar output before it enters the ADC. Each stage is described below.




Stage 1


The purpose of stage 1 is to filter out any DC bias the guitar may be applying to its AC output. We attached the guitar using alligator clips from the plug of the cable to the input and ground. The 1uF capacitor effectively blocks any DC bias and the two 1M-ohm resistors introduce a 2.5V bias to the AC signal to center it. In actuality, the bias introduced depended on the power source we were using. Variances were usually in the plus or minus 50mV range. These variances didn’t affect our software in any significant way because software was able to compensate for the variance. It is important to note the 3DB frequency (half amplitude) of the filter created by the 1uF capacitor and 1M-ohm resistor. The frequency of this high-pass filter is given by f = 1/(2*pi*R3*C) = 0.16 Hz.


Stage 2


The purpose of this stage is to amplify the guitar signal but block DC amplification. Since the DC offset is 2.5V, amplifying the bias would cause the opamp to rail. For this reason, we put in a capacitor to ground, effectively blocking DC amplification. The low pass filter to ground formed by the capacitor and resistor has a 3DB cutoff frequency of f = 1/(2*pi*R3*C) = 3.39 Hz. The opamp is set up in a non-inverting amplifier configuration, which has a voltage gain of Av = 1 + R2/R3 = 1 + 20.1/1 = 21.1. The values of V+ and V- will be discussed in stage 3 because they are defined by the necessities of that stage.


Stage 3


Stage 3 is very similar to stage 2. Its purpose is to further amplify the signal so that it gets into the 5V to 0V range (5V peek to peek with a 2.5V bias). This amplifier has a gain of Av = 1 + R2/R3 where R2 is the series combination of the 327-ohm and 328-ohm resistors (655 -ohms). Thus Av = 77.5. The total gain from stages 2 and 3 is thus 21.1 * 77.5 = 1636, yeilding a peek to peek voltage of 3mV * 1636 = 4.9V. Its important to note here that the values of V+ and V- are arbitrary as long as they exceed roughly 5.5V and -5.5V, respectively. We need these to be the rails of the op-amp to prevent clipping at roughly 4.5V (because the LM358 looses a diode-drop's worth of voltage). By increasing the rails of the opamp, we were able to amplify the AC into the 5V to 0V rails. This was done by trial and error: we turned the knobs on the voltage source until we saw no clipping on the oscilloscope. The low pass filter to ground formed by the capacitor and resistor has a 3DB cutoff frequency of f = 1/(2*pi*R3*C) = 5.13Hz. Thus, overall, the circuit cuts out any frequencies below 5.13Hz. This isn’t a problem because this low frequency is out of the range of human hearing and unachievable on the guitar.


Stage 4


The purpose of this stage was to limit the voltage seen by the MCU. Since we didn't want to overload the ADC port, we needed to limit the voltage seen by ADC to within one diode drop of 5V or 0V. This is why we used the two diodes with the 10k resistor. The MCU's input port resistance dwarfs this 10k resistor but it provides a place to dissipate any power going to 5V or 0V through either diode. Without this 10k resistance one of the diodes would dissipate the power, probably burning it out.



Program Design


We used an interrupt driven task scheduler to implement our tuner. We set timer.0 to interrupt every 0.2 ms and used the interrupt to drive two tasks: sample() and buttonSM().


The task buttonSM() is run every 0.2 ms. It samples and filters the input from the ADC, measures the length of ten periods, and compares it with a target period for the string being tuned. If the measured period is less than the target period the program sets the LEDs indicating that the string’s frequency is too high. If it is greater than the target period the programs sets the LEDs indicated that the string’s frequency is too low. If it is within a predetermined bound from the target the ‘in tune’ LED is lit.


Organization Chart

Fig. 4: The box represents the program flow when task sample() is run. Because the task is run every 0.2 ms the signal x(t) is sampled at 5 kHz.



The user determines which string is being tuned by pressing one of 6 buttons where each button corresponds to a string. The task buttonSM() is run every 30 ms and detects the button presses. It then sets the state variable ‘string’ to the value corresponding to the pressed button and runs set_string(). The task set_string() outputs the name of the string being tuned to the LCD and sets all the period bounds and filter coefficients based on the values stored in arrays.


During initialization, three different levels of bounds on the period are determined for each string. The largest bounds set the limit on triggering. No triggering or period measurements will be made while the input frequency is outside these largest bounds. The smallest bounds determine if the string is in tune. As long as the input frequency is within the tight bounds it is within 0.5% of the desired frequency and the string is considered to be in tune. The medium sized bounds are in between and determine which LEDs are lit. The largest bounds are created by multiplying and dividing the target period by a factor that corresponds to roughly a whole step. The notes that we hear are related to the frequency by the following equation:



In the equation, ‘C0’is the frequency of the note C0 and ‘n’ is the number of semitones above C0. It is easy to see that multiplying or dividing the target frequency by 2^(1/12) creates frequency bounds that corresponds to plus or minus one semitone.




To calculate the period of the input signal we counted the number of samples between zero-crossings. Zero-crossings occur when the input signal goes from below to above a bias voltage centered in the middle of the signal's voltage range. All of the guitar strings contain higher harmonics and the second harmonics are large enough that they may cause additional zero-crossings on the input signal. Therefore, if the higher harmonics are not removed through filtering a true measurement of the period can not be obtained.


In choosing a filtering method we needed to trade off between ideal filter characteristics and speed of filtering. Since the second harmonics are always twice the fundamental frequency the filter did not need to have a steep cutoff. Also, because the device was limited by how many calculations could be performed, the filter needed to be very fast. Therefore, we used a simple low-pass infinite impulse response (IIR) filter to remove the higher harmonics. The filter does not have a very steep cutoff but is very fast to implement because it requires only two multiplies and one divide, which can be implemented via a shift. For an input signal x(n) and a filtered signal y(n) the filtering is performed with the following equation:



In the equation ‘a’ is a parameter between zero and one which determines the width of the low pass filter. If ‘a’ is near zero the filter is similar to an all-pass filter and very little filtering is done. If ‘a’ is near one the filter has a very steep cutoff and only very low frequencies will be retained. To see this, observe that the frequency response of the filter is given below where T is the sampling period of 0.2 ms:




Because the denominator increases with frequency (until the frequency reaches half of the sampling frequency) this filter acts as a low pass filter. To filter the guitar strings such that only the fundamental frequency caused zero-crossings we had to make sure that the second harmonic was greatly attenuated with respect to the first harmonic. However, we could not make ‘a’ too large or we would not be able to detect the fundamental frequency. Therefore the program sets the value of ‘a’ depending on which string is being tuned. Larger values are used for the low strings and smaller values for the high strings. Below are two graphs, one in the time domain and one in the frequency domain, showing the unfiltered and filtered samples of the b-string. Notice that filter H is shown in the second graph.

Fig. 5: This is a 16 ms segment of the input from ADC. Notice that the secondary humps in the unfiltered signal cause zero-crossings. Also, the amplitude of the filtered signal is much less than the unfiltered signal.




Fig. 6: These are the DFTs of the filtered and unfiltered 16 ms segment of the input from ADC along with the frequency response (Discrete Time Fourier Transform) of the filter. Notice that the second harmonic is attenuated by a factor of 0.2 while the first harmonic is attenuated by a factor of 0.55.



Things We Tried That Didn't Work



At first we thought we could achieve the high gain necessary using only one op-amp. This proved to be impractical because the output fluctuated substantially and was somewhat noisy. After we moved to the two op-amp scheme we found that the gain on stage 3 was very sensitive. We knew we wanted a gain of roughly 1666 (5V/3mV) but we had to try a lot of different resistor combinations for both R2 and R3 (of both stage 2 and 3) until we found one that was suitable. In retrospect it may have been more efficient to use a potentiometer for either R2 or R3 or both.



Initially we measured the input frequency by measuring the length of only one period. This was a very poor idea because the period of the high E string corresponds to only 15 samples. This meant we had very poor accuracy when trying to determine the true period of the input. To add some accuracy we tried to interpolate a straight line between the points on both sides of the zero-crossings. This involved two add operations and one divide. This technique added some accuracy but was still very unreliable when the input signal was not a nice sine wave. We therefore ended up counting ten periods instead of one to achieve necessary accuracy and stability. 




When we first looked at the input signals from the guitar we thought that perhaps we would not need to do any filtering because the input signals had spikes that coincided with their fundamental frequencies. We reasoned that if we simply moved the bias up to the point that only the spikes would cause triggering then we would not need to worry about the smaller humps caused by the higher harmonics. However, although the signals appeared nice over small time spans their form fluctuated over longer time spans. As the string rang the orientation and size of the secondary humps changed such that there was never a bias which would always trigger on only the fundamental frequency.





Since we couldn't always bring the guitar to lab (and we had to test the accuracy and amplification of the circuit) we had to use the signal generator to fake the guitar input. The way we did this was rather tricky. Even when the amplitude of the signal generator was turned all the way down, the amplitude was still about 50mV peek to peek (remember we needed to get down to 3mV peek to peek to fake the guitar signal amplitude). Thus, we ran the signal generator output through a potentiometer, which we tuned until we got a 3mV peek to peek signal. It was difficult to determine the exact amplitude because the oscilloscope couldn't cut through the huge amount of noise coming out of the signal generator. We had to "eyeball" the amplitude from the oscilloscope screen. Surprisingly, the guitar was a much better signal generator than the one in the lab. The guitar input was significantly less noisy, although the hardware cut out all the noise (even from the signal generator) before routing the signal to the ADC. 


We tested the accuracy of the device by tuning the guitar until the middle LED was lit, which meant the device read the input as being in-tune. To assure accuracy, we plugged the guitar into a handheld guitar tuner and saw that that the input was usually a few percent below what it should have been. We calibrated the device accordingly and re-tested until we matched the handheld guitar tuner’s assessment.



Results of the Design


The necessary time-bound was that all computation be done during the sampling interval or 0.2 ms. By examining the graphs we see that there are no discontinuities--which would indicate timing problems. Testing also showed that there were no timing or concurrency problems.


By using a handheld Korg guitar tuner. We saw that our tuned frequencies where within 1% of the intended value. The resolution on this device is not very good, so that is the closest we could estimate the frequency. Our calculated accuracy is 0.5%.


There may be some RF interference from the long wire going to the CPU. We don’t think this is very significant because it didn’t disrupt our input signal. Therefore it’s unlikely we disrupted other groups’ devices.


The device is very user friendly. The user sees which string they are tuning on the LCD, can select a new string by pushing one of six buttons, and can see the deviation in frequency on the five LEDs of the STK500 board.





Our guitar tuner conformed to our expectations. When we started we were not sure what kind of stability and accuracy we could achieve in our tuner. As we continued to improve the device we became more and more impressed and satisfied with our tuner's performance.


If we were to do this over again we would want to investigate some of the advantages and disadvantages of a fasters sampling frequency. While developing our tuner we did not want to sample faster than we could compute the frequency. However, now that we know what is involved in frequency calculation we could have attempted to sample faster to improve accuracy.


We developed the basic structure for our design by researching past ECE 476 projects as well as researching online. Some good sources of information were online newsgroups where people were able to contribute there ideas on guitar tuners freely. Because people in the newsgroups were anonymous and were offering their information to help people building devices similar to ours we assumed to that the basic very low-level design information was not copyrighted. We wrote all the code in our device and we developed all the specifics of our design ourselves with some help from the TAs.


IEEE Code of Ethics Considerations

1.      to accept responsibility in making engineering decisions consistent with the safety, health and welfare of the public, and to disclose promptly factors that might endanger the public or the environment;

Our project represents very little of a danger to health and welfare of the public. The only dangers, which would arise from someone reassembling our circuits, would be and overheated or blown circuit element.

2.      to avoid real or perceived conflicts of interest whenever possible, and to disclose them to affected parties when they do exist;

Our guitar tuner was built for our own education and enjoyment for ECE 476. Therefore there are no potential conflicts of interest.

3.      to be honest and realistic in stating claims or estimates based on available data;

We were honest and realistic to the best of our knowledge.

4.      to reject bribery in all its forms;

Unfortunately this was not applicable to our project.

5.      to improve the understanding of technology, its appropriate application, and potential consequences;

We gained understanding in DSP and analog hardware design. We therefore furthered our own understanding of technology.

6.      to maintain and improve our technical competence and to undertake technological tasks for others only if qualified by training or experience, or after full disclosure of pertinent limitations;

We improved our technical competence through design and testing. We were well aware of our limitations as junior ECE students who haven’t had a lot of experience with design.

7.      to seek, accept, and offer honest criticism of technical work, to acknowledge and correct errors, and to credit properly the contributions of others;

We mentioned those who helped us in the report and were always honest with one another about project problems. We freely discussed our design with others creating guitar tuners.

8.      to treat fairly all persons regardless of such factors as race, religion, gender, disability, age, or national origin;

There was no discrimination of any kind involved in our project.

9.      to avoid injuring others, their property, reputation, or employment by false or malicious action;

We did not commit any false or malicious action during the course of this project.

10.  to assist colleagues and co-workers in their professional development and to support them in following this code of ethics.

We always helped people that asked questions of us thus helping them to develop a further understand of electrical engineering.


Appendix A: Costs

2 breadboards:                 $10.00

1 LCD Display:                $5.00

1 Mega32:                        $8.00

1 LM358 (op amps):        $0.43

8 Resistors:                       $0.08

3 Capacitors:                    $0.75

2 1N914 (diodes):            $0.02


Total:                               $24.28


All of these components were available in the lab so we didn’t use any vendors.


Appendix B: Task Breakdown


Dmitry Berenson: Hardware design, testing, and implementation. Creating web-site.


Galen Reeves: Software filtering. Program design, testing, and implementation.



Appendix C: Data Sheets

LM358 op amp.

ATMEGA32 microcontroller.


STK500 prototype board.

1N914 diode.


Appendix D: Commented Code


//final project - Guitar tuner

#include <Mega32.h>

#include <math.h> // for sine

#include <stdio.h> // for sprintf

#include <string.h>

#include <stdlib.h>


//I like these definitions

#define begin {

#define end   }


//LCD stuff

#define LCDwidth 16


     .equ __lcd_port = 0x15;


#include <lcd.h>

unsigned char lcd_buffer[17];


//set definitions

#define infinit_T 2000

#define bias 110



   Standard E tuning

       frequency      period     samples at 5kHz (0.2ms)

   E = 82.4069   s0   0.0121     60.675

   A = 110.0000  s1   0.0091     45.45

   D = 146.8324  s2   0.0068     34.05

   G = 195.9978  s3   0.0051     25.5

   B = 246.9417  s4   0.0040     20.248

   e = 329.6277  s5   0.0030     15.169



// Sampling variabls

unsigned char Sold,S;         // hold past two samples

unsigned int measured_T;      // measured period

unsigned int target_T;        // target period

unsigned char edgesSeen=0;     // number of periouds seen


//variables to hold string frequency bounds

unsigned int T_low, T_high;           //largest bounds

unsigned int T_low2, T_high2;         //medium bounds

unsigned int low_bound, high_bound; //small bounds


//arrays to hold string specific values

unsigned int T_array[6];      // hold target periods for strings

unsigned int low_array[6];

unsigned int high_array[6];

unsigned int low_array2[6];

unsigned int high_array2[6];

unsigned int tight_array[6];


//state variables

unsigned char string;   //string to be tuned

char name[]="EADGBe";   //display string name


//variable for arithmatic

unsigned int i;

float c1,c2;


//variables for filter

unsigned int filter_array[6]; // hold filter settings

unsigned int filter1;         // alpha

unsigned int filter2;         // 1-alpha


// timer variables

unsigned int time1,time2;



void sample(void);       //take an ADC sample and output result to LEDs

void buttonSM(void);     //set string to tune

void initialize(void);   //all the usual mcu stuff

void set_string(void);  //set parameters specific to string



//timer 0 compare ISR

interrupt [TIM0_COMP] void timer0_compare(void)


    //Decrement timer variables

    if (time1>0)   --time1;  // sample()

    if (time2>0)    --time2; // buttonSM()




//Entry point and task scheduler loop

void main(void)







      if(time1 == 0) sample();    //sample input and tune

      if(time2 == 0) buttonSM();  //set string to tune





//sample - take an ADC sample and output result to LEDs

void sample(void)


   time1 = 1; //return in 200 us


   //sample from ADC   

   Sold = S;  //stor old value

   S = ADCH;  //sample

   ADCSR.6 = 1; //take new sample with ADC


   //filter input signal

   S= (char)((filter1*(int)Sold + filter2*(int)S)>>4);


   //Increment period if it is not maxed out

   if (measured_T < infinit_T )


   else                      //otherwise

      PORTB = ~0b00000001;   //display ready led


   //Test for zero-crossing 

   if(Sold < bias && S >= bias)  //if trigger



      edgesSeen++; //increment number of edges seen


      //make sure T is not infinte

      if(measured_T < infinit_T)



           //after seeing 10 complete periouds

         if(edgesSeen == 10)


            edgesSeen = 0; //reset number of edges seen


            //accempt measured_T only if it is in right range

            if(measured_T < T_high && measured_T > T_low)


                 //set LEDs based on range of measured_T

               if(measured_T > T_high2)

                    PORTB = ~0b00001000;

               if(measured_T > high_bound && measured_T <= T_high2)

                    PORTB = ~0b00010000;

               if(measured_T >= low_bound && measured_T <= high_bound)

                     PORTB = ~0b00100000;

               if(measured_T > T_low2 && measured_T < low_bound)

                     PORTB = ~0b01000000;

               if(measured_T <= T_low2)

                     PORTB = ~0b10000000;


            measured_T = 0;

         end // if 10th edge

      end //if less than infinit_T


       else // if measured_T is infinite

         measured_T = 0;


   end //end trigger




void buttonSM(void)



   time2 = 150; // return to buttonSM in 30 ms


   //set each string based on which button is pressed



      case 0x04: // button0  on PD2 - E

                 string = 0;




      case 0x08: // button1  on PD2 - A

                 string = 1;




      case 0x10: // button2  on PD2 - D

                 string = 2;




      case 0x20: // button3  on PD2 - G

                 string = 3;




      case 0x40: // button4  on PD2 - B

                 string = 4;




      case 0x80: // button5  on PD2 - e

                 string = 5;







//set_string() - set string dependent variabls

void set_string(void)



   //display string name on LCD


   lcd_gotoxy(8, 0);



   //set string bounds from arrays

   target_T = T_array[string];

   filter1 = filter_array[string];

   filter2 = 16-filter1;

   T_low = low_array[string];

   T_high = high_array[string];

   T_low2 = low_array2[string];

   T_high2 = high_array2[string];

   high_bound = target_T + tight_array[string];

   low_bound = target_T - tight_array[string];




//Set it all up

void initialize(void)



   //set up ports

   DDRA=0x00;    // PORT A takes input from ADC

   DDRC=0xff;    // PORT C outputs to LCD

   DDRD=0x00;    // PORT D is an input from buttons

   DDRB=0xff;    // PORT B outputs to LEDs


   //turn LEDs off



   //set up timer 0

   TIMSK=2;    //turn on timer 0 cmp match ISR

   OCR0 = 50;  //set the compare to 250 time ticks

   //prescalar to 64 and turn on clear-on-match



   //init the task timer

   time1 = time2 = 0;


   //LCD initialization






   //set up ADC

   ADMUX = 0b00100000;

   ADCSR = 0b10000110;

   ADCSR.6 = 1; // start conversion


   //initialize string values

   T_array[0] = 607-8-2;

   T_array[1] = 455-7-2;

   T_array[2] = 341-7-1;

   T_array[3] = 255-3-2;

   T_array[4] = 202-4+1;

   T_array[5] = 152-3;


   //initialize tight strig bounds

   tight_array[0] = 2;

   tight_array[1] = 2;

   tight_array[2] = 1;

   tight_array[3] = 1;

   tight_array[4] = 0;

   tight_array[5] = 0;


   //initialize string bounds

   c1 = 1.05; //1.0293;

   c2 = 1/c1; //0.9715;



      high_array[i] = (unsigned int)(c1*((float)T_array[i]));

      low_array[i] = (unsigned int)(c2*((float)T_array[i]));

      high_array2[i] = T_array[i] + (tight_array[i]+3);

      low_array2[i] = T_array[i] - (tight_array[i]+3);



   //initialize filters

   filter_array[0] = 15;

   filter_array[1] = 15;

   filter_array[2] = 12;

   filter_array[3] = 12;

   filter_array[4] = 4;

   filter_array[5] = 4;


   //set initial string to E

   string = 0;



   //crank up the ISRs