Instructables

Arduino Frequency Detection

Featured
Picture of Arduino Frequency Detection
IMG_0041 copy.jpg
As a follow up to the Arduino Audio Input tutorial that I posted last week, I wrote a sketch which analyzes a signal coming into the Arduino's analog input and determines the frequency.  The code uses a sampling rate of 38.5kHz and is generalized for arbitrary waveshapes.  I've also turned the LED attached to pin 13 into a clipping indicator, so you know if you need to adjust your signal's amplitude as you send it into the Arduino.

Some project ideas for the code presented here include:

pitch reactive projects- change the color of RGB LEDs with pitch, or make a lock that only opens when you sing a certain pitch or melody
audio to MIDI conversion- get the Arduino to translate an incoming signal into a series of MIDI messages. See my instructable about getting the Arduino to send and receive MIDI for lots of example code to get started
audio effects- use the frequency information to reconstruct an audio signal from the tone() library or with some stored samples to make a cool effects box/synthesizer

The first step of this project is to set up the audio input circuit.  I wrote a detailed Instructable about that here.
 
Remove these adsRemove these ads by Signing Up

Step 1: Detection of Signal Slope

First I wanted to experiment with peak detection, so I wrote a piece of code (below) that outputs a high signal when the incoming audio signal has a positive slope, and outputs a low signal when the incoming audio signal has a negative slope.  For a simple sine wave, this will generate a pulse signal with the same frequency as the sine wave and a duty cycle of 50% (a square wave).  This way, the peaks are always located where the pulse wave toggles between its high and low states.

The important portion of the code is reproduced below.  All of this code takes place in the ADC interrupt (interrupts and runs each time a new analog in value is ready from A0, more info about what interrupts are and why we use them can be found here)

  prevData = newData;//store previous value
  newData = ADCH;//get value from A0
  if (newData > prevData){//if positive slope
    PORTB |= B00010000;//set pin 12 high
  }
  else if (newData < prevData){if negative slope
    PORTB &= B11101111;//set pin 12 low
  }


I should note here that in this tutorial I use direct port manipulation to turn off and on the output pin (pin 12) of the Arduino.  I did this because port manipulation is a much faster way of addressing the Arduino's pins than the digitalWrite() command.  Since I had to put all the code above inside an interrupt routine that was going off at 38.5kHz, I needed the code to be as efficient as possible.  You can read more about port manipulation on the Arduino website, or see the comments I've written above to understand what each line does.  You'll also notice in the code below that I used some unfamiliar commands in the setup() function so that I could get the Arduino's analog input to sample at a high frequency.  More info on that can be found in my Arduino Audio Input tutorial.

Fig 1 shows the pulse output in blue and the sine wave in yellow on an oscilloscope.  Notice how the pulse output toggles each time the sine wave reaches a maximum or minimum.  Fig 2 shows the pulse output in blue for an arbitrary waveshape in yellow.  Notice here how pulse wave takes on an irregular duty cycle because the incoming signal (yellow) is much more complicated than a sine wave.

raptorofaxys7 months ago
Hey there! This was a great read! I'm a fan of yours - your 'ibles are always interesting and very well put together.

I just wanted to mention that a few years ago I built an Arduino-based guitar tuner and had also tried this kind of frequency detection approach, but, as you mention yourself, I ultimately found it to be unruly when fed arbitrary waveforms. After some research, I implemented a modified YIN (http://audition.ens.fr/adc/pdf/2002_JASA_YIN.pdf) on Arduino, and this worked extremely well while keeping the code simple.

The project writeup is here, and the code is freely downloadable from that page: http://deambulatorymatrix.blogspot.ca/2010/11/digital-chromatic-guitar-tuner-2008.html

All the best, and keep up the great work! :D
I think I may use your code - thanks for posting it. I want to build a system to adjust the tension on bicycle spokes by pinging the spokes and determining the resonant frequency, then adjusting all the spokes to match the mean - an extremely similar process to guitar tuning (if a guitar had 6 identical strings :-) ), which is actually what gave me the idea. The frequency of the spokes on my bike is centered around 350Hz, so amandaghassaei's code probably won't do, as she says that it starts to become inaccurate at 350Hz. Of course I could use something faster than an arduino, maybe one of the arm-based arduino-alikes, but the thought of building this with an atmega is very tempting.

Thanks,

Graham
PS I expect most people's bike spokes will resonate at a lower frequency, but I have shorter spokes as it's an e-bike with a hub motor.

Quick FYI, the whole point of adjusting spokes tension is to true the wheel (make it straight as possible). In my experience (mostly Free ride style), tension on the spokes varies according to any warp in the rim itself.. and in the case of my bikes, after doing a few 6ft drops the rims get tweaked and some of the spokes need to really be cranked up to get the rim trued again. hope this helps in any troubleshooting you might be having problems with.

Wow, that sounds like quite an original use for this kind of setup! I know my dad tunes his own spokes using a piano, but using electronics would probably be more accurate :) Best of luck, and keep us posted!
RU4Realz gtoal5 months ago
sounds like a cool project!
amandaghassaei (author)  raptorofaxys6 months ago
thanks! I like your project a lot! looks like the YIN algorithm works great. you should enter the microcontroller contest:
http://www.instructables.com/contest/
Hahaha, thanks for the suggestion! I knew about the contests but did not realize you had so many going in parallel. I would have loved to enter, but I live in good ol' Quebec, which makes me ineligible. :-(
RobinCh1 month ago

Hi miss Amanda!

It's really great what you have made! I tried this on my Arduino Uno and it works perfect! But now, I have a problem. I want to use this code on my other Arduino, the Arduino Due. For this, I had to download the new beta-software 1.5 (Instead of 1.0 for the other Arduino's) because the standard software doesn't support the Due (he's too recent I guess). After trying a lot of things, I could finally connect him to the software! But now I uploaded your code again with the new software to the new Arduino, and I have a lot of errors, but I changed nothing... This is the list the software gave me:

_1e_versie_Arduino_sketch.ino: In function 'void setup()':

_1e_versie_Arduino_sketch:34: error: 'cli' cannot be used as a function

_1e_versie_Arduino_sketch:39: error: 'ADCSRA' was not declared in this scope

_1e_versie_Arduino_sketch:40: error: 'ADCSRB' was not declared in this scope

_1e_versie_Arduino_sketch:42: error: 'ADMUX' was not declared in this scope

_1e_versie_Arduino_sketch:42: error: 'REFS0' was not declared in this scope

_1e_versie_Arduino_sketch:43: error: 'ADLAR' was not declared in this scope

_1e_versie_Arduino_sketch:45: error: 'ADPS2' was not declared in this scope

_1e_versie_Arduino_sketch:45: error: 'ADPS0' was not declared in this scope

_1e_versie_Arduino_sketch:46: error: 'ADATE' was not declared in this scope

_1e_versie_Arduino_sketch:47: error: 'ADIE' was not declared in this scope

_1e_versie_Arduino_sketch:48: error: 'ADEN' was not declared in this scope

_1e_versie_Arduino_sketch:49: error: 'ADSC' was not declared in this scope

_1e_versie_Arduino_sketch:51: error: 'sei' was not declared in this scope

_1e_versie_Arduino_sketch.ino: In function 'void ADC_vect()':

_1e_versie_Arduino_sketch:57: error: 'ADCH' was not declared in this scope

_1e_versie_Arduino_sketch:65: error: 'PORTB' was not declared in this scope

_1e_versie_Arduino_sketch.ino: In function 'void loop()':

_1e_versie_Arduino_sketch:74: error: 'PORTB' was not declared in this scope

What do I have to do about it? I don't understand why it doesn't work in this software...

Thank you so much for the help! I really appreciate it!

Greetings,

Robin C,

Belgium

amandaghassaei (author)  RobinCh1 month ago

this code will have to be rewritten for the due, haven't used one, sorry!

Oh no ;s and do you know somebody who can work with this device? I have to use this one for school... ;s Thank you for your response!! :D

Hi Amanda!

I was making a tengu clone using Arduino and because I wanted it to react to different frequencies in different manner, I tried to use your code for frequency detection, to detect the frequency of square wave output of comparator (which is being fed by an amplified sound wave), however when I tested it, the Serial Monitor says nothing but "inf hz", can you please tell me as to where am I going wrong? I am using an electret microphone and LM358 dual op amp IC for amplification and as a comparator.

amandaghassaei (author)  Ketan_Sharma1 month ago

what's the amplitude of the signal going into the arduino?

moodydood1 month ago

Hi Amanda:

Thank you for this tutorial. Is there a way to parse a complex waveform (for example, a guitar chord) in such a way that detection of each note is possible? Or is the waveform too complex to handle? I was using your code for a guitar tuner and while it worked great when you played a single note, the output would be garbled when you played a chord. Again, thanks for your tutorial and your time.

