3

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 (" TCNT1");
//TCNT1 = 0; //reset timer register
}

void setup ()
{
Serial.begin (115200);
Serial.println ("Started.");
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 << CS12) | (1 << CS11) | (0 >> 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 (" Hz, ");
  Serial.print (timeNow);
  Serial.println (" TCNT0");
}

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

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

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

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

        irDataIndex++;

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

audioFrequencyBefore = audioFrequency;

}  // end of loop
  • Please show a full compilable code (including the loop function). Serial printing in an ISR is not a good thing. You can very fast fill up the serial buffer that way. What frequency are you trying to measure? Since the signals on a standard headphone jack are often differential (for example in the range of +-1.2V) how are you processing the signal before feeding it into the Arduino? – chrisl Oct 20 at 22:32
  • Of course serial printing in the ISR isn't a good idea, but i gotta get my info somehow. When that issue is fixed, i'll remove this print command. The frequency i'm trying to measure is the audio frequency of a signal that i generate. No song, microphone input or anything, just a simple square tone i generate. Well, multiple tones. – MyFairJulie Oct 20 at 22:50
6

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 (" TCNT1");
    //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.

| improve this answer | |
  • Interesting, so the timer-1 tip would work on all 328p based Arduinos right? +1 – Tim_Stewart Oct 21 at 0:58
  • 1
    @Tim_Stewart: Indeed. It's a feature of the ATmega328p (and many other ATmegas), not of the Arduino board that hosts it. – Edgar Bonet Oct 21 at 8:52
  • @EdgarBonet Could i connect the audio wire (i have a jumper cable hooked up to the left channel of a headphobne plug) directly to pin 8 and use the input capture functionality? – MyFairJulie Oct 21 at 10:06
  • @MyFairJulie: See amended answer. – Edgar Bonet Oct 21 at 11:35
  • @EdgarBonet In fact i already use an analog comparator as seen here. Can just set up the input capture interrupt? Or do i still need the analog comparator interrupt? – MyFairJulie Oct 21 at 12:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.