// LM: This sketch computes and displays a gated oscillator's frequency, // based on data received from a binary counter TTL circuit and a GPS // pulse-per-second time duration reference. // A 16x2 LCD displays the computed result (1) and raw data (2). // The Si5351 part supports an optional test clock generator. #define LCD_PRESENT #include #include Adafruit_SI5351 clockgen = Adafruit_SI5351(); #ifdef LCD_PRESENT #include // Frequency (result) display #endif #define KBITS 2 // Interrupt pin for count of Hz / CYCLES_PER_COUNT #define Q9 3 // Duplicate of KBITS, but not involved in interrupt #define INFO_LED 13 // Reserved #define PULSE_STATE A0 // State of signal derived from PPS #define ENABLE_MEASUREMENT A1 // Gated with PPS derived signal #define REQUEST_MEASUREMENT A2 // Sense state of pushbutton #define MEASUREMENT_DURATION 4 // Number of seconds for which measurement is enabled (hardware) #define SERIAL_BAUD 9600 const long TWOSEC = 2000; const long ONESEC = 1000; const long HALFSEC = 500; const long QTRSEC = 250; const int NDPINS = 10; int DIOpins[NDPINS] = {4, 5, 6, 7, 8, 9, 10, 11, 12, Q9}; const int CYCLES_PER_COUNT = 1024; // Presently the count from Q9 (74HC4040 pin 14) volatile unsigned long bCount = 0; // Count of blocks - See CYCLES_PER_COUNT definition above unsigned long remainder = 0; // Modulus vBase10 mod(CYCLES_PER_COUNT) as binary unsigned long vBase10 = 0; // Value after conversion to decimal #ifdef LCD_PRESENT // LCD stuff - const int ROWS = 2; // If 20x4 change ROWS to 4 const int COLS = 16; // If 20x4 change COLS to 20 const boolean lcdPresent = false; // Enable/disable LCD code LiquidCrystal_I2C lcd(0x27, COLS, ROWS); // Instantiate #endif String s1 = ""; String s2 = ""; void setup() { pinMode(PULSE_STATE, INPUT); pinMode(ENABLE_MEASUREMENT, OUTPUT); pinMode(REQUEST_MEASUREMENT, INPUT); // External pushbutton digitalWrite(INFO_LED, LOW); // HIGH-enable digitalWrite(ENABLE_MEASUREMENT, LOW); // HIGH-enable for (int j = 0; j < NDPINS; j++) { pinMode(DIOpins[j], INPUT); // From counter outputs (remainder bits) } pinMode(INFO_LED, OUTPUT); boolean clockGenOK = true; if (clockgen.begin() != ERROR_NONE) { infoCode(1); clockGenOK = false; delay(ONESEC); // Test clock generator is not required } #ifdef LCD_PRESENT lcd.init(); displayLCD("GPS Freq Counter"," www.lloydm.net "); delay(TWOSEC); // Extend duration of splash screen #endif if (clockGenOK) { #ifdef TEST // Configure test frequency init_5MHz(); #else // init_6p25MHz(); init_40MHz(); #endif clockgen.enableOutputs(true); // Enable clock generator } #ifdef SERIAL_BAUD Serial.begin(SERIAL_BAUD); // Temporary output #endif // Count KHz (Hz / CYCLES_PER_COUNT) attachInterrupt(digitalPinToInterrupt(KBITS), isrCount, FALLING); // Scheduler setup } void loop() { if (digitalRead(REQUEST_MEASUREMENT) == HIGH) { while (digitalRead(REQUEST_MEASUREMENT) == HIGH) ; // Debounce scheduleMeasurement(); infoCode(2); // Kilroy was here! (Also delay a moment.) processData(); displayResult(); // Loop until another measurement requested } } void init_5MHz() { // Set channel 0 output to 5 MHz // Multiply 25 MHz by 15 = 375 MHz. clockgen.setupPLLInt(SI5351_PLL_A, 15); // Divide by 75 = 5 MHz. clockgen.setupMultisynthInt(0, SI5351_PLL_A, 75); } void init_6p25MHz() { // Set channel 0 output to 6.25 MHz // Multiply 25 MHz by 16 = 400 MHz. clockgen.setupPLLInt(SI5351_PLL_A, 16); // Divide by 64 = 6.25 MHz. clockgen.setupMultisynthInt(0, SI5351_PLL_A, 64); } void init_40MHz() { // Set channel 0 output to 40 MHz // Multiply 25 MHz by 16 = 400 MHz. clockgen.setupPLLInt(SI5351_PLL_A, 16); // Divide by 10 = 40 MHz. clockgen.setupMultisynthInt(0, SI5351_PLL_A, 10); } void scheduleMeasurement() { bCount = 0; displayLCD(" Scheduling "," measurement "); while (digitalRead(PULSE_STATE) == HIGH) delay(random(QTRSEC)); digitalWrite(ENABLE_MEASUREMENT, HIGH); displayLCD(" Measurement "," enabled... "); while (digitalRead(PULSE_STATE) == LOW) delay(random(HALFSEC)); // Measurement in progress - ENABLE_MEASUREMENT and PULSE_STATE are HIGH while (digitalRead(PULSE_STATE) == HIGH) delay(random(HALFSEC)); // Measurement complete - Remove from schedule digitalWrite(ENABLE_MEASUREMENT, LOW); return; } void processData() { vBase10 = bCount * CYCLES_PER_COUNT; detachInterrupt(digitalPinToInterrupt(KBITS)); remainder = 0; for (int j = 0; j < NDPINS; j++) { if (digitalRead(DIOpins[j]) == HIGH) // Binary 1 remainder += pwr2(j); } attachInterrupt(digitalPinToInterrupt(KBITS), isrCount, FALLING); vBase10 += remainder; } unsigned long pwr2(int j) { switch (j) { case 0 : return 1; case 1 : return 2; case 2 : return 4; case 3 : return 8; case 4 : return 16; case 5 : return 32; case 6 : return 64; case 7 : return 128; case 8 : return 256; case 9 : return 512; case 10 : return 1024; case 11 : return 2048; case 12 : return 4096; default : return 0; } } void displayResult() { freq(); s2 = ""; s2.concat(String(bCount)); s2.concat("*1024+"); s2.concat(String(remainder)); displayLCD(s1, s2); } void freq() { // Format s1 as frequency in Hz unsigned long jFreq = vBase10 / MEASUREMENT_DURATION; s1 = String(jFreq); // Integer part if (MEASUREMENT_DURATION == 4) { // To do: Generalize unsigned long jMod = vBase10 % MEASUREMENT_DURATION; if (jMod == 1) s1.concat(".25"); else if (jMod == 2) s1.concat(".5"); else if (jMod == 3) s1.concat(".75"); } s1.concat(" Hz"); } void isrCount() { bCount++; } void myClear() { #ifdef LCD_PRESENT lcd.clear(); lcd.setCursor(0,0); #endif } void displayLCD(String line1, String line2) { #ifdef LCD_PRESENT int row = ROWS/2 - 1; // Same as if..else myClear(); lcd.setCursor(0, row); lcd.print(line1); lcd.setCursor(0, row+1); lcd.print(line2); lcd.backlight(); #endif } // Development and debug utilities void binRem() { // Debug s2 = String(remainder); s2.concat(" "); for (int j = 0; j < NDPINS; j++) { if (digitalRead(DIOpins[j]) == HIGH) // Binary 1 s2.concat("1"); else s2.concat("0"); } } void infoCode(int iCode) { for (int i = 0; i < iCode; i++) { onInfoLED(); delay(500); offInfoLED(); delay(500); } } void onInfoLED() { digitalWrite(INFO_LED, HIGH); } void offInfoLED() { digitalWrite(INFO_LED, LOW); }