Go Down

Topic: Frequency Counter with LCD display - complete code (Read 182 times) previous topic - next topic

arduinoaleman

Hi

- You are looking for a frequency counter (1Hz-3MHz) that can also display period time?


The code does NOT require a special library. The variable names for the HW setup can be found in the ATmega datasheet.



Code: [Select]
/*  Frequency Counter With LCD Display - by arduinoaleman - May 2015

    
    For analog signals have a look at my preamplifier circuit (arduino forum / look for "frequency counter amplifier"

    Pin D5 of Arduino must be used for frequency input. The LCD uses pins 8 thru 13.
  
    Counter1 : 16 bits / used to count the frequency impules
    Counter2 : 8 bits / used to genarate a 1000ms or 100ms gate time for measurement
    
    I use "bitClear(TIMSK0,TOIEO)" instead of "TIMSK0 &=~(1<<TOIE0)"
    I use "bitSet(TIMSK0,TOIEO)"   instead of "TIMSK0 |=~(1<<TOIE0)"
        
    The serial monitor will also show some (unformatted) results.
 
    
 */
 

#include <avr/interrupt.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,10,11,12,13);

volatile unsigned long frequency=0;            
volatile boolean       measurement_ready;
volatile unsigned char overflow_counter;     // number of overflows within gate_time
volatile unsigned int  time_so_far;              // number of ISR calls
volatile unsigned int  gate_time;                

void measurement(int ms) {

    bitClear(TIMSK0,TOIE0);     // disable counter0 in order to disable millis() and delay()
                                             // this will prevent extra interrupts that disturb the measurement
    delayMicroseconds(66);      // wait for other interrupts to finish
    gate_time=ms;                  // usually 1000 (ms)
  
    // setup of counter 1 which will be used for counting the signal impulses

    TCCR1A=0;                  // reset timer/counter1 control register A
    TCCR1B=0;                  // reset timer/counter1 control register B
    TCCR2A=0;                  // reset timer/counter1 control register A
    TCCR2B=0;                  // reset timer/counter2 control register A
    
    // setup of counter2 which will be used to create an interrupt every millisecond (used for gate time)

    TCCR2B |= B00000101;        // set prescale factor of counter2 to 128 (16MHz/128 = 125000Hz)
                                               // by setting CS22=1, CS21=0, CS20=1

    bitSet(TCCR2A,WGM21) ;      // set counter2 to CTC mode
                                              // WGM22=0, WGM21=1, WGM20=0                  
    OCR2A = 124;                      // CTC divider will divide 125Kz by 125
  
    measurement_ready=0;        // reset
    time_so_far=0;                    // reset
    bitSet(GTCCR,PSRASY);        // reset the prescaler
    TCNT1=0;                           // set frequency counter1 to 0
    TCNT2=0;                           // set gate time counter2 to 0
      
    bitSet(TIMSK2,OCIE2A);        // enable counter2 interrupts
    TCCR1B |= B00000111;        // set CS12, CS11 and CS10 to "1" which starts counting
                                               // on T1 pin (Arduino pin D5)
 }


ISR(TIMER2_COMPA_vect) {
 
  if (time_so_far >= gate_time) {          // end of gate time, measurement is ready
    TCCR1B &= B11111000;                 // stop counter1 by setting CS12, CS11 and CS10 to "0"
    bitClear(TIMSK2,OCIE2A);              // disable counter2 interrupts
    bitSet(TIMSK0,TOIE0);       // enable Timer0 again // millis and delay
    measurement_ready=true;                // set global flag for end count period
                                                          // calculate now frequeny value
    frequency=0x10000 * overflow_counter;  // mult #overflows by 65636 (0x10000)
    frequency += TCNT1;                 // add counter1 contents for final value
    overflow_counter=0;                         // reset overflow counter
  }
  else {
    time_so_far++;                            // count number of interrupt events
    if bitRead(TIFR1,TOV1)  {               // if Timer/Counter 1 overflow flag = "1" then ...
       overflow_counter++;                  // increase number of counter1 overflows
       bitSet(TIFR1,TOV1);               // reset counter1 overflow flag
    }
  };
}


void setup() {

  pinMode(5,INPUT);
  lcd.begin(16, 2);            // defines a LCD with 16 columns and 2 rows
  lcd.clear();
  
  for (int row=0; row<=1; row++) {
    for (int column=0; column<=15; column++) {   // LDC test
       lcd.setCursor(column,row);
       lcd.print("*");
       delay(70);
    };
  }
  lcd.clear();
  Serial.begin(9600);
}


