├── BOM_V1.csv ├── CTI OCXO.jpg ├── Controller Schematic V2.jpg ├── Controller Schematic V2_1.jpg ├── Controller.jpg ├── GPSDO-Gerbers.zip ├── GPSDO-iso_gerberV2.zip ├── Isolated Outputs.jpg ├── LICENSE ├── OCXO Schematic V2.jpg ├── Other OCXOs V2.jpg ├── Other OCXOs.jpg ├── PCB-Bottom.jpg ├── PCB-Top.jpg ├── Power Distribution V2.jpg ├── README.md ├── Serial_monitor.py ├── gpsdo_V3_71.ino └── mail_ocxo_log.py /BOM_V1.csv: -------------------------------------------------------------------------------- 1 | RefDes;Value;Pattern;Name;Manufacturer 2 | 1MHz;1MHz;Testpoint;TESTPOINT; 3 | Bliley;NV47M1008;Bliley;Bliley;Bliley 4 | C1, C2, C3, C6, C102, C104, C106, C107, C109, C111, C112, C113, C202, C204;100nF;CAP_0805;CAP_FIXED; 5 | C4;1uFT;CAP_1805;CAP_POLARIZED1; 6 | C5, C8, C9;100uF;CAPPR-2.5/6.5h12;CAP_POLARIZED1; 7 | C7;1nF;CAP_0805;CAP_FIXED; 8 | C10;10uFT;CAP_1210;CAP_POLARIZED1; 9 | C11, C12;10uF;CAP_1206;CAP_FIXED; 10 | C101, C116, C117;2.2uFT;CAP_0805;CAP_POLARIZED1; 11 | C103;100uF/12V;CAP_1210;CAP_POLARIZED1; 12 | C105;15pF;CAP_0805;CAP_FIXED; 13 | C108, C110;4.7uFT;CAP_0805;CAP_POLARIZED1; 14 | C114;100nF;CAP_0805;CAP_POLARIZED1; 15 | C115;1000uF;CAPPR-5/10.5h22;CAP_POLARIZED1; 16 | C201;22pF;CAP_0805;CAP_FIXED; 17 | C203;100uF/25V;CAP_1210;CAP_POLARIZED1; 18 | ChassisGND;;Solder Pad;Solder Pad; 19 | Comp;Comp;Testpoint;TESTPOINT; 20 | CTI;OC5SC25;CTI;CTI;CTI 21 | D1;1N5711;D-upright;DIO_SCHOTTKY; 22 | D101;1N4004;DO-35;DIODE; 23 | F101;2A;Fuse Holder;FUSE; 24 | GND1, GND_;GND;Testpoint;TESTPOINT; 25 | IO101;;TerminalBlock2;TerminalBlock2; 26 | IsoTemp;OCXO 143-141;Isotemp;Isotemp;Isotemp Research Inc. 27 | J1, J4;;HDR-1x3T/2.54/7x2;644456-3;TE Connectivity 28 | J2;;HDR-1x5T/2.54/12x2;LPPB051NGCN-RC;Sullins 29 | J3, J101;;031-5637;031-5637;Amphenol 30 | J5, J102;LED;HDR-1x2T/2.54/5x2;644456-2;TE Connectivity 31 | L101, L103, L201;;DO-7;FERRITE_CORE; 32 | L102;33uH;CAP-2.54/4x2.5;IND_IRON_CORE; 33 | L202;10uH;CAP-2.54/4x2.5;IND_IRON_CORE; 34 | Oscilloquartz;8663-XS;Oscilloquartz;Oscilloquartz;Oscilloquartz 35 | Q101;;TO-18V;2N2907A;ST Microelectronics 36 | R1, R2, R3, R5, R6, R8, R9, R10, R11, R102, R103;100R;RES_0805;RES_FIXED; 37 | R4;3K9;RES_0805;RES_FIXED; 38 | R7;1K;RES_0805;RES_FIXED; 39 | R12, R13;68R;RES_0805;RES_FIXED; 40 | R14, R101, R115;220R;RES_0805;RES_FIXED; 41 | R16, R17, R201;0R;RES_0805;RES_FIXED; 42 | R44, R105, R112;NI;RES_0805;RES_FIXED; 43 | R104, R113, R205, R206;10K;RES_0805;RES_FIXED; 44 | R107;10K;3296W;262X;CTS 45 | R108, R109;39K;RES_0805;RES_FIXED; 46 | R110;27K;RES_0805;RES_FIXED; 47 | R114;10M;RES_0805;RES_FIXED; 48 | R202;0R NI;RES_0805;RES_FIXED; 49 | R203, R204;50R;RES_0805;RES_FIXED; 50 | U1;;DIP-16;SN74HC390N;Texas Instruments 51 | U2;PD10;DIP-8;picDiv; 52 | U3;;TO-92-100;MC78L05ABP;Motorola 53 | U4;;DIP-16;MM74HC4046N;Fairchild 54 | U5;;Arduino Nano 3.0;Arduino Nano 3.0;Arduino 55 | U6, U7;;TO-92-100;LM35CZ;National Semiconductor 56 | U101;;DIP-8;REF02EZ;Analog Devices 57 | U102, U103;;DIP-14;74HC14AN;Motorola 58 | U104;;TO-220;LM7808CT;National Semiconductor 59 | U105;;TO-220;LM7812CT;National Semiconductor 60 | U106;;TO-220;LM7805CT;National Semiconductor 61 | V-Adj;V-adj;Testpoint;TESTPOINT; 62 | Y201;65256;Oscilloquartz;Trimble;Trmble 63 | -------------------------------------------------------------------------------- /CTI OCXO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/CTI OCXO.jpg -------------------------------------------------------------------------------- /Controller Schematic V2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Controller Schematic V2.jpg -------------------------------------------------------------------------------- /Controller Schematic V2_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Controller Schematic V2_1.jpg -------------------------------------------------------------------------------- /Controller.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Controller.jpg -------------------------------------------------------------------------------- /GPSDO-Gerbers.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/GPSDO-Gerbers.zip -------------------------------------------------------------------------------- /GPSDO-iso_gerberV2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/GPSDO-iso_gerberV2.zip -------------------------------------------------------------------------------- /Isolated Outputs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Isolated Outputs.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 paulvee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OCXO Schematic V2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/OCXO Schematic V2.jpg -------------------------------------------------------------------------------- /Other OCXOs V2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Other OCXOs V2.jpg -------------------------------------------------------------------------------- /Other OCXOs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Other OCXOs.jpg -------------------------------------------------------------------------------- /PCB-Bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/PCB-Bottom.jpg -------------------------------------------------------------------------------- /PCB-Top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/PCB-Top.jpg -------------------------------------------------------------------------------- /Power Distribution V2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulvee/Lars-GPSDO-V1/5929ff9baac5bf8e7404c3377790d58f5112b8f0/Power Distribution V2.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lars Walenius-GPSDO 2 | Files for my Lars Walenius (RIP) GPSDO project 3 | 4 | Sept-2023: NOTE that I have a new development, V4.1. Have a look at that project as well. 5 | 6 | The blog of the project can be found here: https://www.eevblog.com/forum/projects/lars-diy-gpsdo-with-arduino-and-1ns-resolution-tic/ it also includes my activities, stumbling and experiences. 7 | 8 | My own blog will detail my changes and experiences with this project. It can be found here : http://www.paulvdiyblogs.net/2020/07/a-high-precision-10mhz-gps-disciplined.html 9 | 10 | I have started a new blog and a new github project to deal with the monitoring, measuring and logging of the GPSDO to keep this part cleaner. 11 | The blog can be found here (http://www.paulvdiyblogs.net/2020/10/monitoring-measuring-logging-gpsdo.html), and the dedicated Github here (https://github.com/paulvee/GPSDO-Monitoring). 12 | 13 | Firmware and scripts 14 | ==================== 15 | The version of the Arduino code I use (gpsdo_V3_70) is a modified one from Lars' original to be able to use the circuit without the 1Mohm discharge resistor. 16 | It also implements a PID based fan controller for the ambient temperature of the PGSDO enclosure itself. Controller Schematic V2.1 shows the hardware components for the fan driver. Look at my blog for details. This version of the Lars code also implements the run-time setting of the fan controller parameters, so you don't need to re-compile all the time. The Help file (f1) and the variable settings (f2) incorporate these changes. Note that these values are not stored in the EEPROM, there is no space left. The latest version 3.70 has a few updates mostly on the PID controller for the fan. I noticed that it was not working correctly when my office temperature plummited in the winter time. I researched the issue and learned a lot more about tuning PID loops. The way I'm using it is very different from most other textbook applications, due to the very large inertia that the temperature inside the enclosure poses for the PID. I ended up changing the PID gain parameters dramatically, and also cleaned-up the code and comments. 17 | 18 | There are two Python scripts that run on a Raspberry Pi that collect the Lars report through the serial interface of the Arduino Nano, and by the end of the day e-mail me the daily results to my account. The mail script is activated by a cron job. The monitoring script is installed by a simple systemd service file. 19 | 20 | 21 | PCB layout 22 | ========== 23 | I made four mistakes on the layout of the PCB and did a few things I should have done better. 24 | Let me sum them up and also add some advise on how to use the board. 25 | 1. The TO-92 footprint for the LM35's are the wrong way around. This is easy to fix by bending the middle pin the other way so you can swap the power and ground pins. 26 | 27 | 2. The footprint for the Oscilloquartz OCXO is wrong. To isolate the OCXO temperature from the PCB, I used a 10mm piece of foam, on the bottom and also build a box around it with 10mm thick walls. I extended the OCXO leads so they are long enough to protrude through the foam to the bottom siude of the PCB. Where the two wrong connections are, I drilled a 2mm hole in the PCB and used an isolated wire to go to the bottom side and then connect them to the right connections. Because this OCXO is also a 12V version, I connected it straight to the incoming 12VDC supply. You could also use a 15VDC supply and add another LM7812 and mount that on the chassis. The output of the LM7812 goes to the OCXO and also to the input terminal on the PCB where the regular 12V DC comes in. Sounds complicated, but really isn't. 28 | 29 | 3. Not only the legs, but also the locations for the two LM35 temp sensors are swapped. U6 is the ambient temperature sensor that is now connected to the OCXO and U5 is the OCXO sensor that is now measuring the ambient temperature. You could cut the traces and swap the connections to the Arduino, or make the changes in the software, which is easier. My Version 3 of the firmware has that modification. 30 | 31 | 4. There is no connection to the positive side of C117, a Tantalum decoupling capacitor. You can make this connection, or not simple not install C117. 32 | 33 | a. The location of U3 is a little too close to the edge of the board. I use metal enclosures where the board slips in on rails, and I had to cut the pins on the edge of the chip and I also used some tape over them to make sure they don't short to the enclosure. 34 | 35 | b. There are provisions on the board for sine wave OCXO's. 36 | 37 | c. I forgot to add a connector on the PCB for a power LED because at first I didn't think I needed one. You can easily add an LED and tie it to any exposed 5V place with a resistor and ground. 38 | 39 | d. Use a high efficiency LED for the lock indicator. There is a noticeable temperature effect when there is no lock. I now use a 10K series resistor to the LED to minimize the effect. 40 | 41 | e. I use a socket for the GPS module, and bend the pins of the module a bit to flatten the angle to roughly 45-60 degrees. I also use an antenna cable to connect it to the back panel through an SMA socket. 42 | 43 | f. I suggest you seriously isolate the OCXO you are going to use with a foam box. Some of them radiate a lot of heat. I included U6, the LM35, within the isolation box to properly measure the OCXO temperature. In the case of the Trimble, where I didn't make a footprint because it was too large, I mounted it on top of a little foam and connected the pins with short wires before I added a box on top of it. 44 | 45 | g. I have not used U7, I put a socket in it's place. This the special chip that creates a 1Hz output from the 10MHz from the GPSDO yet. Info can be found here: http://www.leapsecond.com/pages/ppsdiv/ppsdiv.asm 46 | 47 | h. Make sure you use a good quality 1nF NPO capacitor for C1. I used Chinese general purpose ones before, and there was a lot more stability when I changed it. 48 | 49 | i. After the burn-in period, make sure you take the trimmer out and replace it with resistors. It makes a big difference on the temperature sensitivity because it is so close to the oven of the OCXO's. 50 | 51 | j. The ambient sensor LM35 (U6) is too close to the OCXO, so it picks up a lot of heat from it's temperature. The quick fix is to add some 5-8cm leads to the pins of the LM35 and move it out of the way. I suggest you move it close to the circuit around C1, because that has a significant temperature influence on the loop operation. 52 | 53 | k. The current output booster for the REF02 is not needed, and gives problems. The 74HC14N circuit (U102) and the related parts draws less than 4 mA. The REF02 can drive up to 10 mA. Replace R101 with a 0 Ohm resistor and do not install Q101. 54 | 55 | 56 | IF you are a purist, it's easy to use this PCB with the original Lars' design by using some wires to bridge the extra gates. R7 can be changed back to the original 10M Ohm value and if you either ground the end that now goes to the Arduino output pin, or change the software to always output a low on that pin, you have the original design back. 57 | 58 | Modifications 59 | ============= 60 | My modifictions based on the original V1 schematics that the PCB's are based upon, are documented in the V2 schematics. 61 | 62 | Isolated Outputs and Fan Controller 63 | =================================== 64 | I have created a PCB that can be placed inside the GPSDO enclosure. It will add two isolated output channles for the 10MHz and also the fan controller. The circuit diagram and the Gerber files are avaiable. Isolated Outputs.jpg & GPSDO-iso_gerberV2.zip 65 | 66 | -------------------------------------------------------------------------------- /Serial_monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.7 2 | #------------------------------------------------------------------------------- 3 | # Name: serial_monitor.py 4 | # Purpose: Serial port monitoring on a RaspberryPi 5 | # 6 | # Author: paulv 7 | # 8 | # Created: 20-07-2018 9 | # Copyright: (c) paulv 2018 2019 10 | # Licence: 11 | #------------------------------------------------------------------------------- 12 | 13 | # sudo apt-get install python3-serial 14 | import serial 15 | import logging 16 | import logging.handlers 17 | import sys 18 | import os 19 | import traceback 20 | 21 | 22 | # To enable the serial port on the GPIO connector, use raspi-config or: 23 | # sudo nano /boot/config.txt 24 | # enable_uart=1 25 | # reboot 26 | 27 | DEBUG = False 28 | 29 | port = "/dev/ttyAMA0" 30 | serialPort = serial.Serial(port, baudrate=9600, timeout=10.0) 31 | 32 | # data path is on a USB stick to protect the SD card 33 | data_path = "/mnt/usb/" 34 | 35 | # -- Logger definitions 36 | LOG_FILENAME = data_path+"ocxo.log" 37 | LOG_LEVEL = logging.INFO # Could be e.g. "INFO", "DEBUG", "ERROR" or "WARNING" 38 | 39 | 40 | class MyLogger(object): 41 | ''' 42 | Replace stdout and stderr with logging to a file so we can run this script 43 | even as a daemon and still capture all the stdout and stderr messages in the log. 44 | 45 | ''' 46 | def __init__(self, logger, level): 47 | """Needs a logger and a logger level.""" 48 | self.logger = logger 49 | self.level = level 50 | 51 | def write(self, message): 52 | # Only log if there is a message (not just a new line) 53 | # typical for serial data with a cr/lf ending 54 | if message.rstrip() != "": 55 | self.logger.log(self.level, message.rstrip()) 56 | 57 | 58 | def init(): 59 | global logger, handler 60 | 61 | if DEBUG:print ("Setting up the logger functionality") 62 | logger = logging.getLogger(__name__) 63 | logger.setLevel(LOG_LEVEL) 64 | handler = logging.handlers.TimedRotatingFileHandler(LOG_FILENAME, when="midnight", backupCount=31) 65 | formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') 66 | handler.setFormatter(formatter) 67 | logger.addHandler(handler) 68 | 69 | # pipe the stdout and stderr messages to the logger 70 | sys.stdout = MyLogger(logger, logging.INFO) 71 | sys.stderr = MyLogger(logger, logging.ERROR) 72 | 73 | 74 | def main(): 75 | if DEBUG:print("Serial logger") 76 | 77 | init() 78 | 79 | if DEBUG:print("Opened port", port, "for serial tracing") 80 | 81 | try: 82 | while True: 83 | while (serialPort.inWaiting()>0): 84 | try: 85 | ser_input = serialPort.readline().decode('utf-8') 86 | print(ser_input) 87 | except (OSError, serial.serialutil.SerialException): 88 | pass 89 | if DEBUG : print("No data available") 90 | except UnicodeDecodeError: 91 | pass 92 | if DEBUG: print("decode error") 93 | 94 | except KeyboardInterrupt: # Ctrl-C 95 | print("\nCtrl-C - Terminated") 96 | os._exit(1) 97 | 98 | except Exception as e: 99 | sys.stderr.write("Got exception: %s" % (e)) 100 | print(traceback.format_exc()) 101 | os._exit(1) 102 | 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /gpsdo_V3_71.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ONLY for 328 based Arduinos!! 4 | With modulo 50000 5 | With getcommand from JimH (replaces dipswitches) 6 | Added timer_us to ns TIC values to get much much larger capture range 7 | Changed PI-loop now uses float with remainder and not DI-term for P-term 8 | Changed average TIC+DAC storing to instant TIC+TNCT1 in hold mode 9 | Still EEPROM storage of 3hour averages and dac start value 10 | Added EEPROM storage of time constant and gain and many more parameters(see getcommand "help" below) 11 | 12 | Please check gain and timeConst and adapt to your used VCO. Holdmode is very useful to check range of VCO 13 | 14 | -Hardware: 15 | This version uses input capture on D8 !!! so jumper D2-D8 if you have an old shield with 1PPS to D2 16 | Uses 1 ns res/ 1 us TIC and internal PWM (2 x 8bits) 17 | 1PPS to capture interrupt (D8)and HC4046 pin 14 18 | 1MHz from HC390 (div2*5)to HC4046 pin 3 19 | 5MHz from HC390 (div2)to timer1 (D5) 20 | 1N5711+3.9k in series from HC4046 pin 15 to ADC0. 1nF NPO + 10M to ground on ADC0 21 | 16bit PWM DAC with two-pole lowpass filter from D3 (39k+4.7uF+39k+4.7uF) and D11 (10M) 22 | Put a LED + resistor on D13 to show Lock status 23 | Optional temperature sensors on ADC2 (used for temperature compensation) and ADC1 (just indication) 24 | ADC3 can be read and used for whatever you want 25 | For UNO a recommendation is to connect one jumper from reset to a 10uf capacitor connected to ground. 26 | With this jumper shorted the Arduino UNO will not reset every time the serial monitor is started. 27 | For downloading a new program the jumper need to be taken away. 28 | 29 | *** paulv modifications V3: 30 | * added interrupt driven decharge of C1 capacitor instead of 10M resistor 31 | * added optional software probe inside ISR to monitor what's going on 32 | * print **** instead of unLock to make it better standout 33 | * needed to swap temp sensors because of pcb layout error 34 | * changed the temperature labels in the report to a more meaningful OCXO and ambient 35 | * added a low-pass filter for the temperaturs 36 | * added a PID fan controller that uses the enclosure temp sensor as input to drive a fan 37 | * 38 | * My changes start with paulv: in comments so they are easy to locate 39 | * 40 | * Select: Arduino Nano - Old Bootloader 41 | * 42 | */ 43 | 44 | #include 45 | 46 | // paulv: added a decharge for C1 after the ADC read cycle 47 | const int decharge = 10; // Decharge pin for C1. Port is toggled between hi-Z input and output driven low 48 | const int probe = 12; // create a sw probing signal within the ISR to help visualizing what is going on 49 | 50 | // Fan controller 51 | const int fan_pin = 6; // Fan PWM output. Pin 9 should also work but does NOT! 52 | double ambient_t; // ambient temperature 53 | double pid; // The result of the PID controller 54 | int duty_cycle; // PWM value for the fan, also pid 55 | double tempError; 56 | double lastError; 57 | double cumError; 58 | double rateError; 59 | double tempSetPoint = 42.5; // (v) the target temperature for the enclosure which will buffer the OCXO oven. 60 | int pwm_baseline = 25; // 20 is minimum, 25 is safer. Low PWM to keep the fan running and baseline 61 | int max_pwm = 255; // maximum PWM value 62 | // PID gain factors. These work for me but could probably use a bit of further tuning depending on your hardware 63 | // Due to the large intertia of the system, the Kp gain must be high, but the damping (Ki) can be very low. 64 | // There are also only very minor jumps or transistions so Kd can be low as well. 65 | double Kp = 150.0; // x 150 66 | double Ki = 0.5; // y 67 | double Kd = 2.0; // x 68 | 69 | 70 | int warmUpTime = 60; // gives 1 minute hold during eg OCXO or Rb warmup. Set to eg 3 for VCTCXO 71 | long dacValueOut = 37375; // 16bit PWM-DAC setvalue=startvalue Max 65535 (if nothing stored in the EEPROM) 72 | long dacValue; // this is also same as "DACvalueOld" Note: is "0-65535" * timeconst 73 | long dacValue2; // had to add this for calculation in PI-loop 74 | long dacValueWithTempComp; // Note: is "0-65535" * timeconst 75 | const int PWMhighPin = 3; // high PWM-DAC pin (connected to 39k) 76 | const int PWMlowPin = 11; // low PWM-DAC pin (connected to 10M) 77 | int valuePWMhigh; // high PWM-DAC byte 78 | int valuePWMlow; // low PWM-DAC byte 79 | 80 | volatile int TIC_Value; // analog read 0 - time value. About 1ns per bit with 3.7kohm+1nF 81 | int TIC_ValueOld;//old not filtered TIC_value 82 | int TIC_Offset = 500; // ADC value for Reference time 83 | long TIC_ValueFiltered; // prefiltered TIC value 84 | long TIC_ValueFilteredOld;// old filtered value 85 | long TIC_ValueFilteredForPPS_lock; // prefiltered value just for PPS lock 86 | 87 | volatile unsigned int timer1CounterValue; //counts 5MHz clock modulo 50000 88 | long timer1CounterValueOld = 0 ; 89 | unsigned int TCNT1new = 0;//in main loop 90 | unsigned int TCNT1old = 0; 91 | unsigned long overflowCount = 0; // counter for timer1 overflows 92 | long timer_us; // timer1 value in microseconds offset from 1pps 93 | long timer_us_old; // used for diff_ns 94 | long diff_ns; // difference between old and new TIC_Value 95 | long diff_ns_ForPPS_lock; // prefiltered value just for PPS lock 96 | 97 | long tempADC1; // paulv: changed to long, analog read 1 - for example for internal NTC if oscillator external to Arduino 98 | long tempADC2; // analog read 2 - for oscillator temp eg LM35 or NTC - used for temperature compensation 99 | long tempADC2_Filtered; // analog read 2 - temp filtered for temp correction 100 | long tempCoeff = 0; // 650 = 1x10-11/°C if 1°C= +10bit temp and 65dac bits is 1x10-11 // set to -dacbits/tempbits*100? 101 | long tempRef = 280; // offset for temp correction calculation - 280 is about 30C for LM35 (set for value at normal room temperature) 102 | unsigned int temperature_Sensor_Type = 0; // 103 | 104 | // paulv: IIR filter for the temperature sensors 105 | // avg = avg + ((new_value - avg) / filter_weight) 106 | long tempADC1_IIR; 107 | long tempADC2_IIR; 108 | int IIR_Filter_Weight = 3; // IIC filter weight value 109 | 110 | long timeConst = 32; // Time constant in seconds 111 | long timeConstOld = 32; // old Time constant 112 | int filterDiv = 2; // filterConst = timeConst / filterDiv 113 | long filterConst = 16; // pre-filter time const in secs (TIC-filtering) 114 | long filterConstOld = 16; // old Filter time constant 115 | 116 | float I_term; //for PI-loop 117 | float P_term; 118 | long I_term_long; 119 | float I_term_remain; 120 | 121 | long gain = 12; //VCO freq DAC bits per TIC bit (65536/VCOrange in ppb (eg. with 1nS/bit and 100ppb DACrange gives gain=655)) 122 | float damping = 3.0; //Damping in loop 123 | 124 | unsigned long time; //seconds since start 125 | unsigned long timeOld; //last seconds since start 126 | unsigned int missedPPS; // incremented every time pps is missed 127 | unsigned long timeSinceMissedPPS; 128 | volatile boolean PPS_ReadFlag = false; // set true every time pps is received 129 | int lockPPSlimit = 100; // if TIC filtered for PPS within +- this for lockPSfactor * timeConst = PPSlocked 130 | int lockPPSfactor = 5; // see above 131 | unsigned long lockPPScounter; // counter for PPSlocked 132 | boolean PPSlocked; //digital pin and prints 0 or 1 133 | const int ppsLockedLED = 13; // LED pin for pps locked 134 | 135 | int i; // counter for 300secs before storing temp and dac readings average 136 | int j; // counter for stored 300sec readings 137 | int k; // counter for stored 3hour readings 138 | unsigned int StoreTIC_A[144]; //300sec storage 139 | unsigned int StoreTempA[144]; 140 | unsigned int StoreDAC_A[144]; 141 | long sumTIC; 142 | long sumTIC2; 143 | long sumTemp; 144 | long sumTemp2; 145 | unsigned long sumDAC; 146 | unsigned long sumDAC2; 147 | 148 | unsigned int totalTime3h; // counter for power-up time updated every third hour 149 | unsigned int restarts; // counter for restarts/power-ups 150 | boolean restartFlag = true; 151 | 152 | unsigned int ID_Number; 153 | 154 | boolean lessInfoDisplayed; 155 | boolean nsDisplayedDecimals; 156 | 157 | // for get command 158 | int incomingByte; // for incoming serial data in getCommand 159 | enum Modes {hold, run}; 160 | Modes opMode = run; //operating mode 161 | Modes newMode = hold; // used to reset timer_us when run is set and at to many missing PPS 162 | unsigned int holdValue; //DAC value for Hold mode 163 | 164 | // for TIC linearization 165 | float TICmin = 12.0; 166 | float TICmax = 1012.0; 167 | float x3 = 0.03; 168 | float x2 = 0.1; 169 | float x1; 170 | float TIC_Scaled; 171 | float TIC_ValueCorr; 172 | float TIC_ValueCorrOld; 173 | float TIC_ValueCorrOffset; 174 | 175 | 176 | //////////////////////////////////////////////////////////////////////////////////////////////// 177 | // Timer1 capture interrupt routine - this runs at rising edge of 1PPS on D8 178 | // paulv: **** modified to add a digital decharge of C1 179 | ISR (TIMER1_CAPT_vect) 180 | { 181 | timer1CounterValue = ICR1; // read the captured timer1 200ns counter value 182 | // digitalWrite(probe, 1); // *** paulv create a signal to probe the ADC cycle with a scope 183 | TIC_Value = analogRead(A0); // ns value 184 | // digitalWrite(probe, 0); // *** paulv stop the probe 185 | pinMode(decharge,OUTPUT); // *** paulv change from hi-Z to output 186 | digitalWrite(decharge, 0); // *** paulv bring it low to start de-charging of C1 187 | PPS_ReadFlag = true; 188 | delayMicroseconds(20); // *** paulv allow sufficient time to fully drain the capacitor 189 | pinMode(decharge,INPUT); // *** paulv revert the port back to hi-Z 190 | } 191 | 192 | 193 | //////////////////////////////////////////////////////////////////////////////////////////////// 194 | void calculation() 195 | { 196 | // set timer1 start value in the beginning 197 | if (time < 2 || (time > warmUpTime-2 && time < warmUpTime)) 198 | { 199 | TCNT1 = 25570 ; // is guessed value to get around 25000 next time 200 | } 201 | 202 | // TIC linearization 203 | //x2 = (((TICmid-TICmin)/(TICmax-TICmin)*1000)- 500.0)/250.0 - 0.05; // just for info 204 | x1 = 1.0 - x3 - x2; 205 | TIC_Scaled = ((float)TIC_Offset - TICmin)/(TICmax - TICmin)*1000; // Scaling for TIC_Offset 206 | TIC_ValueCorrOffset = TIC_Scaled * x1 + TIC_Scaled * TIC_Scaled * x2 / 1000.0 + TIC_Scaled * TIC_Scaled * TIC_Scaled * x3 /1000000.0; 207 | 208 | TIC_Scaled = ((float)TIC_Value - TICmin)/(TICmax - TICmin)*1000; // Scaling for TIC_Value 209 | TIC_ValueCorr = TIC_Scaled * x1 + TIC_Scaled * TIC_Scaled * x2 / 1000.0 + TIC_Scaled * TIC_Scaled * TIC_Scaled * x3 /1000000.0; 210 | 211 | // timer_us 212 | timer_us = timer_us + 50000 - (((timer1CounterValue - timer1CounterValueOld) * 200 + TIC_Value - TIC_ValueOld)+50000500)/1000; 213 | 214 | if (newMode == run) // reset timer_us if change from hold mode to run mode 215 | { 216 | timer_us = 0 ; 217 | timer_us_old = 0 ; 218 | TIC_ValueFilteredOld = TIC_Offset * filterConst; 219 | newMode = hold; 220 | } 221 | 222 | if (time < 3 || (time > warmUpTime-1 && time < warmUpTime+1)) // reset in the beginning and end of warmup 223 | { 224 | timer_us = 0 ; 225 | } 226 | 227 | if ((abs(timer_us)- 2) > timeConst * 65536 / gain / 1000 && opMode == run && time > warmUpTime ) 228 | { 229 | timer_us = 0 ; 230 | timer_us_old = 0 ; 231 | TIC_ValueFilteredOld = TIC_Offset * filterConst; 232 | } 233 | 234 | if (TIC_ValueOld == 1023) // reset if 10MHz was missing 235 | { 236 | timer_us = 0 ; 237 | timer_us_old = 0 ; 238 | TIC_ValueFilteredOld = TIC_Offset * filterConst; 239 | } 240 | 241 | 242 | // Diff_ns 243 | if (TIC_ValueCorr > TIC_ValueCorrOld) 244 | { 245 | diff_ns = (timer_us - timer_us_old)*1000 + long (TIC_ValueCorr - TIC_ValueCorrOld + 0.5); // = Frequency in ppb if updated every second! Note: TIC linearized 246 | } 247 | else 248 | { 249 | diff_ns = (timer_us - timer_us_old)*1000 + long (TIC_ValueCorr - TIC_ValueCorrOld -0.5); 250 | } 251 | // time - is supposed to be approximately seconds since start 252 | time = time + (overflowCount + 50)/100; 253 | overflowCount = 0; 254 | 255 | // missedPPS 256 | if (time - timeOld > 1) 257 | { 258 | missedPPS = missedPPS + 1; 259 | timeSinceMissedPPS = 0; 260 | } 261 | else 262 | { 263 | timeSinceMissedPPS = timeSinceMissedPPS + 1; 264 | } 265 | 266 | ////// PPS locked 267 | 268 | // Low Pass Filter of TIC_Value for PPS lock // /16 is used as 500ns error and /16 is about 30ns that seems reasonable 269 | TIC_ValueFilteredForPPS_lock = TIC_ValueFilteredForPPS_lock + (TIC_Value * 16 - TIC_ValueFilteredForPPS_lock) / 16; 270 | 271 | // Low Pass Filter of diff_ns for PPS lock 272 | diff_ns_ForPPS_lock = diff_ns_ForPPS_lock + (diff_ns * 16 - diff_ns_ForPPS_lock) / 16; 273 | 274 | lockPPScounter = lockPPScounter + 1; 275 | 276 | if (abs(TIC_ValueFilteredForPPS_lock / 16 - TIC_Offset) > lockPPSlimit) 277 | { 278 | lockPPScounter = 0; 279 | } 280 | if (abs(diff_ns_ForPPS_lock/16) > 20)// if freq more than 20ppb wrong (had to add this to avoid certain combinations not covered by above) 281 | { 282 | lockPPScounter = 0; 283 | } 284 | 285 | if (lockPPScounter > timeConst * lockPPSfactor) 286 | { 287 | PPSlocked = 1; 288 | } 289 | else 290 | { 291 | PPSlocked = 0; 292 | } 293 | 294 | // turn on LED 13 if "locked" 295 | digitalWrite(ppsLockedLED,PPSlocked); 296 | 297 | ////// 298 | 299 | // read ADC1 and 2 - temperature 300 | // ***** paulv: swap the two sensors due to PCB layout error 301 | // ***** below is the original code 302 | // int dummyreadADC = analogRead(A1); //without this ADC1 is influenced by ADC0 303 | // tempADC1 = analogRead(A1); 304 | // dummyreadADC = analogRead(A2); //without this ADC2 is influenced by ADC1 305 | // tempADC2 = analogRead(A2); 306 | // dummyreadADC = analogRead(A0); //without this TIC_Value (ADC0) is influenced by ADC2 307 | // 308 | // and here the fixed code swapping A1 with A2 309 | int dummyreadADC = analogRead(A2); //without this ADC1 is influenced by ADC0 310 | tempADC1 = analogRead(A2); 311 | dummyreadADC = analogRead(A1); //without this ADC2 is influenced by ADC1 312 | tempADC2 = analogRead(A1); 313 | dummyreadADC = analogRead(A0); //without this TIC_Value (ADC0) is influenced by ADC2 314 | 315 | 316 | // set filter constant 317 | filterConst = timeConst / filterDiv; 318 | filterConst = constrain (filterConst, 1,1024); 319 | if (PPSlocked == 0 || opMode == hold) filterConst = 1; 320 | 321 | // recalculation of value 322 | if(timeConst != timeConstOld) 323 | { 324 | dacValue = dacValue / timeConstOld * timeConst; 325 | } 326 | 327 | if(filterConst != filterConstOld) 328 | { 329 | TIC_ValueFilteredOld = TIC_ValueFilteredOld / filterConstOld * filterConst; 330 | TIC_ValueFiltered = TIC_ValueFiltered / filterConstOld * filterConst; 331 | } 332 | 333 | // Low Pass Filter for TICvalue (Phase Error) 334 | // Remember that TIC_ValueFiltered is multiplied by filterConst 335 | 336 | // Don´t update if outlier. Accepts diff_ns less than same ns as vco range in ppb + 200ns 337 | if abs(diff_ns <6500)// First check to avoid overflow in next calculation (also max VCO range is about 6500ns/s) 338 | { 339 | if( abs(diff_ns * gain) < (65535 + 200 * gain)) 340 | { 341 | TIC_ValueFiltered = TIC_ValueFiltered + ((timer_us*1000 + TIC_Value) * filterConst - TIC_ValueFiltered + (filterConst/2)) / filterConst; 342 | } 343 | } 344 | 345 | if (time > warmUpTime && opMode == run) // Don't change DAC-value during warm-up time or if in hold mode 346 | { 347 | ////// PI-loop ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 348 | P_term = (TIC_ValueFiltered - TIC_Offset * filterConst) / float(filterConst) * gain; // remember /timeConst is done before dacValue is sent out 349 | I_term = P_term / damping / float(timeConst) + I_term_remain; 350 | I_term_long = long(I_term); 351 | I_term_remain = I_term - I_term_long; 352 | dacValue += I_term_long; 353 | dacValue2 = dacValue + P_term; 354 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 355 | } 356 | else (dacValue2 = dacValue); // No change 357 | 358 | // Low Pass Filter for temperature 359 | tempADC2_Filtered = tempADC2_Filtered + (tempADC2 * 100 - tempADC2_Filtered) / 100; 360 | 361 | // Temperature correction for DAC value 362 | dacValueWithTempComp = dacValue2 + ((tempRef * 100 - tempADC2_Filtered) * tempCoeff / 10000 * timeConst); 363 | 364 | // Check that dacvalue is within limits 365 | if (dacValue < 0) 366 | { dacValue = 0;} 367 | if (dacValue > (65535 * timeConst)) 368 | { dacValue = (65535 * timeConst);} 369 | 370 | dacValueOut = dacValueWithTempComp / timeConst; // PWM-DAC value 371 | if (dacValueOut < 0) 372 | { dacValueOut = 0;} 373 | if (dacValueOut > 65535) 374 | { dacValueOut = 65535;} 375 | 376 | // manual set of dacvalue if in hold and not 0, if zero hold old value 377 | if (holdValue > 0 && opMode == hold) 378 | {dacValueOut = holdValue ;} 379 | 380 | // Set "16bit DAC" 381 | valuePWMhigh = highByte(dacValueOut); 382 | valuePWMlow = lowByte(dacValueOut); 383 | analogWrite(PWMhighPin,valuePWMhigh); 384 | analogWrite(PWMlowPin,valuePWMlow); 385 | 386 | // Increment restart at time 100 (100 chosen arbitrary) 387 | if (time > 100 && restartFlag == true) 388 | { 389 | restarts = restarts + 1; 390 | EEPROM.write(991, highByte(restarts)); 391 | EEPROM.write(992, lowByte(restarts)); 392 | restartFlag = false; 393 | } 394 | 395 | ////////////////////////////////////////////////////////////////////////////////////// 396 | //Storage of average readings that is later printed 397 | 398 | 399 | sumTIC = sumTIC + (TIC_Value * 10); 400 | sumTemp = sumTemp + (tempADC2 * 10); 401 | sumDAC = sumDAC + dacValueOut; 402 | i = i + 1; 403 | if (i == 300) // 300sec 404 | { 405 | if (opMode == run) 406 | {StoreTIC_A[j]= sumTIC / i;} 407 | else 408 | {StoreTIC_A[j]= TIC_Value;} 409 | 410 | sumTIC2 = sumTIC2 + sumTIC / i; 411 | sumTIC = 0; 412 | StoreTempA[j]= sumTemp / i; 413 | sumTemp2 = sumTemp2 + sumTemp / i; 414 | sumTemp = 0; 415 | if (opMode == run) 416 | {StoreDAC_A[j]= sumDAC / i;} 417 | else 418 | {StoreDAC_A[j]= (49999 - timer1CounterValue);} 419 | 420 | sumDAC2 = sumDAC2 + sumDAC / i; 421 | sumDAC = 0; 422 | i = 0; 423 | j = j + 1; 424 | if (j % 36 == 0) // store every 36 x 300sec (3 hours) 425 | { 426 | sumTIC2 = sumTIC2 / 36; 427 | if (opMode == run) 428 | { 429 | EEPROM.write(k, highByte(sumTIC2)); 430 | EEPROM.write(k+144, lowByte(sumTIC2)); 431 | } 432 | else 433 | { 434 | EEPROM.write(k, highByte(TIC_Value)); 435 | EEPROM.write(k+144, lowByte(TIC_Value)); 436 | } 437 | sumTIC2 = 0; 438 | 439 | sumTemp2 = sumTemp2 / 36; 440 | if (opMode == run) 441 | { 442 | sumTemp2 = sumTemp2 + 20480 ; 443 | if (lockPPScounter > 10800) 444 | { 445 | sumTemp2 = sumTemp2 + 20480 ; 446 | } 447 | } 448 | 449 | if (time < 20000) // first after start 450 | { 451 | sumTemp2 = sumTemp2 + 10240 ; 452 | } 453 | 454 | EEPROM.write(k+576, highByte(sumTemp2)); 455 | EEPROM.write(k+720, lowByte(sumTemp2)); 456 | sumTemp2 = 0; 457 | 458 | sumDAC2 = sumDAC2 / 36; 459 | 460 | if (opMode == run) 461 | { 462 | EEPROM.write(k+288, highByte(sumDAC2)); 463 | EEPROM.write(k+432, lowByte(sumDAC2)); 464 | } 465 | else 466 | { 467 | EEPROM.write(k+288, highByte(49999 - timer1CounterValue)); 468 | EEPROM.write(k+432, lowByte(49999 - timer1CounterValue)); 469 | } 470 | 471 | if (opMode == run && lockPPScounter > 10800) 472 | { 473 | EEPROM.write(1017, highByte(sumDAC2)); 474 | EEPROM.write(1018, lowByte(sumDAC2)); 475 | } 476 | 477 | sumDAC2 = 0; 478 | 479 | if (j == 144) // 144 x 300sec (12 hours) 480 | { 481 | j = 0; 482 | } 483 | k = k + 1; 484 | if (k == 144) // 144 x 10800sec (18 days) 485 | { 486 | k = 0; 487 | } 488 | EEPROM.write(1023, k); // store present k (index of 3 hour average, used in setup) 489 | 490 | totalTime3h = totalTime3h + 1; 491 | EEPROM.write(993, highByte(totalTime3h)); 492 | EEPROM.write(994, lowByte(totalTime3h)); 493 | 494 | 495 | } 496 | } 497 | 498 | // storage of old parameters 499 | timer1CounterValueOld = timer1CounterValue; 500 | TIC_ValueOld = TIC_Value; 501 | TIC_ValueCorrOld = TIC_ValueCorr; 502 | timer_us_old = timer_us; 503 | timeConstOld = timeConst; 504 | filterConstOld = filterConst; 505 | timeOld = time; 506 | TIC_ValueFilteredOld = TIC_ValueFiltered; 507 | 508 | } 509 | 510 | //////////////////////////////////////////////////////////////////////////////////////////////// 511 | void getCommand() 512 | { 513 | char ch; 514 | long zz; // *** paulv Lars used "z" as the name, but I wanted to use it. Sorry Lars 515 | 516 | enum Command { // this is the command set 517 | a = 'a', A = 'A', // set damping 518 | b = 'b', B = 'B', // set reference temperature 519 | c = 'c', C = 'C', // set temperature coefficient 520 | d = 'd', D = 'D', // set dacvalue (followed by a value) 521 | e = 'e', E = 'E', // erase (followed by a value) 522 | f = 'f', F = 'F', // help (followed by a value) 523 | g = 'g', G = 'G', // gain (followed by new value) 524 | h = 'h', H = 'H', // hold (followed by a DAC value note: 0 will set only hold) 525 | i = 'i', I = 'I', // toggles less or more info 526 | j = 'j', J = 'J', // set temperature sensor type 527 | // k 528 | l = 'l', L = 'L', // set TIC linearization parameters min max square 529 | // m 530 | n = 'n', N = 'N', // set ID number 531 | o = 'o', O = 'O', // TIC_Offset (followed by new value) 532 | p = 'p', P = 'P', // set prefilter div 533 | // q 534 | r = 'r', R = 'R', // run 535 | s = 's', S = 'S', // save (followed by a value) 536 | t = 't', T = 'T', // time const (followed by new value) 537 | // *** paulv 538 | u = 'u', U = 'U', // mimimum PWM setting for the fan 539 | v = 'v', V = 'V', // Ambient set temp for fan regulation 540 | 541 | w = 'w', W = 'W', // set warmup time (to allow for warm up of oscillator) 542 | // *** paulv PID gain settings 543 | x = 'x', X = 'X', // Kp gain 544 | y = 'y', Y = 'Y', // Ki gain 545 | z = 'z', Z = 'Z' // Kd gain 546 | 547 | }; 548 | 549 | if (Serial.available() > 0) { //process if something is there 550 | ch = Serial.read(); 551 | 552 | switch(ch) { 553 | 554 | case a: // set damping command 555 | case A: 556 | zz = Serial.parseInt(); //needs new line or carriage return set in Arduino serial monitor 557 | if (zz >=50 && zz <= 1000) 558 | { 559 | damping = zz / 100.0; 560 | Serial.print(F("Damping ")); 561 | Serial.println(damping); 562 | } 563 | else { Serial.println(F("Not a valid damping value - Shall be between 50 and 1000"));} 564 | break; 565 | 566 | 567 | case b: // set temperature offset command 568 | case B: 569 | zz = Serial.parseInt(); 570 | if (zz >=1 && zz <= 1023) 571 | { 572 | tempRef = zz; 573 | Serial.print(F("Temperature offset ")); 574 | Serial.println(tempRef); 575 | } 576 | else { Serial.println(F("Not a valid temperature offset value - Shall be between 1 and 1023"));} 577 | break; 578 | 579 | 580 | case c: // Set temperature coefficient 581 | case C: 582 | zz = Serial.parseInt(); 583 | if (zz >=0 && zz <= 10000) 584 | { 585 | tempCoeff = zz; 586 | Serial.print(F("Temperature Coefficient ")); 587 | Serial.println(tempCoeff); 588 | } 589 | 590 | else if (zz >=10001 && zz <= 20000) 591 | { 592 | tempCoeff = (10000-zz); 593 | Serial.print(F("Temperature Coefficient ")); 594 | Serial.println(tempCoeff); 595 | } 596 | 597 | else { Serial.println(F("Not a valid temperature coefficient value - Shall be between 0 and 20000"));} 598 | break; 599 | 600 | 601 | case d: // set dacValue command 602 | case D: 603 | zz = Serial.parseInt(); 604 | if (zz >=1 && zz <= 65535) 605 | { 606 | dacValue = zz * timeConst; 607 | Serial.print(F("dacValue ")); 608 | Serial.println(zz); 609 | } 610 | else { Serial.println(F("Not a valid dacValue - Shall be between 1 and 65535"));} 611 | break; 612 | 613 | 614 | case e: // erase command 615 | case E: 616 | zz = Serial.parseInt(); 617 | switch (zz) { 618 | 619 | case 1: 620 | Serial.println(F("Erase 3h storage in EEPROM ")); 621 | for (int i = 0; i < 864; i++) 622 | {EEPROM.write(i, 0);} 623 | EEPROM.write(1023, 0); 624 | k = 0 ; //reset 3hours counter 625 | break; 626 | 627 | case 22: 628 | Serial.println(F("Erase all EEPROM to zero")); 629 | for (int i = 0; i < 1024; i++) 630 | {EEPROM.write(i, 0);} 631 | k = 0 ; //reset 3hours counter 632 | break; 633 | 634 | case 33: 635 | Serial.println(F("Erase all EEPROM to -1")); 636 | for (int i = 0; i < 1024; i++) 637 | {EEPROM.write(i, 255);} 638 | k = 0 ; //reset 3hours counter 639 | break; 640 | 641 | default: 642 | Serial.println(F("Not a valid value for erase - Shall be 1 or 22")); 643 | } 644 | 645 | break; 646 | 647 | case f: // help command 648 | case F: 649 | zz = Serial.parseInt(); 650 | 651 | switch (zz) { 652 | 653 | case 1: 654 | Serial.println(""); 655 | Serial.println(F("Info and help - To get values for gain etc type f2 , f3 reads ADC3 and f4 EEPROM")); 656 | printHeader1_ToSerial(); 657 | Serial.print("\t"); 658 | printHeader2_ToSerial(); 659 | Serial.println(""); 660 | Serial.println(""); 661 | Serial.println(F("Typing a will set a new damping between between 0.50 and 10.00 set 50 to 1000")); 662 | Serial.println(F("Typing b will set a new tempRef between 1 and 1023")); 663 | Serial.println(F("Typing c will set a new tempCoeff set between 0 and 10000. Adding 10000 gives negative tc")); 664 | Serial.println(F("Typing d will set a new dacValue between 1 and 65535")); 665 | Serial.println(F("Typing e will erase the 3 hour storage in EEPROM if value 1 and all EEPROM if 22 (33 sets all EEPROM to FF)")); 666 | Serial.println(F("Typing g will set a new gain between 10 and 65535")); 667 | Serial.println(F(" gain = (65536/settable VCOrange in ppb) (eg. 100ppb DACrange gives gain=655)")); 668 | Serial.println(F("Typing h will set hold mode and the entered dacValue if not h0 that uses the old")); 669 | Serial.println(F("Typing i with value 1 will toggle ns decimal point else will toggle amount of information ")); 670 | Serial.println(F("Typing j Set temp sensor type 0=raw 1=LM35 2=10kNTC+68k 3=10kNTC+47k (second digit=adc1 eg 3x)")); 671 | Serial.println(F("Typing l will set TIC linearization parameters min max square")); 672 | Serial.println(F(" values 1-500 sets min to 0.1-50, values 800-1023 sets max, values 1024-1200 sets square to 0.024-0.200")); 673 | Serial.println(F("Typing n will set ID number 0-65535 that is displayed ")); 674 | Serial.println(F("Typing o will set a new TIC_Offset between 10 and 1020 ns")); 675 | Serial.println(F("Typing p will set a new prefilter div between 2 and 4")); 676 | Serial.println(F("Typing r will set run mode")); 677 | Serial.println(F("Typing s will save gain etc to EEPROM if value 1 and dacvalue if 2")); 678 | Serial.println(F("Typing t will set a new time constant between 4 and 32000 seconds")); 679 | Serial.println(F("Typing u will set a new minimum PWM for the fan")); 680 | Serial.println(F("Typing v will set a new max ambient temp for the fan in degrees celsius")); 681 | Serial.println(F("Typing w will set a new warmup time between 2 and 1000 seconds")); 682 | Serial.println(F("Typing x will set a new Kp gain x 10 for the PID fan regulation")); 683 | Serial.println(F("Typing y will set a new Ki gain x 10 for the PID fan regulation")); 684 | Serial.println(F("Typing z will set a new Kd gain x 10 for the PID fan regulation")); 685 | Serial.println(""); 686 | printHeader3_ToSerial(); 687 | break; 688 | 689 | case 2: 690 | Serial.println(""); 691 | Serial.print(F("Gain ")); Serial.print("\t"); Serial.print(gain); Serial.print("\t"); 692 | Serial.print(F("Damping ")); Serial.print("\t"); Serial.print(damping); Serial.print("\t"); 693 | Serial.print(F("TimeConst ")); Serial.print("\t"); Serial.print(timeConst); Serial.print("\t"); 694 | Serial.print(F("FilterDiv ")); Serial.print("\t"); Serial.print(filterDiv); Serial.print("\t"); 695 | Serial.print(F("TIC_Offset ")); Serial.print("\t"); Serial.println(TIC_Offset); 696 | Serial.print(F("TempRef ")); Serial.print("\t"); Serial.print(tempRef); Serial.print("\t"); 697 | Serial.print(F("TempCoeff ")); Serial.print("\t"); Serial.print(tempCoeff); Serial.print("\t"); 698 | Serial.print(F("TICmin ")); Serial.print("\t"); Serial.print(TICmin,1); Serial.print("\t"); 699 | Serial.print(F("TICmax ")); Serial.print("\t"); Serial.print(TICmax,0); Serial.print("\t"); 700 | Serial.print(F("Square comp ")); Serial.print("\t"); Serial.println(x2,3); 701 | Serial.print(F("Warm up time ")); Serial.print("\t"); Serial.print(warmUpTime); Serial.print("\t"); 702 | Serial.print(F("LockPPScounter ")); Serial.print("\t"); Serial.print(lockPPScounter); Serial.print("\t"); 703 | Serial.print(F("MissedPPS ")); Serial.print("\t"); Serial.print(missedPPS); Serial.print("\t"); 704 | Serial.print(F("TimeSinceMissedPPS ")); Serial.println(timeSinceMissedPPS); 705 | Serial.print(F("ID_Number ")); Serial.print("\t"); Serial.print(ID_Number); Serial.print("\t"); 706 | Serial.print(F("Restarts ")); Serial.print("\t"); Serial.print(restarts); Serial.print("\t"); 707 | Serial.print(F("Total hours")); Serial.print("\t"); Serial.println(totalTime3h * 3); 708 | // *** paulv 709 | Serial.print(F("PWM Baseline ")); Serial.print("\t"); Serial.print(pwm_baseline); Serial.print("\t"); 710 | Serial.print(F("Cool Baseline ")); Serial.print("\t"); Serial.println(tempSetPoint); 711 | Serial.print(F("Kp ")); Serial.print("\t"); Serial.print(Kp); Serial.print("\t"); 712 | Serial.print(F("Ki ")); Serial.print("\t"); Serial.print(Ki); Serial.print("\t"); 713 | Serial.print(F("Kd ")); Serial.print("\t"); Serial.println(Kd); 714 | 715 | Serial.println(""); 716 | printHeader3_ToSerial(); 717 | break; 718 | 719 | case 3: 720 | Serial.println (""); 721 | Serial.print (F("ADC3 = ")); 722 | Serial.println(analogRead(A3)); 723 | Serial.println (""); 724 | break; 725 | 726 | case 4: 727 | Serial.println (""); 728 | Serial.println (F("EEPROM content: ")); 729 | Serial.print (F("restarts = ")); 730 | zz=(EEPROM.read(991)*256 + EEPROM.read(992)); Serial.println((unsigned int)zz); 731 | Serial.print (F("totalTime3h = ")); 732 | zz=(EEPROM.read(993)*256 + EEPROM.read(994)); Serial.println((unsigned int)zz); 733 | Serial.print (F("temperature_Sensor_Type = ")); 734 | zz=(EEPROM.read(995)*256 + EEPROM.read(996)); Serial.println((unsigned int)zz); 735 | Serial.print (F("ID_Number = ")); 736 | zz=(EEPROM.read(997)*256 + EEPROM.read(998)); Serial.println((unsigned int)zz); 737 | Serial.print (F("TICmin = ")); 738 | zz=(EEPROM.read(999)*256 + EEPROM.read(1000)); Serial.println((unsigned int)zz); 739 | Serial.print (F("TICmax = ")); 740 | zz=(EEPROM.read(1001)*256 + EEPROM.read(1002)); Serial.println((unsigned int)zz); 741 | Serial.print (F("x2 = ")); 742 | zz=(EEPROM.read(1003)*256 + EEPROM.read(1004)); Serial.println((unsigned int)zz); 743 | Serial.print (F("TIC_Offset = ")); 744 | zz=(EEPROM.read(1005)*256 + EEPROM.read(1006)); Serial.println((unsigned int)zz); 745 | Serial.print (F("filterDiv = ")); 746 | zz=(EEPROM.read(1007)*256 + EEPROM.read(1008)); Serial.println((unsigned int)zz); 747 | Serial.print (F("warmUpTime = ")); 748 | zz=(EEPROM.read(1009)*256 + EEPROM.read(1010)); Serial.println((unsigned int)zz); 749 | Serial.print (F("damping = ")); 750 | zz=(EEPROM.read(1011)*256 + EEPROM.read(1012)); Serial.println((unsigned int)zz); 751 | Serial.print (F("tempRef = ")); 752 | zz=(EEPROM.read(1013)*256 + EEPROM.read(1014)); Serial.println((unsigned int)zz); 753 | Serial.print (F("tempCoeff = ")); 754 | zz=(EEPROM.read(1015)*256 + EEPROM.read(1016)); Serial.println((unsigned int)zz); 755 | Serial.print (F("dacValueOut = ")); 756 | zz=(EEPROM.read(1017)*256 + EEPROM.read(1018)); Serial.println((unsigned int)zz); 757 | Serial.print (F("gain = ")); 758 | zz=(EEPROM.read(1019)*256 + EEPROM.read(1020)); Serial.println((unsigned int)zz); 759 | Serial.print (F("timeConst = ")); 760 | zz=(EEPROM.read(1021)*256 + EEPROM.read(1022)); Serial.println((unsigned int)zz); 761 | Serial.print (F("k = ")); 762 | Serial.println (EEPROM.read(1023)); 763 | Serial.println (""); 764 | break; 765 | 766 | default: 767 | Serial.println(F("Not a valid value for help - Shall be 1 to 4")); 768 | } 769 | break; 770 | 771 | 772 | 773 | case g: // gain command 774 | case G: 775 | zz = Serial.parseInt(); 776 | if (zz >=10 && z <= 65534) 777 | { 778 | gain = zz; 779 | Serial.print(F("Gain ")); 780 | Serial.println(zz); 781 | } 782 | else { Serial.println(F("Not a valid gain value - Shall be between 10 and 65534"));} 783 | break; 784 | 785 | case h: // hold command 786 | case H: 787 | zz = Serial.parseInt(); 788 | if (zz >=0 && zz <= 65535) 789 | { 790 | opMode = hold; 791 | newMode = hold; 792 | Serial.print(F("Hold ")); 793 | holdValue = zz ; 794 | Serial.println(holdValue); 795 | } 796 | else { Serial.println(F("Not a valid holdValue - Shall be between 0 and 65535"));} 797 | 798 | break; 799 | 800 | case i: // help command 801 | case I: 802 | zz = Serial.parseInt(); 803 | if (zz == 1) 804 | { nsDisplayedDecimals = !nsDisplayedDecimals ; } 805 | else 806 | { lessInfoDisplayed = !lessInfoDisplayed ; } 807 | break; 808 | 809 | case j: // temperature_Sensor_Type 810 | case J: 811 | zz = Serial.parseInt(); 812 | if (zz >=0 && zz <= 99) 813 | { 814 | temperature_Sensor_Type = zz; 815 | Serial.print(F("temperature_Sensor_Type ")); 816 | Serial.println(zz); 817 | } 818 | else { Serial.println(F("Not a valid temperature_Sensor_Type value - Shall be between 0 and 99"));} 819 | break; 820 | 821 | case l: // set TIC linearization parameters command 822 | case L: 823 | zz = Serial.parseInt(); 824 | if (zz >=1 && zz <= 500) 825 | { 826 | TICmin = zz / 10.0; 827 | Serial.print(F("TICmin ")); 828 | Serial.println(TICmin); 829 | } 830 | else if (zz >=800 && zz <= 1023) 831 | { 832 | TICmax = zz; 833 | Serial.print(F("TICmax ")); 834 | Serial.println(TICmax); 835 | } 836 | else if (zz >=1024 && zz <= 1200) 837 | { 838 | x2 = (zz -1000 ) / 1000.0; 839 | Serial.print(F("square compensation ")); 840 | Serial.println(x2); 841 | } 842 | else { Serial.println(F("Not a valid value"));} 843 | break; 844 | 845 | case n: // ID_number 846 | case N: 847 | zz = Serial.parseInt(); 848 | if (zz >=0 && zz <= 65534) 849 | { 850 | ID_Number = zz; 851 | Serial.print(F("ID_Number ")); 852 | Serial.println(ID_Number); 853 | } 854 | else { Serial.println(F("Not a valid ID_Number value - Shall be between 0 and 65534"));} 855 | break; 856 | 857 | case o: // TIC_Offset command 858 | case O: 859 | zz = Serial.parseInt(); 860 | if (zz >=10 && zz <= 1020) // *** was 200 861 | { 862 | TIC_Offset = zz; 863 | Serial.print(F("TIC_Offset ")); 864 | Serial.println(TIC_Offset); 865 | } 866 | else { Serial.println(F("Not a valid TIC_offset - Shall be between 10 and 1020"));} 867 | break; 868 | 869 | case p: // set prefilter div command 870 | case P: 871 | zz = Serial.parseInt(); 872 | if (zz >=2 && zz <= 4) 873 | { 874 | filterDiv = zz; 875 | Serial.print(F("Prefilter div ")); 876 | Serial.println(filterDiv); 877 | } 878 | else { Serial.println(F("Not a valid prefilter value - Shall be between 2 and 4"));} 879 | break; 880 | 881 | case r: // run command 882 | case R: 883 | Serial.println(F("Run")); 884 | opMode = run; 885 | newMode = run; 886 | break; 887 | 888 | case s: // save command 889 | case S: 890 | zz = Serial.parseInt(); 891 | switch (zz) { 892 | 893 | case 1: 894 | Serial.print(F("Saved Gain and TimeConstant etc "));// 895 | EEPROM.write(995, highByte(temperature_Sensor_Type)); 896 | EEPROM.write(996, lowByte(temperature_Sensor_Type)); 897 | EEPROM.write(997, highByte(ID_Number)); 898 | EEPROM.write(998, lowByte(ID_Number)); 899 | EEPROM.write(999, highByte(int(TICmin * 10.0))); 900 | EEPROM.write(1000, lowByte(int(TICmin * 10.0))); 901 | EEPROM.write(1001, highByte(int(TICmax))); 902 | EEPROM.write(1002, lowByte(int(TICmax))); 903 | EEPROM.write(1003, highByte(int(x2 * 1000.0))); 904 | EEPROM.write(1004, lowByte(int (x2 * 1000.0))); 905 | EEPROM.write(1005, highByte(TIC_Offset)); 906 | EEPROM.write(1006, lowByte(TIC_Offset)); 907 | EEPROM.write(1007, highByte(filterDiv)); 908 | EEPROM.write(1008, lowByte(filterDiv)); 909 | EEPROM.write(1009, highByte(warmUpTime)); 910 | EEPROM.write(1010, lowByte(warmUpTime)); 911 | EEPROM.write(1011, highByte(int (damping *100))); 912 | EEPROM.write(1012, lowByte(int (damping *100))); 913 | EEPROM.write(1013, highByte(tempRef)); 914 | EEPROM.write(1014, lowByte(tempRef)); 915 | EEPROM.write(1015, highByte(tempCoeff)); 916 | EEPROM.write(1016, lowByte(tempCoeff)); 917 | EEPROM.write(1019, highByte(gain)); 918 | EEPROM.write(1020, lowByte(gain)); 919 | EEPROM.write(1021, highByte(timeConst)); 920 | EEPROM.write(1022, lowByte(timeConst)); 921 | Serial.println(""); 922 | break; 923 | 924 | case 2: 925 | Serial.print(F("Saved DacValue ")); 926 | EEPROM.write(1017, highByte(dacValueOut)); 927 | EEPROM.write(1018, lowByte(dacValueOut)); 928 | Serial.println(""); 929 | break; 930 | 931 | default: 932 | Serial.println(F("Not a valid value for save - Shall be 1 or 2")); 933 | } 934 | break; 935 | 936 | case t: // time constant command 937 | case T: 938 | zz = Serial.parseInt(); 939 | if (zz >=4 && zz <= 32000) 940 | { 941 | timeConst = zz; 942 | Serial.print(F("time constant ")); 943 | Serial.println(timeConst); 944 | } 945 | else { Serial.println(F("Not a valid time constant - Shall be between 4 and 32000"));} 946 | break; 947 | 948 | case u: // Set the minimum PWM for the fan 949 | case U: 950 | zz = Serial.parseInt(); 951 | if (zz > 0 && zz < 255) 952 | { 953 | pwm_baseline = zz; 954 | Serial.print(F("PWM Baseline ")); 955 | Serial.println(pwm_baseline); 956 | } 957 | else { Serial.println(F("Not a valid PWM Baseline value - Shall be between 0 and 255"));} 958 | break; 959 | 960 | case v: // Set ambient temperature for fan regulation 961 | case V: 962 | zz = Serial.parseInt(); 963 | tempSetPoint = zz; 964 | Serial.print(F("Cool Baseline ")); 965 | Serial.println(tempSetPoint); 966 | break; 967 | 968 | case w: // set warm up time command 969 | case W: 970 | zz = Serial.parseInt(); 971 | if (zz >=2 && zz <= 1000) 972 | { 973 | warmUpTime = zz; 974 | Serial.print(F("Warmup time ")); 975 | Serial.println(warmUpTime); 976 | } 977 | else { Serial.println(F("Not a valid warmup time - Shall be between 2 and 1000"));} 978 | break; 979 | 980 | case x: // Set kP gain /10 981 | case X: 982 | zz = Serial.parseInt(); 983 | Kp = zz / 100.0; 984 | Serial.print(F("Kp ")); 985 | Serial.println(Kp); 986 | break; 987 | 988 | case y: // set Ki gain /10 989 | case Y: 990 | zz = Serial.parseInt(); 991 | Ki = zz / 100.0; 992 | Serial.print(F("Ki ")); 993 | Serial.println(Ki); 994 | break; 995 | 996 | case z: // set Kd gain /10 997 | case Z: 998 | zz = Serial.parseInt(); 999 | Kd = zz / 100.0; 1000 | ; 1001 | Serial.print(F("Kd ")); 1002 | Serial.println(Kd); 1003 | break; 1004 | 1005 | default: 1006 | Serial.println(F("No valid command")); 1007 | break; 1008 | }; 1009 | 1010 | while(Serial.available() > 0) { 1011 | ch = Serial.read(); //flush rest of line 1012 | } 1013 | } 1014 | } 1015 | 1016 | 1017 | //////////////////////////////////////////////////////////////////////////////////////////////// 1018 | void printDataToSerial() 1019 | { 1020 | Serial.print("\t"); // *** paulv 1021 | Serial.print(duty_cycle); // *** paulv added pwm duty cycle to report header 1022 | Serial.print("\t"); 1023 | Serial.print((time), DEC); 1024 | Serial.print("\t"); 1025 | if (TIC_Value == 1023) 1026 | { 1027 | Serial.print(F("Missing 10MHz?")); 1028 | Serial.print("\t"); 1029 | } 1030 | else if (nsDisplayedDecimals == false) 1031 | { 1032 | Serial.print(((float)timer_us *1000) + TIC_ValueCorr-TIC_ValueCorrOffset, 0); 1033 | Serial.print("\t"); 1034 | } 1035 | else 1036 | { 1037 | Serial.print(((float)timer_us *1000) + TIC_ValueCorr-TIC_ValueCorrOffset, 1); 1038 | Serial.print("\t"); 1039 | } 1040 | Serial.print(dacValueOut, DEC); 1041 | Serial.print("\t"); 1042 | if (temperature_Sensor_Type == 0) 1043 | { Serial.print(tempADC2, DEC); } 1044 | else{ 1045 | // paulv: IIR Low Pass Filter for temperature sensor readings 1046 | tempADC2_IIR = tempADC2_IIR + ((tempADC2 - tempADC2_IIR) / IIR_Filter_Weight); 1047 | Serial.print(temperature_to_C(tempADC2_IIR,temperature_Sensor_Type%10), 1); 1048 | } 1049 | Serial.print("\t"); 1050 | if (time > warmUpTime && opMode == run) 1051 | { 1052 | if (PPSlocked == 0) {Serial.print(F("****"));} // paulv: changed from NoLock to better stand out 1053 | else {Serial.print(F("Locked"));} 1054 | Serial.print("\t"); 1055 | } 1056 | else if (time > warmUpTime) 1057 | { 1058 | Serial.print(F("Hold")); 1059 | Serial.print("\t"); 1060 | } 1061 | else 1062 | { 1063 | Serial.print(F("WarmUp")); 1064 | Serial.print("\t"); 1065 | } 1066 | 1067 | if (lessInfoDisplayed == false) 1068 | { 1069 | Serial.print(diff_ns, DEC); 1070 | Serial.print("\t"); 1071 | Serial.print(TIC_ValueFiltered * 10 / filterConst, DEC); 1072 | Serial.print("\t"); 1073 | Serial.print(timeConst, DEC); 1074 | Serial.print("\t"); 1075 | Serial.print(filterConst, DEC); 1076 | Serial.print("\t"); 1077 | Serial.print(((49999-timer1CounterValue)), DEC); 1078 | Serial.print("\t"); 1079 | 1080 | // paulv: temp1 = Ambient temp 1081 | if (temperature_Sensor_Type/10 == 0) 1082 | { Serial.print(tempADC1, DEC); } 1083 | else{ 1084 | // paulv: IIR Low Pass Filter for temperature sensor readings 1085 | tempADC1_IIR = tempADC1_IIR + ((tempADC1 - tempADC1_IIR) / IIR_Filter_Weight); 1086 | Serial.print(temperature_to_C(tempADC1_IIR,temperature_Sensor_Type/10), 1); 1087 | } 1088 | Serial.print("\t"); 1089 | 1090 | if (i == 1) 1091 | { 1092 | Serial.print(F("Five minute averages: TIC+DAC+temp")); 1093 | Serial.print("\t"); 1094 | } 1095 | if (i == 2) 1096 | { 1097 | Serial.print(F("Now acquiring value: ")); 1098 | Serial.print(j); 1099 | Serial.print("\t"); 1100 | } 1101 | if ((i >= 4) && (i <= 147)) 1102 | { 1103 | Serial.print((i-4), DEC); 1104 | Serial.print("\t"); 1105 | Serial.print((StoreTIC_A[i-4]), DEC); 1106 | Serial.print("\t"); 1107 | Serial.print((StoreDAC_A[i-4]), DEC); 1108 | Serial.print("\t"); 1109 | unsigned int x = StoreTempA[i-4]; 1110 | if (temperature_Sensor_Type == 0) 1111 | { Serial.print(x, DEC); } 1112 | else 1113 | { Serial.print(temperature_to_C(x/10, temperature_Sensor_Type%10), 1); } 1114 | Serial.print("\t"); 1115 | } 1116 | if (i == 148) 1117 | { 1118 | Serial.print(F("Three hour averages: TIC+DAC+temp")); 1119 | Serial.print("\t"); 1120 | } 1121 | if (i == 149) 1122 | { 1123 | Serial.print(F("Now acquiring value: ")); 1124 | Serial.print(k); 1125 | Serial.print("\t"); 1126 | } 1127 | if ((i >= 150) && (i <=293)) 1128 | { 1129 | Serial.print((i-150+1000), DEC); 1130 | Serial.print("\t"); 1131 | Serial.print((EEPROM.read(i-150+0)*256 + EEPROM.read(i-150+144)), DEC); 1132 | Serial.print("\t"); 1133 | unsigned int x = EEPROM.read(i-150+288)*256 + EEPROM.read(i-150+432); 1134 | Serial.print(x, DEC); 1135 | Serial.print("\t"); 1136 | x = EEPROM.read(i-150+576)*256 + EEPROM.read(i-150+720); 1137 | if ((x > 0) && (x < 65535)) 1138 | { 1139 | if (temperature_Sensor_Type == 0) 1140 | { Serial.print(x%10240, DEC); } 1141 | else 1142 | { Serial.print(temperature_to_C((x%10240)/10, temperature_Sensor_Type%10), 1); } 1143 | } 1144 | else 1145 | { Serial.print(x, DEC); } 1146 | 1147 | Serial.print("\t"); 1148 | int y = x/10240; 1149 | switch (y) { 1150 | case 0: 1151 | if (x > 0){Serial.print(F("Hold"));} 1152 | break; 1153 | case 1: 1154 | Serial.print(F("Restarted+hold")); 1155 | break; 1156 | case 2: 1157 | Serial.print(F("****")); // paulv: changed from noLock to better stand out 1158 | break; 1159 | case 3: 1160 | Serial.print(F("Restarted")); 1161 | break; 1162 | case 4: 1163 | Serial.print(F("Locked")); 1164 | break; 1165 | } 1166 | Serial.print("\t"); 1167 | } 1168 | if (i == 295) 1169 | { 1170 | Serial.print(F("TimeConst = ")); 1171 | Serial.print(timeConst); 1172 | Serial.print(F(" sec ")); 1173 | Serial.print("\t"); 1174 | } 1175 | if (i == 296) 1176 | { 1177 | Serial.print(F("Prefilter = ")); 1178 | Serial.print(filterConst); 1179 | Serial.print(F(" sec ")); 1180 | Serial.print("\t"); 1181 | } 1182 | if (i == 297) 1183 | { 1184 | Serial.print(F("Damping = ")); 1185 | Serial.print(damping); 1186 | Serial.print(F(" Gain = ")); 1187 | Serial.print(gain); 1188 | Serial.print("\t"); 1189 | } 1190 | if (i == 298) 1191 | { 1192 | Serial.print(F("Type f1 to get help+info")); 1193 | Serial.print("\t"); 1194 | } 1195 | if (i == 299) 1196 | { 1197 | printHeader2_ToSerial(); 1198 | } 1199 | 1200 | } // end of If (lessInfoDisplayed) 1201 | Serial.println(""); 1202 | 1203 | } 1204 | 1205 | //////////////////////////////////////////////////////////////////////////////////////////////// 1206 | void printHeader1_ToSerial() 1207 | { 1208 | Serial.print(F("\n\rArduino GPSDO with 1ns TIC by Lars Walenius")); // paulv: added a new line in front 1209 | } 1210 | 1211 | //////////////////////////////////////////////////////////////////////////////////////////////// 1212 | void printHeader2_ToSerial() 1213 | { 1214 | // Serial.print(F("Rev. 3.0 170801")); paulv: original 1215 | Serial.print(F("Rev. 3.7.1 paulv mods")); // paulv: added my mod indication 1216 | if ((ID_Number >= 0) && (ID_Number < 65535)) 1217 | { 1218 | Serial.print(F(" ID:")); 1219 | Serial.print(ID_Number); 1220 | Serial.print("\t"); 1221 | } 1222 | } 1223 | 1224 | //////////////////////////////////////////////////////////////////////////////////////////////// 1225 | void printHeader3_ToSerial() 1226 | { 1227 | Serial.print("\t"); // *** paulv 1228 | Serial.print("duty_cycle"); // *** paulv added fan pwm duty cycle to report 1229 | Serial.print("\t"); 1230 | Serial.print(F("time")); 1231 | Serial.print("\t"); 1232 | Serial.print(F("ns")); 1233 | Serial.print("\t"); 1234 | Serial.print(F("dac")); 1235 | Serial.print("\t"); 1236 | Serial.print(F("OCXO")); // *** paulv changed from temp 1237 | Serial.print("\t"); 1238 | Serial.print(F("status")); 1239 | Serial.print("\t"); 1240 | Serial.print(F("diff_ns filtX10")); // doesn't need a tab seperator 1241 | Serial.print("\t"); 1242 | Serial.print(F("tc")); 1243 | Serial.print("\t"); 1244 | Serial.print(F("filt")); 1245 | Serial.print("\t"); 1246 | Serial.print(F("timer1")); 1247 | Serial.print("\t"); 1248 | Serial.print(F("ambient")); // *** paulv: changed from temp1 1249 | Serial.print("\n\r"); // *** paulv: new line 1250 | } 1251 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 1252 | 1253 | float temperature_to_C(int RawADC, int sensor) 1254 | { 1255 | float TempC; 1256 | float floatADC = RawADC; 1257 | switch (sensor) { 1258 | 1259 | case 1: //LM35 1260 | TempC = RawADC * 1100.0 / 1024.0 *0.1; 1261 | break; 1262 | case 2: //10k NTC beta 3950 + 68k (15-60C) 1263 | TempC = floatADC * floatADC * 0.0002536 - floatADC * 0.2158 + 88.48 - floatADC * floatADC * floatADC * 0.0000001179; 1264 | break; 1265 | case 3: //10k NTC beta 3950 + 47k (15-65C) 1266 | TempC = floatADC * floatADC * 0.0001564 - floatADC * 0.1734 + 92.72 - floatADC * floatADC * floatADC * 0.00000005572 ; 1267 | break; 1268 | case 4: //10k NTC beta 3950 + 39k (25-70C) 1269 | TempC = floatADC * floatADC * 0.0001667 - floatADC * 0.181 + 99.21 - floatADC * floatADC * floatADC * 0.00000006085 ; 1270 | break; 1271 | case 5: //22k NTC beta 3950 + 120k (15-65C) 1272 | TempC = floatADC * floatADC * 0.0001997 - floatADC * 0.1953 + 92.11 - floatADC * floatADC * floatADC * 0.00000008010 ; 1273 | break; 1274 | case 8: //LM35 if Aref is low 1275 | TempC = RawADC * 1070.0 / 1024.0 *0.1; 1276 | break; 1277 | case 9: //LM35 fahrenheit 1278 | TempC = RawADC * 1100.0 / 1024.0 *0.1; 1279 | TempC = TempC * 1.8 + 32; 1280 | break; 1281 | default: 1282 | TempC = RawADC; 1283 | } 1284 | if (RawADC == 0){TempC = 0;} 1285 | return TempC; // Return the Temperature in C or raw 1286 | } 1287 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 1288 | void setup() 1289 | { 1290 | Serial.begin(9600); 1291 | Serial.println("GPSDO starting up"); 1292 | pinMode(ppsLockedLED,OUTPUT); 1293 | 1294 | // paulv: additional declarations 1295 | pinMode(decharge,INPUT); // setup the port that will decharge C1 1296 | pinMode(probe,OUTPUT); // setup the port to allow a sw probe inside de ISR 1297 | 1298 | // change the standard PWM frequency of 976.56 Hz for pins 5 and 6, we use 6 1299 | TCCR0B = TCCR0B & B11111000 | B00000010; // set timer 0 divisor to 8 for a PWM frequency of 7,812.50 Hz 1300 | // The Noctua fan I'm using has a target frequency of 25KHz, with an acceptable range between 1301 | // 21KHz and 28KHz, but that's not possible. 1302 | Serial.println("kick starting the fan"); 1303 | duty_cycle = 100; 1304 | analogWrite(fan_pin, duty_cycle); // kick-start the fan 1305 | delay(5000); 1306 | 1307 | // Print info and header in beginning 1308 | printHeader1_ToSerial(); 1309 | Serial.print("\t"); // prints a tab 1310 | 1311 | // Read data from EEPROM to variables 1312 | unsigned int y; 1313 | y = EEPROM.read(991)*256 + EEPROM.read(992); 1314 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1315 | { 1316 | restarts = y ; 1317 | } 1318 | y = EEPROM.read(993)*256 + EEPROM.read(994); 1319 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1320 | { 1321 | totalTime3h = y ; 1322 | } 1323 | y = EEPROM.read(995)*256 + EEPROM.read(996); 1324 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1325 | { 1326 | temperature_Sensor_Type = y ; 1327 | } 1328 | y = EEPROM.read(997)*256 + EEPROM.read(998); 1329 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1330 | { 1331 | ID_Number = y ; 1332 | } 1333 | y = EEPROM.read(999)*256 + EEPROM.read(1000); 1334 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1335 | { 1336 | TICmin = y / 10.0 ; 1337 | } 1338 | y = EEPROM.read(1001)*256 + EEPROM.read(1002); 1339 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1340 | { 1341 | TICmax = y; 1342 | } 1343 | y = EEPROM.read(1003)*256 + EEPROM.read(1004); 1344 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1345 | { 1346 | x2 = y / 1000.0 ; 1347 | } 1348 | y = EEPROM.read(1005)*256 + EEPROM.read(1006); 1349 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1350 | { 1351 | TIC_Offset = y; 1352 | } 1353 | y = EEPROM.read(1007)*256 + EEPROM.read(1008); 1354 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1355 | { 1356 | filterDiv = y; 1357 | } 1358 | y = EEPROM.read(1009)*256 + EEPROM.read(1010); 1359 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1360 | { 1361 | warmUpTime = y; 1362 | } 1363 | y = EEPROM.read(1011)*256 + EEPROM.read(1012); 1364 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1365 | { 1366 | damping = y / 100.0; 1367 | } 1368 | y = EEPROM.read(1013)*256 + EEPROM.read(1014); 1369 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1370 | { 1371 | tempRef = y; 1372 | } 1373 | y = EEPROM.read(1015)*256 + EEPROM.read(1016); 1374 | if ((y > 0) && (y < 65535)) // set last stored xx if not 0 or 65535 1375 | { 1376 | tempCoeff = y; 1377 | } 1378 | y = EEPROM.read(1017)*256 + EEPROM.read(1018); 1379 | if ((y > 0) && (y < 65535)) // set last stored dacValueOut if not 0 or 65535 1380 | { 1381 | dacValueOut = y; 1382 | } 1383 | y = EEPROM.read(1019)*256 + EEPROM.read(1020); 1384 | if ((y >= 10) && (y <= 65534)) // set last stored gain if between 10 and 65534 1385 | { 1386 | gain = y; 1387 | } 1388 | 1389 | y = EEPROM.read(1021)*256 + EEPROM.read(1022); 1390 | if ((y >= 4) && (y <= 32000)) // set last stored timeConst if between 4 and 32000 1391 | { 1392 | timeConst = y; 1393 | } 1394 | k = EEPROM.read (1023); // last index of stored 3 hour average 1395 | if ((k > 143)|| (k < 0)) k = 0; //reset if k is invalid (eg with a new processor) 1396 | 1397 | // Set "16bit DAC" 1398 | valuePWMhigh = highByte(dacValueOut); 1399 | valuePWMlow = lowByte(dacValueOut); 1400 | analogWrite(PWMhighPin,valuePWMhigh); 1401 | analogWrite(PWMlowPin,valuePWMlow); 1402 | 1403 | // Set initial values 1404 | dacValue = dacValueOut * timeConst; 1405 | timeConstOld = timeConst; 1406 | filterConstOld = filterConst; 1407 | TIC_ValueFilteredOld = TIC_Offset * filterConst; 1408 | TIC_ValueFiltered = TIC_Offset * filterConst; 1409 | TIC_ValueFilteredForPPS_lock = TIC_Offset * 16; 1410 | tempADC2_Filtered = tempRef * 100; 1411 | 1412 | tempADC1_IIR = tempRef-100; // paulv: setup initial filter value for ambient temps 1413 | tempADC2_IIR = tempRef; // paulv: setup initial filter value for OCXO temps 1414 | 1415 | // Set analog ref to about 1.1 Volt 1416 | analogReference(INTERNAL); 1417 | TIC_Value = analogRead(A0);// just a dummy read 1418 | 1419 | // Setup Timer 1 - counts events on pin D5. Used with 5MHz external clock source (needs to be less than 8MHz). Clock on rising edge. 1420 | 1421 | TCCR1A = 0; // reset Timer 1 1422 | TCCR1B = 0; 1423 | 1424 | TCCR1B |= (1 << WGM12); //configure timer1 for CTC mode 1425 | 1426 | OCR1A = 49999; // timer1 counts 50000 counts instead of 65536 to give same TCNT1 value every second 1427 | 1428 | TIMSK1 |= (1 << ICIE1); //interrupt on Capture (1PPS) 1429 | 1430 | TCCR1B |= ((1 << CS10) | (1 << CS11) | (1 << CS12)| (1 << ICES1) | (1 << ICNC1)); // start Timer 1 and interrupt with noise cancel 1431 | 1432 | 1433 | // Print info and header in beginning 1434 | printHeader2_ToSerial(); 1435 | Serial.println(""); // prints a carriage return 1436 | Serial.println(F("Type f1 to get help+info")); 1437 | printHeader3_ToSerial(); 1438 | 1439 | //clear PPS flag and go on to main loop 1440 | PPS_ReadFlag = false; 1441 | 1442 | } 1443 | 1444 | 1445 | // *** paulv The fan controller 1446 | void control_fan() { 1447 | // Use the low pass filtered ambient temperature sensor value for more stability. 1448 | // Do not interfere with the original code so convert the value to Celsius here again. 1449 | ambient_t = temperature_to_C(tempADC2_IIR,temperature_Sensor_Type/10); // use the GPSDO sensor 1450 | 1451 | // run the PID calculation 1452 | // no need to bother calculating the elapsed time, it is always 1 s 1453 | tempError = ambient_t - tempSetPoint; // determine error away from setpoint, make it a positive delta 1454 | // this is analog to using the "reversed" mode 1455 | cumError += tempError; // compute the integral 1456 | rateError = (tempError - lastError); // compute the derivative 1457 | 1458 | pid = (Kp * tempError) + (Ki * cumError) + (Kd * rateError); // calculate pid = duty_cycle 1459 | duty_cycle = pid; // pid is double, DC is int 1460 | // set the min-max boundaries 1461 | if (duty_cycle < pwm_baseline) { duty_cycle = pwm_baseline; } // lowest speed, also avoid negative numbers 1462 | // set the max boundary 1463 | if (duty_cycle >= max_pwm) { duty_cycle = max_pwm; } // max = 100% = 255 1464 | 1465 | // remember the last error value 1466 | lastError = tempError; 1467 | 1468 | // set the updated pwm for the fan 1469 | analogWrite(fan_pin, duty_cycle); 1470 | } 1471 | 1472 | 1473 | void loop() 1474 | { 1475 | if (PPS_ReadFlag == true) // set by capture of PPS on D8 1476 | { 1477 | calculation(); 1478 | control_fan(); // *** paulv 1479 | getCommand(); 1480 | printDataToSerial(); 1481 | delay(100); // delay 100 milliseconds to give the PPS locked LED some time on if turned off in next step 1482 | if ((dacValueOut < 3000 || dacValueOut > 62535) && opMode == run) 1483 | { 1484 | digitalWrite(ppsLockedLED,false); // turn off (flash)LED 13 if DAC near limits 1485 | } 1486 | PPS_ReadFlag = false; 1487 | } 1488 | 1489 | // timer1 overflow counter // if no 10MHz time will not increment as no overflows will be registered 1490 | TCNT1old = TCNT1new; 1491 | TCNT1new = TCNT1; 1492 | if (TCNT1new < TCNT1old) // if just got an overflow on TCNT1 may miss some during calc+print etc 1493 | { 1494 | overflowCount++; // normally will increment every 10ms (50000x200ns) used for time since start(seconds) 1495 | if (overflowCount > 31 && (overflowCount - 30) % 100 == 0) // sense if more than 1sec since last pps pulse 1496 | { 1497 | Serial.println(F(" No PPS")); 1498 | digitalWrite(ppsLockedLED,false); // blink the LED two times if no PPS 1499 | delay(50); 1500 | digitalWrite(ppsLockedLED,true); 1501 | delay(20); 1502 | digitalWrite(ppsLockedLED,false); 1503 | delay(100); 1504 | digitalWrite(ppsLockedLED,true); 1505 | delay(20); 1506 | digitalWrite(ppsLockedLED,false); 1507 | if (overflowCount > 2000 ) newMode = run; // resets timer_us etc after 20s without PPS in calculation function 1508 | if (overflowCount > 20000 )lockPPScounter = 0; // resets locked after about 200secs without PPS 1509 | getCommand(); 1510 | if (time < warmUpTime)// avoid incrementing time before pps comes first time 1511 | { 1512 | overflowCount = 0 ; 1513 | } 1514 | } 1515 | } 1516 | } 1517 | -------------------------------------------------------------------------------- /mail_ocxo_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #------------------------------------------------------------------------------- 3 | # Name: module1 4 | # Purpose: 5 | # 6 | # Author: paulv 7 | # 8 | # Created: 13-03-2020 9 | # Copyright: (c) paulv 2020 10 | # Licence: 11 | #------------------------------------------------------------------------------- 12 | 13 | import os 14 | import re 15 | import subprocess 16 | import sys, traceback 17 | import email 18 | from time import time, sleep, gmtime, strftime, localtime 19 | import zipfile 20 | 21 | VERSION="1.0" # initial version 22 | DEBUG = False 23 | 24 | # here is where we store the errors and warnings 25 | log_file = "/mnt/usb/ocxo.log" 26 | zip_file = "/mnt/usb/bliley.zip" 27 | mail_address = "pw.versteeg@gmail.com" 28 | 29 | 30 | def mail_err_log(): 31 | ''' 32 | Just before a new day has been found by cron, this function emails the daily 33 | local error logs, but only if an error condition has been reported. 34 | ''' 35 | try: 36 | print("zip the file") 37 | os.chdir('/mnt/usb') 38 | zipfile.ZipFile('bliley.zip', mode='w').write('ocxo.log', compress_type=zipfile.ZIP_DEFLATED) 39 | except Exception as e: 40 | print(e) 41 | try: 42 | if os.path.isfile(log_file): 43 | with open(zip_file, "r") as fin: 44 | f_data = fin.read() 45 | 46 | # send it out as an attachement 47 | cmd = 'mpack -s "Bliley log file" {} {}'.format(zip_file, mail_address) 48 | print("mail_ocxo_log cmd : {}".format(cmd)) 49 | subprocess.call([cmd], shell=True) 50 | 51 | except Exception as e: 52 | send_mail("error", "Unexpected Exception in mail_ocxo_log() {0}".format(e)) 53 | return 54 | 55 | 56 | 57 | def main(): 58 | print("Mail ocxo log Version {}".format(VERSION)) 59 | mail_err_log() 60 | 61 | if __name__ == '__main__': 62 | main() 63 | --------------------------------------------------------------------------------