diff --git a/ADF4351_module.jpg b/ADF4351_module.jpg new file mode 100644 index 0000000..2bfd43a Binary files /dev/null and b/ADF4351_module.jpg differ diff --git a/LCD-160x128_3s.jpg b/LCD-160x128_3s.jpg new file mode 100644 index 0000000..50a8fd7 Binary files /dev/null and b/LCD-160x128_3s.jpg differ diff --git a/README.md b/README.md index b25cbef..2afc820 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,12 @@ The first breadboard prototype: In normal operation this is displayed on the small OLED: + +In normal operation this is displayed on the 1.8" LCD: + + +ADF4351 module with low noise regulator: + + +The module is connected to a dedicaed SPI interface which does not have any transition in normal operation mode to improve phase noise. + diff --git a/software/GPSDO.ino b/software/GPSDO.ino index 6ed67e6..251ed44 100644 --- a/software/GPSDO.ino +++ b/software/GPSDO.ino @@ -1,5 +1,5 @@ -/******************************************************************************************************* - STM32 GPSDO v0.05a by André Balsa, June 2021 +/********************************************************************************************************** + STM32 GPSDO v0.05e by André Balsa, February 2022 GPLV3 license Reuses small bits of the excellent GPS checker code Arduino sketch by Stuart Robinson - 05/04/20 From version 0.03 includes a command parser, so the GPSDO can receive commands from the USB serial or @@ -15,15 +15,16 @@ This program is supplied as is, it is up to the user of the program to decide if the program is suitable for the intended purpose and free from errors. -*******************************************************************************************************/ +**********************************************************************************************************/ -// GPSDO with optional I2C SSD1306 display, STM32 MCU, DFLL in software +// GPSDO with STM32 MCU, optional OLED/LCD display, various sensors, DFLL in software, optional Bluetooth +// ADF435x for additional frequency generation added -/******************************************************************************************************* - This Arduino with STM32 Core package sketch implements a GPSDO with display option. It uses an SSD1306 - 128x64 I2C OLED display. It reads the GPS for 1 or 5 seconds and copies the half-dozen or so default - NMEA sentences from the GPS to either the USB serial or Bluetooth serial ports (but not both) if - verbose mode is enabled. That is followed by various sensors data and the FLL and OCXO data. +/********************************************************************************************************** + This Arduino with STM32 Core package sketch implements a GPSDO with display options. It uses an SSD1306 + 128x64 I2C OLED display or SPI LCD. It reads the GPS for 1 or 5 seconds and copies the half-dozen or so + default NMEA sentences from the GPS to either the USB serial or Bluetooth serial ports (but not both) + if verbose mode is enabled. That is followed by various sensors data and the FLL and OCXO data. This is an example printout from a working GPSDO running firmware version v0.04e: Wait for GPS fix max. 1 second @@ -68,25 +69,28 @@ The USB serial port is set at 115200 baud, the Bluetooth serial port at 57600 baud, and the GPS serial port is set initially at 9600 baud then reconfigured at 38400 baud. -*******************************************************************************************************/ -/* Libraries required to compile: +**********************************************************************************************************/ +/* Libraries required to compile, depending on configured options: - TinyGPS++ - - U8g2/u8x8 graphics library + - U8g2/u8x8 graphics library, see https://github.com/olikraus/u8g2 - Adafruit AHTX0 - Adafruit BMP280 - - Adafruit MCP4725 12-bit DAC library + - Adafruit MCP4725 12-bit DAC library (not needed if using the recommended 16-bit PWM DAC) - movingAvg library, on STM32 architecture needs a simple patch to avoid warning during compilation + - Color LCD support requires the installation of the Adafruit ST7735 and ST7789 LCD library + and the Adafruit GFX library. - For commands parsing, uses SerialCommands library found here: - https://github.com/ppedro74/Arduino-SerialCommands + For commands parsing, uses SerialCommands library found here: + https://github.com/ppedro74/Arduino-SerialCommands And also requires the installation of support for the STM32 MCUs by installing the STM32duino - package (STM32 core version 2.0.0 or later). -*******************************************************************************************************/ + package (STM32 Core version 2.2.0 or later). +**********************************************************************************************************/ /* Commands implemented: - V : returns program name, version and author - F : flush ring buffers - d/u p/d 1/10 : adjust Vctl down/up PWM/DAC fine/coarse, example dp1 means decrease PWM by 1. + - SP : set PWM to value between 1 and 65535 /* Commands to be implemented: - L0 to L9 : select log levels @@ -96,7 +100,7 @@ - L8 : NMEA stream from GPS module only mode - L9 : NMEA + full status -/******************************************************************************************************* +/********************************************************************************************************** Program Operation - This program is a GPSDO with optional OLED display. It uses a small SSD1306 128x64 I2C OLED display. At startup the program starts checking the data coming from the GPS for a valid fix. It reads the GPS NMEA stream for 1/5 seconds and if there is no fix, prints a message on the @@ -104,7 +108,13 @@ NMEA stream coming from the GPS is copied to the serial monitor also. The DFLL is active as soon as the GPS starts providing a 1PPS pulse. The 10MHz OCXO is controlled by a voltage generated by either the 16-bit PWM or the MCP4725 I2C DAC; this voltage (Vctl) is adjusted once every 429 seconds. -*******************************************************************************************************/ +**********************************************************************************************************/ + +// Version 0.04i and later: Erik Kaashoek has suggested a 10s sampling rate for the 64-bit counter, to save RAM. +// This is work in progress, see the changes in Timer2_Capture_ISR. + +// Version 0.05d and later have the code for setup() and loop() moved to the very bottom of this file, and +// include support for an SPI LCD display, with code contributed by Badwater-Frank. // Enabling the INA219 sensor using the LapINA219 library causes the firmware to lock up after a few minutes // I have not identified the cause, it could be the library, or a hardware issue, or I have a bad sensor, etc. @@ -117,16 +127,24 @@ // 2. Refactor the setup and main loop functions to make them as simple as possible. #define Program_Name "GPSDO" -#define Program_Version "v0.05a" +#define Program_Version "v0.06e" #define Author_Name "André Balsa" -// Define hardware options -// ----------------------- -#define GPSDO_OLED // SSD1306 128x64 I2C OLED display +// Debug options +// ------------- +#define FastBootMode // reduce various delays during boot +#define TunnelModeTesting // reduce tunnel mode timeout + +// Hardware options +// ---------------- +// #define GPSDO_STM32F401 // use an STM32F401 Black Pill instead of STM32F411 (reduced RAM) +//#define GPSDO_OLED // SSD1306 128x64 I2C OLED display +#define GPSDO_LCD_ST7735 // ST7735 160x128 SPI LCD display +// #define GPSDO_LCD_ST7789 // ST7789 240x240 SPI LCD display // #define GPSDO_MCP4725 // MCP4725 I2C 12-bit DAC #define GPSDO_PWM_DAC // STM32 16-bit PWM DAC, requires two rc filters (2xr=20k, 2xc=10uF) -// #define GPSDO_AHT10 // I2C temperature and humidity sensor -// #define GPSDO_GEN_2kHz // generate 2kHz square wave test signal on pin PB9 using Timer 4 +//#define GPSDO_AHT10 // I2C temperature and humidity sensor +//#define GPSDO_GEN_2kHz_PB5 // generate 2kHz square wave test signal on pin PB5 using Timer 3 #define GPSDO_BMP280_SPI // SPI atmospheric pressure, temperature and altitude sensor // #define GPSDO_INA219 // INA219 I2C current and voltage sensor // #define GPSDO_BLUETOOTH // Bluetooth serial (HC-06 module) @@ -134,7 +152,11 @@ #define GPSDO_VDD // Vdd (nominal 3.3V) reads VREF internal ADC channel #define GPSDO_CALIBRATION // auto-calibration is enabled #define GPSDO_UBX_CONFIG // optimize u-blox GPS receiver configuration -#define GPSDO_VERBOSE_NMEA // GPS module NMEA stream echoed to USB serial xor Bluetooth serial +//#define GPSDO_VERBOSE_NMEA // GPS module NMEA stream echoed to USB serial xor Bluetooth serial +// #define GPSDO_PICDIV // generate a 1.2s synchronization pulse for the picDIV +#define OCXO_CTI_OSC5A2B02 // which type of OCXO is in hardware +//#define OCXO_NDK_ENE3311B // which type of OCXO is in hardware +#define GPSDO_ADF4351 // Includes // -------- @@ -153,10 +175,17 @@ const uint16_t waitFixTime = 1; // Maximum time in seconds waiting for a fix before reporting no fix / yes fix // Tested values 1 second and 5 seconds, 1s recommended - #include // https://github.com/JChristensen/movingAvg , needs simple patch // to avoid warning message during compilation +#ifdef GPSDO_PICDIV +#define picDIVsyncPin PB3 // digital output pin used to generate a 1.2s synchronization pulse for the picDIV +#endif // PICDIV + +#ifdef GPSDO_GEN_2kHz_PB5 +#define Test2kHzOutputPin PB5 // digital output pin used to output a test 2kHz square wave +#endif // GEN_2kHz_PB5 + #ifdef GPSDO_BLUETOOTH // UART RX TX HardwareSerial Serial2(PA3, PA2); // Serial connection to HC-06 Bluetooth module @@ -177,7 +206,7 @@ SerialCommands serial_commands_(&Serial, serial_command_buffer_, sizeof(serial_c TinyGPSPlus gps; // create the TinyGPS++ object #include // Hardware I2C library on STM32 - // Uses PB6 (SCL1) and PB7 (SDA1) on Black Pill + // Uses PB6 (SCL1) and PB7 (SDA1) on Black Pill for I2C1 #ifdef GPSDO_AHT10 #include // Adafruit AHTX0 library Adafruit_AHTX0 aht; // create object aht @@ -190,11 +219,38 @@ float ina219volt=0.0, ina219curr=0.0; TwoWire Wire3(PB4,PA8); // Second TwoWire instance for INA219 on SDA3/SCL3 (should be put somewhere more fitting but must stay global) #endif // INA219 +// OLED 0.96 SSD1306 128x64 #ifdef GPSDO_OLED #include // get library here > https://github.com/olikraus/u8g2 U8X8_SSD1306_128X64_NONAME_HW_I2C disp(U8X8_PIN_NONE); // use this line for standard 0.96" SSD1306 #endif // OLED +// LCD 1.8" ST7735 160x128 (tested by Badwater-Frank) +#ifdef GPSDO_LCD_ST7735 +#include // need this adapted for STM32F4xx/F411C: https://github.com/fpistm/Adafruit-GFX-Library/tree/Fix_pin_type +#include +//#include +#include +#define TFT_DC PA1 // note this pin assigment conflicts with the original schematic +#define TFT_CS PA2 +#define TFT_RST PA3 +// For 1.44" and 1.8" TFT with ST7735 use: +Adafruit_ST7735 disp = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); +#endif // LCD + +// LCD 1.8" ST7789 240x240 WARNING!!! NOT TESTED YET +#ifdef GPSDO_LCD +#include // need this adapted for STM32F4xx/F411C: https://github.com/fpistm/Adafruit-GFX-Library/tree/Fix_pin_type +#include +//#include +#include +#define TFT_DC PA1 // note this pin assigment conflicts with the original schematic +#define TFT_CS PA2 +#define TFT_RST PA3 +// For 1.8" TFT with ST7789 use: +Adafruit_ST7789 disp = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); +#endif // LCD + #ifdef GPSDO_MCP4725 #include // MCP4725 12-bit DAC Adafruit library Adafruit_MCP4725 dac; @@ -207,10 +263,30 @@ const uint16_t default_DAC_output = 2400; // 12-bit value, varies from OCXO to O uint16_t adjusted_DAC_output; // we adjust this value to "close the loop" of the DFLL when using the DAC #endif // MCP4725 +#ifdef GPSDO_ADF4351 +#include +// code: v0.4, 27 Aug 2016, Barry Chambers, G8AGN +// we want to use dedicated SPI interface for improved phase noise +// MOSI MISO SCLK +SPIClass mySPI2 (PB15, PB14, PB13); + +#define ADF435x_LE PB12 // sets pin for load enable +uint32_t regs[6]; + +#endif // ADF4351 + +#ifdef OCXO_NDK_ENE3311B const uint16_t default_PWM_output = 35585; // "ideal" 16-bit PWM value, varies with OCXO, RC network, and time and temperature - // 35585 for a second NDK ENE3311B + // 35585 for NDK ENE3311B +#endif // NDK ENE3311B +#ifdef OCXO_CTI_OSC5A2B02 +const uint16_t default_PWM_output = 43600; // "ideal" 16-bit PWM value, varies with OCXO, RC network, and time and temperature + // 43500 for CTI OSC5A2B02 +#endif // CTI OSC5A2B02 + uint16_t adjusted_PWM_output; // we adjust this value to "close the loop" of the DFLL when using the PWM volatile bool must_adjust_DAC = false; // true when there is enough data to adjust Vctl +char trendstr[5] = " ___"; // PWM trend string, set in the adjustVctlPWM() function #define VctlInputPin PB0 // ADC pin to read Vctl from DAC @@ -251,7 +327,8 @@ int16_t avgpwmVctl = 0; #include #define BMP280_CS (PA4) // SPI1 uses PA4, PA5, PA6, PA7 Adafruit_BMP280 bmp(BMP280_CS); // hardware SPI, use PA4 as Chip Select -const uint16_t PressureOffset = 1860; // that offset must be calculated for your sensor and location +const uint16_t PressureOffset = 0; // was 1860 that offset must be calculated for your sensor and location +const uint16_t AltitudeOffset = 50; // that offset must be calculated for your sensor and location float bmp280temp=0.0, bmp280pres=0.0, bmp280alti=0.0; // read sensor, save here #endif // BMP280_SPI @@ -283,6 +360,18 @@ char uptimestr[9] = "00:00:00"; // uptime string char updaysstr[5] = "000d"; // updays string // OCXO frequency measurement + +// special 10s sampling rate data structures (work in progress) +volatile uint16_t esamplingfactor = 10; // sample 64-bit counter every 10 seconds +volatile uint16_t esamplingcounter = 0; // counter from 0 to esamplingfactor +volatile bool esamplingflag = false; + +volatile uint64_t circbuf_esten64[11]; // 10+1 x10 seconds circular buffer, so 100 seconds +volatile uint32_t cbihes_newest = 0; // newest/oldest index +volatile bool cbHes_full = false; // flag set when buffer has filled up +volatile double avgesample = 0; // 100 seconds average with 10s sampling rate + +// other OCXO frequency measurement data structures volatile uint32_t lsfcount=0, previousfcount=0, calcfreqint=10000000; /* Moving average frequency variables Basically we store the counter captures for 10 and 100 seconds. @@ -302,7 +391,11 @@ volatile bool overflowErrorFlag = false; // flag set if there was an overflow volatile uint64_t circbuf_ten64[11]; // 10+1 seconds circular buffer volatile uint64_t circbuf_hun64[101]; // 100+1 seconds circular buffer volatile uint64_t circbuf_tho64[1001]; // 1,000+1 seconds circular buffer +#ifndef GPSDO_STM32F401 volatile uint64_t circbuf_tth64[10001]; // 10,000 + 1 seconds circular buffer +#else // STM32F401 has less RAM +volatile uint64_t circbuf_fth64[5001]; // 5,000 + 1 seconds circular buffer +#endif // GPSDO_STM32F401 volatile uint32_t cbiten_newest=0; // index to oldest, newest data volatile uint32_t cbihun_newest=0; @@ -312,12 +405,47 @@ volatile uint32_t cbitth_newest=0; volatile bool cbTen_full=false, cbHun_full=false, cbTho_full=false, cbTth_full=false; // flag when buffer full volatile double avgften=0, avgfhun=0, avgftho=0, avgftth=0; // average frequency calculated once the buffer is full volatile bool flush_ring_buffers_flag = true; // indicates ring buffers should be flushed + +// Miscellaneous data structures + +#ifdef GPSDO_PICDIV +volatile bool force_armpicDIV_flag = true; // indicates picDIV must be armed waiting to sync on next PPS from GPS module +#endif // PICDIV + volatile bool force_calibration_flag = true; // indicates GPSDO should start calibration sequence + volatile bool ocxo_needs_warming = true; // indicates OCXO needs to warm up a few minutes after power on -const uint16_t ocxo_warmup_time = 15; // ocxo warmup time in seconds; 15s for testing, 300s or 600s normal use +#ifdef FastBootMode + const uint16_t ocxo_warmup_time = 15; // ocxo warmup time in seconds; 15s for testing +#else + const uint16_t ocxo_warmup_time = 300; // ocxo warmup time in seconds; 300s or 600s normal use +#endif // FastBootMode volatile bool tunnel_mode_flag = false; // the GPSDO relays the information directly to and from the GPS module to the USB serial +#ifdef TunnelModeTesting const uint16_t tunnelSecs = 15; // tunnel mode timeout in seconds; 15s for testing, 300s or 600s normal use +#else +const uint16_t tunnelSecs = 300; // tunnel mode timeout in seconds; 15s for testing, 300s or 600s normal use +#endif // TunnelModeTesting + +// Miscellaneous functions +#ifdef GPSDO_ADF4351 +// subroutines for AFD4351 registers +void WriteRegister32(const uint32_t value) //Programm a register with 32 bits +{ + digitalWrite(ADF435x_LE, LOW); //pin 10 on synth chip + for (int i = 3; i >= 0; i--) // loop over 4 x 8bits + mySPI2.transfer((value >> 8 * i) & 0xFF); // shift, masking the byte and sending via SPI + digitalWrite(ADF435x_LE, HIGH); + digitalWrite(ADF435x_LE, LOW); +} + +void SetADF435x() // Programme all the registers of the ADF4350/1 +{ for (int i = 5; i >= 0; i--) // start with register R5 + WriteRegister32(regs[i]); +} +#endif // ADF4351 + // SerialCommands callback functions // This is the default handler, and gets called when no other command matches. @@ -359,6 +487,46 @@ void cmd_tunnel(SerialCommands* sender) sender->GetSerial()->println("Switching to USB Serial <-> GPS tunnel mode"); } +// called for SP (set PWM) command +void cmd_setPWM(SerialCommands* sender) +{ + int32_t pwm; + char* pwm_str = sender->Next(); + if (pwm_str == NULL) // check if a value was specified + { + sender->GetSerial()->println("No PWM value specified, using default"); + pwm = default_PWM_output; + adjusted_PWM_output = pwm; + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + strcpy(trendstr, " Cdf"); + } + else // check the value that was specified + { + pwm = atoi(pwm_str); // note atoi() returns zero if it cannot convert the string to a valid integer + if ((pwm >= 1) && (pwm <= 65535)) // check if the value specified is positive 16-bit integer + { + sender->GetSerial()->print("Setting PWM value "); // if yes, set the value + sender->GetSerial()->println(pwm); + adjusted_PWM_output = pwm; + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + strcpy(trendstr, " Cst"); + } + else // incorrect value specified, print error message + { + sender->GetSerial()->println("PWM value must be positive integer between 1 and 65535, leaving unchanged"); + } + } +} + +// PWM direct control commands (up/down) +// ------------------------------------- +// called for up1 (increase PWM 1 bit) command +void cmd_up1(SerialCommands* sender) +{ + adjusted_PWM_output = adjusted_PWM_output + 1; + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + sender->GetSerial()->println("increased PWM 1 bit"); +} // called for up10 (increase PWM 10 bits) command void cmd_up10(SerialCommands* sender) { @@ -366,14 +534,12 @@ void cmd_up10(SerialCommands* sender) analogWrite(VctlPWMOutputPin, adjusted_PWM_output); sender->GetSerial()->println("increased PWM 10 bits"); } -// called for ud10 (increase DAC 10 bits) command -void cmd_ud10(SerialCommands* sender) +// called for dp1 (decrease PWM 1 bit) command +void cmd_dp1(SerialCommands* sender) { - #ifdef GPSDO_MCP4725 - adjusted_DAC_output = adjusted_DAC_output + 10; - dac.setVoltage(adjusted_DAC_output, false); - sender->GetSerial()->println("increased DAC 10 bits"); - #endif // MCP4725 + adjusted_PWM_output = adjusted_PWM_output - 1; + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + sender->GetSerial()->println("decreased PWM 1 bit"); } // called for dp10 (decrease PWM 10 bits) command void cmd_dp10(SerialCommands* sender) @@ -382,67 +548,63 @@ void cmd_dp10(SerialCommands* sender) analogWrite(VctlPWMOutputPin, adjusted_PWM_output); sender->GetSerial()->println("decreased PWM 10 bits"); } -// called for dd10 (decrease DAC 10 bits) command -void cmd_dd10(SerialCommands* sender) -{ - #ifdef GPSDO_MCP4725 - adjusted_DAC_output = adjusted_DAC_output - 10; - dac.setVoltage(adjusted_DAC_output, false); - sender->GetSerial()->println("decreased DAC 10 bits"); - #endif // MCP4725 -} -// called for up1 (increase PWM 1 bit) command -void cmd_up1(SerialCommands* sender) -{ - adjusted_PWM_output = adjusted_PWM_output + 1; - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); - sender->GetSerial()->println("increased PWM 1 bit"); -} +#ifdef GPSDO_MCP4725 +// MCP4725 DAC direct control commands (up/down) +// --------------------------------------------- // called for ud1 (increase DAC 1 bit) command void cmd_ud1(SerialCommands* sender) { - #ifdef GPSDO_MCP4725 adjusted_DAC_output = adjusted_DAC_output + 1; dac.setVoltage(adjusted_DAC_output, false); sender->GetSerial()->println("increased DAC 1 bit"); - #endif // MCP4725 } -// called for dp1 (decrease PWM 1 bit) command -void cmd_dp1(SerialCommands* sender) +// called for ud10 (increase DAC 10 bits) command +void cmd_ud10(SerialCommands* sender) { - adjusted_PWM_output = adjusted_PWM_output - 1; - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); - sender->GetSerial()->println("decreased PWM 1 bit"); + adjusted_DAC_output = adjusted_DAC_output + 10; + dac.setVoltage(adjusted_DAC_output, false); + sender->GetSerial()->println("increased DAC 10 bits"); } // called for dd1 (decrease DAC 1 bit) command void cmd_dd1(SerialCommands* sender) { - #ifdef GPSDO_MCP4725 adjusted_DAC_output = adjusted_DAC_output - 1; dac.setVoltage(adjusted_DAC_output, false); sender->GetSerial()->println("decreased DAC 1 bit"); - #endif // MCP4725 } +// called for dd10 (decrease DAC 10 bits) command +void cmd_dd10(SerialCommands* sender) +{ + adjusted_DAC_output = adjusted_DAC_output - 10; + dac.setVoltage(adjusted_DAC_output, false); + sender->GetSerial()->println("decreased DAC 10 bits"); +} +#endif // MCP4725 -//Note: Commands are case sensitive -SerialCommand cmd_version_("V", cmd_version); -SerialCommand cmd_flush_("F", cmd_flush); -SerialCommand cmd_calibrate_("C", cmd_calibrate); -SerialCommand cmd_tunnel_("T", cmd_tunnel); -// coarse adjust +// SerialCommand commands +// Note: Commands are case sensitive +SerialCommand cmd_version_("V", cmd_version); // print program name and version +SerialCommand cmd_flush_("F", cmd_flush); // flush ring buffers +SerialCommand cmd_calibrate_("C", cmd_calibrate); // force calibration +SerialCommand cmd_tunnel_("T", cmd_tunnel); // activate tunnel mode +SerialCommand cmd_setPWM_("SP", cmd_setPWM); // note this command takes a 16-bit PWM value (1 to 65535) as an argument +// 16-bit PWM commands +SerialCommand cmd_up1_("up1", cmd_up1); SerialCommand cmd_up10_("up10", cmd_up10); -SerialCommand cmd_ud10_("ud10", cmd_ud10); +SerialCommand cmd_dp1_("dp1", cmd_dp1); SerialCommand cmd_dp10_("dp10", cmd_dp10); -SerialCommand cmd_dd10_("dd10", cmd_dd10); -// fine adjust -SerialCommand cmd_up1_("up1", cmd_up1); +#ifdef GPSDO_MCP4725 +// MCP4725 DAC commands SerialCommand cmd_ud1_("ud1", cmd_ud1); -SerialCommand cmd_dp1_("dp1", cmd_dp1); +SerialCommand cmd_ud10_("ud10", cmd_ud10); SerialCommand cmd_dd1_("dd1", cmd_dd1); +SerialCommand cmd_dd10_("dd10", cmd_dd10); +#endif // MCP4725 // loglevel uint8_t loglevel = 7; // see commands comments for log level definitions, default is 7 + // note log levels are not implemented yet // Interrupt service routines @@ -654,1059 +816,1460 @@ void flushringbuffers(void) { flush_ring_buffers_flag = false; // clear flag } -void setup() +void pinModeAF(int ulPin, uint32_t Alternate) { - // Wait 1 second for things to stabilize - delay(1000); - - // Setup 2Hz Timer - HardwareTimer *tim2Hz = new HardwareTimer(TIM9); - - // configure blueledpin in output mode - pinMode(blueledpin, OUTPUT); + int pn = digitalPinToPinName(ulPin); - // configure yellow_led_pin in output mode - pinMode(yellowledpin, OUTPUT); - - tim2Hz->setOverflow(2, HERTZ_FORMAT); // 2 Hz - tim2Hz->attachInterrupt(Timer_ISR_2Hz); - tim2Hz->resume(); + if (STM_PIN(pn) < 8) { + LL_GPIO_SetAFPin_0_7( get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate); + } else { + LL_GPIO_SetAFPin_8_15(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate); + } - // Setup serial interfaces - Serial.begin(115200); // USB serial - Serial1.begin(9600); // Hardware serial 1 to GPS module - #ifdef GPSDO_BLUETOOTH - // HC-06 module baud rate factory setting is 9600, - // use separate program to set baud rate to 115200 - Serial2.begin(BT_BAUD); // Hardware serial 2 to Bluetooth module - #endif // BLUETOOTH + LL_GPIO_SetPinMode(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), LL_GPIO_MODE_ALTERNATE); +} - // setup commands parser - serial_commands_.SetDefaultHandler(cmd_unrecognized); - serial_commands_.AddCommand(&cmd_version_); - serial_commands_.AddCommand(&cmd_flush_); - serial_commands_.AddCommand(&cmd_calibrate_); - serial_commands_.AddCommand(&cmd_tunnel_); +#ifdef GPSDO_UBX_CONFIG +void ubxconfig() // based on code by Brad Burleson +{ + // send UBX commands to set optimal configuration for GPSDO use + // we are going to change a single parameter from default by + // setting the navigation mode to "stationary" - serial_commands_.AddCommand(&cmd_up10_); - serial_commands_.AddCommand(&cmd_ud10_); - serial_commands_.AddCommand(&cmd_dp10_); - serial_commands_.AddCommand(&cmd_dd10_); + bool gps_set_success = false; // flag setting GPS configuration success - serial_commands_.AddCommand(&cmd_up1_); - serial_commands_.AddCommand(&cmd_ud1_); - serial_commands_.AddCommand(&cmd_dp1_); - serial_commands_.AddCommand(&cmd_dd1_); + // This UBX command sets stationary mode and confirms it + Serial.println("Setting u-Blox M8 receiver navigation mode to stationary: "); + uint8_t setNav[] = { + 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53}; + while(!gps_set_success) + { + sendUBX(setNav, sizeof(setNav)/sizeof(uint8_t)); + Serial.println(); + Serial.println("UBX command sent, waiting for UBX ACK... "); + gps_set_success=getUBX_ACK(setNav); + if (gps_set_success) + Serial.println("Success: UBX ACK received! "); + else + Serial.println("Oops, something went wrong here... "); + } +} + +// Send a byte array of UBX protocol to the GPS +void sendUBX(uint8_t *MSG, uint8_t len) { + for(int i=0; i 9) { + // All packets in order! + Serial.println(" (SUCCESS!)"); + return true; + } + + // Timeout if no valid response in 3 seconds + if (millis() - startTime > 3000) { + Serial.println(" (FAILED!)"); + return false; + } + // Make sure data is available to read + if (Serial1.available()) { + b = Serial1.read(); + + // Check that bytes arrive in sequence as per expected ACK packet + if (b == ackPacket[ackByteID]) { + ackByteID++; + Serial.print(b, HEX); + } + else { + ackByteID = 0; // Reset and look again, invalid order + } + + } + } +} +#endif // UBX_CONFIG + +void tunnelgps() +// GPSDO tunnel mode operation +{ + #ifdef GPSDO_BLUETOOTH // print calibrating started message to either + Serial2.println(); // Bluetooth serial xor USB serial + Serial2.print(F("Entering tunnel mode...")); + Serial2.println(); + #else Serial.println(); - Serial.println(F(Program_Name)); - Serial.println(F(Program_Version)); + Serial.print(F("Entering tunnel mode...")); Serial.println(); + #endif // BLUETOOTH - #ifdef GPSDO_UBX_CONFIG - // Reconfigure the GPS receiver - // first send the $PUBX configuration commands - delay(3000); // give everything a moment to stabilize - Serial.println("GPS checker program started"); - Serial.println("Sending $PUBX commands to GPS"); - // first send the $PUBG configuration commands - Serial1.print("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); // disable all VTG messages (useless since we are stationary) - Serial1.print("$PUBX,41,1,0003,0003,38400,0*24\r\n"); // set GPS baud rate to 38400 in/out protocols NMEA+UBX - Serial1.flush(); // empty the buffer - delay(100); // give it a moment - Serial1.end(); // close serial port - Serial1.begin(38400); // re-open at new rate - delay(3000); - // second, send the proprietary UBX configuration commands - Serial.println("Now sending UBX commands to GPS"); - ubxconfig(); - #endif // UBX_CONFIG - - // Initialize I2C - Wire.begin(); - // try setting a higher I2C clock speed - Wire.setClock(400000L); + // tunnel mode operation goes here + uint32_t endtunnelmS = millis() + (tunnelSecs * 1000); + uint8_t GPSchar; + uint8_t PCchar; + while (millis() < endtunnelmS) + { + if (Serial1.available() > 0) + { + GPSchar = Serial1.read(); + Serial.write(GPSchar); // echo NMEA stream to USB serial + } + if (Serial.available() > 0) + { + PCchar = Serial.read(); + Serial1.write(PCchar); // echo PC stream to GPS serial + } + } + // tunnel mode operation ends here - #ifdef GPSDO_OLED - // Setup OLED I2C display - // Note that u8x8 library initializes I2C hardware interface - disp.setBusClock(400000L); // try to avoid display locking up - disp.begin(); - disp.setFont(u8x8_font_chroma48medium8_r); - disp.clear(); - disp.setCursor(0, 0); - disp.print(F(Program_Name)); - disp.print(F(" - ")); - disp.print(F(Program_Version)); - #endif // OLED + #ifdef GPSDO_BLUETOOTH // print calibrating started message to either + Serial2.println(); // Bluetooth serial xor USB serial + Serial2.print(F("Tunnel mode exited.")); + Serial2.println(); + #else + Serial.println(); + Serial.print(F("Tunnel mode exited.")); + Serial.println(); + #endif // BLUETOOTH - #ifdef GPSDO_INA219 - ina219.begin(&Wire3); // calibrates ina219 sensor Edit: start the sensor on the third I2C controller - Wire.setClock(400000L); - #endif // INA219 + tunnel_mode_flag = false; // reset flag, exit tunnel mode +} +void docalibration() +// OCXO Vctl calibration routine: find an approximate value for Vctl +{ + unsigned long startWarmup = millis(); // we need a rough timer + if (ocxo_needs_warming) { + // spend a few seconds/minutes here waiting for the OCXO to warm + // show countdown timer on OLED display + // and report on either USB serial or Bluetooth serial + // Note: during calibration the GPSDO does not accept any commands + uint16_t countdown = ocxo_warmup_time; + // put som content into the displays + #ifdef GPSDO_OLED + disp.clear(); // display warmup message on OLED + disp.setCursor(0, 0); + disp.print(F(Program_Name)); + disp.print(F(" - ")); + disp.print(F(Program_Version)); + disp.setCursor(0, 2); + disp.print(F("OCXO warming up")); + disp.setCursor(0, 3); + disp.print(F("Please wait")); + #endif // OLED + #ifdef GPSDO_LCD_ST7735 + disp.fillScreen(ST7735_BLACK); // display warmup message on LCD ST7735 + disp.setTextColor(ST7735_YELLOW, ST7735_BLACK); + disp.setTextSize(2); + disp.setCursor(0, 0); + disp.print(F(" ")); + disp.print(F(Program_Name)); + // + disp.setTextColor(ST7735_BLUE, ST7735_BLACK); + disp.setTextSize(1); + disp.setCursor(115, 5); + disp.print(F(Program_Version)); + // + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); + disp.setCursor(0, 24); + disp.print(F("OCXO warming up")); + disp.setCursor(0, 32); + disp.print(F("Please wait")); + #endif // LCD_ST7735 - #ifdef GPSDO_MCP4725 - // Setup I2C DAC, read voltage on PB0 - adjusted_DAC_output = default_DAC_output; // initial DAC value - dac.begin(0x60); - // Output Vctl to DAC, but do not write to DAC EEPROM - dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 so 2048 should be 1/2 Vdd = approx. 1.65V - #endif // MCP4725 - analogReadResolution(12); // make sure we read 12 bit values when we read from PB0 - Wire.setClock(400000L); + while (countdown) { + yellow_led_state = 2; // blink yellow LED + + #ifdef GPSDO_OLED + disp.setCursor(5, 4); + disp.print(countdown); + disp.print(F("s ")); + #endif // OLED + + #ifdef GPSDO_LCD_ST7735 + disp.setCursor(0, 40); + disp.print(countdown); + disp.print(F("s ")); + #endif // LCD_ST7735 + + #ifdef GPSDO_BLUETOOTH // print warming up message to either + Serial2.println(); // Bluetooth serial xor USB serial + Serial2.print(F("Warming up ")); + Serial2.print(countdown); + Serial2.println(F("s")); + #else + Serial.println(); + Serial.print(F("Warming up ")); + Serial.print(countdown); + Serial.println(F("s")); + #endif // BLUETOOTH - #ifdef GPSDO_AHT10 - if (! aht.begin()) { - Serial.println("Could not find AHT10? Check wiring"); - while (1) delay(10); + // do nothing for 1s + delay(1000); + countdown--; + } + ocxo_needs_warming = false; // reset flag, next "hot" calibration skips ocxo warmup } - Serial.println("AHT10 found"); - Wire.setClock(400000L); - #endif // AHT10 - - // generate a 2kHz square wave on PB9 PWM pin, using Timer 4 channel 4 - // PB9 is Timer 4 Channel 4 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c - analogWrite(VctlPWMOutputPin, 127); // configures PB9 as PWM output pin at default frequency and resolution - analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz - analogWriteResolution(16); // set PWM resolution to 16 bits (the maximum for the STM32F411CEU6) - adjusted_PWM_output = default_PWM_output; // initial PWM value - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); // 32767 for 16 bits -> 50% duty cycle so a square wave + // proceed with calibration + #ifdef GPSDO_BLUETOOTH // print calibrating started message to either + Serial2.println(); // Bluetooth serial xor USB serial + Serial2.print(F("Calibrating...")); + Serial2.println(); + #else + Serial.println(); + Serial.print(F("Calibrating...")); + Serial.println(); + #endif // BLUETOOTH - #ifdef GPSDO_BMP280_SPI - // Initialize BMP280 - if (!bmp.begin()) { - Serial.println(F("Could not find a valid BMP280 sensor, check wiring or " - "try a different address!")); - while (1) delay(10); - } + #ifdef GPSDO_OLED + disp.clear(); // display calibrating message on OLED + disp.setCursor(0, 0); + disp.print(F(Program_Name)); + disp.print(F(" - ")); + disp.print(F(Program_Version)); + disp.setCursor(0, 2); + disp.print(F("Calibrating...")); + disp.setCursor(0, 3); + disp.print(F("Please wait")); + #endif // OLED - // Default settings from datasheet. - bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ - Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ - Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ - Adafruit_BMP280::FILTER_X16, /* Filtering. */ - Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ - #endif // BMP280_SPI - - Serial.println(F("GPSDO Starting")); - Serial.println(); + #ifdef GPSDO_LCD_ST7735 + disp.fillRect(0, 19, 160, 108, ST7735_BLACK); // clear display if command is used + disp.setCursor(0, 32); + disp.print(F("Warm up...done")); + disp.setCursor(0, 40); + disp.print(F("Calibrating... ")); // spaces needed to overwrite previous + disp.setCursor(0, 48); + disp.print(F("Please wait")); + #endif // LCD_ST7735 - // Setup and start Timer 2 which measures OCXO frequency - // setup pin used as ETR (10MHz external clock from OCXO) - pinMode(PA15, INPUT_PULLUP); // setup PA15 as input pin - pinModeAF(PA15, GPIO_AF1_TIM2); // setup PA15 as TIM2 channel 1 / ETR + /* The calibration algorithm + * The objective of the calibration is to find the approximate Vctl to obtain + * 10MHz +/- 0.1Hz. + * + * we can use either a PID algorithm or a simple linear interpolation algorithm + * + * The following describes a simple linear interpolation algorithm + * + * We first output 1.5V for the DAC or PWM, wait 30 seconds and note the 10s frequency average. + * Next we output 2.5V for the DAC or PWM, wait 30 seconds and note the 10s frequency average. + * Now we calculate the Vctl for 10MHz +/- 0.1Hz using linear interpolation between the two points. + */ + // for 12-bit DAC + // 1.5V for DAC = 4096 x (1.5 / 3.3) = 1862 results in frequency f1 = 10MHz + e1 + // 2.5V for DAC = 4096 x (2.5 / 3.3) = 3103 results in frequency f2 = 10MHz + e2 + // for 16-bit PWM + // 1.5V for PWM = 65536 x (1.5 / 3.2) = 30720 results in frequency f1 = 10MHz + e1 + // 2.5V for PWM = 65536 x (2.5 / 3.2) = 51200 results in frequency f2 = 10MHz + e2 + // where f2 > f1 (most OCXOs have positive slope). + double f1, f2, e1, e2; + // make sure we have a fix and data + while (!cbTen_full) delay(1000); + // measure frequency for Vctl=1.5V + Serial.println(F("set PWM 1.5V, wait 15s")); + // + #if defined GPSDO_OLED || defined GPSDO_LCD_ST7735 + #ifdef GPSDO_OLED + disp.setCursor(0, 3); + disp.clearLine(3); + #endif // OLED + #ifdef GPSDO_LCD_ST7735 + disp.setCursor(0, 56); + #endif // LCD + disp.print(F("Vctl: 1.5V / 15s")); + #endif // OLED | LCD + // + analogWrite(VctlPWMOutputPin, 30720); + delay(15000); + f1 = avgften; + // + Serial.print(F("f1 (average frequency for 1.5V Vctl): ")); + Serial.print(f1,1); + Serial.println(F(" Hz")); + // + #if defined GPSDO_OLED || defined GPSDO_LCD_ST7735 + #ifdef GPSDO_OLED + disp.setCursor(0, 4); + disp.clearLine(4); + disp.setCursor(0, 5); + disp.clearLine(5); + #endif // OLED + #ifdef GPSDO_LCD_ST7735 + disp.setCursor(0, 64); + #endif // LCD + disp.print(F("F1:")); + disp.print(f1,1); + disp.print(F(" Hz")); + disp.setCursor(0, 80); + #endif // OLED | LCD + disp.print(F("Vctl: 2.5V / 15s")); + // + // make sure we have a fix and data again + while (!cbTen_full) delay(1000); + // measure frequency for Vctl=2.5V + Serial.println(F("set PWM 2.5V, wait 15s")); + analogWrite(VctlPWMOutputPin, 51200); + delay(15000); + f2 = avgften; + // + Serial.print(F("f2 (average frequency for 2.5V Vctl): ")); + Serial.print(f2,1); + Serial.println(F(" Hz")); + // + // slope s is (f2-f1) / (51200-30720) for PWM + // So F=10MHz +/- 0.1Hz for PWM = 30720 - (e1 / s) + // set Vctl + // adjusted_PWM_output = formula + adjusted_PWM_output = 30720 - ((f1 - 10000000.0) / ((f2 - f1) / 20480)); + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + // + // calibration done, perform some outputs to serial and display + // + Serial.print(F("Calculated PWM: ")); + Serial.println(adjusted_PWM_output); + // + #if defined GPSDO_OLED || defined GPSDO_LCD_ST7735 + #ifdef GPSDO_OLED + disp.setCursor(0, 6); + disp.clearLine(6); + #endif // OLED + #ifdef GPSDO_LCD_ST7735 + disp.setCursor(0, 88); + #endif // LCD + disp.print(F("F2:")); + disp.print(f2,1); + disp.println(F(" Hz")); + disp.println(F(" ")); + disp.print(F("...Calibration done.")); - // setup Timer 2 in input capture mode, active input channel 3 - // to latch counter value on rising edge - - // Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. - HardwareTimer *FreqMeasTim = new HardwareTimer(TIM2); + #endif // OLED | LCD - // Configure rising edge detection to measure frequency - FreqMeasTim->setMode(3, TIMER_INPUT_CAPTURE_RISING, PB10); + #ifdef GPSDO_BLUETOOTH // print calibration finished message to either + Serial2.println(); // Bluetooth serial xor USB serial + Serial2.print(F("Calibration done.")); + Serial2.println(); + #else + Serial.println(); + Serial.print(F("Calibration done.")); + Serial.println(); + #endif // BLUETOOTH - // Configure 32-bit auto-reload register (ARR) with maximum possible value - TIM2->ARR = 0xffffffff; // count to 2^32, then wraparound (approximately every 429 seconds) + delay(3000); // see values on display for this time - // Configure the ISR for the timer overflow interrupt - FreqMeasTim->attachInterrupt(Timer2_Overflow_ISR); + #ifdef GPSDO_OLED + disp.clear(); + #endif // OLED + + #ifdef GPSDO_LCD_ST7735 + disp.fillScreen(ST7735_BLACK); + #endif // LCD_ST7735 - // Configure the ISR for the 1PPS capture - FreqMeasTim->attachInterrupt(3, Timer2_Capture_ISR); + force_calibration_flag = false; // reset flag, calibration done +} // end of docalibration() - // select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register - TIM2->SMCR |= TIM_SMCR_ECE; // 0x4000 - - // start the timer - FreqMeasTim->resume(); - - // Initialize movingAvg objects (note this allocates space on heap) and immediately read 1st value - #ifdef GPSDO_VDD - avg_adcVdd.begin(); - adcVdd = analogRead(AVREF); - avgVdd = avg_adcVdd.reading(adcVdd); - # endif // VDD - - #ifdef GPSDO_VCC - avg_adcVcc.begin(); - adcVcc = analogRead(VccDiv2InputPin); - avgVcc = avg_adcVcc.reading(adcVcc); - # endif // VCC - - avg_dacVctl.begin(); - dacVctl = analogRead(VctlInputPin); - avgdacVctl = avg_dacVctl.reading(dacVctl); - - avg_pwmVctl.begin(); - pwmVctl = analogRead(VctlPWMInputPin); - avgpwmVctl = avg_pwmVctl.reading(pwmVctl); - - startGetFixmS = millis(); - - // setup done -} - -void pinModeAF(int ulPin, uint32_t Alternate) -{ - int pn = digitalPinToPinName(ulPin); - - if (STM_PIN(pn) < 8) { - LL_GPIO_SetAFPin_0_7( get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate); - } else { - LL_GPIO_SetAFPin_8_15(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate); - } - - LL_GPIO_SetPinMode(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), LL_GPIO_MODE_ALTERNATE); -} - -#ifdef GPSDO_UBX_CONFIG -void ubxconfig() // based on code by Brad Burleson +#ifdef GPSDO_MCP4725 +void adjustVctlDAC() +// This should reach a stable DAC output value / a stable 10000000.00 frequency +// after an hour or so { - // send UBX commands to set optimal configuration for GPSDO use - // we are going to change a single parameter from default by - // setting the navigation mode to "stationary" - - bool gps_set_success = false; // flag setting GPS configuration success - - // This UBX command sets stationary mode and confirms it - Serial.println("Setting u-Blox M8 receiver navigation mode to stationary: "); - uint8_t setNav[] = { - 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53}; - while(!gps_set_success) - { - sendUBX(setNav, sizeof(setNav)/sizeof(uint8_t)); - Serial.println(); - Serial.println("UBX command sent, waiting for UBX ACK... "); - gps_set_success=getUBX_ACK(setNav); - if (gps_set_success) - Serial.println("Success: UBX ACK received! "); - else - Serial.println("Oops, something went wrong here... "); - } -} - -// Send a byte array of UBX protocol to the GPS -void sendUBX(uint8_t *MSG, uint8_t len) { - for(int i=0; i= 10000000.01) { + if (avgfhun >= 10000000.10) { + // decrease DAC by ten bits = coarse + adjusted_DAC_output = adjusted_DAC_output - 10; + } else { + // decrease DAC by one bit = fine + adjusted_DAC_output--; + } + dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 + } + // or increase frequency + else if (avgfhun <= 9999999.99) { + if (avgfhun <= 9999999.90) { + // increase DAC by ten bits = coarse + adjusted_DAC_output = adjusted_DAC_output + 10; + } else { + // increase DAC by one bit = fine + adjusted_DAC_output++; + } + dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 } - Serial1.println(); + // or do nothing because avgfrequency over last 100s is 10000000.00Hz + must_adjust_DAC = false; // clear flag and we are done } +#endif // MCP4725 -// Calculate expected UBX ACK packet and parse UBX response from GPS -boolean getUBX_ACK(uint8_t *MSG) { - uint8_t b; - uint8_t ackByteID = 0; - uint8_t ackPacket[10]; - unsigned long startTime = millis(); - Serial.print(" * Reading ACK response: "); - - // Construct the expected ACK packet - ackPacket[0] = 0xB5; // header - ackPacket[1] = 0x62; // header - ackPacket[2] = 0x05; // class - ackPacket[3] = 0x01; // id - ackPacket[4] = 0x02; // length - ackPacket[5] = 0x00; - ackPacket[6] = MSG[2]; // ACK class - ackPacket[7] = MSG[3]; // ACK id - ackPacket[8] = 0; // CK_A - ackPacket[9] = 0; // CK_B - - // Calculate the checksums - for (uint8_t i=2; i<8; i++) { - ackPacket[8] = ackPacket[8] + ackPacket[i]; - ackPacket[9] = ackPacket[9] + ackPacket[8]; - } - - while (1) { - - // Test for success - if (ackByteID > 9) { - // All packets in order! - Serial.println(" (SUCCESS!)"); - return true; +#ifdef GPSDO_PWM_DAC +void adjustVctlPWM() +// This should reach a stable PWM output value / a stable 10000000.00 frequency +// after an hour or so, and 10000000.000 after eight hours or so +{ + // check first if we have the data, then do ultrafine and veryfine frequency + // adjustment, when we are very close + // ultimately the objective is 10000000.000 over the last 1000s (16min40s) + if ((cbTho_full) && (avgftho >= 9999999.990) && (avgftho <= 10000000.010)) { + + // decrease frequency; 1000s based + if (avgftho >= 10000000.001) { + if (avgftho >= 10000000.005) { + // decrease PWM by 5 bits = very fine + adjusted_PWM_output = adjusted_PWM_output - 5; + strcpy(trendstr, " vf-"); + } + else { + // decrease PWM by one bit = ultrafine + adjusted_PWM_output = adjusted_PWM_output - 1; + strcpy(trendstr, " uf-"); + } } - - // Timeout if no valid response in 3 seconds - if (millis() - startTime > 3000) { - Serial.println(" (FAILED!)"); - return false; + // or increase frequency; 1000s based + else if (avgftho <= 9999999.999) { + if (avgftho <= 9999999.995) { + // increase PWM by 5 bits = very fine + adjusted_PWM_output = adjusted_PWM_output + 5; + strcpy(trendstr, " vf+"); + } + else { + // increase PWM by one bit = ultrafine + adjusted_PWM_output = adjusted_PWM_output + 1; + strcpy(trendstr, " uf+"); + } } - - // Make sure data is available to read - if (Serial1.available()) { - b = Serial1.read(); - - // Check that bytes arrive in sequence as per expected ACK packet - if (b == ackPacket[ackByteID]) { - ackByteID++; - Serial.print(b, HEX); - } - else { - ackByteID = 0; // Reset and look again, invalid order + } + ///// next check the 100s values in second place because we are too far off + // decrease frequency; 100s based + else if (avgfhun >= 10000000.01) { + if (avgfhun >= 10000000.10) { + // decrease PWM by 100 bits = coarse + adjusted_PWM_output = adjusted_PWM_output - 100; + strcpy(trendstr, " c- "); } - + else { + // decrease PWM by ten bits = fine + adjusted_PWM_output = adjusted_PWM_output - 10; + strcpy(trendstr, " f- "); + } + } + // or increase frequency; 100s based + else if (avgfhun <= 9999999.99) { + if (avgfhun <= 9999999.90) { + // increase PWM by 100 bits = coarse + adjusted_PWM_output = adjusted_PWM_output + 100; + strcpy(trendstr, " c+ "); + } + else { + // increase PWM by ten bits = fine + adjusted_PWM_output = adjusted_PWM_output + 10; + strcpy(trendstr, " f+ "); } } -} -#endif // UBX_CONFIG + else { // here we keep setting, because it is exact 10000000.000MHz + strcpy(trendstr, " hit"); + } + // write the computed value to PWM + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + must_adjust_DAC = false; // clear flag and we are done + } // end adjustVctlPWM +#endif // GPSDO_PWM_DAC -void loop() -{ - serial_commands_.ReadSerial(); // process any command from either USB serial (usually - // the Arduino monitor) xor Bluetooth serial (e.g. a smartphone) - if (force_calibration_flag) docalibration(); else - if (tunnel_mode_flag) tunnelgps(); else - - if (gpsWaitFix(waitFixTime)) // wait up to waitFixTime seconds for fix, returns true if we have a fix - { - #ifdef GPSDO_BLUETOOTH - Serial2.println(); - Serial2.println(); - Serial2.print(F("Fix time ")); - Serial2.print(endFixmS - startGetFixmS); - Serial2.println(F("mS")); - #else - Serial.println(); - Serial.println(); - Serial.print(F("Fix time ")); - Serial.print(endFixmS - startGetFixmS); - Serial.println(F("mS")); - #endif // BLUETOOTH +bool gpsWaitFix(uint16_t waitSecs) +{ + // waits a specified number of seconds for a fix, + // returns true as soon as fix available or false on timeout - GPSLat = gps.location.lat(); - GPSLon = gps.location.lng(); - GPSAlt = gps.altitude.meters(); - GPSSats = gps.satellites.value(); - GPSHdop = gps.hdop.value(); + uint32_t endwaitmS; + uint8_t GPSchar; - hours = gps.time.hour(); - mins = gps.time.minute(); - secs = gps.time.second(); - day = gps.date.day(); - month = gps.date.month(); - year = gps.date.year(); + #ifdef GPSDO_BLUETOOTH + Serial2.println(); + //Serial2.print(F("Wait for GPS fix max. ")); + //Serial2.print(waitSecs); + //if (waitSecs > 1) Serial2.println(F(" seconds")); else Serial2.println(F(" second")); + #else + Serial.println(); + //Serial.print(F("Wait for GPS fix max. ")); + //Serial.print(waitSecs); + //if (waitSecs > 1) Serial.println(F(" seconds")); else Serial.println(F(" second")); + #endif // Bluetooth + endwaitmS = millis() + (waitSecs * 1000); - if (must_adjust_DAC && cbHun_full) // in principle just once every 429 seconds, and only if we have valid data + while (millis() < endwaitmS) + { + if (Serial1.available() > 0) { - // use different algorithms for 12-bit I2C DAC and STM32 16-bit PWM DAC - #ifdef GPSDO_MCP4725 - adjustVctlDAC(); - #endif // MCP4725 - #ifdef GPSDO_PWM_DAC - adjustVctlPWM(); - #endif // PWM_DAC + GPSchar = Serial1.read(); + gps.encode(GPSchar); + #ifdef GPSDO_VERBOSE_NMEA + #ifdef GPSDO_BLUETOOTH + Serial2.write(GPSchar); // echo NMEA stream to Bluetooth serial + #else + Serial.write(GPSchar); // echo NMEA stream to USB serial + #endif // Bluetooth + #endif // VERBOSE_NMEA } - dacVctl = analogRead(VctlInputPin); // read the Vctl voltage output by the DAC - avgdacVctl = avg_dacVctl.reading(dacVctl); // average it - - pwmVctl = analogRead(VctlPWMInputPin); // read the filtered Vctl voltage output by the PWM - avgpwmVctl = avg_pwmVctl.reading(pwmVctl); // average it - - #ifdef GPSDO_VCC - adcVcc = analogRead(VccDiv2InputPin); // read Vcc - avgVcc = avg_adcVcc.reading(adcVcc); // average it - # endif // VCC - - #ifdef GPSDO_VDD - adcVdd = analogRead(AVREF); // Vdd is read internally as Vref - avgVdd = avg_adcVdd.reading(adcVdd); // average it - #endif // VDD + if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.date.isUpdated()) + { + endFixmS = millis(); //record the time when we got a GPS fix + return true; + } + } + return false; +} - #ifdef GPSDO_BMP280_SPI - bmp280temp = bmp.readTemperature(); // read bmp280 sensor, save values - bmp280pres = bmp.readPressure(); - bmp280alti = bmp.readAltitude(); - #endif // BMP280_SPI - #ifdef GPSDO_INA219 - ina219volt = ina219.busVoltage(); // read ina219 sensor, save values - ina219curr = ina219.shuntCurrent(); - #endif // INA219 +void printGPSDOstats(Stream &Serialx) +{ + float tempfloat; - uptimetostrings(); // get updaysstr and uptimestr + Serialx.print(F("Uptime: ")); + Serialx.print(updaysstr); + Serialx.print(F(" ")); + Serialx.println(uptimestr); + + Serialx.println(F("New GPS Fix: ")); - yellow_led_state = 0; // turn off yellow LED + tempfloat = ( (float) GPSHdop / 100); - #ifdef GPSDO_BLUETOOTH - printGPSDOstats(Serial2); // print stats to Bluetooth Serial - #else // xor - printGPSDOstats(Serial); // print stats to USB Serial - #endif // BLUETOOTH + Serialx.print(F("Lat: ")); + Serialx.print(GPSLat, 6); + Serialx.print(F(" Lon: ")); + Serialx.print(GPSLon, 6); + Serialx.print(F(" Alt: ")); + Serialx.print(GPSAlt, 1); + Serialx.println(F("m")); + Serialx.print(F("Sats: ")); + Serialx.print(GPSSats); + Serialx.print(F(" HDOP: ")); + Serialx.println(tempfloat, 2); + Serialx.print(F("UTC Time: ")); - #ifdef GPSDO_OLED - displayscreen1(); - #endif // OLED - - startGetFixmS = millis(); // have a fix, next thing that happens is checking for a fix, so restart timer - } - else // no GPS fix could be acquired for the last five seconds + if (hours < 10) { - yellow_led_state = 1; // turn on yellow LED - - #ifdef GPSDO_OLED - disp.clear(); // display no fix message on OLED - disp.setCursor(0, 0); - disp.print(F(Program_Name)); - disp.print(F(" - ")); - disp.print(F(Program_Version)); - disp.setCursor(0, 1); - disp.print(F("Wait fix ")); - disp.print( (millis() - startGetFixmS) / 1000 ); - disp.print(F("s")); - #endif // OLED - - #ifdef GPSDO_BLUETOOTH // print no fix message to either - Serial2.println(); // Bluetooth serial or USB serial - Serial2.print(F("Waiting for GPS Fix ")); - Serial2.print( (millis() - startGetFixmS) / 1000 ); - Serial2.println(F("s")); - #else - Serial.println(); - Serial.print(F("Waiting for GPS Fix ")); - Serial.print( (millis() - startGetFixmS) / 1000 ); - Serial.println(F("s")); - #endif // BLUETOOTH + Serialx.print(F("0")); + } - // no fix, raise flush_ring_buffers_flag - flush_ring_buffers_flag = true; + Serialx.print(hours); + Serialx.print(F(":")); + + if (mins < 10) + { + Serialx.print(F("0")); } -} -void tunnelgps() -// GPSDO tunnel mode operation -{ - #ifdef GPSDO_BLUETOOTH // print calibrating started message to either - Serial2.println(); // Bluetooth serial xor USB serial - Serial2.print(F("Entering tunnel mode...")); - Serial2.println(); - #else - Serial.println(); - Serial.print(F("Entering tunnel mode...")); - Serial.println(); - #endif // BLUETOOTH - // tunnel mode operation goes here - uint32_t endtunnelmS = millis() + (tunnelSecs * 1000); - uint8_t GPSchar; - uint8_t PCchar; - while (millis() < endtunnelmS) + Serialx.print(mins); + Serialx.print(F(":")); + + if (secs < 10) { - if (Serial1.available() > 0) - { - GPSchar = Serial1.read(); - Serial.write(GPSchar); // echo NMEA stream to USB serial - } - if (Serial.available() > 0) - { - PCchar = Serial.read(); - Serial1.write(PCchar); // echo PC stream to GPS serial - } + Serialx.print(F("0")); } - // tunnel mode operation ends here + + Serialx.print(secs); + Serialx.print(F(" Date: ")); + + Serialx.print(day); + Serialx.print(F("/")); + Serialx.print(month); + Serialx.print(F("/")); + Serialx.println(year); + + Serialx.println(); + Serialx.println(F("Voltages: ")); + #ifdef GPSDO_MCP4725 + float Vctl = (float(avgdacVctl)/4096) * 3.3; + Serialx.print("Vctl: "); + Serialx.print(Vctl); + Serialx.print(" DAC: "); + Serialx.println(adjusted_DAC_output); + #endif // MCP4725 + + float Vctlp = (float(avgpwmVctl)/4096) * 3.3; + Serialx.print("VctlPWM: "); + Serialx.print(Vctlp); + Serialx.print(" PWM: "); + Serialx.println(adjusted_PWM_output); + + #ifdef GPSDO_VCC + // Vcc/2 is provided on pin PA0 + float Vcc = (float(avgVcc)/4096) * 3.3 * 2.0; + Serialx.print("Vcc: "); + Serialx.println(Vcc); + #endif // VCC + + #ifdef GPSDO_VDD + // internal sensor Vref + float Vdd = (1.21 * 4096) / float(avgVdd); // from STM32F411CEU6 datasheet + Serialx.print("Vdd: "); // Vdd = Vref on Black Pill + Serialx.println(Vdd); + #endif // VDD + + #ifdef GPSDO_INA219 + // current sensor for the OCXO + Serialx.print(F("OCXO voltage: ")); + Serialx.print(ina219volt, 2); + Serialx.println(F("V")); + Serialx.print(F("OCXO current: ")); + Serialx.print(ina219curr, 0); + Serialx.println(F("mA")); + #endif // INA219 + + // OCXO frequency measurements + Serialx.println(); + Serialx.println(F("Frequency measurements using 64-bit counter:")); + // temporarily added to check proper 16-bit PWM operation + // Serialx.print(F("TIM4 ARR: ")); // should in principle be 48000-1, because 96MHz / 2kHz = 48000 + // Serialx.println(TIM4->ARR); // and yes, verified + // Serialx.print(F("TIM4 ch4 CCR: ")); // in principle, the PWM value x 48000/65536 + // Serialx.println(TIM4->CCR4); // and also yes, verified + // end of temp code + // if (overflowErrorFlag) Serialx.println(F("ERROR: overflow ")); + // Serialx.print(F("Most Significant 32 bits (OverflowCounter): ")); + // Serialx.println(tim2overflowcounter); + // Serialx.print(F("Least Significant 32 bits (TIM2->CCR3): ")); + // Serialx.println(lsfcount); + Serialx.print(F("64-bit Counter: ")); + Serialx.println(fcount64); + Serialx.print(F("Frequency: ")); + Serialx.print(calcfreq64); + Serialx.println(F(" Hz")); - #ifdef GPSDO_BLUETOOTH // print calibrating started message to either - Serial2.println(); // Bluetooth serial xor USB serial - Serial2.print(F("Tunnel mode exited.")); - Serial2.println(); - #else - Serial.println(); - Serial.print(F("Tunnel mode exited.")); - Serial.println(); - #endif // BLUETOOTH + Serialx.print("10s Frequency Avg: "); + Serialx.print(avgften,1); + Serialx.print(F(" Hz")); + Serialx.print(F(" count: ")); + Serialx.println(cbiten_newest); + + Serialx.print("100s Frequency Avg: "); + Serialx.print(avgfhun,2); + Serialx.print(F(" Hz")); + Serialx.print(F(" count: ")); + Serialx.println(cbihun_newest); + + Serialx.print("1,000s Frequency Avg: "); + Serialx.print(avgftho,3); + Serialx.print(F(" Hz")); + Serialx.print(F(" count: ")); + Serialx.println(cbitho_newest); + + Serialx.print("10,000s Frequency Avg: "); + Serialx.print(avgftth,4); + Serialx.print(F(" Hz")); + Serialx.print(F(" count: ")); + Serialx.println(cbitth_newest); + + #ifdef GPSDO_BMP280_SPI + // BMP280 measurements + Serialx.println(); + Serialx.print(F("BMP280 Temperature = ")); + Serialx.print(bmp280temp, 1); + Serialx.println(" *C"); + Serialx.print(F("Pressure = ")); + Serialx.print((bmp280pres+PressureOffset)/100, 1); + Serialx.println(" hPa"); + Serialx.print(F("Approx altitude = ")); + Serialx.print(bmp280alti+AltitudeOffset, 1); /* Adjusted to local forecast! */ + Serialx.println(" m"); + #endif // BMP280_SPI + + #ifdef GPSDO_AHT10 + // AHT10 measurements + sensors_event_t humidity, temp; + aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data + Serialx.print("AHT10 Temperature: "); + Serialx.print(temp.temperature); + Serialx.println(" *C"); + Serialx.print("Humidity: "); + Serialx.print(humidity.relative_humidity); + Serialx.println("% rH"); + #endif // AHT10 - tunnel_mode_flag = false; // reset flag, exit tunnel mode + Serialx.println(); } -void docalibration() -// GPSDO calibration routine + +#ifdef GPSDO_OLED +void displayscreen_OLED() // show GPSDO data on OLED display { - unsigned long startWarmup = millis(); // we need a rough timer - if (ocxo_needs_warming) { - // spend a few seconds/minutes here waiting for the OCXO to warm - // show countdown timer on OLED display - // and report on either USB serial or Bluetooth serial - // Note: during calibration the GPSDO does not accept any commands - uint16_t countdown = ocxo_warmup_time; - while (countdown) { - yellow_led_state = 2; // blink yellow LED - - #ifdef GPSDO_OLED - disp.clear(); // display warmup message on OLED - disp.setCursor(0, 0); - disp.print(F(Program_Name)); - disp.print(F(" - ")); - disp.print(F(Program_Version)); - disp.setCursor(0, 2); - disp.print(F("OCXO warming up")); - disp.setCursor(0, 3); - disp.print(F("Please wait")); - disp.setCursor(5, 4); - disp.print(countdown); - disp.print(F("s")); - #endif // OLED - - #ifdef GPSDO_BLUETOOTH // print warming up message to either - Serial2.println(); // Bluetooth serial xor USB serial - Serial2.print(F("Warming up ")); - Serial2.print(countdown); - Serial2.println(F("s")); - #else - Serial.println(); - Serial.print(F("Warming up ")); - Serial.print(countdown); - Serial.println(F("s")); - #endif // BLUETOOTH + float tempfloat; - // do nothing for 1s - delay(1000); - countdown--; + // OCXO frequency + disp.setCursor(0, 1); + disp.print(F("F ")); + // display 1s, 10s or 100s value depending on whether data is available + if (cbTen_full) { + if (cbHun_full) { // if we have data over 100 seconds + if (avgfhun < 10000000) { + disp.setCursor(2, 1); disp.print(" "); + } + else disp.setCursor(2, 1); + disp.print(avgfhun, 2); // to 2 decimal places + disp.print("Hz "); + } + else { // nope, only 10 seconds + if (avgften < 10000000) { + disp.setCursor(2, 1); disp.print(" "); + } + else disp.setCursor(2, 1); + disp.print(avgften, 1); // to 1 decimal place + disp.print("Hz "); } - ocxo_needs_warming = false; // reset flag, next "hot" calibration skips ocxo warmup } - // proceed with calibration - #ifdef GPSDO_BLUETOOTH // print calibrating started message to either - Serial2.println(); // Bluetooth serial xor USB serial - Serial2.print(F("Calibrating...")); - Serial2.println(); - #else - Serial.println(); - Serial.print(F("Calibrating...")); - Serial.println(); - #endif // BLUETOOTH + else { // we don't have any averages + calcfreqint = calcfreq64; // convert to 32-bit integer + if (calcfreqint < 10000000) { + disp.setCursor(2, 1); disp.print(" "); + } + else disp.setCursor(2, 1); + disp.print(calcfreqint); // integer + disp.print("Hz "); + } - #ifdef GPSDO_OLED - disp.clear(); // display calibrating message on OLED - disp.setCursor(0, 0); - disp.print(F(Program_Name)); - disp.print(F(" - ")); - disp.print(F(Program_Version)); + // Latitude + //disp.clearLine(2); disp.setCursor(0, 2); - disp.print(F("Calibrating...")); + disp.print(GPSLat, 6); + // Longitude + //disp.clearLine(3); disp.setCursor(0, 3); - disp.print(F("Please wait")); - #endif // OLED + disp.print(GPSLon, 6); + // Altitude and Satellites + //disp.clearLine(4); + disp.setCursor(0, 4); + disp.print(GPSAlt); + disp.print(F("m ")); + disp.setCursor(9, 4); + disp.print(F("Sats ")); + disp.print(GPSSats); + if (GPSSats < 10) disp.print(F(" ")); // clear possible digit when sats >= 10 + // HDOP + //disp.clearLine(5); + disp.setCursor(0, 5); + // choose HDOP or uptime + //disp.print(F("HDOP ")); + //tempfloat = ((float) GPSHdop / 100); + //disp.print(tempfloat); + disp.print(F("Up ")); + disp.print(updaysstr); + disp.print(F(" ")); + disp.print(uptimestr); - /* The calibration algorithm - * The objective of the calibration is to find the approximate Vctl to obtain - * 10MHz +/- 0.1Hz. - * - * we can use either a PID algorithm or a simple linear interpolation algorithm - * - * The following describes a simple linear interpolation algorithm - * - * We first output 1.5V for the DAC or PWM, wait 30 seconds and note the 10s frequency average. - * Next we output 2.5V for the DAC or PWM, wait 30 seconds and note the 10s frequency average. - * Now we calculate the Vctl for 10MHz +/- 0.1Hz using linear interpolation between the two points. - */ - // for 12-bit DAC - // 1.5V for DAC = 4096 x (1.5 / 3.3) = 1862 results in frequency f1 = 10MHz + e1 - // 2.5V for DAC = 4096 x (2.5 / 3.3) = 3103 results in frequency f2 = 10MHz + e2 - // for 16-bit PWM - // 1.5V for PWM = 65536 x (1.5 / 3.2) = 30720 results in frequency f1 = 10MHz + e1 - // 2.5V for PWM = 65536 x (2.5 / 3.2) = 51200 results in frequency f2 = 10MHz + e2 - // where f2 > f1 (most OCXOs have positive slope). - double f1, f2, e1, e2; - // make sure we have a fix and data - while (!cbTen_full) delay(1000); - // measure frequency for Vctl=1.5V - Serial.println(F("set PWM 1.5V, wait 15s")); - analogWrite(VctlPWMOutputPin, 30720); - delay(15000); - Serial.print(F("f1 (average frequency for 1.5V Vctl): ")); - f1 = avgften; - Serial.print(f1,1); - Serial.println(F(" Hz")); - // make sure we have a fix and data again - while (!cbTen_full) delay(1000); - // measure frequency for Vctl=2.5V - Serial.println(F("set PWM 2.5V, wait 15s")); - analogWrite(VctlPWMOutputPin, 51200); - delay(15000); - Serial.print(F("f2 (average frequency for 2.5V Vctl): ")); - f2 = avgften; - Serial.print(f2,1); - Serial.println(F(" Hz")); - // slope s is (f2-f1) / (51200-30720) for PWM - // So F=10MHz +/- 0.1Hz for PWM = 30720 - (e1 / s) - // set Vctl - // adjusted_PWM_output = formula - adjusted_PWM_output = 30720 - ((f1 - 10000000.0) / ((f2 - f1) / 20480)); - Serial.print(F("Calculated PWM: ")); - Serial.println(adjusted_PWM_output); - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); - // calibration done - - #ifdef GPSDO_BLUETOOTH // print calibration finished message to either - Serial2.println(); // Bluetooth serial xor USB serial - Serial2.print(F("Calibration done.")); - Serial2.println(); + // Time + //disp.clearLine(6); + disp.setCursor(0, 6); + + if (hours < 10) + { + disp.print(F("0")); + } + + disp.print(hours); + disp.print(F(":")); + + if (mins < 10) + { + disp.print(F("0")); + } + + disp.print(mins); + disp.print(F(":")); + + if (secs < 10) + { + disp.print(F("0")); + } + + disp.print(secs); + disp.print(F(" ")); + + // Date + //disp.clearLine(7); + disp.setCursor(0, 7); + + disp.print(day); + disp.print(F("/")); + disp.print(month); + disp.print(F("/")); + disp.print(year); + + #ifdef GPSDO_BMP280_SPI + // BMP280 temperature + disp.setCursor(10, 6); + disp.print(bmp280temp, 1); + disp.print(F("C")); + #endif // BMP280_SPI + + #ifdef GPSDO_VCC + disp.setCursor(11, 2); + // Vcc/2 is provided on pin PA0 + float Vcc = (float(avgVcc)/4096) * 3.3 * 2.0; + disp.print(Vcc); + disp.print(F("V")); + #endif // VCC + + #ifdef GPSDO_VDD + // internal sensor Vref + disp.setCursor(11, 3); + float Vdd = (1.21 * 4096) / float(avgVdd); // from STM32F411CEU6 datasheet + disp.print(Vdd); // Vdd = Vref on Black Pill + disp.print(F("V")); + #endif // VDD + + disp.setCursor(11, 7); // display PWM/DAC value + #ifdef GPSDO_PWM_DAC + disp.print(adjusted_PWM_output); #else - Serial.println(); - Serial.print(F("Calibration done.")); - Serial.println(); - #endif // BLUETOOTH + disp.print(adjusted_DAC_output); + #endif // PWM_DAC +} +#endif // OLED + +#ifdef GPSDO_LCD_ST7735 +void displayscreen_LCD_ST7735() // show GPSDO data on LCD ST7735 display + // we use font1 8x6 pix and font2 16x12 pix +{ + float tempfloat; + + // Latitude + disp.setCursor(0, 40); + disp.print(F("Lat: ")); + disp.print(GPSLat, 6); + disp.print(F(" ")); + // Longitude + disp.setCursor(0, 48); + disp.print(F("Lon: ")); + disp.print(GPSLon, 6); + disp.print(F(" ")); + // Altitude + disp.setCursor(0, 56); + disp.print(F("Alt: ")); + disp.print(GPSAlt, 2); + disp.print(F("m ")); + //Satellites + disp.setCursor(90, 40); + disp.print(F("Sats: ")); + disp.print(GPSSats); + if (GPSSats < 10) disp.print(F(" ")); // clear possible digit when sats >= 10 + // HDOP + disp.setCursor(0, 64); + // choose HDOP or uptime + //disp.print(F("HDOP ")); + //tempfloat = ((float) GPSHdop / 100); + //disp.print(tempfloat); + disp.print(F("UpT: ")); + disp.print(updaysstr); + //disp.print(F(" ")); + disp.setCursor(30, 72); + disp.print(uptimestr); - #ifdef GPSDO_OLED - disp.clear(); - #endif // OLED + #ifdef GPSDO_BMP280_SPI + // BMP280 temperature + disp.setCursor(90, 64); + disp.print(F(" ")); + disp.print((char)247); + //disp.print((char)9); + disp.print(F("C: ")); + disp.print(bmp280temp, 1); + // BMP280 pressure + disp.setCursor(90, 72); + disp.print(F("hPa: ")); + disp.print(((bmp280pres+PressureOffset)/100), 1); + #endif // BMP280_SPI + + #ifdef GPSDO_VCC + disp.setCursor(90, 48); + // Vcc/2 is provided on pin PA0 + float Vcc = (float(avgVcc)/4096) * 3.3 * 2.0; + disp.print(F("5V0: ")); + disp.print(Vcc); + disp.print(F("V")); + #endif // VCC + + #ifdef GPSDO_VDD + // internal sensor Vref + disp.setCursor(90, 56); + float Vdd = (1.21 * 4096) / float(avgVdd); // from STM32F411CEU6 datasheet + disp.print(F("3V3: ")); + disp.print(Vdd); // Vdd = Vref on Black Pill + disp.print(F("V")); + #endif // VDD + + disp.setCursor(0, 80); // display PWM/DAC value + #ifdef GPSDO_PWM_DAC + disp.print(F("PWM: ")); + disp.print(adjusted_PWM_output); + disp.print(F(trendstr)); + #else + disp.print(F("DAC: ")); + disp.print(adjusted_DAC_output); + disp.print(F(trendstr)); + #endif // PWM_DAC - force_calibration_flag = false; // reset flag, calibration done -} + // display vref value + disp.setCursor(90, 80); // display vref value + float Vctlp = (float(avgpwmVctl)/4096) * 3.3; + disp.print(F("Vctl: ")); + disp.print(Vctlp); + disp.print(F("V")); + + // Display Headline and Version + disp.setTextColor(ST7735_YELLOW, ST7735_BLACK); + disp.setTextSize(2); + disp.setCursor(0, 0); + disp.print(F(" ")); + disp.print(F(Program_Name)); + // + disp.setTextSize(1); + disp.setCursor(115, 5); + disp.setTextColor(ST7735_BLUE, ST7735_BLACK); + disp.print(F(Program_Version)); + disp.setTextSize(2); + + // OCXO frequency + disp.setCursor(0, 19); + disp.setTextColor(ST7735_YELLOW, ST7735_BLACK); -#ifdef GPSDO_MCP4725 -void adjustVctlDAC() -// This should reach a stable DAC output value / a stable 10000000.00 frequency -// after an hour or so -{ - // decrease frequency - if (avgfhun >= 10000000.01) { - if (avgfhun >= 10000000.10) { - // decrease DAC by ten bits = coarse - adjusted_DAC_output = adjusted_DAC_output - 10; - } else { - // decrease DAC by one bit = fine - adjusted_DAC_output--; - } - dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 - } - // or increase frequency - else if (avgfhun <= 9999999.99) { - if (avgfhun <= 9999999.90) { - // increase DAC by ten bits = coarse - adjusted_DAC_output = adjusted_DAC_output + 10; - } else { - // increase DAC by one bit = fine - adjusted_DAC_output++; + // display 1s, 10s or 100s value depending on whether data is available + if (cbTen_full) { + if (cbTho_full) { // if we have data over 1000 seconds + if (avgftho < 10000000) { + disp.print(" "); + } + disp.print(avgftho, 3); // to 3 decimal places + } + else if (cbHun_full) { + if (avgfhun < 10000000) { + disp.print(" "); + } + disp.print(avgfhun, 2); // to 2 decimal places + disp.print(" "); } - dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 - } - // or do nothing because avgfrequency over last 100s is 10000000.00Hz - must_adjust_DAC = false; // clear flag and we are done -} -#endif // MCP4725 -void adjustVctlPWM() -// This should reach a stable DAC output value / a stable 10000000.00 frequency -// after an hour or so, and 10000000.000 after eight hours or so -{ - // decrease frequency - if (avgfhun >= 10000000.01) { - if (avgfhun >= 10000000.10) { - // decrease PWM by 100 bits = coarse - adjusted_PWM_output = adjusted_PWM_output - 100; - } else { - // decrease PWM by ten bits = fine - adjusted_PWM_output = adjusted_PWM_output - 10; - } - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); - } - // or increase frequency - else if (avgfhun <= 9999999.99) { - if (avgfhun <= 9999999.90) { - // increase PWM by 100 bits = coarse - adjusted_PWM_output = adjusted_PWM_output + 100; - } else { - // increase PWM by ten bits = fine - adjusted_PWM_output = adjusted_PWM_output + 10; + else { // nope, only 10 seconds + if (avgften < 10000000) { + disp.print(" "); + } + disp.print(avgften, 1); // to 1 decimal place + disp.print(" "); } - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); } - // or proceed to ultrafine because avgfrequency over last 100s is 10000000.00Hz - - // check first if we have the data, then do ultrafine frequency adjustment - // ultimately the objective is 10000000.000 over the last 1000s (16min40s) - if ((cbTho_full) && (avgftho >= 9999999.990) && (avgftho <= 10000000.010)) { - - // decrease frequency - if (avgftho >= 10000000.001) { - if (avgftho >= 10000000.005) { - // decrease PWM by 5 bits = very fine - adjusted_PWM_output = adjusted_PWM_output - 5; - } else { - // decrease PWM by one bit = ultrafine - adjusted_PWM_output = adjusted_PWM_output - 1; - } - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); - } - // or increase frequency - else if (avgftho <= 9999999.999) { - if (avgftho <= 9999999.995) { - // increase PWM by 5 bits = very fine - adjusted_PWM_output = adjusted_PWM_output + 5; - } else { - // increase PWM by one bit = ultrafine - adjusted_PWM_output = adjusted_PWM_output + 1; - } - analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + else { // we don't have any averages and print integer value + calcfreqint = calcfreq64; // convert to 32-bit integer + if (calcfreqint < 10000000) { + disp.print(" "); + } + disp.print(calcfreqint); // integer + disp.print(" "); // these are used for more exact dispaly } + // due to limited space small character unit + disp.setTextSize(1); + disp.setCursor(144, 19); + // due to some unknown issue in printing the freq digits/unit we clear the section of the display first + disp.fillRect(144, 19, 15, 20, ST7735_BLACK); + disp.print("Hz"); + + // clock and date + disp.setTextSize(2); + disp.setTextColor(ST7735_RED, ST7735_BLACK); + // Date + disp.setCursor(2, 94); + //disp.print(F("Date: ")); + disp.print(day); + disp.print(F(".")); + disp.print(month); + disp.print(F(".")); + disp.print(year); + + // Time + disp.setTextColor(ST7735_GREEN, ST7735_BLACK); + disp.setCursor(2, 113); + + if (hours < 10) + { + disp.print(F("0")); } - // or do nothing because avgfrequency over last 1000s is 10000000.000Hz - must_adjust_DAC = false; // clear flag and we are done -} + disp.print(hours); + disp.print(F(":")); -bool gpsWaitFix(uint16_t waitSecs) -{ - // waits a specified number of seconds for a fix, - // returns true as soon as fix available or false on timeout + if (mins < 10) + { + disp.print(F("0")); + } - uint32_t endwaitmS; - uint8_t GPSchar; + disp.print(mins); + disp.print(F(":")); - #ifdef GPSDO_BLUETOOTH - Serial2.println(); - //Serial2.print(F("Wait for GPS fix max. ")); - //Serial2.print(waitSecs); - //if (waitSecs > 1) Serial2.println(F(" seconds")); else Serial2.println(F(" second")); - #else - Serial.println(); - //Serial.print(F("Wait for GPS fix max. ")); - //Serial.print(waitSecs); - //if (waitSecs > 1) Serial.println(F(" seconds")); else Serial.println(F(" second")); - #endif // Bluetooth + if (secs < 10) + { + disp.print(F("0")); + } - endwaitmS = millis() + (waitSecs * 1000); + disp.print(secs); + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); + disp.print(F(" UTC")); + + // reset all font stuff for normal display + disp.setTextSize(1); + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); - while (millis() < endwaitmS) - { - if (Serial1.available() > 0) - { - GPSchar = Serial1.read(); - gps.encode(GPSchar); - #ifdef GPSDO_VERBOSE_NMEA - #ifdef GPSDO_BLUETOOTH - Serial2.write(GPSchar); // echo NMEA stream to Bluetooth serial - #else - Serial.write(GPSchar); // echo NMEA stream to USB serial - #endif // Bluetooth - #endif // VERBOSE_NMEA - } +} +#endif // LCD_ST7735 - if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.date.isUpdated()) - { - endFixmS = millis(); //record the time when we got a GPS fix - return true; - } +void uptimetostrings() { + // translate uptime variables to strings + uptimestr[0] = '0' + uphours / 10; + uptimestr[1] = '0' + uphours % 10; + uptimestr[3] = '0' + upminutes / 10; + uptimestr[4] = '0' + upminutes % 10; + uptimestr[6] = '0' + upseconds / 10; + uptimestr[7] = '0' + upseconds % 10; + + if (updays > 99) { // 100 days or more + updaysstr[0] = '0' + updays / 100; + updaysstr[1] = '0' + (updays % 100) / 10; + updaysstr[2] = '0' + (updays % 100) % 10; + } + else { // less than 100 days + updaysstr[0] = '0'; + updaysstr[1] = '0' + updays / 10; + updaysstr[2] = '0' + updays % 10; } - return false; } +void setup() +{ + // Wait 1 second for things to stabilize + delay(1000); + + // setup 2kHz test signal on PB5 if configured + #ifdef GPSDO_GEN_2kHz_PB5 // note this uses Timer 3 Channel 2 + analogWrite(Test2kHzOutputPin, 127); // configures PB5 as PWM output pin at default frequency and resolution + analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz + analogWriteResolution(16); // default PWM resolution is 8 bits, change it to 16 bits + analogWrite(Test2kHzOutputPin, 32767); // 32767 for 16 bits -> 50% duty cycle so a square wave + #endif // GEN_2kHz_PB5 + + // Setup 2Hz Timer + HardwareTimer *tim2Hz = new HardwareTimer(TIM9); + + // configure blueledpin in output mode + pinMode(blueledpin, OUTPUT); + + // configure yellow_led_pin in output mode + pinMode(yellowledpin, OUTPUT); + + tim2Hz->setOverflow(2, HERTZ_FORMAT); // 2 Hz + tim2Hz->attachInterrupt(Timer_ISR_2Hz); + tim2Hz->resume(); + + // Setup serial interfaces + Serial.begin(115200); // USB serial + Serial1.begin(9600); // Hardware serial 1 to GPS module + #ifdef GPSDO_BLUETOOTH + // HC-06 module baud rate factory setting is 9600, + // use separate program to set baud rate to 115200 + Serial2.begin(BT_BAUD); // Hardware serial 2 to Bluetooth module + #endif // BLUETOOTH + + // setup commands parser + serial_commands_.SetDefaultHandler(cmd_unrecognized); + serial_commands_.AddCommand(&cmd_version_); + serial_commands_.AddCommand(&cmd_flush_); + serial_commands_.AddCommand(&cmd_calibrate_); + serial_commands_.AddCommand(&cmd_tunnel_); + serial_commands_.AddCommand(&cmd_setPWM_); + + serial_commands_.AddCommand(&cmd_up1_); + serial_commands_.AddCommand(&cmd_up10_); + serial_commands_.AddCommand(&cmd_dp1_); + serial_commands_.AddCommand(&cmd_dp10_); + + #ifdef GPSDO_MCP4725 + // MCP4725 DAC commands + serial_commands_.AddCommand(&cmd_ud1_); + serial_commands_.AddCommand(&cmd_ud10_); + serial_commands_.AddCommand(&cmd_dd1_); + serial_commands_.AddCommand(&cmd_dd10_); + #endif // MCP4725 + + Serial.println(); + Serial.println(F(Program_Name)); + Serial.println(F(Program_Version)); + Serial.println(); + + #ifdef GPSDO_LCD_ST7735 + // Setup LCD SPI ST7735 display + disp.initR(INITR_BLACKTAB); // 1.8" LCD + delay(500); + disp.fillScreen(ST7735_BLACK); + disp.setTextColor(ST7735_YELLOW, ST7735_BLACK); // + disp.setRotation(1); // 0..3 max, here we use 90° = landscape + disp.setFont(); + disp.setTextSize(3); + // splash screen + disp.setCursor(40, 30); + disp.print(F(Program_Name)); + disp.setTextSize(1); + disp.setCursor(60, 65); + //disp.print(F(" - ")); + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); + disp.print(F(Program_Version)); + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); + disp.setTextSize(1); + #endif // LCD_ST7735 -void printGPSDOstats(Stream &Serialx) -{ - float tempfloat; + #ifdef GPSDO_UBX_CONFIG + // Reconfigure the GPS receiver + // first send the $PUBX configuration commands + delay(3000); // give everything a moment to stabilize + Serial.println("GPS checker program started"); + Serial.println("Sending $PUBX commands to GPS"); + // first send the $PUBG configuration commands + Serial1.print("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); // disable all VTG messages (useless since we are stationary) + Serial1.print("$PUBX,41,1,0003,0003,38400,0*24\r\n"); // set GPS baud rate to 38400 in/out protocols NMEA+UBX + Serial1.flush(); // empty the buffer + delay(100); // give it a moment + Serial1.end(); // close serial port + Serial1.begin(38400); // re-open at new rate + delay(3000); + // second, send the proprietary UBX configuration commands + Serial.println("Now sending UBX commands to GPS"); + ubxconfig(); + #endif // UBX_CONFIG + + // Initialize I2C + Wire.begin(); + // try setting a higher I2C clock speed + Wire.setClock(400000L); - Serialx.print(F("Uptime: ")); - Serialx.print(updaysstr); - Serialx.print(F(" ")); - Serialx.println(uptimestr); + #ifdef GPSDO_OLED + // Setup OLED I2C display + // Note that u8x8 library initializes I2C hardware interface + disp.setBusClock(400000L); // try to avoid display locking up + disp.begin(); + disp.setFont(u8x8_font_chroma48medium8_r); + disp.clear(); + disp.setCursor(0, 0); + disp.print(F(Program_Name)); + disp.print(F(" - ")); + disp.print(F(Program_Version)); + #endif // OLED - Serialx.println(F("New GPS Fix: ")); - - tempfloat = ( (float) GPSHdop / 100); + #ifdef GPSDO_INA219 + ina219.begin(&Wire3); // calibrates ina219 sensor Edit: start the sensor on the third I2C controller + Wire.setClock(400000L); + #endif // INA219 - Serialx.print(F("Lat: ")); - Serialx.print(GPSLat, 6); - Serialx.print(F(" Lon: ")); - Serialx.print(GPSLon, 6); - Serialx.print(F(" Alt: ")); - Serialx.print(GPSAlt, 1); - Serialx.println(F("m")); - Serialx.print(F("Sats: ")); - Serialx.print(GPSSats); - Serialx.print(F(" HDOP: ")); - Serialx.println(tempfloat, 2); - Serialx.print(F("UTC Time: ")); + #ifdef GPSDO_MCP4725 + // Setup I2C DAC, read voltage on PB0 + adjusted_DAC_output = default_DAC_output; // initial DAC value + dac.begin(0x60); + // Output Vctl to DAC, but do not write to DAC EEPROM + dac.setVoltage(adjusted_DAC_output, false); // min=0 max=4096 so 2048 should be 1/2 Vdd = approx. 1.65V + #endif // MCP4725 + + analogReadResolution(12); // make sure we read 12 bit values when we read from PB0 + Wire.setClock(400000L); - if (hours < 10) - { - Serialx.print(F("0")); + #ifdef GPSDO_AHT10 + if (! aht.begin()) { + Serial.println("Could not find AHT10? Check wiring"); + while (1) delay(10); } + Serial.println("AHT10 found"); + Wire.setClock(400000L); + #endif // AHT10 - Serialx.print(hours); - Serialx.print(F(":")); + // generate a 2kHz square wave on PB9 PWM pin, using Timer 4 channel 4 + // PB9 is Timer 4 Channel 4 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c + analogWrite(VctlPWMOutputPin, 127); // configures PB9 as PWM output pin at default frequency and resolution + analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz + analogWriteResolution(16); // set PWM resolution to 16 bits (the maximum for the STM32F411CEU6) + adjusted_PWM_output = default_PWM_output; // initial PWM value + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); // 32767 for 16 bits -> 50% duty cycle so a square wave - if (mins < 10) - { - Serialx.print(F("0")); + #ifdef GPSDO_BMP280_SPI + // Initialize BMP280 + if (!bmp.begin()) { + Serial.println(F("Could not find a valid BMP280 sensor, check wiring or " + "try a different address!")); + while (1) delay(10); } - Serialx.print(mins); - Serialx.print(F(":")); + // Default settings from datasheet. + bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ + Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ + Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ + Adafruit_BMP280::FILTER_X16, /* Filtering. */ + Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ + #endif // BMP280_SPI + + #ifdef GPSDO_ADF4351 + // connect OCXO 10Mhz to ref input + pinMode(ADF435x_LE, OUTPUT); // Setup chip select pin + // genrerate register values with AD SW for EVAL-ADF4351 + // actual 50MHz out, 10MHz reference + regs[0] = 0xA00000; + regs[1] = 0x8008011; + regs[2] = 0x4E42; + regs[3] = 0x4B3; + regs[4] = 0xE5003C; + regs[5] = 0x580005; + + digitalWrite(ADF435x_LE, HIGH); // disable access by default + mySPI2.begin(); // Init SPI bus + mySPI2.setDataMode(SPI_MODE0); // CPHA = 0 and Clock active when high + mySPI2.setBitOrder(MSBFIRST); // deal with bytes MSB first + + SetADF435x(); // Programm all the registers of the ADF4350/1 + Serial.println(F("ADF435x initialized")); + SetADF435x(); // Programm again, will never come back here + #endif //ADF4351 - if (secs < 10) - { - Serialx.print(F("0")); - } + Serial.println(F("GPSDO Starting")); + Serial.println(); - Serialx.print(secs); - Serialx.print(F(" Date: ")); + // Setup and start Timer 2 which measures OCXO frequency + // setup pin used as ETR (10MHz external clock from OCXO) + pinMode(PA15, INPUT_PULLUP); // setup PA15 as input pin + pinModeAF(PA15, GPIO_AF1_TIM2); // setup PA15 as TIM2 channel 1 / ETR + + // setup Timer 2 in input capture mode, active input channel 3 + // to latch counter value on rising edge - Serialx.print(day); - Serialx.print(F("/")); - Serialx.print(month); - Serialx.print(F("/")); - Serialx.println(year); + // Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. + HardwareTimer *FreqMeasTim = new HardwareTimer(TIM2); + + // Configure rising edge detection to measure frequency + FreqMeasTim->setMode(3, TIMER_INPUT_CAPTURE_RISING, PB10); - Serialx.println(); - Serialx.println(F("Voltages: ")); - #ifdef GPSDO_MCP4725 - float Vctl = (float(avgdacVctl)/4096) * 3.3; - Serialx.print("Vctl: "); - Serialx.print(Vctl); - Serialx.print(" DAC: "); - Serialx.println(adjusted_DAC_output); - #endif // MCP4725 + // Configure 32-bit auto-reload register (ARR) with maximum possible value + TIM2->ARR = 0xffffffff; // count to 2^32, then wraparound (approximately every 429 seconds) - float Vctlp = (float(avgpwmVctl)/4096) * 3.3; - Serialx.print("VctlPWM: "); - Serialx.print(Vctlp); - Serialx.print(" PWM: "); - Serialx.println(adjusted_PWM_output); + // Configure the ISR for the timer overflow interrupt + FreqMeasTim->attachInterrupt(Timer2_Overflow_ISR); + + // Configure the ISR for the 1PPS capture + FreqMeasTim->attachInterrupt(3, Timer2_Capture_ISR); - #ifdef GPSDO_VCC - // Vcc/2 is provided on pin PA0 - float Vcc = (float(avgVcc)/4096) * 3.3 * 2.0; - Serialx.print("Vcc: "); - Serialx.println(Vcc); - #endif // VCC + // select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register + TIM2->SMCR |= TIM_SMCR_ECE; // 0x4000 + + // start the timer + FreqMeasTim->resume(); + // Initialize movingAvg objects (note this allocates space on heap) and immediately read 1st value #ifdef GPSDO_VDD - // internal sensor Vref - float Vdd = (1.21 * 4096) / float(avgVdd); // from STM32F411CEU6 datasheet - Serialx.print("Vdd: "); // Vdd = Vref on Black Pill - Serialx.println(Vdd); - #endif // VDD - - #ifdef GPSDO_INA219 - // current sensor for the OCXO - Serialx.print(F("OCXO voltage: ")); - Serialx.print(ina219volt, 2); - Serialx.println(F("V")); - Serialx.print(F("OCXO current: ")); - Serialx.print(ina219curr, 0); - Serialx.println(F("mA")); - #endif // INA219 - - // OCXO frequency measurements - Serialx.println(); - Serialx.println(F("Frequency measurements using 64-bit counter:")); - // temporarily added to check proper 16-bit PWM operation - // Serialx.print(F("TIM4 ARR: ")); // should in principle be 48000-1, because 96MHz / 2kHz = 48000 - // Serialx.println(TIM4->ARR); // and yes, verified - // Serialx.print(F("TIM4 ch4 CCR: ")); // in principle, the PWM value x 48000/65536 - // Serialx.println(TIM4->CCR4); // and also yes, verified - // end of temp code - // if (overflowErrorFlag) Serialx.println(F("ERROR: overflow ")); - // Serialx.print(F("Most Significant 32 bits (OverflowCounter): ")); - // Serialx.println(tim2overflowcounter); - // Serialx.print(F("Least Significant 32 bits (TIM2->CCR3): ")); - // Serialx.println(lsfcount); - Serialx.print(F("64-bit Counter: ")); - Serialx.println(fcount64); - Serialx.print(F("Frequency: ")); - Serialx.print(calcfreq64); - Serialx.println(F(" Hz")); - Serialx.print("10s Frequency Avg: "); - Serialx.print(avgften,1); - Serialx.println(F(" Hz")); - Serialx.print("100s Frequency Avg: "); - Serialx.print(avgfhun,2); - Serialx.println(F(" Hz")); - Serialx.print("1,000s Frequency Avg: "); - Serialx.print(avgftho,3); - Serialx.println(F(" Hz")); - Serialx.print("10,000s Frequency Avg: "); - Serialx.print(avgftth,4); - Serialx.println(F(" Hz")); - - #ifdef GPSDO_BMP280_SPI - // BMP280 measurements - Serialx.println(); - Serialx.print(F("BMP280 Temperature = ")); - Serialx.print(bmp280temp, 1); - Serialx.println(" *C"); - Serialx.print(F("Pressure = ")); - Serialx.print((bmp280pres+PressureOffset)/100, 1); - Serialx.println(" hPa"); - Serialx.print(F("Approx altitude = ")); - Serialx.print(bmp280alti, 1); /* Adjusted to local forecast! */ - Serialx.println(" m"); - #endif // BMP280_SPI - - #ifdef GPSDO_AHT10 - // AHT10 measurements - sensors_event_t humidity, temp; - aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data - Serialx.print("AHT10 Temperature: "); - Serialx.print(temp.temperature); - Serialx.println(" *C"); - Serialx.print("Humidity: "); - Serialx.print(humidity.relative_humidity); - Serialx.println("% rH"); - #endif // AHT10 + avg_adcVdd.begin(); + adcVdd = analogRead(AVREF); + avgVdd = avg_adcVdd.reading(adcVdd); + # endif // VDD + + #ifdef GPSDO_VCC + avg_adcVcc.begin(); + adcVcc = analogRead(VccDiv2InputPin); + avgVcc = avg_adcVcc.reading(adcVcc); + # endif // VCC + + avg_dacVctl.begin(); + dacVctl = analogRead(VctlInputPin); + avgdacVctl = avg_dacVctl.reading(dacVctl); - Serialx.println(); + avg_pwmVctl.begin(); + pwmVctl = analogRead(VctlPWMInputPin); + avgpwmVctl = avg_pwmVctl.reading(pwmVctl); + + startGetFixmS = millis(); + + // setup done } -#ifdef GPSDO_OLED -void displayscreen1() +void loop() { - //show GPSDO data on OLED display - float tempfloat; + serial_commands_.ReadSerial(); // process any command from either USB serial (usually + // the Arduino monitor) xor Bluetooth serial (e.g. a smartphone) - // OCXO frequency - disp.setCursor(0, 1); - disp.print(F("F ")); - // display 1s, 10s or 100s value depending on whether data is available - if (cbTen_full) { - if (cbHun_full) { // if we have data over 100 seconds - if (avgfhun < 10000000) { - disp.setCursor(2, 1); disp.print(" "); - } - else disp.setCursor(2, 1); - disp.print(avgfhun, 2); // to 2 decimal places - disp.print("Hz "); - } - else { // nope, only 10 seconds - if (avgften < 10000000) { - disp.setCursor(2, 1); disp.print(" "); - } - else disp.setCursor(2, 1); - disp.print(avgften, 1); // to 1 decimal place - disp.print("Hz "); - } - } - else { // we don't have any averages - calcfreqint = calcfreq64; // convert to 32-bit integer - if (calcfreqint < 10000000) { - disp.setCursor(2, 1); disp.print(" "); - } - else disp.setCursor(2, 1); - disp.print(calcfreqint); // integer - disp.print("Hz "); - } + if (tunnel_mode_flag) tunnelgps(); else + + if (gpsWaitFix(waitFixTime)) // wait up to waitFixTime seconds for fix, returns true if we have a fix + { + if (force_calibration_flag) docalibration(); // if we have gps fix we can start calibration + #ifdef GPSDO_BLUETOOTH + Serial2.println(); + Serial2.println(); + Serial2.print(F("Fix time ")); + Serial2.print(endFixmS - startGetFixmS); + Serial2.println(F("mS")); + #else + Serial.println(); + Serial.println(); + Serial.print(F("Fix time ")); + Serial.print(endFixmS - startGetFixmS); + Serial.println(F("mS")); + #endif // BLUETOOTH - // Latitude - //disp.clearLine(2); - disp.setCursor(0, 2); - disp.print(GPSLat, 6); - // Longitude - //disp.clearLine(3); - disp.setCursor(0, 3); - disp.print(GPSLon, 6); - // Altitude and Satellites - //disp.clearLine(4); - disp.setCursor(0, 4); - disp.print(GPSAlt); - disp.print(F("m ")); - disp.setCursor(9, 4); - disp.print(F("Sats ")); - disp.print(GPSSats); - if (GPSSats < 10) disp.print(F(" ")); // clear possible digit when sats >= 10 - // HDOP - //disp.clearLine(5); - disp.setCursor(0, 5); - // choose HDOP or uptime - //disp.print(F("HDOP ")); - //tempfloat = ((float) GPSHdop / 100); - //disp.print(tempfloat); - disp.print(F("Up ")); - disp.print(updaysstr); - disp.print(F(" ")); - disp.print(uptimestr); + GPSLat = gps.location.lat(); + GPSLon = gps.location.lng(); + GPSAlt = gps.altitude.meters(); + GPSSats = gps.satellites.value(); + GPSHdop = gps.hdop.value(); - // Time - //disp.clearLine(6); - disp.setCursor(0, 6); + hours = gps.time.hour(); + mins = gps.time.minute(); + secs = gps.time.second(); + day = gps.date.day(); + month = gps.date.month(); + year = gps.date.year(); - if (hours < 10) - { - disp.print(F("0")); - } - disp.print(hours); - disp.print(F(":")); + if (must_adjust_DAC && cbHun_full) // in principle just once every 429 seconds, and only if we have valid data + { + // use different algorithms for 12-bit I2C DAC and STM32 16-bit PWM DAC + #ifdef GPSDO_MCP4725 + adjustVctlDAC(); + #endif // MCP4725 + #ifdef GPSDO_PWM_DAC + adjustVctlPWM(); + #endif // PWM_DAC + } - if (mins < 10) - { - disp.print(F("0")); - } + dacVctl = analogRead(VctlInputPin); // read the Vctl voltage output by the DAC + avgdacVctl = avg_dacVctl.reading(dacVctl); // average it + + pwmVctl = analogRead(VctlPWMInputPin); // read the filtered Vctl voltage output by the PWM + avgpwmVctl = avg_pwmVctl.reading(pwmVctl); // average it - disp.print(mins); - disp.print(F(":")); + #ifdef GPSDO_VCC + adcVcc = analogRead(VccDiv2InputPin); // read Vcc + avgVcc = avg_adcVcc.reading(adcVcc); // average it + # endif // VCC - if (secs < 10) - { - disp.print(F("0")); - } + #ifdef GPSDO_VDD + adcVdd = analogRead(AVREF); // Vdd is read internally as Vref + avgVdd = avg_adcVdd.reading(adcVdd); // average it + #endif // VDD - disp.print(secs); - disp.print(F(" ")); + #ifdef GPSDO_BMP280_SPI + bmp280temp = bmp.readTemperature(); // read bmp280 sensor, save values + bmp280pres = bmp.readPressure(); + bmp280alti = bmp.readAltitude(); + #endif // BMP280_SPI - // Date - //disp.clearLine(7); - disp.setCursor(0, 7); + #ifdef GPSDO_INA219 + ina219volt = ina219.busVoltage(); // read ina219 sensor, save values + ina219curr = ina219.shuntCurrent(); + #endif // INA219 + + uptimetostrings(); // get updaysstr and uptimestr - disp.print(day); - disp.print(F("/")); - disp.print(month); - disp.print(F("/")); - disp.print(year); + yellow_led_state = 0; // turn off yellow LED - #ifdef GPSDO_BMP280_SPI - // BMP280 temperature - disp.setCursor(10, 6); - disp.print(bmp280temp, 1); - disp.print(F("C")); - #endif // BMP280_SPI + #ifdef GPSDO_BLUETOOTH + printGPSDOstats(Serial2); // print stats to Bluetooth Serial + #else // xor + printGPSDOstats(Serial); // print stats to USB Serial + #endif // BLUETOOTH - #ifdef GPSDO_VCC - disp.setCursor(11, 2); - // Vcc/2 is provided on pin PA0 - float Vcc = (float(avgVcc)/4096) * 3.3 * 2.0; - disp.print(Vcc); - disp.print(F("V")); - #endif // VCC + #ifdef GPSDO_OLED + displayscreen_OLED(); + #endif // OLED - #ifdef GPSDO_VDD - // internal sensor Vref - disp.setCursor(11, 3); - float Vdd = (1.21 * 4096) / float(avgVdd); // from STM32F411CEU6 datasheet - disp.print(Vdd); // Vdd = Vref on Black Pill - disp.print(F("V")); - #endif // VDD + #ifdef GPSDO_LCD_ST7735 + displayscreen_LCD_ST7735(); + #endif // LCD_ST7735 + + startGetFixmS = millis(); // have a fix, next thing that happens is checking for a fix, so restart timer + } + else // no GPS fix could be acquired for the last five seconds + { + yellow_led_state = 1; // turn on yellow LED + + #ifdef GPSDO_OLED + disp.clear(); // display no fix message on OLED + disp.setCursor(0, 0); + disp.print(F(Program_Name)); + disp.print(F(" - ")); + disp.print(F(Program_Version)); + disp.setCursor(0, 1); + disp.print(F("Wait fix ")); + disp.print( (millis() - startGetFixmS) / 1000 ); + disp.print(F("s")); + #endif // OLED - disp.setCursor(11, 7); // display PWM/DAC value - #ifdef GPSDO_PWM_DAC - disp.print(adjusted_PWM_output); - #else - disp.print(adjusted_DAC_output); - #endif // PWM_DAC -} -#endif // OLED + #ifdef GPSDO_LCD_ST7735 + disp.fillScreen(ST7735_BLACK); // display warmup message on LCD ST7735 + disp.setTextColor(ST7735_YELLOW, ST7735_BLACK); + disp.setTextSize(2); + disp.setCursor(0, 0); + disp.print(F(" ")); + disp.print(F(Program_Name)); + // + disp.setTextColor(ST7735_BLUE, ST7735_BLACK); + disp.setTextSize(1); + disp.setCursor(115, 5); + disp.print(F(Program_Version)); + // + //disp.fillRect(0, 19, 160, 108, ST7735_BLACK); + disp.setTextColor(ST7735_WHITE, ST7735_BLACK); + disp.setCursor(0, 40); + disp.print(F("Wait for GPS fix: ")); + disp.print( (millis() - startGetFixmS) / 1000 ); + disp.print(F("s")); + #endif // LCD_ST7735 -void uptimetostrings() { - // translate uptime variables to strings - uptimestr[0] = '0' + uphours / 10; - uptimestr[1] = '0' + uphours % 10; - uptimestr[3] = '0' + upminutes / 10; - uptimestr[4] = '0' + upminutes % 10; - uptimestr[6] = '0' + upseconds / 10; - uptimestr[7] = '0' + upseconds % 10; - - if (updays > 99) { // 100 days or more - updaysstr[0] = '0' + updays / 100; - updaysstr[1] = '0' + (updays % 100) / 10; - updaysstr[2] = '0' + (updays % 100) % 10; - } - else { // less than 100 days - updaysstr[0] = '0'; - updaysstr[1] = '0' + updays / 10; - updaysstr[2] = '0' + updays % 10; + #ifdef GPSDO_BLUETOOTH // print no fix message to either + Serial2.println(); // Bluetooth serial or USB serial + Serial2.print(F("Waiting for GPS Fix ")); + Serial2.print( (millis() - startGetFixmS) / 1000 ); + Serial2.println(F("s")); + #else + Serial.println(); + Serial.print(F("Waiting for GPS Fix ")); + Serial.print( (millis() - startGetFixmS) / 1000 ); + Serial.println(F("s")); + #endif // BLUETOOTH + + // no fix, raise flush_ring_buffers_flag + flush_ring_buffers_flag = true; + strcpy(trendstr, " nof"); + // no fix at iniial startup, set PWM to reasonable value + if (!force_calibration_flag) { + adjusted_PWM_output = default_PWM_output; + analogWrite(VctlPWMOutputPin, adjusted_PWM_output); + } } -} +} // end of loop()