amandaghassaei (author)  moodydood1 month ago

you could do this with an fft (fast fourier transform), maybe try searching for ways people have done that. It takes a bit more processing.

Fozzibehr1 month ago

Would it be possible to have the arduino dectect multiple frequencys, and then do something based on which frequency?(Like if it detects frequencys <1k hz then light up this light, if frequency is >1k hz then light up another light?

amandaghassaei (author)  Fozzibehr1 month ago

yes, you might also look at a spectrum analyzer chip like this

amandaghassaei (author)  amandaghassaei1 month ago

not sure why that link's not working:

https://www.sparkfun.com/products/10468

jzzxh3 months ago
Thanks your nice tut!! It's possible detect frequency over 30Khz or? i did an experiment that generate 2xkhz signal, but Serial.print the frequency was 19xxx(op LM386) , it's a limitation of arduino?

BIG THANKS.
amandaghassaei (author)  jzzxh3 months ago

if you ramp up the ADC clock a bunch (which also has the effect of increasing noise on your analog measurements) you might be able to do frequency detection that high. Not sure how bad the noise would be though, I haven't tried.

shredape8 months ago
I'm trying to build a guitar tuner based on your Audio Input and General Frequency Detection programs. So far, it's worked out by combining the 2 programs and I'm able to read a freqeuncy that is rather close to the generated tone. My concern is that it won't be accurate enough for a guitar tuner.

My method of "noticing" a pitch is to match the frequency with an arbritrary array value which corresponds to an outputed note. For example, a 440Hz incoming signal will generate an A4 note on an LED matrix. This works fine sometimes, but it can't be very reliable because I sometimes get false data which can lead to the wrong note being outputted.

I am using the following equations to calculate frequency:

frequency = 38462/float(period); //calculate frequency timer rate/period
noteIndex = round((log(frequency/15.434))/0.0578) - 1; //calcualte note value in array from frequency

Can you please help me figure out why my tuner isn't outputting the correct input frequency all of the time?
amandaghassaei (author)  shredape8 months ago
can you tell me a little more about how it fails? high/low? can you Serial.print() any of this data and copy it so I can help you better?
At the sampling rate this is running you can't entirely resolve frequencies above roughly 300-350 hz. Mimicking them on output is one thing, estimating pitch is a bit more tricky. While you can theoretically sample up to about 19k (Nyquist) that won't give you the resolution you need to correctly judge periodicity 100% - its a cool project it just has bounds dictated by the sampling theorem and arduino's capabilities. e.g. Your high e is 329.63 Hz - at a sampling rate of 38k the period is 115.28 samples long but you cant interpolate in this situation so you have to round - 115 samples gives you 330 Hz which is close, but an octave above 659.26 is 57.6 samples so 57 samples equals roughly 666 Hz and 58 gives you 655 Hz - the higher the frequency the greater the drift you will start to see. I ran into this problem working on my master's thesis (http://www.bosleymusic.com/Papers/DonBosleyThesis_06172013_Edit.pdf) and short of oversampling, there is no workaround.

The second issue specific to guitar is that it also produces a relatively complex waveform made up of lots of harmonics of constanly varying amplitudes. All the examples are fixed amplitude. You need to low pass filter the signal either prior to getting it into arduino or use a digital LPF inside the code to remove the upper harmonics in the guitar signal. A simple start would be use the bridge pickup and roll off your tone.

The major thing is ensure your signal is properly biased at 2.5 volts like it says in step 2 - this project is essentially a zero crossing detector/comparator with a bias point of 2.5 volts instead of 0. You could accomplish biasing your AC guitar output with a single supply opamp or bjt setup. If you have specific questions about any of this I'll be glad to help also.
amandaghassaei (author)  Bosleymusic5 months ago
good points here. You can increase the ADC clock to increase the # of samples per cycle, but I'm not sure how fast you can go until noise becomes an issue. The limitations of Arduino unfortunately.
rmaulana6 months ago
where the 38462 Hz interrupt rate comes from? I opened the Arduino Interrupt tutorial that you've posted and I used every equations in it, I still can't figure it out :(
amandaghassaei (author)  rmaulana6 months ago
yeah it's a little confusing, so I've set the ADC clock to 500kHz using a 32 prescaler on the arduino's 16MHz clock:
16mHz/32=500kHz
and then it takes 13 clock cycles for the arduino to perform ADC, so:
500khz/13 = 38.4kHz
gustavo14ve6 months ago
Hi amandaghassaei, It is very good, congratulation!
I have a question for you, If I want to use a electric condenser microphone what do you recomend me to change?

Thank you
amandaghassaei (author)  gustavo14ve6 months ago
you might have to change the values of the resistors connected tot he amp so that it provides more amplification. Do you have an oscilloscope?
Thank you for your answer.
No, I haven´t but I can find one...So, the same circuit work (I don´t need change or add anything) and only I have to change the values of the resistors?

Thank in advance.
amandaghassaei (author)  gustavo14ve6 months ago
yes
sparky0919 months ago
Here is the code u wanted me to post ... dont know what went wrong with the reply button !!! ... just for a quick review... i was trying to control the speed of a DC motor by the freq that is read by this program. the merging dint work and it displayed only -1hz


//clipping indicator variables
boolean clipping = 0;

//data storage variables
byte newData = 0;
byte prevData = 0;

//other variables
unsigned int timer = 0;//counts period of wave
unsigned int period;
int frequency;
int i;
int motorPin1=4;
int motorPin2=5;
void setup(){

i=frequency;

Serial.begin(9600);

pinMode(13,OUTPUT);//led indicator pin
pinMode(motorPin1, OUTPUT);
pinMode(motorPin2, OUTPUT);

cli();//diable interrupts

//set up continuous sampling of analog pin 0

//clear ADCSRA and ADCSRB registers
ADCSRA = 0;
ADCSRB = 0;

ADMUX |= (1 == REFS0); //set reference voltage
ADMUX |= (1 == ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only

ADCSRA |= (1 == ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
ADCSRA |= (1 == ADATE); //enabble auto trigger
ADCSRA |= (1 == ADIE); //enable interrupts when measurement complete
ADCSRA |= (1 == ADEN); //enable ADC
ADCSRA |= (1 == ADSC); //start ADC measurements

sei();//enable interrupts
}

ISR(ADC_vect) {//when new ADC value ready

prevData = newData;//store previous value
newData = ADCH;//get value from A0
if (prevData < 127 && newData >=127){//if increasing and crossing midpoint
period = timer;//get period
timer = 0;//reset timer
}


if (newData == 0 || newData == 1023){//if clipping
PORTB |= B00100000;//set pin 13 high- turn on clipping indicator led
clipping = 1;//currently clipping
}

timer++;//increment timer at rate of 38.5kHz
}

void loop(){
if (clipping){//if currently clipping
PORTB &= B11011111;//turn off clippng indicator led
clipping = 0;
}

frequency = 38462/period;//timer rate/period
//print results
Serial.print(frequency);
Serial.println(" hz");

delay(100);

for(i=1500;i<2500;i++){


rotateRight(300, 1000);//Turn Motor at 300rpm
}

for(i=2500;i<5000;i++){



rotateRight(100, 1000);//Turn Motor at 100rpm
}

}

void rotateRight(int speedOfRotate, int length){
analogWrite(motorPin2, speedOfRotate); //rotates motor
digitalWrite(motorPin1, LOW); // set the Pin motorPin1 LOW
delay(length); //waits
digitalWrite(motorPin2, LOW); // set the Pin motorPin2 LOW
}
amandaghassaei (author)  sparky0918 months ago
hey, sorry it took me so long to get back to you, did you ever resolve this? this code looks ok to me, are you sure something didn't change in the wiring?
yes i resolved it :) i have another question ... this program of yours disables all the analog input pins except A0 .. I have another program that uses this A0 pin as well and i am trying to sync these two ... so can i take two inputs from A0 ? will there be a problem ?
amandaghassaei (author)  sparky0916 months ago
my program keeps A0 occupied all the time :(
Oh ... i really need one other port from the analog pins ... can i use RCtime to manually override the digital pins for the purpose ? or is it possible to reprogram the one you wrote to enable one more analog port ?
Ok thank you
amandaghassaei (author)  sparky0916 months ago
yes, use rctime.
Can I use piezoelectric transducers for the detection of freq from each string of a guitar?
amandaghassaei (author)  elchavodelocio7 months ago
this code only allows for one channel of input at a time.
Thanks a lot.
excellent
Pro

Get More Out of Instructables

Already have an Account?

close

PDF Downloads
As a Pro member, you will gain access to download any Instructable in the PDF format. You also have the ability to customize your PDF download.

Upgrade to Pro today!