Pulse and frequency counting using direct input on Timer1

Hello,

I have written some sketchy code, to test if it is possible to measure some reasonably high frequencies with an Uno, without any external components.

In my setup I use one Uno to generate a simple signal (output on/off), and apparently I am able to generate and read a signal of appx. 1 MHz.

Doing various modifications to change frequency seems to reflect correctly in my measurement, but I do have one major problem: I don't have a frequency counter nor a scope, so I cannot verify my readings :wink:

I would really appreciate if someone with an instrument would take the time to load the following few lines of code on a "naked" Uno, and report what the output frequency is:

void setup() {
pinMode(5, OUTPUT); //we use this pin for pulse output
noInterrupts(); // Disable all INT (speeds up things a little)
}
void loop() {
PORTD = B00100000; // sets digital pin 5 HIGH
PORTD = B00000000; // sets digital pin 5 LOW
}

I attach the code for my freq counter which should be fairly self-explanatory, but I will try to elaborate on relevant details if requested.

/* --------------------------------------------------------------------------------
Arduino Uno Pulse/freq counter - testing draft

SYNOPSIS:
  This is a "better-than-nothing" pulse & frequency counter.

uC RESSOURCES:
  I use Timer1 for counting. Even though it is not an asynch counter, it should still
  be useable if input freq is reasonably lower that the system clockfreq.

  If the timer overflows during sample time, it will increment the var: overflowT1.
  (Output Compare is enabled in the code, but I still use max = 65535 to have as little
   interference with the timer during counting - to avoid missing pulses)

  Timer 1 is a 16 bit timer, so max is 65535, giving some (theoretical) ranges of:
  
    6 MHz@0,01s = 60.000 pulses (delay = 10ms)
    12 MHz@0,005s = 60.000 pulses (delay = 5ms)
  
  So far, it looks fairly convincing - at least I get different readings for various
  setups, and the observed frequencies are in the right neighborhood ;-)
  
CONNECTION, IF & TEST:
  I count the pulses on Timer1 input pin (Arduino Uno pin 5).
  
  The input is purely digital, - i.e. it will probably have to be a neat TTL/5V 
  square-wave signal.
  
  When I take the input from another Arduino Uno running 
  this simple snippet of code, I get a reading about 1MHz:
  
      void setup() {
        pinMode(5, OUTPUT); //we use this pin for pulse output
        noInterrupts(); // Disable all INT (speeds up things a little)
      }
      void loop() {
        PORTD = B00100000; // sets digital pin 5 HIGH
        PORTD = B00000000; // sets digital pin 5 LOW
      }

OPTIONS:
  I have added some simple sample-time control, giving the sample times (in mS):
    5
    10
    50
    100
    500
    1000
  
  Just hit +/- in the Arduino Serial Monitor (remember to place cursor in input field)


TODO:
  Find out how long time a OCR-INT takes, so the time can be added to sample time
  when calculating freq (for freq around 1 MHz@0.1S it probably gives an error about 0.1%)

 **** All content is Open Source ****
 ***  Sven Karlsen, april 2014    ***
 ** always share, to show You care **
 
-------------------------------------------------------------------------------- */


// ************************************************************************************************************************************
const unsigned long OCR1_PRESET = 65535;// preset TOP value for Timer1 Output Compare (used for either OCR1A or OCR1B)

unsigned long pulseCnt = 0;     // for a snapshot of the timer value
int overflowT1 = 0;             // timer overflow (65535)
int sampleTime = 5;             // start with shortest sample time (5 ms)
int sampleFactor1 = 10;
int sampleFactor2 = 2;

/* ************************************************************************************************************************************
 Timer 1 setup
 
 This code sets up Timer1, - it has been prepared for various other purposes, so the OCR-settings have been included.
 
 If you only want to use the code for a counter, you may omit the OCIE1X & OCR1X actions and use the ISR(TIMER1_OVF_vect){} int
 instead.
 
 If you use OCR, and set the value < 65535, then you must either reset Timer1 value in the ISR (enable TCNT1 = 0), or reset Timer1
 somewhere else in the code.
------------------------------------------------------------------------------------------------------------------------------------ */
void initT1() {                                   // setup timer1
                                                  // SK: see Atmel Data Sheet on ATmega328 for my pg/table refs
  
      TCCR1A = 4;                                 // timer Mode, - Timer1 is set in CTC Mode (Clear Timer on Compare match)
                                                  // SK: pg. 136 table 15.4

      TCCR1B = 1<<CS12 | 1<<CS11 | 1<<CS10;       // timer Input clock, - input clock is set to external on rising edge
                                                  // SK: pg 137 table 15.5

      TIMSK1 = 1<<TOIE1;                          // enable timer overflow interrupt
      TIMSK1 = 1<<OCIE1A;                         // enable timer output compare reg A interrupt
//      TIMSK1 = 1<<OCIE1B;                         // enable timer output compare reg B interrupt

      OCR1A = OCR1_PRESET;                        // preset OCR1A register ( if using ISR(TIMER1_COMPA_vect) {} )
//      OCR1B = OCR1_PRESET;                        // preset OCR1A register ( if using ISR(TIMER1_COMPB_vect) {} )

      TCNT1 = 0;                                  // reset timer1 value
  }