void loop() {
  float period;    
  float floatfrq;  
  int range;
  long frq;

  measurement(1000);       // 1000ms standard gate time
    
  while (measurement_ready==false);
  frq=frequency;

  floatfrq=frq;                    // type conversion (required!!)
  period=(1/floatfrq);                // period = 1/Frequenz -
  
  if ((frq >= 0)&& (frq < 10))           {range=0;};                                             // Hertz
  if ((frq >= 10)&& (frq < 100))         {range=1;};   
  if ((frq >= 100)&& (frq < 1000))       {range=2;};
  if ((frq >= 1000)&& (frq < 10000))     {range=3; floatfrq=floatfrq/1000;};      // KHz
  if ((frq >= 10000)&& (frq < 100000))   {range=4; floatfrq=floatfrq/1000;};
  if ((frq >= 100000)&& (frq < 1000000)) {range=5; floatfrq=floatfrq/1000;};
  if (frq >= 1000000)                    {range=6; floatfrq=floatfrq/1000000;};          // MHz
  
  Serial.print("Frequency (Hz): ");
  Serial.print(frq);
  Serial.print("     Period (sec): ");
  Serial.println(period,7);  
  
  lcd.setCursor(0, 0);                
  lcd.print("Freq:           ");        
  lcd.setCursor(0, 1);                
  lcd.print("Per.:         ");
  
  switch(range) {            
    case 0:                                          // 1Hz thru 10Hz
       lcd.setCursor(6, 0);   lcd.print(frq);
       lcd.setCursor(13,0);   lcd.print("Hz ");
       lcd.setCursor(6, 1);   lcd.print(period,3);  
       lcd.setCursor(13, 1);  lcd.print("s ");            
       break;
    case 1:   // 10Hz thru 100Hz
       period=period*1000;        // convert from seconds to ms      
       lcd.setCursor(6, 0);   lcd.print(frq);
       lcd.setCursor(13,0);   lcd.print("Hz ");    
       lcd.setCursor(6, 1);   lcd.print(period,2);  
       lcd.setCursor(13, 1);  lcd.print("ms");            
       break;
    case 2:    // 100Hz thru 1KHz
       period=period*1000;
       lcd.setCursor(6, 0);   lcd.print(frq);
       lcd.setCursor(13,0);   lcd.print("Hz ");    
       lcd.setCursor(6, 1);   lcd.print(period,3);  
       lcd.setCursor(13, 1);  lcd.print("ms");                  
       break;
    case 3:    // 1KHz thru 10KHz
       period=period*1000;
       lcd.setCursor(6, 0);   lcd.print(floatfrq,3);
       lcd.setCursor(13,0);   lcd.print("KHz");    
       lcd.setCursor(6, 1);   lcd.print(period,3);  
       lcd.setCursor(13, 1);  lcd.print("ms");          
       break;
     case 4:   // 10KHz thru 100KHz
       period=period*1000*1000;                  
       lcd.setCursor(6, 0);   lcd.print(floatfrq,2);
       lcd.setCursor(13,0);   lcd.print("KHz");    
       lcd.setCursor(6, 1);   lcd.print(period,2);  
       lcd.setCursor(13, 1);  lcd.print("us");          
       break;
      case 5:   // 100KHz thru 1MHz
       period=period*1000*1000;                      // convert from s to µ
       lcd.setCursor(6, 0);   lcd.print(floatfrq,1);
       lcd.setCursor(13,0);   lcd.print("KHz");    
       lcd.setCursor(6, 1);   lcd.print(period,3);  
       lcd.setCursor(13, 1);  lcd.print("us");          
       break;
     case 6:   // above 1MHz
       period=period*1000*1000;
       lcd.setCursor(6, 0);   lcd.print(floatfrq,3);
       lcd.setCursor(13,0);   lcd.print("MHz");    
       lcd.setCursor(6, 1);   lcd.print(period,3);  
       lcd.setCursor(13, 1);  lcd.print("us");          
       break;      
  }
}  






Changeing the gate time requires several changes. The accuracy is in the 4 to 5 digit range. For low freqencies it is lower.
If you are looking for a preamplifier search for "frequency amplifier" in the Arduino forum. I posted a circuit diagram for a 2Hz-20MHz(plus) amplifier.

On account of the 9000 character limitation of this forum I had to remove lots of comments in my code.
I am sorry for that. You do not need to understand the code. Just let it run.

Have fun

arduinoaleman - May 2015


ps:
If you have any questions, please use the forum for communication.
Arduino is one thing. Real live is another thing. Maybe I can help you with Arduino one day. I will leave the rest to your real friends. However, do not forget to ask the right questions. If nobody understands your questions, nobody will help you.

CrossRoads

You could always Reply and Attach your code below the message box.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

arduinoaleman

#2
May 22, 2015, 07:10 pm Last Edit: May 23, 2015, 09:59 am by robtillaart
Hi

on account of the Arduino forum restriction of 9000 characters I had to delete some information from my post. This is also true for many comments in my code, so I would like to explain some more.

