Let's build our HOMEMADE whistle detector circuit. The idea is more than simple. Using a microphone we detect sound. Next we amplify that signal two times in order to have a square pulse signal with the same frequency as the input signal from the microphone. Then we connect that square wave to one Arduino input and measure the frequency using interruptions. Next we check if the range is between (800 and 1800Hz in my case) and activate any other l
by: ELECTRONOOBS on 2026-05-31
You should know:
A whistle sound is practically a sine wave.
Usually the frequency of a whistle is between 800 and 2500Hz.
Ok, this is how will the project go. We will build the detecting circuit. We read the frequency with arduino and then we control stuff. I've prepared for you some examples. Once we build the circuit, check those examples. Before, check the part list that you need below:
LM324: LINK eBay
Electret mic: LINK eBay
Arduino NANO: LINK eBay
10 turn Pot: LINK eBay
Capacitors: LINK eBay
LCD: LINK eBay
Relay: LINK eBay
5V LED strip: LINK eBay
IRFZ44N MOSFET: LINK eBay
You have the full part list above.
We will use an electret microphone. Electret Condenser Microphone, as the name suggests is a parallel plate capacitor and works on the principle of a variable capacitance. It consists of two plates, one fixed (called the back plate) and the other moveable (called Diaphragm) with a small gap between them. An electric potential charges the plate. When sound strikes the diaphragm it starts moving, thereby changing the capacitance between the plates which in turn results in a variable electric current to flow.

Sound travel through air in a form of a pressure wave. When that wave reaches the inside microphone capacitive plates, that will result in voltage change. The microphone has an internal transistor that will amplify that signal. So, all we have to do is to add a pullup resistor of 10k ohms in this case connected to 5V and the negative pin of the microphone to ground. You can identify the negative pin of the microphone looking at these 3 PCB lines that you could see below.

Now the microphone is powered and if we connect the oscilloscope to the output in the scheamtic above, we could observe the audio wave. In the photo below you have an example of a 1200Hz sine sound wave made with an mobile App. As you can see the peak to peak voltage is very, very low.

To amplify the audio signal I’ve used an operational amplifier or better said OPAMP. I've used the LM324 which is not a high-performance amplifier but it will do the job more than perfect for this easy project. Besides it has 4 amplifiers integrated in just one chip, and since we need to amplify two times, this is great. Ok so now I connect the output from the microphone to a 10uF capacitor as high-pass filter and in series with this capacitor I add 1k ohm resistor. You see, the amplifier gain is given by the two resistors and formula below. I want a gain of 100 so the second resistors should be 100 times bigger than the 1k ohm one.

I place the OPAMP on the breadboard and add the 100k ohms resistor between the negative input pin and the output which are pins 13 and 14. Now I supply 5V and ground to the Vcc and GND pins which are pin 4 and 11 of the LM324.
In order to set a certain offset of the output I connect the positive input which is pin 12 to the middle pin of a 10 turns potentiometer. The whistle wave as you can see is a si ne wave wo it will be the same amount of time in the negative side as in the positive side. Since the maximum voltage in this project will be 5V of the Arduino, I want to set the offset exactly in the middle of that voltage. I turn the potentiometer till I get 2.5 volts on the middle pin. By the way, the other two pins of the potentiometer are connected to 5V and GND. Now I observe the output signal on the oscilloscope. I’ve used an Android App to create a sine wave sound. I set the frequency at 1.2KHz and put the smartphone close to the microphone. As you can see below the microphone signal is around 20 or 30 mV peak to peak and full of noise. But the output from the OPAMP is close to 2V peak to peak, and if I get the phone even closer it will saturate at 2.5V peak to peak since that is the maximum voltage that we have.

Ok so we have our signal amplified. The OPAMP configuration that we’ve used is an inverting one, that’s why the output signal is inverted as we can see in the photo above on my oscilloscope. When the input is positive the output is negative and vice versa. Also, we still have a sine wave. What I want is a square wave with the same frequency as this signal. So, for that I amplify the signal once again with the second amplifier and this will give me the square wave that I want. I connect the output from the first amplifier to the positive input of the second. The negative input of the second amplifier is connected once again to a potentiometer in order to set the middle voltage. This second configuration of the OPAMP is a comparator. It has infinite gain so we will have a positive pulse when the signal on the positive input is higher than the one set on the negative input and a low pulse when the input is below the set voltage on the negative pin.

I set the potentiometer to around 2.5V once again and observe the output on the oscilloscope. Great, I’ve got my square wave. As expected, I’ve got 5 volts when the input signal is above 2.5V and 0 volts when the input is below 2.5V. That gives me a square wave with the exact same frequency as the first input and that’s exactly what I wanted.

Before we go to the Arduino part I want to check my whistle frequency range. For that I place the frequency label on the oscilloscope and whistle from the lowest to the highest pitch that I can. My range is between 800Hz and 1600. But I’ve tried this test with a women whistle and I’ve reached up to 2500Hz so you should decide the range that you’ll use in the code.
2.1 Frequency meter + Serial monitor
Let’s go over the Arduino part. I’ll connect the second output from the amplifier to digital pin 8. I’ve used Arduino UNO for this test but the code will work both for NANO or Mini as well. If you use Arduino MEGA you’ll have to change some registers because that one won’t use the same.
Ok so what we want is to measure the frequency of the input signal. For that we will measure the time it takes the pulse to rise and then fall back and multiply that by two since the sine wave spends the same amount of time on both sides. That time will be the period of the signal and if invert the period time we get the frequency in Hertz. To do this I’ve used pin change interruptions and for that first add this two registers configuration before the setup loop. This will make digital pin 8 create an interruption on both state changes. Any time that digital pin 8 in this case will change its value we will enter an interruption routine or ISR. So, when the signal will rise I enter the interruption and start a time counter. When the pulse will fall I enter the interruption once again and stop that counter. The difference between the present time and the las counter value will give me the width time of the pulse.

I’ve used the micros function to count time so the value will be in micro seconds. This is the pulse width measurement interruption vector. In the void loop I multiply the measured value by two in order to obtain the full signal period as said before. Now I divide 1 by the period and get the frequency. Since I was working in microseconds I have to multiply the frequency value by one million in order to obtain Hertz from seconds not from microseconds.
That’s it, now I’ve got the frequency. Now I print the value on the Serial monitor. Download the next code, upload it to the Arduino, connect the circuit and open serial monitor.
/*Whistling sound switch; author: ELECTRONOOBS
* Subscribe: http://www.youtube.com/c/ELECTRONOOBS
*
* Activate any Arduino loop just by whistling. The circuit is very easy.
* Check the entire tutorial here: http://www.electronoobs.com/eng_arduino_tut19.php *
*
* Input pin is D8
* Relay output is D13
* PWM buzzer output is D3
*/
//We create a variable for the time width value of the input signal
unsigned long last_pulse_time_counter, present_time;
//We create 1 variables to store the previous value of the input signal (if LOW or HIGH)
byte last_PIN_state;
//To store the final width value we create this variables (this will be half of the period)
//Variables
float Period = 0;
float Frequency_micro = 0;
float Frequency = 0;
int Pulse_amount = 0;
int pulse_is_measured = 0;
int sum_count = 0; //Increaase the count amount by one in each loop only if the range is correct
float freq_sum = 0;
float freq_mean = 0;
void setup() {
/*
* Port registers allow for lower-level and faster manipulation of the i/o pins of the microcontroller on an Arduino board.
* The chips used on the Arduino board (the ATmega8 and ATmega168) have three ports:
-B (digital pin 8 to 13)
-C (analog input pins)
-D (digital pins 0 to 7)
//All Arduino (Atmega) digital pins are inputs when you begin...
*/
PCICR |= (1 << PCIE0); //enable PCMSK0 scan
PCMSK0 |= (1 << PCINT0); //Set pin "D8" trigger an interrupt on "any" state change.
//Start the serial in order to see the result on the monitor
//Remember to select the same baud rate on the serial monitor
Serial.begin(250000);
}
void loop() {
/* Ok, so in the loop the only thing that we do in this example is to print
* the received values on the Serial monitor. The PWM values are read in the ISR below.
*/
if(pulse_is_measured) //Only when a full pulse is measured we enter the loop
{
pulse_is_measured = 0; //We reset "the pulse is detected" for the next loop
Frequency_micro = 1/Period; //Invert the period and get the frequency (in Hertz by us)
Frequency = Frequency_micro*1000000; //Multiply by one million and get frequency (in Hertz by seconds)
sum_count = sum_count+1; //Increaase the count amount by one in each loop only if the range is correct
freq_sum = freq_sum + Frequency; //calculating a mean
freq_mean = freq_sum/sum_count;
if(sum_count >= 200) //I want 200 detected pulses with the same frequency range in a raw to
{ //be detected before I give the mean.
Serial.print("Freq: "); //Print the values
Serial.println(freq_mean);
sum_count=0; //Reset the values
freq_mean = 0;
freq_sum = 0;
}
}//end of if pulse is measured
}//end of loop
//This is the pulse width interruption routine
//----------------------------------------------
ISR(PCINT0_vect){
//First we take the present_time value in micro seconds using the micros() function
present_time = micros();
///////////////////////////////////////Signal input
if(PINB & B00000001){ //We make an AND with the pin state register, We verify if pin 8 is HIGH???
if(last_PIN_state == 0){ //If the last state was 0, then we have a state change...
last_PIN_state = 1; //Store the current state into the last state for the next loop
Period = present_time - last_pulse_time_counter; //We make the time difference. Channel 1 is current_time - timer_1.
last_pulse_time_counter = present_time; //Set pulse_time_counter to current value.
pulse_is_measured = 1; //We indicate that one full width was measured
}
}
else if(last_PIN_state == 1){ //If pin 8 is LOW and the last state was HIGH then we have a state change
last_PIN_state = 0; //Store the current state into the last state for the next loop
}
}
Now I open the serial monitor, set the bud rate of 250000 and start whistling. I detect my whistle range and note that down since I'll use it in the next part.
Now let's control something. In the enxt part we detect if we are in the correct rande and turn on adn off an LED and buzzer.
2.2 LED and buzzer control
Using an if statement I detect the range. In this case between 800Hz and 1.8KHz. If the measured frequency is in that range I start a new counter called square pulse counter. You see, I don’t want just one square wave pulse to have the desired frequency because that will result in an error where any sound could trigger the circuit. I want a bunch of them one followed by the other. In this way I will make sure that the signal is the one that I want and not just a random noise. Any time that the measured frequency is not in the desired range I reset the pulse counter. In this case I need 60 pulses in a raw to have the frequency in the desired range. If I detect that then I trigger my circuit. Increasing this number will decrease the sensibility of the detecting circuit.

/*Whistling sound switch; author: ELECTRONOOBS
* Subscribe: http://www.youtube.com/c/ELECTRONOOBS
*
* Activate any Arduino loop just by whistling. The circuit is very easy.
* Check the entire tutorial here: http://www.electronoobs.com/eng_arduino_tut19.php
*
*
* Input pin is D8
* Relay output is D13
* PWM buzzer output is D3
*/
//Inputs/outputs
int LED = 13;
int buzzer = 3;
//We create a variable for the time width value of the input signal
unsigned long last_pulse_time_counter, present_time;
//We create 1 variables to store the previous value of the input signal (if LOW or HIGH)
byte last_PIN_state;
//To store the final width value we create this variables (this will be half of the period)
int Pulse_width;
//Variables
float Period = 0;
float Frequency_micro = 0;
float Frequency = 0;
int Pulse_amount = 0;
int pulse_is_measured = 0;
bool LED_Status = false;
void setup() {
/*
* Port registers allow for lower-level and faster manipulation of the i/o pins of the microcontroller on an Arduino board.
* The chips used on the Arduino board (the ATmega8 and ATmega168) have three ports:
-B (digital pin 8 to 13)
-C (analog input pins)
-D (digital pins 0 to 7)
//All Arduino (Atmega) digital pins are inputs when you begin...
*/
PCICR |= (1 << PCIE0); //enable PCMSK0 scan
PCMSK0 |= (1 << PCINT0); //Set pin "D8" trigger an interrupt on "any" state change.
pinMode(LED,OUTPUT);
digitalWrite(LED,LOW);
pinMode(buzzer,OUTPUT);
digitalWrite(buzzer,LOW);
//Start the serial in order to see the result on the monitor
//Remember to select the same baud rate on the serial monitor
Serial.begin(250000);
}
void loop() {
/* Ok, so in the loop the only thing that we do in this example is to print
* the received values on the Serial monitor. The PWM values are read in the ISR below.
*/
if(pulse_is_measured) //Only when a full pulse is measured we enter the loop
{
pulse_is_measured = 0; //We reset "the pulse is detected" for the next loop
Period = Pulse_width*2; //Multiply by 2 the width and obtain the signal period
Frequency_micro = 1/Period; //Invert the period and get the frequency (in Hertz by us)
Frequency = Frequency_micro*1000000; //Multiply by one million and get frequency (in Hertz by seconds)
if(Frequency > 800 && Frequency < 1800) //Detect the frequency range. Change the values for your range
{
//Serial.print("Freq: "); //Only uncomment this for tests. This will add extra delay to the loop
//Serial.print(Frequency);
//Serial.println(" ");
if(Pulse_amount >= 60) //I want 60 detected pulses with the same frequency range in a raw to
{ //be detected before I activate the output.
LED_Status=!LED_Status; //We invert the relay output (turn on or off)
digitalWrite(LED,LED_Status); //We write the relay status to the pin.
analogWrite(buzzer,230);
delay(200);
analogWrite(buzzer,LOW);
delay(200);
analogWrite(buzzer,230);
delay(200);
analogWrite(buzzer,LOW);
delay(200);
analogWrite(buzzer,230);
delay(200);
analogWrite(buzzer,LOW);
delay(1000); //Add a small delay so we won't detect two whistles one after another
Pulse_amount=0; //Reset the detected pulse amount
}
Pulse_amount = Pulse_amount+1; //Increaase the pulse amount by one in each loop only if the range is correct
}//end of frequency range detect
else
{
Pulse_amount=0; //If the range is not correct, we have an error so we start from the beginning. Reset the amount value.
}
}//end of if pulse is measured
}//end of loop
//This is the pulse width interruption routine
//----------------------------------------------
ISR(PCINT0_vect){
//First we take the present_time value in micro seconds using the micros() function
present_time = micros();
///////////////////////////////////////Signal input
if(PINB & B00000001){ //We make an AND with the pin state register, We verify if pin 8 is HIGH???
if(last_PIN_state == 0){ //If the last state was 0, then we have a state change...
last_PIN_state = 1; //Store the current state into the last state for the next loop
last_pulse_time_counter = present_time; //Set pulse_time_counter to current value.
}
}
else if(last_PIN_state == 1){ //If pin 8 is LOW and the last state was HIGH then we have a state change
last_PIN_state = 0; //Store the current state into the last state for the next loop
Pulse_width = present_time - last_pulse_time_counter; //We make the time difference. Channel 1 is current_time - timer_1.
pulse_is_measured = 1; //We indicate that one full width was measured
}
}
Download the code above and use it with this schematic. Once the whistle is detected I trigger the digital pin 13 which will be connected to an LED and also apply an analog signal to pin 3 connected to a buzzer. I upload the code and make all the connections and let’s give a test to this project.

