-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgpsdo-bt.ino
2657 lines (2328 loc) · 91.2 KB
/
gpsdo-bt.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#define Program_Name "CTI"
#define Program_Version "10 MHz"
#define Author_Name "MacMan72"
// 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)
// IMPORTANT! Don't forget to select the correct board in the Tools->Board menu in the arduino IDE
#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 (testing)
#define GPSDO_PWM_DAC // STM32 16-bit PWM DAC, requires two rc filters (2xr=20k, 2xc=10uF) - note this will become the default
#define GPSDO_AHT10 // AHT10 or AHT20 (recommended) 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_BMP280_I2C // I2C atmospheric pressure, temperature and altitude sensor
#define GPSDO_INA219 // INA219 I2C current and voltage sensor
// #define GPSDO_BLUETOOTH // Bluetooth serial (HC-06 module)
#define GPSDO_VCC // Vcc (nominal 5V) ; reading Vcc requires 1:2 voltage divider to PA0
#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_PICDIV // generate a 1.2s synchronization pulse for the picDIV
#define GPSDO_TM1637 // TM1637 4-digit LED module
// #define GPSDO_TIC // read TIC 12-bit value on PA1 (ADC channel 1), then discharge capacitor using PB2
#define GPSDO_EEPROM // enable STM32 buffered EEPROM emulation library
// Includes
// --------
#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION < 0x02020000)
#error "Due to API changes, this sketch is compatible with STM32_CORE_VERSION >= 0x02020000 (2.2.0 or later)"
#endif
// Increase HardwareSerial (UART) TX and RX buffer sizes from default 64 characters to 256.
// The main worry here is that we could miss some characters from the u-blox GPS module if
// the processor is busy doing something else (e.g. updating the display, reading a sensor, etc)
// specially since we increase the GPS baud rate from 9600 to 38400.
#define SERIAL_TX_BUFFER_SIZE 256 // Warning: > 256 could cause problems, see comments in STM32 HardwareSerial library
#define SERIAL_RX_BUFFER_SIZE 256
bool report_tab_delimited = false; // true for tab delimited reporting, false for human-readable reporting
uint64_t report_line_no = 0; // line number for tab delimited reporting, 0 if no GPS fix
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 <movingAvg.h> // 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
// HC-06 Bluetooth module
#ifdef GPSDO_BLUETOOTH
// UART RX TX
HardwareSerial Serial2(PA3, PA2); // Serial connection to HC-06 Bluetooth module
#define BT_BAUD 57600 // Bluetooth baud rate
#endif // BLUETOOTH
HardwareSerial Serial2(PA3, PA2);
// EEPROM emulation in flash
#ifdef GPSDO_EEPROM
#include <EEPROM.h> // Buffered EEPROM emulation library
#endif // EEPROM
#include <SerialCommands.h> // Commands parser library
char serial_command_buffer_[32]; // buffer for commands library
// The following line determines which serial port we'll listen to
// "\n" means only newline needed to accept command
#ifdef GPSDO_BLUETOOTH
SerialCommands serial_commands_(&Serial2, serial_command_buffer_, sizeof(serial_command_buffer_), "\n", " ");
#else
SerialCommands serial_commands_(&Serial, serial_command_buffer_, sizeof(serial_command_buffer_), "\n", " ");
#endif // BLUETOOTH
#include <TinyGPS++.h> // get library here > http://arduiniana.org/libraries/tinygpsplus/
TinyGPSPlus gps; // create the TinyGPS++ object
#include <Wire.h> // Hardware I2C library on STM32
// AHT10 / AHT20 temperature humidity sensor // Uses PB6 (SCL1) and PB7 (SDA1) on Black Pill for I2C1
#ifdef GPSDO_AHT10
#include <Adafruit_AHTX0.h> // Adafruit AHTX0 library
Adafruit_AHTX0 aht; // create object aht
#endif // AHT10
// INA219 current voltage sensor
#ifdef GPSDO_INA219
#include <Adafruit_INA219.h>
Adafruit_INA219 ina219;
float ina219volt=0.0, ina219curr=0.0;
#endif // INA219
// TM1637 4-digit LED module
#ifdef GPSDO_TM1637
#include <TM1637Display.h> // get library here > https://github.com/avishorp/TM1637
// Module connection pins (Digital Pins)
#define CLK PA8 // interface to TM1637 requires two GPIO pins
#define DIO PB4
TM1637Display tm1637(CLK, DIO); // create tm1637 object
const uint8_t mid_dashes[] = {
SEG_G, // -
SEG_G, // -
SEG_G, // -
SEG_G // -
};
const uint8_t low_oooo_s[] = {
SEG_C | SEG_D | SEG_E | SEG_G, // o
SEG_C | SEG_D | SEG_E | SEG_G, // o
SEG_C | SEG_D | SEG_E | SEG_G, // o
SEG_C | SEG_D | SEG_E | SEG_G // o
};
#endif // TM1637
// OLED 0.96 SSD1306 128x64
#ifdef GPSDO_OLED
#include <U8x8lib.h> // 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 <Adafruit_GFX.h> // need this adapted for STM32F4xx/F411C: https://github.com/fpistm/Adafruit-GFX-Library/tree/Fix_pin_type
#include <Adafruit_ST7735.h>
//#include <Fonts/FreeSansBold18pt7b.h>
#include <SPI.h>
#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_ST7735
// LCD 1.3" ST7789 240x240 (Testing)
#ifdef GPSDO_LCD_ST7789
#include <Adafruit_GFX.h> // need this adapted for STM32F4xx/F411C: https://github.com/fpistm/Adafruit-GFX-Library/tree/Fix_pin_type
#include <Adafruit_ST7789.h>
#include <SPI.h>
#define TFT_DC PB12 // note pin assigment that does not conflict with other interfaces
#define TFT_CS PB13 // in reality, CS not connected, CS not used on 1.3" TFT ST7789 display
#define TFT_RST PB15 // also uses pins PA5, PA6, PA7 for MOSI MISO and SCLK
// For 1.3" LCD with ST7789
Adafruit_ST7789 disp_st7789 = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
bool must_clear_disp_st7789 = false; // flag is set when display has to be cleared
// include the following GFX library fonts
#include <Fonts/FreeMono9pt7b.h> // tiny, but readable in white
#include <Fonts/FreeMonoBold12pt7b.h> // medium size, readable
#endif // LCD_ST7789
// PWM 16-bit DAC
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
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 VctlPWMOutputPin PB9 // digital output pin used to output a PWM value, TIM4 ch4
// Two cascaded RC filters transform the PWM into an analog DC value
#define VctlPWMInputPin PB1 // ADC pin to read Vctl from filtered PWM
volatile int pwmVctl = 0; // variable used to store PWM Vctl read by ADC pin PB1
// VCC - 5V
#ifdef GPSDO_VCC
#define VccDiv2InputPin PA0 // Vcc/2 using resistor divider connects to PA0
int adcVcc = 0;
#endif // VCC
// VDD - 3.3V
#ifdef GPSDO_VDD
int adcVdd = 0; // Vdd is read internally as Vref
#endif // VDD
// movingAvg objects for the voltages measured by MCU ADC
// all averages over 10 samples (10 seconds in principle)
#ifdef GPSDO_VDD
movingAvg avg_adcVdd(10);
int16_t avgVdd = 0;
#endif // VDD
#ifdef GPSDO_VCC
movingAvg avg_adcVcc(10);
int16_t avgVcc = 0;
#endif // VCC
movingAvg avg_pwmVctl(10);
int16_t avgpwmVctl = 0;
// BMP280 atmospheric pressure and temperature sensor
#ifdef GPSDO_BMP280_SPI
// BMP280 - SPI
#include <SPI.h>
#include <Adafruit_BMP280.h>
#define BMP280_CS (PA4) // SPI1 uses PA4, PA5, PA6, PA7
Adafruit_BMP280 bmp(BMP280_CS); // hardware SPI, use PA4 as Chip Select
#endif // BMP280_SPI
#ifdef GPSDO_BMP280_I2C
// BMP280 - I2C
#include <Adafruit_BMP280.h>
Adafruit_BMP280 bmp; // hardware I2C
#endif // BMP280_I2C
#if (defined (GPSDO_BMP280_SPI) || defined (GPSDO_BMP280_I2C))
const uint16_t PressureOffset = 1860; // 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
// LEDs
// Blue onboard LED blinks to indicate ISR is working
#define blueledpin PC13 // Blue onboard LED is on PC13 on STM32F411CEU6 Black Pill
// Yellow extra LED is off, on or blinking to indicate some GPSDO status
#define yellowledpin PB8 // Yellow LED on PB8
volatile int yellow_led_state = 2; // global variable 0=off 1=on 2=1Hz blink
// GPS data
float GPSLat; // Latitude from GPS
float GPSLon; // Longitude from GPS
float GPSAlt; // Altitude from GPS
uint8_t GPSSats; // number of GPS satellites in use
uint32_t GPSHdop; // HDOP from GPS
uint8_t hours, mins, secs, day, month;
uint16_t year;
uint32_t startGetFixmS;
uint32_t endFixmS;
// Uptime data
volatile uint8_t uphours = 0;
volatile uint8_t upminutes = 0;
volatile uint8_t upseconds = 0;
volatile uint16_t updays = 0;
volatile bool halfsecond = false;
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
const uint32_t basefreq=10000000; // OCXO nominal frequency in Hz
volatile uint32_t lsfcount=0, previousfcount=0, calcfreqint=basefreq;
// Frequency check boundaries
const uint32_t lowerfcount = 9999500;
const uint32_t upperfcount = 10000500;
/* Moving average frequency variables
Basically we store the counter captures for 10 and 100 seconds.
When the buffers are full, the average frequency is quite simply
the difference between the oldest and newest data divided by the size
of the buffer.
Each second, when the buffers are full, we overwrite the oldest data
with the newest data and calculate each average frequency.
*/
volatile uint64_t fcount64=0, prevfcount64=0, calcfreq64=basefreq;
// ATTENTION! must declare 64-bit, not 32-bit variable, because of shift
volatile uint64_t tim2overflowcounter = 0; // testing, counts the number of times TIM2 overflows
volatile bool overflowflag = false; // flag set by the overflow ISR, reset by the 2Hz ISR
volatile bool captureflag = false; // flag set by the capture ISR, reset by the 2Hz ISR
volatile bool overflowErrorFlag = false; // flag set if there was an overflow processing error
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;
volatile uint32_t cbitho_newest=0;
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
// picDIV support
#ifdef GPSDO_PICDIV
#define VphaseInputPin PB1 // ADC pin to read Vphase from 1ns-resolution TIC
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
#ifdef FastBootMode
const uint16_t ocxo_warmup_time = 15; // ocxo warmup time in seconds; 15s for testing
const uint16_t ocxo_calib_time = 15; // 15s fast calibration countdown time (for each calibration step)
#else
const uint16_t ocxo_warmup_time = 300; // ocxo warmup time in seconds; 300s or 600s normal use
const uint16_t ocxo_calib_time = 60; // 60s normal calibration countdown time (for each calibration step)
#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
// SerialCommands callback functions
// This is the default handler, and gets called when no other command matches.
void cmd_unrecognized(SerialCommands* sender, const char* cmd)
{
sender->GetSerial()->print("Unrecognized command [");
sender->GetSerial()->print(cmd);
sender->GetSerial()->println("]");
}
// called for V (version) command
void cmd_version(SerialCommands* sender)
{
sender->GetSerial()->print(Program_Name);
sender->GetSerial()->print(" - ");
sender->GetSerial()->print(Program_Version);
sender->GetSerial()->print(" by ");
sender->GetSerial()->println(Author_Name);
}
// called for RD (Report tab Delimited format) command
void cmd_repdel(SerialCommands* sender)
{
report_tab_delimited = true; // set switch
sender->GetSerial()->println("Switching to reporting in Tab Delimited Format");
}
// called for RH (Report Human readable format) command
void cmd_rephum(SerialCommands* sender)
{
report_tab_delimited = false; // reset switch
report_line_no = 0; // reset line counter
sender->GetSerial()->println("Switching to reporting in Human Readable Format");
}
// called for F (flush ring buffers) command
void cmd_flush(SerialCommands* sender)
{
flush_ring_buffers_flag = true; // ring buffers will be flushed inside interrupt routine
sender->GetSerial()->println("Ring buffers flushed");
}
// called for C (calibration) command
void cmd_calibrate(SerialCommands* sender)
{
force_calibration_flag = true; // starts auto-calibration sequence
sender->GetSerial()->println("Auto-calibration sequence started");
}
// called for T (tunnel) command
void cmd_tunnel(SerialCommands* sender)
{
tunnel_mode_flag = true; // switches GPSDO operation to tunnel mode
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);
}
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);
}
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)
{
adjusted_PWM_output = adjusted_PWM_output + 10;
analogWrite(VctlPWMOutputPin, adjusted_PWM_output);
sender->GetSerial()->println("increased PWM 10 bits");
}
// called for dp1 (decrease PWM 1 bit) command
void cmd_dp1(SerialCommands* sender)
{
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)
{
adjusted_PWM_output = adjusted_PWM_output - 10;
analogWrite(VctlPWMOutputPin, adjusted_PWM_output);
sender->GetSerial()->println("decreased PWM 10 bits");
}
#ifdef GPSDO_EEPROM
// add callback functions for PS and PR commands to Store PWM and Recall PWM value in Flash (emulating EEPROM)
// warning! limited number of writes allowed
#endif // EEPROM
// 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_rephum_("RH", cmd_rephum); // activate humand readable reporting
SerialCommand cmd_repdel_("RD", cmd_repdel); // activate tab delimited reporting
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_dp1_("dp1", cmd_dp1);
SerialCommand cmd_dp10_("dp10", cmd_dp10);
#ifdef GPSDO_EEPROM
// add PS and PR commands to Store PWM and Recall PWM value in Flash (emulating EEPROM)
// warning! limited number of writes allowed
#endif // EEPROM
// 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
// Interrupt Service Routine for TIM2 counter overflow / wraparound
void Timer2_Overflow_ISR(void)
{
overflowflag = true;
}
// Interrupt Service Routine for TIM2 counter capture
void Timer2_Capture_ISR(void)
{
captureflag = true;
}
// Interrupt Service Routine for the 2Hz timer
void Timer_ISR_2Hz(void) // WARNING! Do not attempt I2C communication inside the ISR
{ // Toggle pin. 2hz toogle --> 1Hz pulse, perfect 50% duty cycle
digitalWrite(blueledpin, !digitalRead(blueledpin));
halfsecond = !halfsecond; // true @ 1Hz
// read TIM2->CCR3 once per second (when captureflag is set) and if it has changed, calculate OCXO frequency
if (captureflag) {
lsfcount = TIM2->CCR3; // read TIM2->CCR3
captureflag = false; // clear capture flag
if (flush_ring_buffers_flag)
{
flushringbuffers(); // flush ring buffers after a sat fix loss
}
else // check if the frequency counter has been updated and process accordingly
{
// there are two possible cases
// 1. lsfcount is the same as last time -> there is nothing to do, or
// 2. lsfcount is NOT the same as last time -> process
if (lsfcount != previousfcount)
{
// again we must consider two cases
// 1. lsfcount < previousfcount -> a wraparound has occurred, process
// 2. lsfcount > previous fcount -> no wraparound processing required
if (lsfcount < previousfcount)
{
must_adjust_DAC = true; // set flag, once every wraparound / every 429s
// check wraparound flag, it should be set, if so clear it, otherwise raise error flag
tim2overflowcounter++;
if (overflowflag) overflowflag=false; else overflowErrorFlag = true;
}
fcount64 = (tim2overflowcounter << 32) + lsfcount; // hehe now we have a 64-bit counter
if (fcount64 > prevfcount64) { // if we have a new count - that happens once per second
if (((fcount64 - prevfcount64) > lowerfcount) && ((fcount64 - prevfcount64) < upperfcount)) { // if we have a valid fcount, otherwise it's discarded
logfcount64(); // save fcount in the 64-bit ring buffers
calcfreq64 = fcount64 - prevfcount64; // the difference is exactly the OCXO frequency in Hz
}
prevfcount64 = fcount64;
}
}
previousfcount = lsfcount; // this happens whether it has changed or not
}
}
switch (yellow_led_state)
{
case 0:
// turn off led
digitalWrite(yellowledpin, LOW);
break;
case 1:
// turn on led
digitalWrite(yellowledpin, HIGH);
break;
case 2:
// blink led
digitalWrite(yellowledpin, !digitalRead(yellowledpin));
break;
default:
// default is to turn off led
digitalWrite(yellowledpin, LOW);
break;
}
// Uptime clock - in days, hours, minutes, seconds
if (halfsecond)
{
if (++upseconds > 59)
{
upseconds = 0;
if (++upminutes > 59)
{
upminutes = 0;
if (++uphours > 23)
{
uphours = 0;
++updays;
}
}
}
}
}
void logfcount64() // called once per second from ISR to update all the ring buffers
{
// 10 seconds buffer
circbuf_ten64[cbiten_newest]=fcount64;
cbiten_newest++;
if (cbiten_newest > 10) {
cbTen_full=true; // this only needs to happen once, when the buffer fills up for the first time
cbiten_newest = 0; // (wrap around)
}
// 100 seconds buffer
circbuf_hun64[cbihun_newest]=fcount64;
cbihun_newest++;
if (cbihun_newest > 100) {
cbHun_full=true; // this only needs to happen once, when the buffer fills up for the first time
cbihun_newest = 0; // (wrap around)
}
// 1000 seconds buffer
circbuf_tho64[cbitho_newest]=fcount64;
cbitho_newest++;
if (cbitho_newest > 1000) {
cbTho_full=true; // this only needs to happen once, when the buffer fills up for the first time
cbitho_newest = 0; // (wrap around)
}
// 10000 seconds buffer (2 hr 46 min 40 sec)
circbuf_tth64[cbitth_newest]=fcount64;
cbitth_newest++;
if (cbitth_newest > 10000) {
cbTth_full=true; // this only needs to happen once, when the buffer fills up for the first time
cbitth_newest = 0; // (wrap around)
}
calcavg(); // always recalculate averages after logging fcount (if the respective buffers are full)
}
void calcavg() {
// Calculate the OCXO frequency to 1, 2, 3 or 4 decimal places only when the respective buffers are full
// Try to understand the algorithm for the 10s ring buffer first, the others work exactly the same
uint64_t latfcount64, oldfcount64; // latest fcount, oldest fcount stored in ring buffer
if (cbTen_full) { // we want (latest fcount - oldest fcount) / 10
// latest fcount is always circbuf_ten64[cbiten_newest-1]
// except when cbiten_newest is zero
// oldest fcount is always circbuf_ten64[cbiten_newest] when buffer is full
if (cbiten_newest == 0) latfcount64 = circbuf_ten64[10];
else latfcount64 = circbuf_ten64[cbiten_newest-1];
oldfcount64 = circbuf_ten64[cbiten_newest];
// now that we have latfcount64 and oldfcount64 we can calculate the average frequency
avgften = double(latfcount64 - oldfcount64)/10.0;
}
if (cbHun_full) { // we want (latest fcount - oldest fcount) / 100
// latest fcount is always circbuf_hun[cbihun_newest-1]
// except when cbihun_newest is zero
// oldest fcount is always circbuf_hun[cbihun_newest] when buffer is full
if (cbihun_newest == 0) latfcount64 = circbuf_hun64[100];
else latfcount64 = circbuf_hun64[cbihun_newest-1];
oldfcount64 = circbuf_hun64[cbihun_newest];
avgfhun = double(latfcount64 - oldfcount64)/100.0;
}
if (cbTho_full) { // we want (latest fcount - oldest fcount) / 1000
// latest fcount is always circbuf_tho[cbitho_newest-1]
// except when cbitho_newest is zero
// oldest fcount is always circbuf_tho[cbitho_newest] when buffer is full
if (cbitho_newest == 0) latfcount64 = circbuf_tho64[1000];
else latfcount64 = circbuf_tho64[cbitho_newest-1];
oldfcount64 = circbuf_tho64[cbitho_newest];
avgftho = double(latfcount64 - oldfcount64)/1000.0;
// oldest fcount is always circbuf_ten[cbiten_newest-2]
// except when cbiten_newest is <2 (zero or 1)
}
if (cbTth_full) { // we want (latest fcount - oldest fcount) / 10000
// latest fcount is always circbuf_tth[cbitth_newest-1]
// except when cbitth_newest is zero
// oldest fcount is always circbuf_tth[cbitth_newest] when buffer is full
if (cbitth_newest == 0) latfcount64 = circbuf_tth64[10000];
else latfcount64 = circbuf_tth64[cbitth_newest-1];
oldfcount64 = circbuf_tth64[cbitth_newest];
avgftth = double(latfcount64 - oldfcount64)/10000.0;
// oldest fcount is always circbuf_ten[cbiten_newest-2]
// except when cbiten_newest is <2 (zero or 1)
}
}
void flushringbuffers(void) {
cbTen_full = false;
cbHun_full = false;
cbTho_full = false;
cbTth_full = false;
cbiten_newest = 0;
cbihun_newest = 0;
cbitho_newest = 0;
cbitth_newest = 0;
avgften = 0;
avgfhun = 0;
avgftho = 0;
avgftth = 0;
prevfcount64 = 0;
previousfcount = 0;
flush_ring_buffers_flag = false; // clear flag
}
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()
{
// 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<len; i++) {
Serial1.write(MSG[i]);
Serial.print(MSG[i], HEX);
}
Serial1.println();
}
// 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;
}
// 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
// ---------------------------------------------------------------------------------------------
// GPSDO tunnel mode (GPS serial is relayed to Bluetooth serial or USB serial)
// ---------------------------------------------------------------------------------------------
void tunnelgps()
// GPSDO tunnel mode operation
{
#ifdef GPSDO_BLUETOOTH // print entering tunnel mode 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 starts here
uint32_t endtunnelmS = millis() + (tunnelSecs * 1000);
uint8_t GPSchar;
uint8_t PCchar;
while (millis() < endtunnelmS)
{
if (Serial1.available() > 0)
{
GPSchar = Serial1.read();
#ifdef GPSDO_BLUETOOTH
Serial2.write(GPSchar); // echo GPS NMEA serial stream to Bluetooth serial
#else
Serial.write(GPSchar); // echo GPS NMEA serial stream to USB serial
#endif // BLUETOOTH
}
#ifdef GPSDO_BLUETOOTH
if (Serial2.available() > 0)
#else
if (Serial.available() > 0)
#endif // BLUETOOTH
{
#ifdef GPSDO_BLUETOOTH
PCchar = Serial2.read();
#else
PCchar = Serial.read();
#endif // BLUETOOTH
Serial1.write(PCchar); // echo USB serial stream to GPS serial
}
}
// tunnel mode operation ends here
#ifdef GPSDO_BLUETOOTH // print exiting tunnel mode 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
tunnel_mode_flag = false; // reset flag, exit tunnel mode
} // end of tunnel mode routine
// ---------------------------------------------------------------------------------------------
// OCXO warmup delay routine (only needed during a GPSDO "cold start")
// ---------------------------------------------------------------------------------------------
void doocxowarmup()
{
// Spend a few seconds/minutes here just waiting for the OCXO to warmup
// show countdown timer on OLED or LCD display
// and report on either USB serial or Bluetooth serial
// Note: during OCXO warmup the GPSDO does not accept any commands
uint16_t countdown = ocxo_warmup_time;
while (countdown) {
#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_LCD_ST7735
disp.fillScreen(ST7735_BLACK); // display warmup message on LCD ST7735
disp.setCursor(0, 0);
disp.print(F(Program_Name));
disp.print(F(" - "));
disp.print(F(Program_Version));
disp.setCursor(0, 16);
disp.print(F("OCXO warming up"));
disp.setCursor(0, 24);
disp.print(F("Please wait"));
disp.setCursor(0, 32);
disp.print(countdown);
disp.print(F("s"));
#endif // LCD_ST7735
#ifdef GPSDO_LCD_ST7789
// display OCXO warmup message on ST7789 LCD
disp_st7789.fillScreen(ST77XX_BLACK); // clear display
// Display program name and version
disp_st7789.setTextSize(1);
disp_st7789.setFont(&FreeMonoBold12pt7b);
disp_st7789.setTextColor(ST77XX_YELLOW);
disp_st7789.setCursor(0, 16);
disp_st7789.print(F("STM32 "));
disp_st7789.print(F(Program_Name));
disp_st7789.setTextSize(1);
disp_st7789.setFont(&FreeMono9pt7b);
disp_st7789.setTextColor(ST77XX_CYAN);
disp_st7789.setCursor(168, 11);
disp_st7789.print(F(Program_Version));
// display OCXO warming up and countdown
disp_st7789.setCursor(0, 36);
disp_st7789.setTextColor(ST77XX_WHITE);
disp_st7789.print(F("OCXO warming up"));
disp_st7789.setCursor(0, 50);
disp_st7789.print(F("Please wait"));
disp_st7789.setCursor(0, 64);
disp_st7789.print(countdown);
disp_st7789.print(F("s"));
must_clear_disp_st7789 = true;
#endif // LCD_ST7789
#ifdef GPSDO_BLUETOOTH // print warmup countdown message to either
Serial2.println(); // Bluetooth serial xor USB serial
Serial2.print(F("OCXO Warming up, "));
Serial2.print(countdown);
Serial2.println(F("s remaining"));
#else
Serial.println();
Serial.print(F("OCXO Warming up, "));
Serial.print(countdown);
Serial.println(F("s remaining"));
//MAC adding initial messages to BT
Serial2.println(); // Bluetooth serial xor USB serial
Serial2.print(F("OCXO Warming up, "));
Serial2.print(countdown);
Serial2.println(F("s remaining"));
#endif // BLUETOOTH
// do nothing for 1s
delay(1000);
countdown--;
}
ocxo_needs_warming = false; // reset flag, next "hot" calibration skips ocxo warmup
} // end of OCXO warmup routine
// ---------------------------------------------------------------------------------------------
// GPSDO calibration routine
// ---------------------------------------------------------------------------------------------
void docalibration()
// OCXO Vctl calibration: find an approximate value for Vctl
{
yellow_led_state = 2; // blink yellow LED (handled by 2Hz ISR)
if (ocxo_needs_warming) doocxowarmup();
// Note: during calibration the GPSDO does not accept any commands
#ifdef GPSDO_BLUETOOTH // print calibration 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();
//MAC adding initial messages to BT
Serial2.println(); // Bluetooth serial xor USB serial
Serial2.print(F("Calibrating..."));
Serial2.println();
#endif // BLUETOOTH
#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
#ifdef GPSDO_LCD_ST7735
disp.fillScreen(ST7735_BLACK); // display calibrating message on LCD ST7735
disp.setCursor(0, 0);
disp.print(F(Program_Name));
disp.print(F(" - "));
disp.print(F(Program_Version));
disp.setCursor(0, 16);
disp.print(F("Calibrating..."));
disp.setCursor(0, 24);
disp.print(F("Please wait"));
#endif // LCD_ST7735
#ifdef GPSDO_LCD_ST7789
// display calibrating message on LCD ST7789
disp_st7789.fillScreen(ST77XX_BLACK); // clear display
// display program name and version