-
Notifications
You must be signed in to change notification settings - Fork 1
/
QRP_LABS_WSPR.ino
2213 lines (1803 loc) · 74.4 KB
/
QRP_LABS_WSPR.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
// QRP_LABS_WSPR
// Arduino, QRP Labs Arduino shield, SI5351 clock, QRP Labs RX module, QRP Labs relay board.
// NOTE: The tx bias pot works in reverse, fully clockwise is off.
// Added a CANADAUINO WWVB interface to keep time.
// common startup commands with WWVB logging
// enter 1 CAT command, ?V for Rx only or #0 to stay in FRAME TX mode
// The CAT emulation is TenTec Argonaut V at 1200 baud.
// New encoder switch commands:
// Tap : change tuning step 10hz to 1Meg
// DTap: toggle mode WSPR TX Frame to CAT control - RX mode
// Long: change band
// To set a new operation frequency for stand alone Frame Mode, restart the Arduino, start wsjt-x or HRD.
// Tune to one of the magic WSPR frequencies and toggle TX (tune in wsjt will work).
// The new frequency will be stored in EEPROM.
// If using the band hopping feature of WSJT, make sure the first transmit is on the band you wish for
// the default. Only one save is performed per Arduino reset.
//
// A 4:1 frequency relationship between the tx freq and the rx clock is maintained using the
// R dividers in the SI5351. Dividers 1 - rx and 4 - tx will cover 1mhz to 30mhz
// Dividers 16 - rx and 64 - tx will cover 40 khz to 2 mhz
// When using even output divider, PLL c parameter...
// For 1 hz resolution, c = CLOCK / divider.
// For 1.46 hz resolution, c = CLOCK / ( 1.46 * divider ).
// Can then send WSPR by just adding to the b parameter 0,1,2,or3.
// Smallest divider for 1 hz resolution, about 26.
// Max value of c is 1048575
// Use of the R dividers will complicate this.
#include <FreqCount.h> // one UNO I have does not work correctly with this library, another one does
#include <EEPROM.h>
#include <OLED1306_Basic.h>
#define ROW0 0 // text based rows for the 128x64 OLED
#define ROW1 8
#define ROW2 16
#define ROW3 24
#define ROW4 32
#define ROW5 40
#define ROW6 48
#define ROW7 56
#define SI5351 0x60 // i2c address
#define PLLA 26 // register address offsets for PLL's
#define PLLB 34
#define CLK0_EN 1
#define CLK1_EN 2
#define CLK2_EN 4
// the starting frequency will be read out of EEPROM except the 1st time when EEPROM is blank
#define FREQ 7038600 // starting freq when EEPROM is blank
#define DIV 28 // starting divider for 4*freq*RDIV
#define RDIV 1 // starting sub divider. 1 will cover less than 1mhz to > 30mhz.
// 16 will cover 37khz to 2.3 mhz ( with the 4x factor it is 64 when transmitting )
#define CAT_MODE 0 // computer control of TX
#define FRAME_MODE 1 // self timed frame (stand alone mode)
#define MUTE A1 // receiver module T/R switch pin
#define START_CLOCK_FREQ 27004456L // ( 4498, 4466 )
//#define START_CLOCK_FREQ 2700466600 // test too high
//#define START_CLOCK_FREQ 2700426600 // test too low
//#define CLK_UPDATE_THRESHOLD 59 // errors allowed per minute to consider valid sync to WWVB
#define CLK_UPDATE_THRESHOLD2 48
#define DEADBAND 25 // wwvb signal timing +-deadband
#define stage(c) Serial.write(c)
OLED1306 LCD; // a modified version of LCD_BASIC by Rinky-Dink Electronics
extern unsigned char SmallFont[];
extern unsigned char MediumNumbers[];
extern unsigned char BigNumbers[];
int last_error_count = 60;
char val_print = ' ';
// using even dividers between 6 and 254 for lower jitter
// freq range 2 to 150 without using the post dividers
// we are using the post dividers and can receive down to 40khz
// vco 600 to 900
uint32_t clock_freq = START_CLOCK_FREQ;
uint32_t drift; // calibrate freq result used as a measure of temperature and mapped to correct
// 27 master clock drift
uint32_t freq = FREQ; // ssb vfo freq
const uint32_t cal_freq = 3000000; // calibrate frequency
long cal_result;
const uint32_t cal_divider = 200;
uint32_t divider = DIV;
uint32_t audio_freq = 1466; // wspr 1400 to 1600 offset from base vfo freq
uint8_t Rdiv = RDIV;
uint8_t operate_mode = FRAME_MODE; //FRAME_MODE CAT_MODE // start in stand alone timing mode
uint8_t wspr_tx_enable; // transmit enable
uint8_t wspr_tx_cancel; // CAT control RX command cancels tx
uint8_t cal_enable;
long tm_correct_count = 60000; // add or sub one ms for time correction per this many ms
int8_t tm_correction = 0; // 0, 1 or -1 time correction
int8_t tm_correction2; // frame sync to wwvb signal falling edge
// Download WSPRcode.exe from http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe and run it in a dos window
// Bad link now - K1JT has moved to SourceForge
// Type (for example): WSPRcode "K1ABC FN33 37" 37 is 5 watts, 30 is 1 watt, 33 is 2 watts, 27 is 1/2 watt
// ( Use capital letters in your call and locator when typing in the message string. No extra spaces )
// Using the editing features of the dos window, mark and copy the last group of numbers
// Paste into notepad and replace all 3 with "3," all 2 with "2," all 1 with "1," all 0 with "0,"
// Remove the comma on the end
// the current message is "KE1MU FN54 23"
const char wspr_msg[] = {
3, 1, 2, 2, 0, 2, 2, 2, 3, 2, 2, 2, 1, 1, 3, 2, 0, 0, 3, 0, 0, 1, 0, 1, 3, 3, 1, 2, 0, 2,
0, 2, 2, 2, 3, 2, 0, 1, 0, 1, 2, 2, 0, 0, 0, 0, 3, 0, 3, 1, 2, 0, 3, 1, 0, 1, 2, 0, 2, 3,
1, 2, 3, 2, 0, 0, 0, 1, 1, 2, 1, 0, 1, 2, 3, 0, 1, 0, 2, 3, 0, 0, 3, 0, 3, 1, 0, 2, 0, 3,
3, 2, 3, 2, 1, 0, 2, 2, 1, 2, 0, 0, 0, 2, 3, 2, 2, 1, 0, 2, 1, 3, 1, 0, 1, 1, 0, 0, 3, 1,
2, 3, 2, 0, 0, 1, 1, 3, 2, 2, 0, 2, 2, 3, 0, 1, 2, 0, 3, 1, 2, 2, 0, 0, 0, 2, 2, 1, 1, 0,
3, 2, 1, 1, 0, 2, 2, 1, 3, 0, 0, 2
};
uint8_t band; // current band
struct BAND {
int pin; // band relay switching
uint32_t low; // low frequency limit
uint32_t high; // high frewquency limit
};
uint8_t wband = 3; // wspr band switching via encoder long press
uint8_t sstate[1]; // switch state, one switch
// relay board was jumpered to NOT have filter 1 always in line and antenna connects to the bnc
// on the arduino shield. ( otherwise highest freq would need to be in position 1 and output would
// be from the relay board )
struct BAND band_info[6] = { // filter selected
{ 7, 40000, 600000 }, // 630m
{ A0, 600000, 2500000 }, // 160m
{ 10, 2500000, 5000000 }, // 80m
{ 11, 5000000, 11500000 }, // 30m
{ 12,11500000, 20000000 }, // 17m
{ A3,20000000, 30000000 } // 10m
};
// wspr frequencies for eeprom save routine. Only these frequencies will be saved.
const uint32_t magic_freq[10] = {
474200, 1836600, 3568600, 7038600, 10138700, 14095600, 18104600, 21094600, 24924600, 28124600
};
#define WWVB_OUT 9
#define WWVB_PWDN 8 // was the low enable. Rewired the WWVB receiver to get power from this I/O pin.
// this reverses the logic so it is now high to enable. With only two wires in the
// cable, this seems like the best way to remove the need for the 9 volt battery
// that was powering the wwvb receiver and to avoid taking the WSPR unit apart to
// switch the wiring to +5 volts instead of an I/O pin.
uint64_t wwvb_data, wwvb_sync, wwvb_errors;
uint8_t wwvb_quiet = 0; // wwvb debug print flag, set to 1 for printing
// or enter 1 CAT command( ?V for Rx only or #0 to stay in FRAME mode with logging )
uint8_t wwvb_stats[8]; // bit distribution over 60 seconds
uint8_t wwvb_last_err; // display last error character received ( will show what causes just one error )
uint8_t DST; // daylight savings bit
uint8_t frame_sec; // frame timer counts 0 to 120
int frame_msec;
// uint8_t tick; // start each minute, what was this for ? to match displayed time with computer time
// but disturbs the trending bit display
// very long term time correction
int FF = 0; // fixed part of fudge factor for frequency counter result ( counting 3 mhz signal )
int ff = 0; // fractional part of the fudge factor ( floats not useful as limited in significant figures )
uint8_t dbug_print_state; // print messages at 1200 baud without blocking
// date, time keeping
int gmon = 1,gday = 1,gyr = 1,ghr,gmin;
int tot_days = 1;
uint16_t leap = 1;
// #define TK 4 // keep time has been run
#define TS 2 // time was set from WWVB decode
// #define TP 1 // print decode indicator
uint8_t time_flags; // WWVB encodes the previous minute, flags to print the correct time
uint8_t trends[60];
uint8_t clr_trends;
unsigned int decodes;
uint8_t report_i; // see if a single trend shows the lsb of minutes position in time.
long int stp = 1000;
int debug_i;
/***************************************************************************/
void ee_save(){
uint8_t i;
static uint8_t last_i = 255;
if( last_i != 255 ) return; // save only the freq of the 1st time transmitting after reset
for( i = 0; i < 10; ++i ){
if( freq == magic_freq[i] ) break;
}
if( i == 10 ) return; // not a wspr frequency
// if( i == last_i ) return; // already wrote this one. ( redundant - only saving 1st power up tx freq now
// instead of the last transmit freq. This allows wsjt band hopping tx
// without wearing out the eeprom
last_i = i;
EEPROM.put(0,Rdiv); // put does not write if data matches
EEPROM.put(1,freq); // and hopefully this will not take long when there is a match
EEPROM.put(5,divider);
}
void ee_restore(){
if( EEPROM[0] == 255 ) return; // blank eeprom
EEPROM.get(0,Rdiv);
EEPROM.get(1,freq);
EEPROM.get(5,divider);
//Serial.println(Rdiv);
//Serial.println(freq);
//Serial.println(divider);
}
void setup() {
uint8_t i;
Serial.begin(1200); // TenTec Argo V baud rate 1200
i2init();
ee_restore(); // get default freq for frame mode from eeprom
pinMode(MUTE,OUTPUT); // receiver t/r switch
digitalWrite(MUTE,LOW); // enable the receiver
pinMode(WWVB_OUT, INPUT); // sample wwvb receiver signal
pinMode(WWVB_PWDN, OUTPUT);
// digitalWrite(WWVB_PWDN,LOW); // enable wwvb receiver
digitalWrite(WWVB_PWDN,HIGH); // power the wwvb receiver with the I/O pin
// set up the relay pins, exercise the relays, delay is 1.2 seconds, so reset at 59 seconds odd minute to be on time
for( i = 0; i < 6; ++i ){
pinMode(band_info[i].pin,OUTPUT);
digitalWrite( band_info[i].pin,LOW );
delay(200);
digitalWrite( band_info[i].pin,HIGH );
}
i2cd(SI5351,16,0x4f); // clock 0, PLLA
i2cd(SI5351,17,0x4f); // clock 1, PLLA
i2cd(SI5351,18,0x6f); // clock 2, PLLB
// set some divider registers that will never change
for(i = 0; i < 3; ++i ){
i2cd(SI5351,42+8*i,0);
i2cd(SI5351,43+8*i,1);
i2cd(SI5351,47+8*i,0);
i2cd(SI5351,48+8*i,0);
i2cd(SI5351,49+8*i,0);
}
si_pll_x(PLLB,cal_freq,cal_divider,0); // calibrate frequency on clock 2
si_load_divider(cal_divider,2,0,1);
si_pll_x(PLLA,Rdiv*4*freq,divider,0); // receiver 4x clock
si_load_divider(divider,0,0,Rdiv*4); // TX clock 1/4th of the RX clock
si_load_divider(divider,1,1,Rdiv); // load divider for clock 1 and reset pll's
i2cd(SI5351,3,0xff ^ (CLK1_EN + CLK2_EN) ); // turn on clocks, receiver and calibrate
// i2cd(SI5351,3,0xff ^ (CLK0_EN + CLK1_EN + CLK2_EN)); // testing only all on, remove tx PWR
digitalWrite(band_info[band].pin,LOW); // in case this turns out to be the correct relay
band_change(); // select the correct relay
//Serial.println(F("Starting..."));
pinMode(13,OUTPUT);
LCD.InitLCD(); // using a modified Nokia library for the OLED
LCD.setFont(SmallFont);
LCD.clrScr();
LCD.print("QRP-LABS WSPR SDR",0,ROW0);
i2flush();
delay(2000);
// LCD.clrRow(0); // ? clear to end from current position
LCD.clrScr();
freq_display();
mode_display();
}
int8_t encoder(){ /* read encoder, return 1, 0, or -1 */
static int8_t mod; /* encoder is divided by 4 because it has detents */
static int8_t dir; /* need same direction as last time, effective debounce */
static int8_t last; /* save the previous reading */
int8_t new_; /* this reading */
int8_t b;
new_ = (PIND >> 2) & 3;
if( new_ == last ) return 0; /* no change */
b = ( (last << 1) ^ new_ ) & 2; /* direction 2 or 0 from xor of last shifted and new data */
last = new_;
if( b != dir ){
dir = b;
return 0; /* require two in the same direction serves as debounce */
}
mod = (mod + 1) & 3; /* divide by 4 for encoder with detents */
if( mod != 2 ) return 0;
if( dir == 2 ) return 1; /* swap return values if it works backwards */
else return -1;
}
/* switch states */
#define IDLE_ 0
#define ARM 1
#define DARM 2
#define DONE 3
#define TAP 4
#define DTAP 5
#define LONGP 6
/* run the switch state machine, generic code for multiple switches even though have only one here */
int8_t switches(){
static uint8_t press_, nopress;
static uint32_t tm;
int i,j;
int8_t sw;
int8_t s;
if( tm == millis() ) return 0; // run once per millisecond
tm = millis();
/* get the switch readings, low active but invert bits */
sw = ((PIND & 0x10) >> 4) ^ 0x01;
if( sw ) ++press_, nopress = 0; /* only acting on one switch at a time */
else ++nopress, press_ = 0; /* so these simple vars work for all of them */
/* run the state machine for all switches in a loop */
for( i = 0, j = 1; i < 1; ++i ){
s = sstate[i];
switch(s){
case DONE: if( nopress >= 100 ) s = IDLE_; break;
case IDLE_: if( ( j & sw ) && press_ >= 30 ) s = ARM; break; /* pressed */
case ARM:
if( nopress >= 30 ) s = DARM; /* it will be a tap or double tap */
if( press_ >= 240 ) s = LONGP; // long press
break;
case DARM:
if( nopress >= 240 ) s = TAP;
if( press_ >= 30 ) s = DTAP;
break;
}
sstate[i] = s;
j <<= 1;
}
return sstate[0]; // only one switch implemented so can return its value
}
uint8_t band_change(){
if( freq > band_info[band].low && freq <= band_info[band].high ) return 0;
// band change needed
digitalWrite(band_info[band].pin,HIGH);
for( band = 0; band < 6; ++band ){
if( freq > band_info[band].low && freq <= band_info[band].high ) break;
}
if( band == 6 ) band = 5; // default band
digitalWrite( band_info[band].pin,LOW);
return 1;
}
void qsy(uint32_t new_freq){ // change frequency
unsigned char divf;
uint32_t f4;
static uint32_t old_freq = 0;
divf = 0; // flag if we need to reset the PLL's
if( (abs((int32_t)old_freq - (int32_t)new_freq) > 500000) || old_freq == 0){
divf = 1; // large qsy from our current dividers
old_freq = new_freq;
}
freq = new_freq;
if( band_change() ) divf = 1; // check the proper relay is selected
// force freq above a lower limit
if( freq < 40000 ) freq = 40000;
if( freq > 2000000 && Rdiv != 1 ) Rdiv = 1, divf = 1; // tx R is 4
if( freq < 1000000 && Rdiv != 16 ) Rdiv = 16, divf = 1; // tx R is 64
f4 = Rdiv * 4 * freq;
f4 = f4 / 100000; // divide by zero next line if go below 100k on 4x vfo
if( divf ) divider = 7500 / f4; // else we are using the current divider
if( divider & 1) divider += 1; // make it even
if( divider > 254 ) divider = 254;
if( divider < 6 ) divider = 6;
// setup the PLL and dividers if needed
si_pll_x(PLLA,Rdiv*4*freq,divider,0);
if( divf ){
si_load_divider(divider,0,0,Rdiv*4); // tx at 1/4 the rx freq
si_load_divider(divider,1,1,Rdiv); // load rx divider and reset PLL
}
freq_display();
}
void loop() {
static unsigned long ms;
static int temp; // just for flashing the LED when there is I2C activity. Check for I2C hangup.
int8_t t;
if( Serial.availableForWrite() > 20 ) radio_control();
temp += i2poll();
if( ms != millis()){ // run once each ms
ms = millis();
frame_timer(ms);
if( wspr_tx_enable || wspr_tx_cancel ) wspr_tx(ms);
if( cal_enable ) run_cal();
//wwvb_sample(ms);
wwvb_sample2(ms);
if( temp ){
if( temp > 100 ) temp = 100;
--temp;
if( temp ) digitalWrite(13,HIGH);
else digitalWrite(13,LOW);
}
// print out debug messages without waiting for the serial ready
if( wwvb_quiet == 1 && dbug_print_state && Serial.availableForWrite() > 20) dbug_errors( 0, 0, 0, 0, 0 );
t = encoder();
if( t ){
qsy( freq + t * stp );
}
t = switches();
if( t > DONE ){
switch(t){
case TAP:
stp /= 10;
if( stp == 1 ) stp = 1000000;
break;
case DTAP:
operate_mode ^= 1;
break;
case LONGP:
if(++wband > 9 ) wband = 0;
qsy(magic_freq[wband]);
break;
}
sstate[0] = DONE;
mode_display();
}
}
}
void calc_date(){ // from total days and leap flag
const int cal[2][12] =
{ 31,28,31,30,31,30,31,31,30,31,30,31,
31,29,31,30,31,30,31,31,30,31,30,31 };
int i,d;
d = tot_days;
for( i = 0; i < 12; ++i ){
if( d <= cal[leap][i] ) break;
d -= cal[leap][i];
}
gmon = i + 1;
gday = d;
}
void wwvb_decode(){ // WWVB transmits the data for the previous minute just ended
uint16_t tmp;
uint16_t tmp2;
uint16_t yr;
uint16_t hr;
uint16_t mn;
uint16_t dy;
uint8_t i;
tmp2 = frame_sec;
tmp = frame_msec; // capture milliseconds value before it is corrected so we can print it.
++decodes;
yr = wwvb_decode2( 53, 0x1ff ); // year is 0 to 99
dy = wwvb_decode2( 33, 0xfff ); // day is 0 to 365/366
hr = wwvb_decode2( 18, 0x7f );
mn = wwvb_decode2( 8, 0xff );
leap = wwvb_decode2( 55, 0x1 );
DST = wwvb_decode2( 57, 0x1 ); // in effect bit ( using bit 58 gave wrong time for one day )
if( ( mn & 1 ) == 0 ){ //last minute was even so just hit the 60 second mark in the frame
// only apply clock corrections in the middle of the two minute frame or may
// otherwise mess up the frame timing
if( frame_sec == 59 && frame_msec >= 500 ) ; // ok
else if( frame_sec == 60 && frame_msec < 500 ) ; // ok
else{ // way off, reset to the correct time
frame_sec = 60;
frame_msec = 0;
FF = 0, ff = 0; // reset timing fudge factor
clr_trends = 1; // the trend buckets will be incorrect now
}
}
if( wwvb_quiet == 1 ){ // wwvb logging mode
// Serial.print(decodes);
// Serial.write(' '); Serial.print(tmp2); Serial.write('.'); Serial.print(tmp); // show jitter
// Serial.print(" WWVB ");
// Serial.print("20"); // the year 2100 bug
// Serial.print( yr ); Serial.write(' ');
// Serial.print( dy ); Serial.write(' ');
// Serial.print( hr ); Serial.write(':');
// if( mn < 10 ) Serial.write('0');
// Serial.println(mn);
}
ghr = hr;
gmin = mn;
gyr = yr;
tot_days = dy;
keep_time(); // wwvb sends minute just ended info, so increment
calc_date( );
time_flags |= TS;
}
// wwvb fields decode about the same way
uint16_t wwvb_decode2( uint8_t pos, uint16_t mask ){
uint16_t tmp;
uint16_t val;
tmp = ( wwvb_data >> ( 59 - pos ) ) & mask;
val = 0;
if( tmp & 0x800 ) val += 200;
if( tmp & 0x400 ) val += 100;
if( tmp & 0x100 ) val += 80;
if( tmp & 0x80 ) val += 40;
if( tmp & 0x40 ) val += 20;
if( tmp & 0x20 ) val += 10;
val += (tmp & 0xf);
return val;
}
// the original idea was to correct the 27 mhz clock using the UNO 16 mhz clock as a reference.
// calibrating the SI5351 against the 16mhz clock does not seem to be viable.
// the 16mhz clock varies as much or more than the 27mhz clock with changes in temperature
// this function has been changed to correct the time keeping of the 16 meg clock based upon the 27 mhz reference
// this seems to be working very well with no change in WSPR received delta time for over 48 hours.
void run_cal(){ // count pulses on clock 2 wired to pin 5
// IMPORTANT: jumper W4 to W7 on the arduino shield
long error1,error2;
if( cal_enable == 1 ){
FreqCount.begin(1000); //
++cal_enable;
}
if( FreqCount.available() ){
cal_result = (long)FreqCount.read() + (long)FF;
tm_correction = 0; // default
if( cal_result > 2999996L && cal_result < 3000004 )tm_correction = 0, tm_correct_count = 30000;
else{
if( cal_result < 3000000L ){
tm_correction = -1, error1 = 3000000L - cal_result;
error2 = error1 - 1;
}
else if( cal_result > 3000000L ){
tm_correction = 1, error1 = cal_result - 3000000L;
error2 = error1 + 1;
}
if( error1 != 0 ) error1 = 3000000L / error1;
if( error2 != 0 ) error2 = 3000000L / error2;
tm_correct_count = interpolate( error1,error2,ff );
}
if( wwvb_quiet == 1 && tm_correction == 0 ){ // wwvb logging mode
// Serial.print( result ); Serial.write(' ');
// Serial.print( error ); Serial.write(' ');
// Serial.print(tm_correction); Serial.write(' ');
// Serial.print(tm_correct_count); Serial.write(' ');
}
FreqCount.end();
cal_enable = 0;
temp_correction();
}
}
// amt is a value from 0 to 99 representing the percentage
long interpolate( long val1, long val2, int amt ){
long diff;
long result;
diff = val2 - val1;
result = (diff * amt) / 100;
result += val1;
// Serial.print( val1 ); Serial.write(' '); Serial.print( val2 ); Serial.write(' ');
return result;
}
void clock_correction( int8_t val ){ // time keeping only, change the fudge factor
ff += val; // val is always -1,0,or 1
if( ff >= 100 ) ff = 0, ++FF; // keep ff in 0 to 99 range
if( ff < 0 ) ff = 99, --FF;
}
void temp_correction( ){ // short term drift correction with a linear map
uint32_t local_drift; // corrects for drift due to day/night temperature changes
if( wspr_tx_enable ) return; // ignore this when transmitting
local_drift = map( cal_result, 2999900, 3000000, 20, 0 ); // 20
if( local_drift != drift ){
drift = local_drift;
si_pll_x(PLLB,cal_freq,cal_divider,0);
si_pll_x(PLLA,Rdiv*4*freq,divider,0); // new receiver 4x clock, transmitter 1x clock
}
}
// correct errors on bits that do not change often
// this version moves the counters up to a hard limit and moves down on different bit decoded
// slips in time on weak signal as is called from frame_sync
// this version does not use bit_errors but instead requires trends to be at the limit in order to decode
// less decodes, less false decodes
// bump the trends type for each minute to be what is expected
char wwvb_trends( char val, uint8_t dat ){
static int i = 60;
static int unslip;
uint8_t count;
uint8_t trend_t,new_t;
int w;
static int s_count; // counts from last sync - detect when spaced 9 apart. Most are 10 apart.
#define LIMIT 6 // 2 min 30 max
#define SYNC 32
#define ONES 64
#define ZEROS 128
#define ERR 0
if( clr_trends ){ // reset the data
for( w = 0; w < 60; ++w ) trends[w] = 0;
clr_trends = 0;
}
if( unslip ) unslip = 0;
else if( ++i >= 60 ){
i = 0;
// the sample bucket ends at 1 sec past the time sampled. Test once per two minute frame
if( frame_sec < 60 && (frame_msec < 400 || frame_msec > 600) ){
if( frame_sec == 0 && frame_msec > 600 ) ; // ok, just early
else if( frame_sec == 1 && frame_msec < 400 ) ; // also ok
else if( frame_sec < 30 && frame_sec > 0 ) ++i, report_i = 0; // running slow is normal on weak signals
else unslip = 1, report_i = 0;; // probably a time reset or decode happened
}
}
count = trends[i] & 31; // get existing trend data
trend_t = trends[i] & ( ONES | SYNC | ZEROS );
new_t = ERR; // assume error
if( val == 'S' ) new_t = SYNC;
if( val == '1' ) new_t = ONES;
if( val == '0' ) new_t = ZEROS;
++s_count; // count from the last sync. Attempt early sync to 9 syncs spacing.
if( new_t == SYNC ){
if( s_count == 9 ) report_i = i;
if( s_count == 1 ) report_i = (i+9) % 60; // double sync detect
s_count = 0;
}
if( trend_t == new_t && trend_t != ERR ){ // increment the trend if match
if( count < LIMIT ) ++count;
}
if( trend_t != new_t && new_t != ERR ){ // valid decode, bit changed, age type
if( count == LIMIT && i > 8 ) val = 'x'; // question this change even though valid decode
if( count > 1 ) count -= ( trend_t == SYNC ? 1 : 2 ); // age existing type, favor sync
else{
count = 1;
trend_t = new_t;
}
}
trends[i] = trend_t + count; // save new trend values
// some unnessary playing around. Partial decode turned out to be not useful.
// if( decodes == 0 && ( count >= LIMIT/2 || trend_t == SYNC || i == 8 ) ) part_decode(i);
// return history on errors with only 1 bit incorrect
// if( new_t == ERR && count == LIMIT ){
// if( trend_t == ZEROS ){
// if( bit_errors(dat,0xfc,i) < 2 ) val = 'o';
// }
// if( trend_t == ONES ){
// if( bit_errors(dat,0xf0,i) < 2 ) val = 'i';
// }
// if( trend_t == SYNC ){
// if( bit_errors(dat,0xc0,i) < 2 ) val = 's';
// }
// }
if( i == 0 && val == '.' ) val = 'Z'; // view index on no decode no history
if( i == 0 ) disp_date_time();
LCD.gotoRowCol(7,110);
LCD.putch(val);
LCD.printNumI(i,75,ROW7,2,'0');
if( wwvb_quiet == 1 ){
Serial.write(val);
if( i%10 == 9 ) Serial.write(' ');
}
//debug_i = i;
return val;
}
/*
// return the number of bits different between mask and value
int bit_errors( uint8_t val, uint8_t mask, int i ){
int count;
uint8_t b;
// only here if the val did not decode as exact
// provide different levels of correction depending upon what field the val is in.
// bit 1 that follows the double sync seems to be the most likely bit to decode incorrectly
// require the double sync and other bits to all decode without correcting any bit errors bits 59-9
if( i <= 9 || i == 59 ) return 8; // require exact decode in the minutes field
// hours field changes once per hour, don't correct bit pattern 11111000 which has one bit error for
// both a 0 and a 1.
if( val == 0xf8 && i > 11 && i < 19 && i != 14 ) return 8; // 10,11,14 are unused always zero
// the rest of the fields change once a year or are don't care bytes for this application.
// count how many bits are different from the mask. Decode from trends if only one bit is incorrect.
count = 0, b = 0x80;
val = val ^ mask; // val = error bits
for( i = 0; i < 8; ++i ){ // count the errors
if( val & b ) ++count;
b >>= 1;
}
return count;
}
*/
/*
// correct errors on bits that do not change often
// this version moves the counters up to a hard limit and moves down on different bit decoded
// slips in time on weak signal as is called from frame_sync
char wwvb_trends( char val ){
static int i = 60;
static int unslip;
static uint8_t z; // stale out data counter
uint8_t count;
uint8_t trend_t,new_t;
int w;
#define LIMIT 10 // 2 min 30 max
#define SYNC 32
#define ONES 64
#define ZEROS 128
#define ERR 0
if( clr_trends ){ // reset the data
for( w = 0; w < 60; ++w ) trends[w] = 0;
clr_trends = 0;
}
if( unslip ) unslip = 0;
else if( ++i >= 60 ){
i = 0;
// the sample bucket ends at 1 sec past the time sampled. Test once per two minute frame
if( frame_sec < 60 && (frame_msec < 400 || frame_msec > 600) ){
if( frame_sec == 0 && frame_msec > 600 ) ; // ok, just early
else if( frame_sec == 1 && frame_msec < 400 ) ; // also ok
else if( frame_sec < 30 ) ++i; // running slow is normal on weak signals
else unslip = 1; // probably a time reset or decode happened
}
}
count = trends[i] & 31; // get existing trend data
trend_t = trends[i] & ( ONES | SYNC | ZEROS );
new_t = ERR; // assume error
if( val == 'S' ) new_t = SYNC;
if( val == '1' ) new_t = ONES;
if( val == '0' ) new_t = ZEROS;
if( trend_t == new_t && trend_t != ERR ){ // increment the trend if match
if( count < LIMIT ) ++count;
}
if( trend_t != new_t && new_t != ERR ){ // valid decode, bit changed, age type
if( count > 1 ) count -= ( trend_t == SYNC ? 1 : 2 ); // age existing type, favor sync
else{
count = 1;
trend_t = new_t;
}
}
if( new_t == ERR && wspr_tx_enable == 0){ // slowly stale out the data on errors
++z; // reduce count on 1 out of 16 errors
z &= 15;
if( z == 0 && count > 1 ) --count;
}
trends[i] = trend_t + count; // save new trend values
// return history on errors, do not send trend for the minutes,hours fields
if( new_t == ERR && ( i >= 19 || trend_t == SYNC || i == 4 || i == 10 || i == 11 || i == 14 ) ){
if( count == LIMIT ){
if( trend_t == ZEROS ) val = 'o';
if( trend_t == ONES ) val = 'i';
if( trend_t == SYNC ) val = 's';
}
}
if( i == 0 && val == '.' ) val = 'x'; // view index on no decode no history
if( wwvb_quiet == 1 ){
Serial.write(val);
if( i%10 == 9 ) Serial.write(' ');
}
return val;
}
*/
/*
// remove any errors on don't care bits, force to zero
// force syncs when we think we are in sync
char wwvb_trends( char val ){
const int zeros[17] = {4,10,11,14,20,21,24,34,35,36,37,38,44,54,56,57,58};
static int i,z;
static char last_val;
static int mod10;
static int q;
static int force;
static int fix;
static int syncs[4];
static int disable;
int w;
// double sync reset
if( last_val == 'S' && val == 'S' ){
if( i != 59 ){
i = 59; // index reset next lines of code
force = 0;
}
disable = 0;
}
last_val = val;
if( ++i >= 60 ){ // start of next minute
i = 0;
z = 0;
fix = 0;
if( frame_sec == 4 ) ++i; // slipping in time. Happens on very weak wwvb signal
// this test is 1 sec after the print at second 59
if( frame_sec > 4 && frame_sec < 56 ) disable = 1; // out of correction range, wait for double sync
}
if( val == 'S' && i > 4 && i < 56 ){ // syncs should all be modulo 9, double sync ignored,
syncs[0] = syncs[1]; syncs[1] = syncs[2]; syncs[2] = syncs[3]; syncs[3] = i; // for reporting only
w = i%10;
if( w == mod10 ) ++q;
else q = 0, force = 0;
mod10 = w;
if( q >= 3 && disable == 0 ){ // quorum of syncs on same modulus index
if( mod10 == 9 ) force = 1; // correct index
else if( mod10 >= 4 ) ++i; // slipped in time
else --i; // started early or some other issue like a time reset
q = 0;
}
}
w = val;
if( i == zeros[z] ){
++z;
if( force && val == '.' ) val = 'o'; // remove errors on don't care bits
}
if( force && ( i == 0 || i%10 == 9 ) && val != 'S' ) val = 's';
if( w != val ) ++fix;
if( fix == 9 ) force = 0; // too many fails a minute
if( wwvb_quiet == 1 && i == 59 ){ // report syncs
if( frame_sec < 100 ) Serial.write(' ');
if( frame_sec < 10 ) Serial.write(' ');
Serial.print(frame_sec); Serial.write(' ');
Serial.print("F "); Serial.print(force);
Serial.print(" Fix ");
// if( fix < 10 ) Serial.write(' ');
Serial.print(fix);
Serial.print(" Sync ");
for( w = 0; w < 4; ++w ){
if( syncs[w] < 10 ) Serial.write(' ');
Serial.print(syncs[w]); Serial.write(' ');
}
}
return val;
}
*/
void frame_timer( unsigned long t ){
static unsigned long old_t;
static uint8_t slot;
static long time_adjust;
// 16mhz clock measured at 16001111. Will gain 1ms in approx 14401 ms. Or 1 second in 4 hours.
// the calibrate function has been repurposed to correct the time keeping of the Arduino.
frame_msec += ( t - old_t );
time_adjust += (t - old_t);
if( time_adjust >= tm_correct_count && frame_msec != 0 ){
time_adjust -= tm_correct_count;
frame_msec += tm_correction; // add one, sub one or no change depending upon tm_correction value
}
if( tm_correction2 && frame_msec > 400 && frame_msec < 600 ){
frame_msec += tm_correction2;
tm_correction2 = 0;
}
old_t = t;
if( frame_msec >= 1000 ){
frame_msec -= 1000;
if( ++frame_sec >= 120 ){ // 2 minute slot time
frame_sec -= 120;
if( ++slot >= 8 ) slot = 0; // 10 slots is a 20 minute frame
if( slot == 1 && operate_mode == FRAME_MODE ) wspr_tx_enable = 1;
}
if( frame_sec == 116 ) cal_enable = 1; // do once per slot in wspr quiet time
if( frame_sec == 90 || frame_sec == 30 ) keep_time(); // half minute to avoid colliding with wwvb decodes
// if( frame_sec == 0 || frame_sec == 60 ) tick = 1; // print wwvb decode info if enabled
}
}
void keep_time(){
if( ++gmin >= 60 ){
gmin = 0;
if( ++ghr >= 24 ){
ghr = 0;
++tot_days;
if( tot_days > 365 + leap ) ++gyr, tot_days = 1;
calc_date();
}
}
// if( time_flags & TS ) time_flags = TP + TK; // clear TS but flag a print of wwvb decode indicator
// else time_flags = TK;
}
void wspr_tx( unsigned long t ){