Counter (having trouble with count rates about 148 kHz...)

Hi all,

I have made a simple counter program for the uno and am having trouble with rates above ~148kHz or so... This could be a hardware limitation or something in my code that is bad... Ideally I would be happy counting up to 1 million pulses per second. -Thanks!

counter.ino (1.88 KB)

You can put a prescaler in front to get the count from 1 MHz to 100 kHz, safely within your range. Almost any msi divide x 10 will work, like a 74LS90.

There is no reason to be attaching and detaching the interrupt handlers.

There is NO excuse for using goto.

You should be counting interrupts all the time. Periodically, you want to output a value and reset the counter.

The correct way to do what you want is to have the ISR increment the counter continuously.

Use millis() to manage timing as illustrated in Several Things at a Time. Your approach of if(millis() >= stop_time) will not work when the rollover arises.

When each interval has elapsed then your code in loop() needs to capture the latest value in the counter - something like this

prevCount = latestCount;
noInterrupts();
latestCount = counts;
interrupts()
countsThisInterval = latestCount - prevCount;

...R

So I modified my code. Now I'm having more problems. The reported counts are about 15% higher than they should be (using function generator as source and digital scope to measure freq). That is very weird. In my first code I agree than detaching the interrupts wasn't ideal, but I didn't want the button interrupt to be doing stuff/wasting time while counting...

@Robin2, I looked at your SeveralThingsAtTheSameTime code and do not understand why that is different/better than my stop_time solution. As I see it, your code would also break at the rollover of the millis() output (which takes about 49.7 days). Am I missing something here?

Anyway, it doesn't look like I can measure count rates from 1 Hz to 1 MHz using the uno (I guess due to hardware reasons). Maybe I'll dust off my Due and try it out.

Thanks!

// A simple counter program to count positive pulses which are >12? ns wide and >=2.75 volts amplitude
// doesn't seem to work at count rates above 148 kHz or so???

volatile unsigned long counts = 0; //goes from 0 to 4,294,967,295
unsigned long final_counts = 0;

const int dwell = 1000; //dwell in ms for couter, can be up to 32,767, or about 30 seconds
volatile unsigned long start_time;
volatile unsigned long stop_time;
volatile boolean button = false;
int measured_time;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
 
  pinMode(2,INPUT); // connect counter or function generator (postive pulses)
  pinMode(3,INPUT_PULLUP); // connect momentary pushbutton switch to ground
  attachInterrupt(digitalPinToInterrupt(2), detect_count, RISING); // counts to pin 2, UNO can do LOW, CHANGE, RISING, FALLING
  attachInterrupt(digitalPinToInterrupt(3), detect_button, LOW); // button to pin 3, UNO can do LOW, CHANGE, RISING, FALLING

  Serial.print("Dwell (ms)");
  Serial.print("\t");
  Serial.println("Counts"); 
}

void detect_button(){button = true;}
void detect_count(){counts++;}

void loop() {
 
  if(button == true){
    
    start_time = millis(); // get time since arduino started in ms, can go for 49.7 days before rolling over
    stop_time = start_time + dwell;
    noInterrupts();
    counts = 0;
    interrupts();
    
    loopback:
    if(millis() >= stop_time){
      
      noInterrupts();
      final_counts = counts;
      interrupts();
      
      measured_time = millis() - start_time;
      
      Serial.print(measured_time); // report resulting counts 
      Serial.print("\t\t");
      Serial.println(final_counts);
    }
    else{  
      goto loopback;
    }
    button = false;
 
  }
 
}
  attachInterrupt(digitalPinToInterrupt(3), detect_button, LOW); // button to pin 3, UNO can do LOW, CHANGE, RISING, FALLING

While you CAN use LOW, does that really make sense?

Why do you need an interrupt to read a switch pressed by a ridiculously slow human?

      goto loopback;

Still the crappy goto.

@PaulS: You are right about not needing an interrupt to detect the button press. Fixed that, and it fixed my count accuracy problem!

The goto for me is a sweet sweet crutch, I am too dumb to do anything different currently. It seems to be a logical and elegant solution here, but I do agree that in bigger more complicated codes using multiple gotos would be confusing.

counter_v3.ino (1.63 KB)

jamabot:
As I see it, your code would also break at the rollover of the millis() output (which takes about 49.7 days). Am I missing something here?

YES

Due to the peculiarities of integer maths if (millis() - prevMillis >= interval) will give the correct result even when the roll-over happens.

You can try it with pencil and paper with an 8-bit byte to see how it happens.

...R

    loopback:
    if(millis() >= stop_time){
      
      noInterrupts();
      final_counts = counts;
      interrupts();
      
      measured_time = millis() - start_time;
      
      Serial.print(measured_time); // report resulting counts 
      Serial.print("\t\t");
      Serial.println(final_counts);
    }
    else{  
      goto loopback;
    }

I make it this:

    while (millis() < stop_time) {
      // do nothing!
    }
    
    // Then, once we're finished doing nothing, we do what comes next.
    noInterrupts();
    final_counts = counts;
    interrupts();
    
    measured_time = millis() - start_time;
    
    Serial.print(measured_time); // report resulting counts 
    Serial.print("\t\t");
    Serial.println(final_counts);

(no goto needed)

Although I would also add Robin2's suggestion from reply #7.

Ok, thanks a lot for the help guys!

I thought I would put my finalized code up. Also, I tested it on the DUE. So with the UNO, max count rate is ~140 kHz. On the DUE, it is ~240 kHz. Not as big of a bump as I was expecting.

counter.ino (2.53 KB)

You can use one of the timers as a counter.

Here is some modification of your code using Timer1 as a counter driven from an external source. The correct way to do it is shown by Nick Gammon here in the first example sketch Gammon Forum : Electronics : Microprocessors : Timers and counters

I have given a simplified version which does not count TCNT1 overflows and is limited to the 16 bit value. It also uses micros() instead of a timer for the dwell which would be more accurate, and it does not disable the timer0 millis() interrupts like Nick Gammon's code does.

//frequency counter using Timer1 counter without overflow count
//TCNT1 16 bit max value = 65,534
//20ms sample period gives frequency counter to a bit over 3.2 mhz

unsigned int dwell = 20000; // dwell in microseconds for counter
unsigned long final_counts;
unsigned long start_time;
unsigned long measured_time;

void setup()
{
  Serial.begin(115200);
  TCCR1A = 0; //initialize Timer1
  TCCR1B = 0;
  TCNT1 = 0;

  pinMode( 5, INPUT_PULLUP); //external source pin for timer1
}

void loop()
{
    start_time = micros();
    TCNT1 = 0;//initialize counter
    // External clock source on Timer1, pin (D5). Clock on rising edge.
    // Setting bits starts timer
    TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12); //external clock source pin D5 rising edge

    while (micros() - start_time < dwell) {} // do nothing but wait and count during dwell time

    TCCR1B = 0; //stop counter
    final_counts = TCNT1; //frequency limited by unsigned int TCNT1 without rollover counts

    measured_time = micros() - start_time;

    Serial.print(measured_time); // report resulting counts
    Serial.print("\t\t");
    Serial.println(50 * final_counts); //20ms sample in Hz
  }

I don't have a signal generator, but I tested it to 2.667 MHz with the following test code which was shown to toggle a pin in 6 clock cycles. Fastest output possible - #12 by nickgammon - Programming Questions - Arduino Forum ran it on another 16Mhz Arduino and jumpered pin 3 to pin 5.

void setup()
{
  pinMode(3, OUTPUT);
  while(1)
   PIND = 1 << 3;
}
void loop(){}

Ok thanks DrDiettrich and cattledog, I was wondering about using the timer. I'll check this out in the next few days. Have a good Turkey day everyone!