# Arduino Frequency Detection

356,543

449

187

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.

## 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.

```//Detection of signal slope with 38.5kHz sampling rate and interrupts
//by Amanda Ghassaei
//https://www.instructables.com/id/Arduino-Frequency-Detection/
//Sept 2012

/*
* This program is free software; you can redistribute it and/or modify
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

//clipping indicator variables
boolean clipping = 0;

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

void setup(){

pinMode(13,OUTPUT);//led indicator pin
pinMode(12,OUTPUT);//slope indicator

cli();//disable interrupts

//set up continuous sampling of analog pin 0

ADMUX |= (1 << REFS0); //set reference voltage

sei();//enable interrupts
}

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

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

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

delay(100);
}

```

## Step 2: Mid Point Detection

I decided that I would get more accurate results detecting the frequency of a wave by keeping track of the times the wave crosses 2.5V instead of counting peaks.  In the last step I was essentially finding the places on the wave where the slope = 0 and counting the time between these events.  However, when the slope = 0, noise on the signal is enough to change the direction of the slope and skew my results.  When the wave is crossing 2.5V, it usually has a slope with a magnitude larger than 0, so I would not have to worry about the effects of noise as much.

The important changes to the code are reproduced below.  Since I am measuring the incoming signal from A0 with 8 bit precision (0-255), the midpoint (2.5V) will give a value of 127.  All of the following code takes place in the ADC interrupt (interrupts each time a new analog in value is ready from A0)

prevData = newData;//store previous value
newData = ADCH;//get value from A0
if (prevData < 127 && newData >= 127){//if increasing and crossing midpoint
PORTB |= B00010000;//set pin 12 high
}
else if (prevData > 127 && newData <= 127){//if decreasing and crossing midpoint
PORTB &= B11101111;//set pin 12 low
}

Fig 1 shows the pulse output in blue and the incoming signal to A0 in yellow.  Notice how each time the signal crosses 2.5V, the pulse output toggles.  Specifically, the output goes high when the signal crosses 2.5V with a positive slope and the signal goes low when the signal crosses 2.5V with a negative slope.  Fig 2 shows the pulse output in blue and the audio signal before it has been +2.5V DC offset in yellow.  Remember, this DC offset was necessary to get the audio signal in the 0-5V range for the Arduino's analog input pin, but normally audio signal oscillate around 0V.  In fig 2 you can see how the pulse outputs toggle corresponds to the time when the audio signal crosses 0V.  Fig 3 shows an arbitrary waveform in yellow (again before DC offset) and the pulse output in blue.  Again, the pulse toggles each time the yellow signal crosses 0V, notice how the behavior of the pulse output with the arbitrary waveform is more complex than with the sine wave.
```//Detection of midpoint crossing with 38.5kHz sampling rate and interrupts
//by Amanda Ghassaei
//https://www.instructables.com/id/Arduino-Frequency-Detection/
//Sept 2012

/*
* This program is free software; you can redistribute it and/or modify
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

//clipping indicator variables
boolean clipping = 0;

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

void setup(){

pinMode(13,OUTPUT);//led indicator pin
pinMode(12,OUTPUT);//output pin

cli();//diable interrupts

//set up continuous sampling of analog pin 0

ADMUX |= (1 << REFS0); //set reference voltage

sei();//enable interrupts
}

prevData = newData;//store previous value
newData = ADCH;//get value from A0
if (prevData < 127 && newData >=127){//if increasing and crossing midpoint
PORTB |= B00010000;//set pin 12 high
}
else if (prevData > 127 && newData <=127){//if decreasing and crossing midpoint
PORTB &= B11101111;//set pin 12 low
}

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

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

delay(100);
}

```

## Step 3: Sine Wave Frequency Detection

Next I measured the period of an incoming sine wave, calculated the frequency, and printed the frequency.  To do this I set up a timer in the ADC interrupt that increments each time the interrupt executes (a rate of 38462Hz).  Each time the incoming signal crosses 2.5V with a rising slope I sent the current value of the timer to a variable called "period" and reset the timer to 0.  That code is reproduced below (all takes place within the ADC interrupt).

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 from current timer value
timer = 0;//reset timer
}

timer++;//increment timer

Then in the main loop() function, I calculated the frequency by dividing the timer rate by the period.  I used Serial.print to print these results in the Arduino serial monitor.

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

Fig 1 shows the signal coming into A0.  The start and end of one cycle measured by timer is indicated by the image note.  Fig 2 shows the output from the serial monitor (command/ctrl+shift+m).  This technique works great for sine waves, but when wave become more complicated (and cross 2.5V more than twice in one cycle) this technique breaks down.

```//sine wave freq detection with 38.5kHz sampling rate and interrupts
//by Amanda Ghassaei
//https://www.instructables.com/id/Arduino-Frequency-Detection/
//July 2012

/*
* This program is free software; you can redistribute it and/or modify
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

//clipping indicator variables
boolean clipping = 0;

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

//freq variables
unsigned int timer = 0;//counts period of wave
unsigned int period;
int frequency;

void setup(){

Serial.begin(9600);

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

cli();//diable interrupts

//set up continuous sampling of analog pin 0

ADMUX |= (1 << REFS0); //set reference voltage

sei();//enable interrupts
}

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);
}

```

## Step 4: Generalized Pitch Detection

In this code I generalized my frequency detection algorithm so that it could handle waves of many (hopefully all) shapes.  When writing this code I wanted to stick with the point I made in step 1 about not using the peaks and valleys as markers measure the period of the signal (minimize error due to noise).  I also wanted to write something that was as simple as possible (needs to execute at 38.5kHz) while still being robust enough to handle lots of waveshapes.  I decided to use a technique similar to an oscilloscope trigger.

Basically what I did was choose a voltage that I always knew would be in the bounds of my wave (2.5V).  Then I looked at every time the wave crossed this level with an upward slope, let's call these "threshold events".  If this happened multiple times in one cycle I chose the threshold event with the largest slope to be the beginning of my cycle. Similar to the last step, I used a variable called "time" (incremented at a rate of 38.5kHz) to measure the time between threshold events and stored this is an array called timer[].  I also recorded the slope at each of the threshold events in an array called slope[].  Then I compared the elements of timer[] and slope[] to figure out where there was a match.  Once a match was found, I added up the elements of timer[] to determine the duration of the cycle and sent this value to a global variable called "period."  Then in the main loop() function (all of the steps I've just described happen in the ADC interrupt routine) I used the value of period to calculate the frequency and print it.  I should also add that I put a variable in the code called "noMatch" which helped me to decide that it had been too long since I had a good match and that I should just rerecord the elements of timer[] and slope[].

When writing this I thought about a lot of possible scenarios that might break the algorithm.  The trickiest wave in my mind is one which passes the 2.5V threshold many times in one cycle at similar slopes and spaced out along the cycle similarly.  I you have a wave like this, you should keep slopeTol very low (0-3) and you might find that lowering timerTol (to 5 maybe) helps track the wave correctly.  Also, if you want to measure waves with very steep slopes (like pulse waves) you should set the value of slopeTol up to 100 or even all the way up to 256 to track them better.

