Comparing Crystals using Int0 and Int1


When you get yourself a new Arduino how can you check its crystal freqency?
Well, if you got the PDIP version and you are very skilled you might grab the quartz signal at pin 9 or 10 of the chip:

and you will see a signal like this on an analogue scope:

These are the results of a sample of Arduinos I got:

And I must admit my frequency counter is not new so all the values could be off by some 100 Hz.

With the SMD version it will be hard to get access to CPU pins 7 or 8 (32 TQFP or 32 MLF) or pins 5 or 6 (28 MLF). So you better divide the clock frequency by two in order to pick the signal at one of the header pins. These few lines of code do the job:

const byte outpin  = 11; 

void setup() {
  // generate 8 MHz signal at outpin:
  TCCR2A = B01000010;
  TCCR2B = B00000001; // Prescaler: 1
  pinMode(outpin, OUTPUT);
}

void loop() {}

and you get the 8.000.000 Hz signal at pin-11 of your Arduino.

Recently I wanted to compare the clock frequencies of two Arduinos, and I thought to let them send their pin-11-outputs to the interrupt inputs of a third Arduino (Interrupt pins 2 and 3). It could be done easily like this:

The upper and the lower ones are those to be compared. Their pin-11-outputs go to the interrupt input pins of the middle one.

The output of the lower one goes to pin-2 invoking isr0 which increments the value of count.
The output of the upper one goes to pin-3 invoking isr1 which decrements the value of count.

If their frequencies are the same the value of count will remain to zero. Otherwise count shows the difference of the frequencies. This little program will compare the pulse frequencies of two souces:

long count = 0;
long dt = 10000;
long t = millis() + dt;
/* without this semaphor you will get 
 * erroneous results occasionally
 */
boolean f = false;

void setup() {
  attachInterrupt(0, isr0, RISING);
  attachInterrupt(1, isr1, RISING);
  f = true;
}

void loop() {
  if (millis() < t) return;
  f = false;
  t = t + dt;
  Serial.println(count);
  count = 0;
  f = true;
}

void isr0() { if (f) count++; }

void isr0() { if (f) count--; }

At the end of the day, you might get results like this:

Unfortunately, it only works up to input frequencies of some 50 kHz. At higher frequencies the ISR won't be finished before the next pulse arrives. Why is it so? The reason is: the compiler would not know what you are doing inside the ISR, and so it generates PUSHs and POPs for nearly everything causing an overhead of about 5 μs. So you have to change the prescaler to 256. That will take a long time to get some results.

If you decide to code the ISR yourself and force the compiler to hold the count variable in certain registers no PUSHs and POPs are necessary. Doing this the program accepts frequencies up to 125 kHz.
So, instead of calling attachInterrupt you have to use a specific parameter for the ISR

ISR(INT0_vect, ISR_NAKED) 
Using NAKED no PUSHs, POPs or RETURNS will be generated.
The declaration
register byte b1 asm("r4");
gives you fast access to that byte, eliminates the need for PUSH/POP and it won't be overwritten by other commands.

This version locates two variables in fixed MCU registers which is not supported by IDE versions later than 1.6.6.

The complete source is here:

/* see also:
   http://www.phanderson.com/arduino/ext_int.html
   Interrupt-Pins ATmega328: Pin-2 and Pin-3
   works up to 125kHz
*/

const byte tcnt2 = 131;
register byte sreg asm("r2");   // temporary storage for SREG
register byte eins asm("r17");  // constant as there is no ADDI instruction
register byte b0 asm("r16");    // SUBI, requires register number above 15
register byte b1 asm("r4");
register byte b2 asm("r5");
register byte b3 asm("r6");
volatile long count;
boolean tunix = true;

void setup() {
  Serial.begin(9600);
  Serial.println(__FILE__);
  // setup Timer2:
  TCCR2B = 0;        // Disable Timer2 while we set it up
  TCNT2  = tcnt2;    // Reset Timer Count to tcnt2 out of 255
  TIFR2  = 0;        // Timer2 INT Flag Reg: Clear Timer Overflow Flag
  TIMSK2 = 1;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  TCCR2A = 0;        // Timer2 Control Reg A: Wave Gen Mode normal
  TCCR2B = 5;        // Timer2 Control Reg B: Timer Prescaler set to 128
  // setup Int0, Int1:
  EICRA = B00001010; // configure both for falling
  EIMSK = B00000011; // mask for INT1 and INT0
}

void loop() {
  if (tunix) return;
  Serial.println(count);
  b0 = 0;
  b1 = 0; 
  b2 = 0;
  b3 = 0;
  EIMSK = B00000011; // reenable INT1 and INT0
  tunix = true;
}

//Timer2 Overflow Interrupt Vector, called every 1ms
ISR(TIMER2_OVF_vect) {
  static int msCnt;
  if (++msCnt > 999) {
    EIMSK = 0; // disable INT1 and INT0
    ((byte*)(&count))[0] = b0;
    ((byte*)(&count))[1] = b1;
    ((byte*)(&count))[2] = b2;
    ((byte*)(&count))[3] = b3;
    tunix = false;
    msCnt = 0;           // Resets the interrupt counter
  }
  TCNT2 = tcnt2;     // Reset Timer to tcnt2 out of 255
  TIFR2 = 0;         // Timer2 INT Flag Reg: Clear Timer Overflow Flag
};

ISR(INT0_vect, ISR_NAKED) { // count++;
  asm("in   %4,__SREG__" "\n\t"   // save flags
      "ldi  %5,1"  "\n\t"
      "add  %0,%5" "\n\t"
      "adc  %1,r1" "\n\t"
      "adc  %2,r1" "\n\t"
      "adc  %3,r1" "\n\t"
      "out __SREG__,%4" "\n\t"  // restore flags
      "reti"
      :
      "=r" (b0),
      "=r" (b1),
      "=r" (b2),
      "=r" (b3),
      "=r" (sreg),
      "=r" (eins)
      : : );
}

ISR(INT1_vect, ISR_NAKED) {
  asm("in   %4,__SREG__" "\n\t"   // save flags
      "subi %0,1"  "\n\t"
      "sbc  %1,r1" "\n\t"
      "sbc  %2,r1" "\n\t"
      "sbc  %3,r1" "\n\t"
      "out __SREG__,%4" "\n\t"  // restore flags
      "reti"
      :
      "=r" (b0),
      "=r" (b1),
      "=r" (b2),
      "=r" (b3),
      "=r" (sreg)
      //"=r" (save)
      : : );
}

With this program you get the difference faster and with higher precision:

As can be seen the crystals are quite stable, and there is a constant difference of 5 Hz. Due to the prescaler the crystals differ by 640 Hz that is 40 ppm.


contact: nji(at)gmx.de