Small and fast audio tone detection by autocorrelation

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:
line_in.png

1 Like

The ham stuff that I've dealt with just uses zero cross detection.