Generally this piece of code seems to handle lots of shapes very well, you can see some of my results in the images above.  The incoming signal is shown in yellow and the threshold event that the Arduino is tracking is indicated by a pulse of pin 12 (blue).
```//generalized wave freq detection with 38.5kHz sampling rate and interrupts
//by Amanda Ghassaei
//https://www.instructables.com/id/Arduino-Frequency-Detection/
//Sept 2012

/*
* This program is free software; you can redistribute it and/or modify
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

//clipping indicator variables
boolean clipping = 0;

//data storage variables
byte newData = 0;
byte prevData = 0;
unsigned int time = 0;//keeps time and sends vales to store in timer[] occasionally
int timer[10];//sstorage for timing of events
int slope[10];//storage fro slope of events
unsigned int totalTimer;//used to calculate period
unsigned int period;//storage for period of wave
byte index = 0;//current storage index
float frequency;//storage for frequency calculations
int maxSlope = 0;//used to calculate max slope as trigger point
int newSlope;//storage for incoming slope data

//variables for decided whether you have a match
byte noMatch = 0;//counts how many non-matches you've received to reset variables if it's been too long
byte slopeTol = 3;//slope tolerance- adjust this if you need
int timerTol = 10;//timer tolerance- adjust this if you need

void setup(){

Serial.begin(9600);

pinMode(13,OUTPUT);//led indicator pin
pinMode(12,OUTPUT);//output pin

cli();//diable interrupts

//set up continuous sampling of analog pin 0 at 38.5kHz

ADMUX |= (1 << REFS0); //set reference voltage

sei();//enable interrupts
}

PORTB &= B11101111;//set pin 12 low
prevData = newData;//store previous value
newData = ADCH;//get value from A0
if (prevData < 127 && newData >=127){//if increasing and crossing midpoint
newSlope = newData - prevData;//calculate slope
if (abs(newSlope-maxSlope)<slopeTol){//if slopes are ==
//record new data and reset time
slope[index] = newSlope;
timer[index] = time;
time = 0;
if (index == 0){//new max slope just reset
PORTB |= B00010000;//set pin 12 high
noMatch = 0;
index++;//increment index
}
else if (abs(timer[0]-timer[index])<timerTol && abs(slope[0]-newSlope)<slopeTol){//if timer duration and slopes match
//sum timer values
totalTimer = 0;
for (byte i=0;i<index;i++){
totalTimer+=timer[i];
}
period = totalTimer;//set period
//reset new zero index values to compare with
timer[0] = timer[index];
slope[0] = slope[index];
index = 1;//set index to 1
PORTB |= B00010000;//set pin 12 high
noMatch = 0;
}
else{//crossing midpoint but not match
index++;//increment index
if (index > 9){
reset();
}
}
}
else if (newSlope>maxSlope){//if new slope is much larger than max slope
maxSlope = newSlope;
time = 0;//reset clock
noMatch = 0;
index = 0;//reset index
}
else{//slope not steep enough
noMatch++;//increment no match counter
if (noMatch>9){
reset();
}
}
}

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

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

void reset(){//clea out some variables
index = 0;//reset index
noMatch = 0;//reset match couner
maxSlope = 0;//reset slope
}

void checkClipping(){//manage clipping indicator LED
if (clipping){//if currently clipping
PORTB &= B11011111;//turn off clipping indicator led
clipping = 0;
}
}

void loop(){

checkClipping();

frequency = 38462/float(period);//calculate frequency timer rate/period

//print results
Serial.print(frequency);
Serial.println(" hz");

delay(100);//feel free to remove this if you want

//do other stuff here
}

```
I also added a bit of code to stop calculating and print frequency data when the amplitude of the wave falls below a certain level.  (If there is little or no signal then the code above sometimes spits out a bunch of garbage).  Here it is:
```//generalized wave freq detection with 38.5kHz sampling rate and interrupts
//by Amanda Ghassaei
//https://www.instructables.com/id/Arduino-Frequency-Detection/
//Sept 2012

/*
* This program is free software; you can redistribute it and/or modify
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

//clipping indicator variables
boolean clipping = 0;

//data storage variables
byte newData = 0;
byte prevData = 0;
unsigned int time = 0;//keeps time and sends vales to store in timer[] occasionally
int timer[10];//sstorage for timing of events
int slope[10];//storage for slope of events
unsigned int totalTimer;//used to calculate period
unsigned int period;//storage for period of wave
byte index = 0;//current storage index
float frequency;//storage for frequency calculations
int maxSlope = 0;//used to calculate max slope as trigger point
int newSlope;//storage for incoming slope data

//variables for decided whether you have a match
byte noMatch = 0;//counts how many non-matches you've received to reset variables if it's been too long
byte slopeTol = 3;//slope tolerance- adjust this if you need
int timerTol = 10;//timer tolerance- adjust this if you need

//variables for amp detection
unsigned int ampTimer = 0;
byte maxAmp = 0;
byte checkMaxAmp;
byte ampThreshold = 30;//raise if you have a very noisy signal

void setup(){

Serial.begin(9600);

pinMode(13,OUTPUT);//led indicator pin
pinMode(12,OUTPUT);//output pin

cli();//diable interrupts

//set up continuous sampling of analog pin 0 at 38.5kHz

ADMUX |= (1 << REFS0); //set reference voltage

sei();//enable interrupts
}

PORTB &= B11101111;//set pin 12 low
prevData = newData;//store previous value
newData = ADCH;//get value from A0
if (prevData < 127 && newData >=127){//if increasing and crossing midpoint
newSlope = newData - prevData;//calculate slope
if (abs(newSlope-maxSlope)<slopeTol){//if slopes are ==
//record new data and reset time
slope[index] = newSlope;
timer[index] = time;
time = 0;
if (index == 0){//new max slope just reset
PORTB |= B00010000;//set pin 12 high
noMatch = 0;
index++;//increment index
}
else if (abs(timer[0]-timer[index])<timerTol && abs(slope[0]-newSlope)<slopeTol){//if timer duration and slopes match
//sum timer values
totalTimer = 0;
for (byte i=0;i<index;i++){
totalTimer+=timer[i];
}
period = totalTimer;//set period
//reset new zero index values to compare with
timer[0] = timer[index];
slope[0] = slope[index];
index = 1;//set index to 1
PORTB |= B00010000;//set pin 12 high
noMatch = 0;
}
else{//crossing midpoint but not match
index++;//increment index
if (index > 9){
reset();
}
}
}
else if (newSlope>maxSlope){//if new slope is much larger than max slope
maxSlope = newSlope;
time = 0;//reset clock
noMatch = 0;
index = 0;//reset index
}
else{//slope not steep enough
noMatch++;//increment no match counter
if (noMatch>9){
reset();
}
}
}

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

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

ampTimer++;//increment amplitude timer
}
if (ampTimer==1000){
ampTimer = 0;
checkMaxAmp = maxAmp;
maxAmp = 0;
}

}

void reset(){//clea out some variables
index = 0;//reset index
noMatch = 0;//reset match couner
maxSlope = 0;//reset slope
}

void checkClipping(){//manage clipping indicator LED
if (clipping){//if currently clipping
PORTB &= B11011111;//turn off clipping indicator led
clipping = 0;
}
}

void loop(){

checkClipping();

if (checkMaxAmp>ampThreshold){
frequency = 38462/float(period);//calculate frequency timer rate/period

//print results
Serial.print(frequency);
Serial.println(" hz");
}

delay(100);//delete this if you want

//do other stuff here
}

```

