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 (&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

1 Answers1

5

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.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81