I prefer easy-to-read code to state-of-the art code that only a few can understand. Some of the code could be more professional. However, it would be more difficult to understand.

The hardware is simple, all you need is a 2x16 LCD display. For other LCDs you have to adjust some lines of code. If you do not have a LCD, just use the serial monitor of your computer. The code is already included in the program. However, the output will not be formatted. The settings like Hz, KHz, MHz, sec, ms, µs will only adjust automatically when using a LCD display.

So, all hardware that you really need is an Arduino with a ATmega328p or 168PA. I used an Arduino UNO and IDE 1.6.

You MUST use D5 pinof your Arduino Uno as the frequency input. This is a prerequirement for using the 16bit Timer/Counter1 of the ATmega chip. (please check the Arduino pin for other boards)

And of course you need a digital signal source with compatible signals (LOW<0.8 Volts / HIGH > 2.7 Volts - max. 5 Volts) - I used a cheap NE555, 2 resistors, a potentiometer and two capacitors (basic NE555 astable mode circuit).

If you want to measure analog signals or low-voltage signals you need a preamplifier. I have built one lately and it works well from 2Hz to more than 10 MHz (this is the limit of my oscilloscope). And it runs on 5 Volts (can be powered by Arduino). I have tested the amplifier with signals from 2HZ to 10MHz with inputs of 50mV up to 5 Volts.
You can find the circuit diagram on the Arduino forum (search for: frequency counter amplifier) or use this direct link: forum.arduino.cc/index.php?topic=324113.0

If you want to measure more than 2MHz with your Arduino, you will need a prescaler (frequency divider). Just add a single TTL compatible 1:10 or 1:100 chip behind the Schmidt trigger of my preamplifier or your TTL-signal-compatible input source.

The frequency counter works as follows:

The 16bit Timer/Counter1 will add up all clocks coming in from pin D5. Timer/Counter2 will generate an interrupt every millisecond (1000 times per second). If there is an overflow in Timer/Counter1, the overflow_counter will be increased by one. After 1000 interrupts (= exactly one second) the number of overflows will be multiplied by 65536 (this is when the counter flows over). In cycle 1000 the current value of the counter will be added, giving you the total number of clock ticks that came in during the last second. And this is equivalent of the frequency you wanted to measure (frequency = clocks per second).

The procedure measurement(1000) will set up the counters and initialise them. After that a WHILE loop will wait until the interrupt servive routine sets measurement_ready to TRUE. This is exactly after 1 second (1000ms or 1000 interrupts).

If you want to change the gate time, make sure you have to change the format of the output as well. A 10000ms gate time (10 seconds) gives you 10 times more clock cycles. So you have to divide this value by ten, before you display it. And for exact values you will have to use a floatingpoint variable so you can show values like "1.7 Hz" instead if "1 Hz).

For hobbyists this frequency counter works very well (apart from lower frequencies you can get 4 or 5 digit accuracy). Especially with higher frequencies the counter gets very acurate. I have decided to display only 4 digits. However, you can adjust that in the LCD output section.

In order to check if my code was uploaded correctly, I have just downloaded it again to my Arduine IDE (past and copy). It worked well.

I hope you will have fun with this project

arduinoaleman
Arduino is one thing. Real live is another thing. Maybe I can help you with Arduino one day. I will leave the rest to your real friends. However, do not forget to ask the right questions. If nobody understands your questions, nobody will help you.

robtillaart

one remark


think that long frq should be unsigned long
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

arduinoaleman

hi robtillard

the ATmega328 of an Arduino Uno has a 16MHz clock. The frequency counter works up to 2 or 3 MHz without a prescaler.

That (measured frequency) fits easily in a LONG variable. UNSIGNED LONG will just double the range. However, in this context it is not required.

Nevertheless, thanks for having a close look at my code.
Arduino is one thing. Real live is another thing. Maybe I can help you with Arduino one day. I will leave the rest to your real friends. However, do not forget to ask the right questions. If nobody understands your questions, nobody will help you.

robtillaart

It might be not required, but (there always is a but ;) two arguments

1) it mirrors frequency which is unsigned long and you want to prevent mixed signed and unsigned math
2) from physical point of view frequencies can never be negative.

but that said, it will work fine
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Paul Stoffregen

It's great that you made this from scratch.  That's certainly a great learning experience.

But you really should have considered the FreqCount library.  It avoids any dead time between gate intervals, so you never have a blind time.  A count missed right after the end of one interval will be picked up in the next.  The FreqCount library is also portable and supports multiple chips.  It works automatically on Leonardo and Teensy, and will likely support other boards in the future, whereas hard-coding the AVR timer registers results in a sketch that's tightly bound to only Arduino Uno.


Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy