Some time ago I ran across an article describing a very fast autocorrelation procedure that could accurately detect a single tone, by sampling two cycles of the waveform. I dug up the article, describing HAM RTTY reception (written in machine code for a 6502 uP) and reproduced the work in C for Arduino.
It works pretty well, with about 100 to 200 Hz detection bandwidth for a 1 kHz tone (or any integer multiple of the target frequency). The input tone could be a 5V rectangular wave, if sampled by a digital (not analog) input, but my version uses the analog comparator inputs on an AVR-based Arduino, and works well for 50 mV to 5V peak to peak audio signals.
Note: because of the sample loop overhead, optimal detection frequency is slightly lower than the target frequency, about 980 Hz in these examples.
Here is the one byte version, with about 200 Hz bandwidth, sine wave input:
// fast audio tone detector, 8 bit autocorrelation function.
// This program detects the presence of an audio tone of
// the specified frequency f0 (defined below)
// output: lights LED with tone present within detection bandwidth
//
// for Arduino ATmega variants
// uses analog comparator module
// pin D6 = capacitively coupled audio input, biased to Vcc/2
// pin D7 = reference voltage, Vcc/2
//samples input for two full successive periods of the target tone frequency and
// compares the two samples for match.
//in theory, exact match will occur only if the sample frequency is an integer multiple
// of the input frequency.
#define LED 13
float f0 = 1000.0; //target frequency in Hz
unsigned int p0 = 1.0E6 / (8.0 * f0); //sample delay in microseconds
union i2b {
unsigned int i;
unsigned char b[2];
} val;
void setup ()
{
Serial.begin (9600);
Serial.print("Sample delay us ");
Serial.println(p0);
pinMode(LED, OUTPUT); //on board LED
digitalWrite(LED, 0);
ADCSRB = 0; // (Disable) ACME: Analog Comparator Multiplexer Enable
DIDR1 = 3; //disable digital inputs on D6/D7 (comparator AIN0/AIN1)
ACSR = (1 << ACI); //clear Analog Comparator interrupt flag, just in case
} // end of setup
void loop () {
static unsigned char i, result, sum;
sum = 0;
val.i = 0;
for (i = 0; i < 16; i++) { //sample the input
if (ACSR & (1 << ACO)) val.i |= 1; //if input is HIGH, set bit in sample
val.i <<= 1;
delayMicroseconds(p0);
} //loop i
if ( (val.b[0] == 0) || (val.b[0] == 0xFF)) return; //no signal
result = val.b[1] ^ val.b[0]; //XOR the two one-byte samples
if (result == 0 ) digitalWrite(LED, 1); //match
while (result) { //count set bits
if (result & 1) sum++;
result >>= 1;
}
if (sum < 2) digitalWrite(LED, 1); //allow 1 set bit
else digitalWrite(LED, 0);
} // end of loop
Here is the 16 bit version (about 100 Hz detection bandwidth for 1Vpp sine wave input):
// fast audio tone detector, 16 bit autocorrelation function.
// This program detects the presence of an audio tone of
// the specified frequency f0 (below)
// output: lights LED with tone present within detection bandwidth
//
// for Arduino ATmega variants
// uses analog comparator module
// pin D6 = capacitively coupled audio input, biased to Vcc/2
// pin D7 = reference voltage, Vcc/2
//samples input for two full successive periods of the target tone frequency and
// compares the two samples for match.
//in theory, exact match will occur only if the sample frequency is an integer multiple
// of the input frequency.
#define LED 13
float f0 = 1000.0; //target frequency in Hz
unsigned int p0 = 1.0E6 / (16.0 * f0); //sample delay in microseconds, 16 samples each of two full periods
void setup ()
{
Serial.begin (9600);
Serial.print("Sample delay us ");
Serial.println(p0);
delay(500);
pinMode(LED, OUTPUT); //on board LED
digitalWrite(LED, 0);
ADCSRB = 0; // (Disable) ACME: Analog Comparator Multiplexer Enable
DIDR1 = 3; //disable digital inputs on D6/D7 (comparator AIN0/AIN1)
ACSR = (1 << ACI); //clear Analog Comparator interrupt flag, just in case
} // end of setup
void loop () {
static unsigned char i, sum;
static unsigned int result;
static unsigned long sample;
sum = 0; //counts sample mismatch bits
sample = 0; //two word sample
for (i = 0; i < 32; i++) { //sample input signal
if (ACSR & (1 << ACO)) sample |= 1; //if input is HIGH, set bit in sample
sample <<= 1;
delayMicroseconds(p0);
} //loop i
result = sample & 0xFFFF;
if ( result == 0 || result == 0xFFFF) {
digitalWrite(LED, 0); //no signal
return;
}
result = ((unsigned int)(sample >> 16)) ^ result; //XOR the two samples
if (result == 0 ) digitalWrite(LED, 1); //match
while (result) { //count the mismatch bits
if (result & 1) sum++;
result >>= 1; //"while" reduces loop count on average
}
if (sum < 2) digitalWrite(LED, 1); //allow 1 bit mismatch
else digitalWrite(LED, 0);
} // end of loop
Input circuit for analog comparator:
