Best chip for frequency/pulse counting?

I am wanting to have a pulse counter that can read down to 10hz and up around 24khz. I would like it to be ISP, IC2, or with minimal wires to Uno. My input signals will be 0-5V. I would like it to operate with a 5V supply also. I plan to put it in a socket or through-hole not surface mount.

Thanks,
Mark

The solution with the minimum number of wires does not require any external chip. You can use the 16 bit hardware timer to count pulses. Arduino does not provide a simple interface to this but I have used the following code to count up to 64k pulses:

 // 16 bit timer defines added by mem to enable redifining the timer used

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#define TIFRn  TIFR5
#define TOVn   TOV5
#else
#define TCCRnA TCCR1A
#define TCCRnB TCCR1B
#define TCNTn  TCNT1
#define TIFRn  TIFR1
#define TOVn   TOV1
#endif

void startCount() {

	// hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;	    // reset timer/countern control register A
  TCCRnB=0;	    // reset timer/countern control register A
  TCNTn=0;	    // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168, pin 47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);	 // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11);
  sbi (TCCRnB ,CS12);
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now
}

unsigned int getCount(){

  unsigned int count;
   TCCRnB = TCCRnB & ~7;   			// Gate Off  / Counter Tn stopped
   count = TCNTn;
   TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now   
}
void setup(){
}

void loop(){
}

Connect the pulse input to Uno pin 5 (make sure the peak is not greater than 5 volts).

startCount() resets the timer and starts

Note that the 16 bit timer is used by the Servo library and for PWM on pins 9 and 10 so this will only work if you don't need those capabilities

Thank you very much for your code!

Are the counted pulses stored in int getCount? Also how much time is a sample of the pulses, for instance does it sample for .5 second, or .25 seconds?

So, if I wanted to watch this on my monitor would I use this code?

 Serial.print(getcount);
  startCount(); //reset counter for next sample

If your code is based off of some other examples that you can mention, I will look at them too so, that I can understand it in more detail.

Thanks for the help,
Mark

the sketch is based on standard code to access the 16 bit counter inside the Arduino chip. The technical details are in the datasheet section covering the 16 bit counter unit (section 16.5 in the latest version of the ATmega328p datasheet)

The count is stored in the TCNT1 register in the Uno chip. this line access this value:
count = TCNTn;

Note that when compiled for the Uno the compiler will substitute TCNT1 for TCNTn. It looks a little complicated because the same code can be used on bot a Uno and a Mega even though these chips use different registers for counting.

This is just a counter, your sketch would control the length of the sample time by adjusting the duration between calling startCount and getCount. How were you intending to control the count duration if you used an external counter?

the library linked below is probably easier to use than the code posted above. It uses a similar technique for counting and has implemented use of another timer to gate the counter if that is what you want to do.

http://interface.khm.de/index.php/lab/experiments/arduino-frequency-counter-library/

mem:
This is just a counter, your sketch would control the length of the sample time by adjusting the duration between calling startCount and getCount. How were you intending to control the count duration if you used an external counter?

Thanks again for the additional information and the link!

I was not sure how I was going to control sample times. I have done some looking for chips but, I had not figured out the control aspects of them yet.

I think calling startCount and getCount should be easy enough using micros() for precision so, I can handle that.

Thank you,
Mark

I found the datasheet you mentioned and the bit about the timer. The datasheet has 500+ pages and lots of things that will be helpful in the long run.

I will run some experiments and see how I do.
Mark

After some experimenting I have some results! I am enjoying learning how to use the code! I also have a couple questions.

I used a 4.56kohm resistor from pin 5 then connected it off and on to ground. If I keep pin 5 grounded through the resistor my result was "5" pulses for a 2 second sample time. However, if I worked the ground resistor to simulate pulses I got numbers that look acceptable for now.

Could my resistor value be too large? Besides needing to put the Serial.print messages outside any of the timers and removing the delay do you see anything I might have done wrong?

Here is my version of your code.

// 16 bit timer defines added by mem to enable redifining the timer used

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#define TIFRn  TIFR5
#define TOVn   TOV5
#else
#define TCCRnA TCCR1A
#define TCCRnB TCCR1B
#define TCNTn  TCNT1
#define TIFRn  TIFR1
#define TOVn   TOV1
#endif

void startCount() {

  // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;	    // reset timer/countern control register A
  TCCRnB=0;	    // reset timer/countern control register A
  TCNTn=0;	    // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168, pin 47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);	 // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11);
  sbi (TCCRnB ,CS12);
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now
}

unsigned int getCount(){

  unsigned int count;
  TCCRnB = TCCRnB & ~7;   			// Gate Off  / Counter Tn stopped
  count = TCNTn;
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now   
}

unsigned long opengate = 0;
unsigned long gatetime = 0;
unsigned long sampletime = 2000;
unsigned int pulses = 0;
int timeset = 1;

void setup(){
  Serial.begin(9600);
  Serial.print("hello ");
}

void loop(){

  if (timeset == 1){
    opengate = millis();
     }
 
  gatetime = millis();
  timeset = 0;

  if (gatetime - opengate  >= sampletime) {
    getCount();
    pulses = TCNT1;
    Serial.println(" here is the count ");
    Serial.print(pulses);
    delay(2000);
    timeset = 1;
    startCount(); 
  }

}

Here are my results.