ISR(TIMER1_COMPA_vect) {                          // install an interrupt service routine (ISR) for Timer1 Output Compare Match Reg A
//ISR(TIMER1_OVF_vect) {                            // install an interrupt service routine (ISR) for Timer1 overflow
    overflowT1++;
//    TCNT1 = 0;                                    // reset timer1 value (only required if using OCR-match less than 65535
  }

// ************************************************************************************************************************************



void setup() {
  // Call initT1 function to setup Timer1
  initT1();                              

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
}


// ************************************************************************************************************************************

void loop() {
  // Count pulses during requested sample time
    overflowT1 = 0;                       // reset overflow counter
    TCNT1 = 0;                            // reset Timer1 value
    TCCR1B = 1<<CS12 | 1<<CS11 | 1<<CS10; // start timer Input clock: external, rising edge
    delay(sampleTime);                    // wait for the set sampling time
    TCCR1B = 0<<CS12 | 0<<CS11 | 0<<CS10; // stop timer Input clock
    pulseCnt = TCNT1;                     // take a snapshot of timer 1 current value
    pulseCnt += overflowT1 * OCR1_PRESET; // add any counts from overflow during sample time

  // Report the observations to serial
  Serial.print(overflowT1);               // print #of Timer1 overflows
  Serial.print(" - pulses@sampletime:"); 
  Serial.print(pulseCnt);                 // print (calculated) pulse count
  Serial.print("@"); 
  Serial.print(sampleTime);               // print the sample time we're using
  Serial.print("mS - freq:"); 
  Serial.print(pulseCnt/sampleTime);      // print freq (integer part)
  Serial.print(","); 
  Serial.print((pulseCnt%sampleTime)*sampleFactor2); // print freq (decimal part)
  Serial.println(" kHz"); 


  // reset and take a break ;-)
  pulseCnt = 0;
  delay(100);

  // see if operator send us a request, - if so (and it's valid) act on it.
  byte serialSignal = Serial.read();
  
  switch(serialSignal) {
    case '+': // longer sampling time
      if (sampleTime < 1000) {
        if (sampleFactor2 == 1) {sampleFactor2 = 2;  sampleFactor1 *= 10;}
        else {sampleFactor2 = 1;}
      }  
      sampleTime = sampleFactor1 / sampleFactor2;
    break;
    case '-': // shorter sampling time
      if (sampleTime > 5) {
        if (sampleFactor2 == 2) {sampleFactor2 = 1;  sampleFactor1 /= 10;}
        else {sampleFactor2 = 2;}
      }  
      sampleTime = sampleFactor1 / sampleFactor2;
    default : break;
  }
  
}

svenkarlsen:
I would really appreciate if someone with an instrument would take the time to load the following few lines of code on a "naked" Uno, and report what the output frequency is:

void loop() {

PORTD = B00100000; // sets digital pin 5 HIGH
  PORTD = B00000000; // sets digital pin 5 LOW
}

1.066666MHz

void loop() {
  while (true)
  {
    PORTD = B00100000; // sets digital pin 5 HIGH
    PORTD = B00000000; // sets digital pin 5 LOW
  }
}

[/quote]
4.0000MHz

void loop() {
  while (true)
  {
    PORTD = B00100000; // sets digital pin 5 HIGH
    PORTD = B00000000; // sets digital pin 5 LOW
    PORTD = B00100000; // sets digital pin 5 HIGH
    PORTD = B00000000; // sets digital pin 5 LOW
    PORTD = B00100000; // sets digital pin 5 HIGH
    PORTD = B00000000; // sets digital pin 5 LOW
    PORTD = B00100000; // sets digital pin 5 HIGH
    PORTD = B00000000; // sets digital pin 5 LOW
  }
}

[/quote]
6.40000MHz

Looking at your code I see two things immediately:

You call Serial.read() without first calling Serial.available() - you must.

You are altering the timer count register TCNT1 within loop() - that's broken,
you are losing counts because of that.

The correct way is to monitor the counter by taking a copy of it regularly and
comparing the successive observed values. So long as you observe it fast enough
that the counter cannot count 2^16 counts between observations you have
complete information and haven't disturbed the counter.

Something like:

unsigned long prev_accurate_time = 0L ;
unsigned long total_count = 0L ;
unsigned long prev_count = 0L ;
unsigned long previous_tick = 0L ;

unsigned int previous = 0 ;

void loop ()
{
  // first perform observation
  unsigned int now = TCNT1 ;
  unsigned long accurate_time = micros () ;

  // then update the long counter
  unsigned long difference = (now - previous) & 0xFFFFL ;  // get unsigned difference
  previous = now ;
  total_count += difference ;

  // check for time passing
  if (millis () - previous_tick >= 1000L)
  {
    previous_tick += 1000L ; // setup for next second.

    // calculate accurate values for the approximate 1s interval, pass to be displayed
    display (total_count - prev_count, 
                 accurate_time - prev_accurate_time) ;

    prev_accurate_time = accurate_time ;  // update for next second
    prev_count = total_count ;


  }
}

Thanks for the info Shannon.

My exercise sketch is aimed at understanding and implementing the instructions for managing the controller features, as specified in the datasheet.