Using the processor Counter/Timers

Hi All

I'm quite inexperienced at Arduino and C coding generally, but have been playing with GPS 1PPS signals and also the concept of frequency references.

I started by reading about this project:

and am using the basic concept of this code to try and use a 1PPS signal to control the on chip Timer1 on a Nano. I am initially trying to count a 5 MHz external signal.

The code runs OK and without the 5MHz signal attached it counts 0, but with the external signal the counts are almost random - so clearly something is very wrong.

I have a 1PPS GPS signal connected to both D2 for INT0 and also D8 for the ICP1 input. THe 5MHz external signal (conditioned through a 74HC14) is connected to D5.

I would be very grateful for any constructive feedback on the following:

#include <LiquidCrystal_I2C.h>
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <PWM.h>

static const int RXPin = 7, TXPin = 6;
static const uint32_t GPSBaud = 9600;

#define INT0_PIN 2     // D2 NANO pin, INTERRUPT 0
#define ICP1     8     // D8 NANO pin, T1's ICP1 input - 1PPS
#define T1_CLK   5     // D5 NANO pin, T1's clock input - 5MHz

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

//I2C LCD device
LiquidCrystal_I2C lcd(0x27,16,2); 
char line0 [17];
char line1 [17];

//Sat count
int satsUsed = 0;
char satBuffer [2];

// signals leading-edge of one_pps_pulse
boolean one_pps_flag;     

// Counter variables
unsigned int prev_t1_count = 0;
unsigned int t1_count = 0;
unsigned int t1_delta = 0;

void updateDisplay()
{
  lcd.setCursor(0,0);
  lcd.print(line0);
  lcd.print(line1);
}

void initDisplay()
{
  for (int i=0; i<=15; i++)
  {
    line0[i] = ' ';
    line1[i] = ' ';
  }

  line0[0] = 'S';
  line0[1] = 'a';
  line0[2] = 't';
  line0[3] = 's';
  line0[4] = ':';
  line0[5] = ' ';

  updateDisplay();
}

void onePPS()
{
  one_pps_flag = true;
}

void timer1_setup (byte mode, int prescale, byte outmode_A, byte outmode_B, byte capture_mode)
{
  // NOTE:  This code found at:
  // http://sphinx.mythic-beasts.com/~markt/ATmega-timers.html
  
  // enforce field widths for sanity
  mode &= 15 ;
  outmode_A &= 3 ;
  outmode_B &= 3 ;
  capture_mode &= 3 ;

  byte clock_mode = 0 ; // 0 means no clocking - the counter is frozen.
  switch (prescale)
  {
    case 1: clock_mode = 1 ; break ;
    case 8: clock_mode = 2 ; break ;
    case 64: clock_mode = 3 ; break ;
    case 256: clock_mode = 4 ; break ;
    case 1024: clock_mode = 5 ; break ;
    default:
      if (prescale < 0)
        clock_mode = 7 ; // external clock
  }
  TCCR1A = (outmode_A << 6) | (outmode_B << 4) | (mode & 3) ;
  TCCR1B = (capture_mode << 6) | ((mode & 0xC) << 1) | clock_mode ;
}

// This custom version of delay() ensures that the gps object
// is being "fed"

static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}

void setup()
{
  lcd.init();
  lcd.clear();
  lcd.backlight();
  initDisplay();
  
  //initialize all timers except for 0, to save time keeping functions
  InitTimersSafe(); 

  Serial.begin(115200);
  ss.begin(GPSBaud);

  // interrupt on falling edge of one_pps signal.
  pinMode(INT0_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(INT0_PIN), onePPS, FALLING); 

  // Timer 1: external clock.  Neg. Edge capture.  No noise cancellation.
  timer1_setup (0x00, -1, 0x00, 0x00, 0x00); 
}

void loop()
{
  satsUsed = gps.satellites.value();
  sprintf (satBuffer, "%03i", satsUsed);

  line0[6] = satBuffer[1];
  line0[7] = satBuffer[2];
  updateDisplay();
  
  smartDelay(100);

  if (millis() > 5000 && gps.charsProcessed() < 10)
    Serial.println(F("No GPS data received: check wiring"));

  if (one_pps_flag)
    {
      // this code should only run once each time the 1PPS interrupt has been triggered
      one_pps_flag = false;
    
      prev_t1_count = t1_count; //save old count
      t1_count = ICR1;          // new counter snapshot

      // compensate for counter wraparound
      if (t1_count < prev_t1_count) 
      {
        t1_delta = 65536 - (prev_t1_count - t1_count);
      }
      else 
      {
        t1_delta = t1_count - prev_t1_count;
      }
      Serial.println(t1_delta, HEX); // an ideal 5MHz count would yeild 4B40 HEX
    }
}

I would be grateful for any feedback on this code:

Note: The Input Capture Pin (8) has its own interrupt so you don't need to connect to Pin 2 to interrupt when the ICR is set.

You don't need to worry about overflow of the ICR. Treat it as an unsigned int and subtract the new time from the old time to get the delta time (ignoring the 76 wraps of the timer each second).

76 wraps of 65,536 counts = 4,980,736
5,000,000 - 4,980,736 = 19,264 = 0x4B40

Here is a sketch I wrote to measure frequency and duty-cycle using the ICR. Maybe it will help as an ICR interrupt handling example:

// Measures the width of pulses on Arduino UNO Pin 8
// Should work up to 65535 clock cycles (4095 microseconds)

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for 
// analogWrite().

void setup()
{
  Serial.begin(115200);

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  //analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  //analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  TIFR1 |= (1 << ICF1); // clear Input Capture Flag so we don't get a bogus interrupt

  // start Timer 1, no prescaler
  TCCR1B |= (1 << CS10); // plus Input Capture Edge Select (rising on D8)
  TCCR1B |= (1 << ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= (1 << ICIE1); // Enable Timer 1 Input Capture Interrupt Enable
  interrupts ();
}

volatile uint16_t PulseHighTime = 0;
volatile uint16_t PulseLowTime = 0;

ISR(TIMER1_CAPT_vect)
{
  static uint16_t firstRisingEdgeTime = 0;
  static uint16_t fallingEdgeTime = 0;
  static uint16_t secondRisingEdgeTime = 0;

  if (PulseLowTime == 0)
  {
    if (TCCR1B & (1 << ICES1))
    {
      // Rising Edge
      if (firstRisingEdgeTime)
      {
        secondRisingEdgeTime = ICR1;
        PulseLowTime = secondRisingEdgeTime - fallingEdgeTime;
        firstRisingEdgeTime = 0;
      }
      else
      {
        firstRisingEdgeTime = ICR1;
        TCCR1B &= ~(1 << ICES1); // Switch to Falling Edge
      }
    }
    else
    {
      // Falling Edge
      fallingEdgeTime = ICR1;
      TCCR1B |= (1 << ICES1); // Switch to Rising Edge
      PulseHighTime = fallingEdgeTime - firstRisingEdgeTime;
    }
  }
}

void loop()
{
  noInterrupts();
  uint16_t pulseHighTime = PulseHighTime;
  uint16_t pulseLowTime = PulseLowTime;
  interrupts();

  // If a sample has been measured
  if (pulseLowTime)
  {
    // Display the pulse length in microseconds
    Serial.print("High time (microseconds): ");
    Serial.println(pulseHighTime / 16.0, 2);
    Serial.print("Low time (microseconds): ");
    Serial.println(pulseLowTime / 16.0, 2);
    Serial.print("Cycle time (microseconds): ");
    Serial.println((pulseHighTime / 16.0) + (pulseLowTime / 16.0), 2);

    uint32_t cycleTime = pulseHighTime + pulseLowTime;
    float dutyCycle = pulseHighTime / (float)cycleTime;
    Serial.print("Duty cycle (%): ");
    Serial.println(dutyCycle * 100.0, 2);

    float frequency = 16000000.0 / cycleTime;
    Serial.print("Frequency (Hz): ");
    Serial.println(frequency, 2);
    Serial.println();

    delay(1000);  // Slow down output

    // Request another sample
    noInterrupts();
    PulseLowTime = 0;
    interrupts();
  }
}