## Recommendations

• ### Photography Class

17,387 Enrolled

## 187 Discussions

Hello Amanda, your program is great, it also works with an irregular violin wave forms but I want to detect 1000 hz. The output comes with steps of 20hz. Please, How can I get that the measure ? Where can I modify the program to detect 1000 hz?
Thank you !

That's a lot of questions to answer in such a limited forum. Two channels? You'll need to utilize another analog port on your Arduino for that. How to change colours depending on frequency? I ended up using the Open Music Labs FHT library for this.
As for gunshot sounds to be yellow and grenade sounds to be green. . . well I hope you're up on your 4th year University DSP math, because you're going to need it.

Was waiting for your input and I guess you accidentally post it under the wrong comment lol. I guess I don't have the knowledge to do the differentiating of gun/grenade noises. But how would I use ADMUX to switch between two input for my channel? Thank you again.

Was able to get this going on A5, once I added:

My next steps will be to add some FastLED code (which I'm well versed in) to light 'er up.

3 replies

I'm trying to do something similar to yours using FastLED as well. When incorporating the led strips with this code. My frequencies reading gets throw off. I was wondering if you were able to get yours working. Any help would be nice. Thanks.

Hey, I was able to get it going, but found it had a limited range and capability for what it was doing. You may be interested in some code I published on github, which includes a FastLED demos repo as well as a sound sampling repo including some FFT stuff (from another library).

https://github.com/atuline

So, yes, I got this going, and yes, it's a great article and I used it as an inspiration to get back into frequency based sound reactive LED displays.

Thanks, your works are nicely done and well written. I have some other questions as well. First, how would I incorporate another audio input, such as 2 channel for left and right? Second, how can I change the fastLED color depending on frequency? Let's say if the audio detects a gunshot, I want the LED to flash yellow, grenade for green and etc. Btw, audio is coming from game noises, I'm not tossing and shooting weapons arouind lol. Thanks for your time and help.

hy amandaghassaei....

i need to read sensor in another Analog PIN to, how i can .?

i was try and the serial get stop...

Thanks for the project!

I have constructed the audio input circuit outlined in the link at the top of this page. I'm trying to construct a device that would only light the Led attached to the Uno when a specific frequency is detected by the microphone. I'm having trouble develop code for this task and need a little guidance.

If anyone can help that will be great,

thanks!

-Alex

2 replies

Hi,
I'm trying to do this too, did you guys find out how?

how to make your coding compatible to arduino wemos d1 r1?
urgent

hi, why 38.5KHz and where within the code is the 38.5KHz continuous sampling, set up?

and what is pin 12 being used for?

Why is the code only using the top 8 bits of the ADC, rather than the full 10 bits

Works great, thanks. I am wondering about the clipping though. The variable 'newData' is declared as a byte and so it only has a possible value of 0 - 255, but the clipping condition checks 'newData' for values greater than 1023.

Hello,

Could a stripped down version of this be used in a ATtiny85? The only functions needed would be getting the frequency, no storage, no frills. Just read a frequency and light an LED at a given threshhold.

Thanks fo any input

It's not good approach to measure the frequency like this. The complex signal contains different amplitudes, phases and frequencies of a sine wave. You may missed fundamental frequency or one or more its harmonics in case of they have small amplitude which compare to large one.

Hi.
How did you come up with the timertolerance and slope tolerance values 3, 10? Thank you

What would happen if timertol is 11 above or 9 below?