Frequency Counter with DutyCycle Measurement

Hi,

Worked this weekend on a frequency counter that also reports the duty cycle. Several variations passed and the current proto is working quite well.
The sketch measures the frequency from 5Hz to 30KHz (~1%) and the duty cycle quite well from 5Hz to 1000Hz.
Above 1000 Hz effects come in that reduces the working range of the duty cycle measurement. I do not understand the cause here yet.

Todo:

  • compare the sketch with a logic analyser.
  • investigate the cause of the drop in working range at higher frequencies.
  • wrap it in a box :wink:

As frequency generator I used an Arduino with HW timer for medium frequencies (500 - 30KHz) and an Arduino using (micro)delays for low frequencies (5 - 2000Hz)

As always remarks are welcome

//
//    FILE: freqCounter06.ino
//  AUTHOR: Rob Tillaart
//    DATE: 2013-04-13
//
// PURPOSE: freq counter
//
// tested working range
//
// FREQ   MIN DC   MAX DC
// -----------------------
// 5       1%      98%
// 10      1%      98%
// 50      1%      98%
// 100     1%      98%
// 250     1%      98%
// 500     1%      98%   
// 1000    1%      98%
// 2500    3%      96%
//  5K     6%      94%
// 10K    12%      88%
// 15K    17%      82%
// 20K    23%      77%
// 25K    29%      70%
// 30K    35%      65%
//

#define DISPLAY_INTERVAL   1000000L
#define IRQPIN             2

volatile unsigned int count = 0;
volatile unsigned int ovf = 0;

unsigned long frequency = 0;
unsigned long highCount = 0; 
unsigned long totalCount = 0;
float dutyCycle = 0;
unsigned long lastDisplay = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("* Frequency Counter 0.6 *");
  Serial.println(" ");
  Serial.println("Connect pulse TTL to Arduino pin 2");
  Serial.println("Connect pulse GND to Arduino GND");

  pinMode(IRQPIN, INPUT);
  attachInterrupt(0, pulse, CHANGE);

  TIMSK1 = (1 << TOIE1); // timer overflow interrupt enabled
  TCCR1A = 0x00; // 
  TCCR1B = 1; // prescaler == 1 => timer runs at clock speed: 16 MHz
}

void loop()
{
  uint32_t now = micros();
  if (now - lastDisplay > DISPLAY_INTERVAL)
  { 
    lastDisplay = now;

    cli();
    {
      frequency = count;
      count = 0;
      totalCount = highCount;
      highCount = 0;
    }
    sei();

    frequency /= 2;
    dutyCycle = totalCount/160000.0;

    Serial.print(frequency);
    Serial.print(" Hz. - ");
    Serial.print(dutyCycle, 1);
    Serial.println(" %");
  }
}


ISR(TIMER1_OVF_vect) 
{
  ovf++;
}

void pulse()
{
  if (PIND & 0x04)
  {
    TCNT1 = 0x0000;
    ovf = 0;
  }
  else
  {
    highCount += TCNT1;
    highCount += ovf*65536L;
  }
  count++;
}

Hi,

'- investigate the cause of the drop in working range at higher frequencies.'
some ideas to investigate

  • serial can have latencies and buffer overruns plus it can 'conflict' with the ISR (--> missed signal flanks). That´s why people try to store data on SD for some interval, then stop measurement and transmit the collected data via serial (see Storage forum)
  • signal quality: check with an oscilloscope, if the signal flanks are clear or somehow floating
  • cross check, if the ISR is really triggered on each change; you may get doubled or missing ISRs (e.g. I some time crosschecked this by inverting a digital out pin every time the ISR was triggered and compared this on a second osci channel with the input square signal)
  • how long does it take your arduino function generator to switch high<->low ? Usually the flank will also take some ns to µs (there will be no no-time 'jump')

Best, Robert

Thanks, that are good points!

Did some math last night:

If I have a 30KHz signal the interrupt is called 60.000 times per second (rising/falling)
If the dutyCycle is 50% the timing is always 16.6 usec between rise and fall and rise etc
if the dutyCycle is 35% the timing is (approx) 11 usec (rise) and 22 (fall)

if I have a 10KHz signal the interrupt is called 10.000 times per second
if the dutycycle is 10% the timing is (approx) 10 usec (rise) and 90 (fall).

So my conclusion is that the current (ISR) code gets (reentry) problems when the smallest period of the PWM hits the 10-12 usec.

I see a partial solution in removing the overflow*65536L part ==> Time to tinker :wink:

Good calculation:-)
You could measure the code with a separate routine, where you run your code wrapped with toggling a digital out pin and see with a scope how long it takes to toggle