hello  here is the count 
78 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
152 here is the count 
5 here is the count 
5 here is the count 
11 here is the count 
9 here is the count 
10 here is the count 
12 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
6 here is the count 
9 here is the count 
5 here is the count 
6 here is the count 
9 here is the count 
13 here is the count 
23 here is the count 
5 here is the count 
5 here is the count 
105 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
384 here is the count 
100 here is the count 
762 here is the count 
5 here is the count

That's coming along. The sketch below has a simplified gate duration check and enables the internal pull-up resistor on the input pin. see how that goes:

// 16 bit timer defines added by mem to enable redifining the timer used

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#define TIFRn  TIFR5
#define TOVn   TOV5
#define pulseInPin 47
#else
#define TCCRnA TCCR1A
#define TCCRnB TCCR1B
#define TCNTn  TCNT1
#define TIFRn  TIFR1
#define TOVn   TOV1
#define pulseInPin 5
#endif

void startCount() {

  // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;	    // reset timer/countern control register A
  TCCRnB=0;	    // reset timer/countern control register A
  TCNTn=0;	    // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168, pin 47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);	 // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11);
  sbi (TCCRnB ,CS12);
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now
}

unsigned int getCount(){

  unsigned int count;
  TCCRnB = TCCRnB & ~7;   			// Gate Off  / Counter Tn stopped
  count = TCNTn;
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now   
}

unsigned long opengate = 0;
unsigned long gatetime = 0;
unsigned long sampletime = 2000;
unsigned int pulses = 0;
int timeset = 1;

void setup(){
  digitalWrite(pulseInPin, HIGH); // turn on pull-ups so pin is not floating
  Serial.begin(9600);
  Serial.print("hello ");
  opengate = millis(); // open the gate ready for first count
}

void loop(){

  // check if sampletime has elapsed since gate was opened
  if( millis()- opengate  >= sampletime) {
    pulses =  getCount();
    Serial.println(" here is the count ");
    Serial.print(pulses);
    delay(2000);   
    startCount(); 
    opengate = millis(); // start over with new gate time
  }
}

Thank you, for the improvements! My readings must have been off because the floating pin. I like how you simplified the duration check, I will have to remember that.

I will experiment with it tonight!

Mark

a minor tweak, the following two variables are no longer used and can be deleted:
unsigned long gatetime = 0;
int timeset = 1;

have fun!

I ran your code on my Uno and for some reason I could not get any counts. I think there might be a problem in the loop but, I could not find it. I set-up a Mega "for a Frequency generator" with a multi-tone sketch to get samples from and with your sketch mixed with my sketch I did get results. However, it seems the minimum pulse count is 5. I get reasonable pulse counts while the tones are changing but, when the tones stop I get "5" until I restart the tones on the Mega.

Here is the code that is working at the moment.

 digitalWrite(13, HIGH);
  

  if (timeset == 1){
    opengate = millis();
     }
 
  gatetime = millis();
  timeset = 0;

  if (gatetime - opengate  >= sampletime) {
    getCount();
    pulses = TCNT1;
    Serial.println(" here is the count ");
    Serial.print(pulses);
    delay(2000);
    timeset = 1;
    startCount(); 
  }

}

Here is the result on the monitor.

hello  here is the count 
220 here is the count 
849 here is the count 
500 here is the count 
546 here is the count 
673 here is the count 
1119 here is the count 
1129 here is the count 
707 here is the count 
1085 here is the count 
1288 here is the count 
1330 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5 here is the count 
5

This version should correctly return the count. I also moved the initialization code into a separate function as this can be called once only from setup.

// 16 bit timer defines added by mem to enable redifining the timer used

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#define TIFRn  TIFR5
#define TOVn   TOV5
#define pulseInPin 47
#else
#define TCCRnA TCCR1A
#define TCCRnB TCCR1B
#define TCNTn  TCNT1
#define TIFRn  TIFR1
#define TOVn   TOV1
#define pulseInPin 5
#endif


void initCounter() {
  // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;	    // reset timer/countern control register A
  TCCRnB=0;	    // reset timer/countern control register A
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168, pin 47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);	 // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11);
  sbi (TCCRnB ,CS12);  
  TCNTn = 0;
}


void startCount() {
  TCNTn=0;	    // counter value = 0
  TCCRnB = TCCRnB | 7;	//  Counter Clock source = pin Tn , start counting now
}

unsigned int getCount(){
  TCCRnB = TCCRnB & ~7;   			// Gate Off  / Counter Tn stopped
  return TCNTn;  
}

unsigned long opengate = 0;        // the time the gate is opened
unsigned long sampletime = 2000;   // the pulse counting duration  

void setup(){
  digitalWrite(pulseInPin, HIGH); // turn on pull-ups so pin is not floating
  Serial.begin(9600);
  Serial.print("Gate time is ");
  Serial.print(sampletime);
  Serial.println(" milliseconds");
  initCounter();
  startCount();
  opengate = millis(); // open the gate ready for first count
}

void loop(){
  // check if sampletime has elapsed since gate was opened
  if( millis()- opengate  >= sampletime) {
    unsigned int pulses =  getCount();
    Serial.println(pulses);  
    startCount(); 
    opengate = millis(); // start over with new gate time
  }
}

Thank you Mem for the code and continuing helping with this process!

Mark

The last code works perfectly! It has a minimum count of 0 when no pulses are given and it seems to be reading correctly with changing frequencies. I have lowered the gatetime to 100ms and it works well that way also!

I will have to test on other things to find the best gatetime for each case.

Thanks Mem! You really helped a ton!
Mark