Measuring the average frequency of input square wave at high frequencies

I'm using TinkerCad and I want to measure the frequency of an input square wave using digital interrupts. Here's the code which I implemented for my Uno-

#include <avr/interrupt.h>
int inputpin=2,t=0;
volatile unsigned long int count = 0;
void setup()
{
  Serial.begin(9600);
  pinMode(inputpin,INPUT);
  attachInterrupt(0, pulse, RISING);
  interrupts();
}

void loop()
{
  delay(1000);
  t+=1;
  Serial.print(count/t);

}

  
void pulse()
{
  count++;
}

With this code, the serial monitor can count almost accurately till 15-20 kHz. But I need accurate results for frequencies upto 150 kHz, whereas my code can't render any output for 150 kHz. I believe I need to change my code to account for detaching interrupts and counting pulses for a certain period using the rising edge and re-attach interrupts again, but I'm a complete beginner and I can't really figure out how to deal with the interrupts in this case to get accurate results upto 150 kHz. I'm using the AFG feature in TinkerCad to generate my input square wave. Can someone help me figure out the code? It would be of great help.

You might have better luck measuring the repetition rate of the pulse train. Square wave contain an infinite number of frequencies.

It can't render any output? Does that mean you get nothing on the Serial Monitor? Or do you see a value but you don't think it's correct?
I would at least change the print() to a println(), otherwise the interpretation of the returned value is a bit tricky.

At 150kHz the MCU has about 100 clock cycles to handle the interrupt and keep some time to do the rest. The interrupt handling inside the MCU needs about 12 cycles per call (without executing any code), saving the most important registers needs also time (if all 32 registers and SREG are saved that sums up to another 33 cycles). The increment is probably about 8 cycles. So more than half of all processing power available is used at least to handle the very simple interrupt handler.

For such high frequencies I would use counter 1 and it's counter input (pin 5). That way the hardware count's the edges and all you have to do is reading the counter value after a specified time.

1 Like

I didn't use the println() since it queues up outputs and takes significant amount of time for arduino to respond to other tasks beyond the interrupts. It doesn't show anything on the Serial Monitor for 150 kHz, yes.

Also, isn't counter 1 related to the digital pin 2 on arduino uno? If I need to use pin 5, I would probably have to use timer interrupts in that case.

Where did this idea come from? All serial output operations queue up whatever you're printing to an output buffer; an interrupt automatically picks a byte out of that buffer whenever the serial hardware is ready to send a byte. Serial.println() just calls Serial.print() with whatever you're printing followed by Serial.print("\r\n"). There's no difference in queuing behavior.

This is something I had been told. Wasn't really sure about that, still followed.

I'm out of ideas right now. Does anyone have any solution to this? How do I go about changing the interrupts status and such in order to count frequencies in the range of 10^5 Hz?

This should go a lot faster, though I'm not sure it will hit 150kHz.
A bit faster if you reduce count to a uint16_t (but that would also require reducing the delay() value.
(this compiles, but isn't actually tested. Most of the speedup comes from avoiding the overhead required by the dynamic callback address in attachInterrupt.)

#include <avr/interrupt.h>

int inputpin=2,t=0;
volatile __uint24 count = 0;

ISR(INT0_vect) {
  count++;
}

void setup()
{
  Serial.begin(9600);
  pinMode(inputpin,INPUT);
  EICRA = ISC01|ISC00;  // Interrupt on rising edge of INT0
  EIMSK = INT0;         // enable INT0 interrupts
  interrupts();         // globaly enabled interrupts
}

void loop()
{
  delay(1000);
  t+=1;
  Serial.print((uint32_t)count/t);
}

Or:

ISR(INT0_vect, ISR_NAKED) {
  asm(" push r0 \n"
      " push r16 \n"        /* save working reg */
      " in r0, 0x3f \n"     /* save flags */
      " lds r16, count \n"  /* first byte */
      " inc r16 \n"         /* increment */
      " sts count, r16 \n"
      " brne 0f \n"         /* check overflow */
      "  lds r16, count+1 \n"
      "  inc r16 \n"        /* do carry */
      "  sts count+1, r16 \n"
      "  brne 0f \n"        /* check second byte overflow */
      "   lds r16, count+2 \n"
      "   inc r16 \n"       /* carry into 3rd byte */
      "   sts count+2, r16 \n"
      "0:"
      " out 0x3f, r0 \n"    /* restore flags */
      " pop r16 \n"         /* restore registers */
      " pop r0 \n"
      " reti\n");
}

By my count, the original attachInterrupt code takes over 100 cycles, the non-ASM ISR code takes about 42 cycles, and the asm code takes about 20.


You could count a lot faster by using T1 in externally-clocked mode (on PD5), but you would have to shorten your interval.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.