There you go, I can turn on and off the LED just by whistling. The circuit is immune to random noise. I can play MUSIC, NOISE, CLAP, etc.
Only the whistle sound will trigger this circuit and that’s because the whistle sound is special. It creates a sine wave with a constant frequency, equal time on both negative and positive parts of the signal and also a defined range. So, finding random noise with the same characteristics is quite difficult, not impossible but difficult.
Now instead of an LED, I connect a relay to digital pin 13. To the relay I connect a 220V lamp. Check ehe next example for that.
2.3 Relay switch
Now instead of an LED, I connect a relay to digital pin 13. To the relay I connect a 220V lamp. Be very careful when working with high voltages and only connect the power when you’re sure that the circuit is ready or restrict your projects to lower power components as 5 or 12V. Now any time I whistle I turn On and Off the light of my room. Isn’t that nice? The detector works from any corner of my room. I was amazed finding that it detects so well, even when I’m out of the room.

/*Whistling sound switch; author: ELECTRONOOBS
* Subscribe: http://www.youtube.com/c/ELECTRONOOBS
*
* Activate any Arduino loop just by whistling. The circuit is very easy.
* Check the entire tutorial here: http://www.electronoobs.com/eng_arduino_tut19.php
*
*
* Input pin is D8
* Relay output is D13
* PWM buzzer output is D3
*/
//Inputs/outputs
int Relay = 13;
//We create a variable for the time width value of the input signal
unsigned long last_pulse_time_counter, present_time;
//We create 1 variables to store the previous value of the input signal (if LOW or HIGH)
byte last_PIN_state;
//To store the final width value we create this variables (this will be half of the period)
int Pulse_width;
//Variables
float Period = 0;
float Frequency_micro = 0;
float Frequency = 0;
int Pulse_amount = 0;
int pulse_is_measured = 0;
bool Relay_status = false;
void setup() {
/*
* Port registers allow for lower-level and faster manipulation of the i/o pins of the microcontroller on an Arduino board.
* The chips used on the Arduino board (the ATmega8 and ATmega168) have three ports:
-B (digital pin 8 to 13)
-C (analog input pins)
-D (digital pins 0 to 7)
//All Arduino (Atmega) digital pins are inputs when you begin...
*/
PCICR |= (1 << PCIE0); //enable PCMSK0 scan
PCMSK0 |= (1 << PCINT0); //Set pin "D8" trigger an interrupt on "any" state change.
pinMode(Relay,OUTPUT);
digitalWrite(Relay,LOW);
//Start the serial in order to see the result on the monitor
//Remember to select the same baud rate on the serial monitor
Serial.begin(250000);
}
void loop() {
/* Ok, so in the loop the only thing that we do in this example is to print
* the received values on the Serial monitor. The PWM values are read in the ISR below.
*/
if(pulse_is_measured) //Only when a full pulse is measured we enter the loop
{
pulse_is_measured = 0; //We reset "the pulse is detected" for the next loop
Period = Pulse_width*2; //Multiply by 2 the width and obtain the signal period
Frequency_micro = 1/Period; //Invert the period and get the frequency (in Hertz by us)
Frequency = Frequency_micro*1000000; //Multiply by one million and get frequency (in Hertz by seconds)
if(Frequency > 800 && Frequency < 1800) //Detect the frequency range. Change the values for your range
{
//Serial.print("Freq: "); //Only uncomment this for tests. This will add extra delay to the loop
//Serial.print(Frequency);
//Serial.println(" ");
if(Pulse_amount >= 60) //I want 60 detected pulses with the same frequency range in a raw to
{ //be detected before I activate the output.
Relay_status=!Relay_status; //We invert the relay output (turn on or off)
digitalWrite(Relay,Relay_status); //We write the relay status to the pin.
delay(1000); //Add a small delay so we won't detect two whistles one after another
Pulse_amount=0; //Reset the detected pulse amount
}
Pulse_amount = Pulse_amount+1; //Increaase the pulse amount by one in each loop only if the range is correct
}//end of frequency range detect
else
{
Pulse_amount=0; //If the range is not correct, we have an error so we start from the beginning. Reset the amount value.
}
}//end of if pulse is measured
}//end of loop
//This is the pulse width interruption routine
//----------------------------------------------
ISR(PCINT0_vect){
//First we take the present_time value in micro seconds using the micros() function
present_time = micros();
///////////////////////////////////////Signal input
if(PINB & B00000001){ //We make an AND with the pin state register, We verify if pin 8 is HIGH???
if(last_PIN_state == 0){ //If the last state was 0, then we have a state change...
last_PIN_state = 1; //Store the current state into the last state for the next loop
last_pulse_time_counter = present_time; //Set pulse_time_counter to current value.
}
}
else if(last_PIN_state == 1){ //If pin 8 is LOW and the last state was HIGH then we have a state change
last_PIN_state = 0; //Store the current state into the last state for the next loop
Pulse_width = present_time - last_pulse_time_counter; //We make the time difference. Channel 1 is current_time - timer_1.
pulse_is_measured = 1; //We indicate that one full width was measured
}
}
Download the code above and use it with this schematic. Once the whistle is detected, the Arduino will trigger digital pin 13 which will be connected to the relay. I upload the code and make all the connections and let’s give a test to this example.

There you go, I can turn on and off the relay just by whistling. The circuit is immune to random noise. I can play MUSIC, NOISE, CLAP, etc.
You could turn on and off lights, maybe an LED strip , the music player, or make the Arduino make any other operation that you want as for example count the time that you whistle and display them on an LCD. That’s not useful but is cool because you made it.
Now instead of an relay, I connect a MOSFET and LED strip to digital pin 13. Check ehe next example for that.
Leave a comment
Please login in order to comment.