</>CoderDIY
← Back to list
Article

Build a DIY Guitar Tuner with Arduino

Author: CoderDIY··Đọc bằng tiếng Việt
🎸

Store-bought guitar tuners are cheap, but building one with an Arduino is a great exercise in understanding how a microcontroller “listens” and analyzes sound — turning an analog sound wave into a concrete frequency number and comparing it to a reference note. The principle here (sampling audio, detecting pitch) is also the foundation behind voice recognition, machine vibration analysis, and plenty of other signal processing applications.

How it works

  1. An electret microphone picks up the vibrating string’s sound, amplified through a simple op-amp stage before reaching the Arduino’s ADC pin.
  2. The Arduino samples the audio signal at a high enough rate (around 5-9kHz, enough to capture standard guitar notes from E2 (~82Hz) to E4 (~330Hz) per the Nyquist sampling theorem).
  3. Instead of a memory-hungry FFT, a simpler autocorrelation algorithm finds the repeating period in the signal — from that period, the fundamental frequency of the note is derived.
  4. The measured frequency is compared against the 6 standard guitar string frequencies (E2, A2, D3, G3, B3, E4); the deviation (measured in cents — a musical unit) is shown via an LED strip or LCD so the player knows whether to tune up or down.
<rect x="10" y="20" width="140" height="60" rx="10" class="box" />
<text x="80" y="45" text-anchor="middle" class="label">Sample ADC</text>
<text x="80" y="62" text-anchor="middle" class="label">(~9kHz)</text>

<rect x="170" y="20" width="140" height="60" rx="10" class="box" />
<text x="240" y="45" text-anchor="middle" class="label">Autocorrelation</text>
<text x="240" y="62" text-anchor="middle" class="label">find repeat period</text>

<rect x="330" y="20" width="140" height="60" rx="10" class="box" />
<text x="400" y="45" text-anchor="middle" class="label">Compute frequency</text>
<text x="400" y="62" text-anchor="middle" class="label">(Hz)</text>

<rect x="490" y="20" width="140" height="60" rx="10" class="box" />
<text x="560" y="45" text-anchor="middle" class="label">Compare to note</text>
<text x="560" y="62" text-anchor="middle" class="label">→ show deviation</text>

<line x1="150" y1="50" x2="170" y2="50" class="arrow" />
<line x1="310" y1="50" x2="330" y2="50" class="arrow" />
<line x1="470" y1="50" x2="490" y2="50" class="arrow" />

<path d="M 560 80 L 560 190 L 80 190 L 80 80" class="arrow" fill="none" />
<text x="320" y="210" text-anchor="middle" class="sublabel">Repeats continuously, updates the display in real time</text>
Pitch-detection loop: sample audio, autocorrelate to find the period, compute frequency, compare and display the deviation.

Parts list

  • Arduino Uno or Nano
  • An electret microphone (2-pin or 3-pin, with a simple amplifier stage using an LM386 op-amp, or a pre-amplified mic module)
  • An LED strip (5-7 LEDs) or a 16x2 LCD to show the sharp/flat deviation
  • Resistors and capacitors for the mic’s filter/bias stage (per the mic module’s datasheet)
  • A small enclosure to package it neatly for carrying along while playing
<rect x="235" y="90" width="170" height="120" rx="10" class="box" />
<text x="320" y="155" text-anchor="middle" class="label">Arduino</text>

<rect x="20" y="30" width="150" height="70" rx="10" class="box" />
<text x="95" y="70" text-anchor="middle" class="label">Mic + amplifier</text>

<rect x="470" y="30" width="150" height="70" rx="10" class="box" />
<text x="545" y="70" text-anchor="middle" class="label">LED strip (5-7)</text>

<line x1="170" y1="65" x2="235" y2="120" class="wire" />
<text x="180" y="95" text-anchor="middle" class="sublabel">Out → A0</text>

<line x1="405" y1="120" x2="470" y2="65" class="wire" />
<text x="455" y="95" text-anchor="middle" class="sublabel">D2-D8</text>
The amplified audio signal feeds into A0; the LEDs, driven by digital pins, show the deviation above or below the reference note.

Sample code

const int MIC_PIN = A0;
const int SAMPLE_SIZE = 256;
const float SAMPLE_RATE_HZ = 9000.0;

int samples[SAMPLE_SIZE];

// Standard frequencies of the 6 guitar strings (E2 A2 D3 G3 B3 E4)
const float STRING_FREQS[] = {82.41, 110.00, 146.83, 196.00, 246.94, 329.63};
const char* STRING_NAMES[] = {"E2", "A2", "D3", "G3", "B3", "E4"};

void setup() {
  Serial.begin(115200);
}

float detectFrequency() {
  unsigned long intervalMicros = 1000000.0 / SAMPLE_RATE_HZ;
  for (int i = 0; i < SAMPLE_SIZE; i++) {
    samples[i] = analogRead(MIC_PIN) - 512; // remove DC offset
    delayMicroseconds(intervalMicros);
  }

  // Autocorrelation: find the lag with the largest accumulated product
  int bestLag = -1;
  long bestCorr = 0;
  for (int lag = 20; lag < 200; lag++) { // roughly the ~45-450Hz range at 9kHz
    long corr = 0;
    for (int i = 0; i < SAMPLE_SIZE - lag; i++) {
      corr += (long)samples[i] * samples[i + lag];
    }
    if (corr > bestCorr) {
      bestCorr = corr;
      bestLag = lag;
    }
  }

  if (bestLag <= 0) return 0;
  return SAMPLE_RATE_HZ / bestLag;
}

void loop() {
  float freq = detectFrequency();
  if (freq < 60 || freq > 400) return; // outside the guitar range, likely noise

  // Find the closest guitar string
  int closest = 0;
  float minDiff = 9999;
  for (int i = 0; i < 6; i++) {
    float diff = abs(freq - STRING_FREQS[i]);
    if (diff < minDiff) {
      minDiff = diff;
      closest = i;
    }
  }

  float cents = 1200.0 * log(freq / STRING_FREQS[closest]) / log(2.0);

  Serial.print(STRING_NAMES[closest]);
  Serial.print(" - Frequency: ");
  Serial.print(freq);
  Serial.print("Hz - Deviation: ");
  Serial.print(cents);
  Serial.println(" cents");

  // cents > 0: sharp (tune down), cents < 0: flat (tune up)
}

Common pitfalls

  • Not removing the DC offset before autocorrelating causes the algorithm to consistently find the wrong lag — electret mics usually output a bias voltage around the middle of the ADC range, which needs subtracting before processing.
  • Sampling too sparsely loses the ability to distinguish the low strings (E2 ~82Hz) — the sample rate needs to be at least double the highest frequency you want to catch, but in practice much higher for good accuracy.
  • Ambient noise makes results jump around erratically — shield the mic and point it directly at the guitar’s sound hole, or only accept a reading when the signal amplitude is large enough.
  • delayMicroseconds isn’t perfectly precise across a long loop, skewing the actual sample rate — for applications needing tighter accuracy, use a Timer interrupt instead of a delay inside the loop.

Extensions

Add an OLED display with a virtual needle like a professional tuner, support alternate tunings (Drop D, DADGAD), or check out the DIY mini oscilloscope with Arduino post for real-time analog signal processing and display to visually debug the captured audio waveform.

Tags:#arduino#audio#music

Comments