Measure audio frequency through pulse duration Hello dear StackExchange users,
i want to develop an Infrared blaster which i can plug into the headphone jack of a phone or console of choice. In order to communicate what frequency to modulate the diode with and how long to send pulses, i simply generate tones for the Arduino to read and interpret.
My problem is that i can't reliably measure the frequency with it. In order to measure the frequency i use an Analog Comparator Interrupt and trigger at falling edge, thus getting always the interrupt after a full pulse occured. I called micros() at every interrupt in order to get the pulse duration, but it was too imprecise. Then i tried using Timer0 with a prescaler of 8 to measure the frequency, but it only counts to 255.
Now i switched over to Timer1 because of it's bigger TCNT register. However for some reason it already overflows at 255 even though i didn't set the OCR1 registers.
This is my current code. Please note that the actual frequency calculation happens in the main loop to not mess with the measurements gained in the ISR.
const int setFrequencySound = 1000;
const int receiveDataSound = 2000;
const int receiveDataEndSound = 3000;

const int readyMode = 0;
const int setFrequencyMode = 1;
const int receiveDataMode = 2;
const int sendDataMode = 3;

volatile unsigned long timeBefore;
volatile unsigned long timeNow;
volatile unsigned long delta;
volatile float audioFrequencyBefore;
volatile float audioFrequency;

volatile float irFrequency;
volatile int irData[100];
volatile int irDataIndex;

volatile int mode = 0;

ISR (ANALOG_COMP_vect)
{
timeNow = TCNT1;
Serial.print (timeNow);
Serial.println (&quot; TCNT1&quot;);
//TCNT1 = 0; //reset timer register
}

void setup ()
{
Serial.begin (115200);
Serial.println (&quot;Started.&quot;);
ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
      | bit (ACIE)    // Analog Comparator Interrupt Enable
      | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)

TCCR1B |= (0 &lt;&lt; CS12) | (1 &lt;&lt; CS11) | (0 &gt;&gt; CS10); //Activate timer and set prescaler to 8
TCNT1 = 0; //Reset timer register
}  // end of setup

void loop ()
{
//Calculate frequency by using the period duration
//determined through the timestamps captured in
//ISR method.
//delta = timeNow - timeBefore;
audioFrequency = 1000000000.0 / (float(timeNow) * 2.0);

if (millis() % 1000 == 0)
{
  Serial.print (audioFrequency, 5);
  Serial.print (&quot; Hz, &quot;);
  Serial.print (timeNow);
  Serial.println (&quot; TCNT0&quot;);
}

//Do not process the same frequency multiple times
if (audioFrequency != audioFrequencyBefore)
{
  switch (mode)
  {
    case readyMode:
      if (audioFrequency == setFrequencySound)
      {
        mode = 1;

        Serial.println (&quot;Start setting IR frequency.&quot;);
      }
      else if (audioFrequency == receiveDataSound)
      {
        mode = 2;

        Serial.println (&quot;Start receiving IR data.&quot;);
      }
      break;
    case setFrequencyMode:
      irFrequency = audioFrequency + 25000;

      Serial.print (&quot;IR frequency set to &quot;);
      Serial.print (irFrequency, 10);
      Serial.println (&quot; Hz.&quot;);
      
      mode = readyMode;
      break;
    case receiveDataMode:
      if (audioFrequency == receiveDataEndSound)
      {
        Serial.println (&quot;Stop receiving IR data.&quot;);
        
        mode = readyMode;
        irDataIndex = 0;
      }
      else
      {
        irData[irDataIndex] = audioFrequency;

        irDataIndex++;

        Serial.print (&quot;IR pulse received: &quot;);
        Serial.print (irData[irDataIndex]);
        Serial.println (&quot; microsecs.&quot;);
      }
      break;
    case sendDataMode:
      Serial.println (&quot;Sending IR data...&quot;);
      break;
  }
}

audioFrequencyBefore = audioFrequency;

}  // end of loop
Timer 1 overflows at 255 because that's how it has been configured by
the Arduino core, as it is intended to provide 8-bit PWM. If you want
to use the timer in normal mode, you should undo the Arduino's
initialization by setting TCCR1A to zero.
Two comments on the ISR:
ISR (ANALOG_COMP_vect)
  {
    timeNow = TCNT1;
    Serial.print (timeNow);
    Serial.println (&quot; TCNT1&quot;);
    //TCNT1 = 0; //reset timer register
  }

First, as has been stated in a previous comment, you should not print
from within an ISR. Also, if you want consistent timing, you should
never reset the timer. Just keep it freely running and use time
differences to get the signal period. Do not worry about the timer
overflowing: if you do the computation with unsigned 16-bit integers
(uint16_t), you will be immune to the overflows. If you reset the
timer you will likely loose some ticks, as you can not read it and reset
it in the same clock cycle.
Lastly, I recommend you read about the “input capture” capability of
Timer 1. This is a feature of the timer designed for solving the exact
kind of problem you have here: the timer will automatically record a
timestamp on an external event. You can have this triggered by a rising
or falling edge on input pin 8, or by the analog comparator if you
prefer. This method gives you zero jitter, which cannot be achieved with
code, even running within an interrupt.

Edit: Answering an extra question in the comment.

Could i connect the audio wire [...] directly to pin 8 and use the
input capture functionality?

It depends on the shape of the signal that comes out of this wire.
First, you have to make sure the signal always stays between the
electric potentials of GND and Vcc (i.e. 0 – 5 V), otherwise you risk
damaging the Arduino.
Note that if the signal comes from a high impedance source, it can
safely go somewhat beyond this range, as the Arduino has all of its
inputs protected by diodes. You just have to limit the current in the
diodes to less than 1 mA. In other words, for each kΩ of output
impedance, you can extend the (unloaded) acceptable voltage range by 1 V
on each side. If the source is low impedance, you can make it high
impedance simply by adding a 10 kΩ resistor in series.
Second point is, you have to make sure the signal has a large enough
voltage span to make the digital input switch between LOW and HIGH.
According to the datasheet of the ATmega328P, when powered at 5 V the
input thresholds are typically around 2.1 V for switching LOW and 2.6 V
for switching HIGH. The difference between those thresholds provides
some hysteresis, which can help cancelling noise. Note that those are
typical values. If you want guaranteed switching, the signal should go
below 1.5 V to get a guaranteed LOW reading (this is VIL) and
above 3 V to get a guaranteed HIGH reading (VIH).
Note also that, if the input thresholds of the digital pin do not fit
your needs, you can use the analog comparator as a trigger for the input
capture.