-
Notifications
You must be signed in to change notification settings - Fork 18
/
WSPR_TX_LP1_Firmware0.60.ino
1410 lines (1211 loc) · 42.2 KB
/
WSPR_TX_LP1_Firmware0.60.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
/*
Software for Zachtek "WSPR-TX_LP1 Version 1"
The Version of this software is stored in the constant "softwareversion" and is displayed on the Serialport att startup
For Arduino Pro Mini ATMega 328 8MHz
Hardware connections:
--------------------------
pin 2 and 3 is Sofware serial port to GPS module
pin 4 is Yellow Status LED. Used as StatusIndicator to display what state the software is currently in.
pin 5,6 and 7 are Relay pins. Pin set as Output and pulled low=Relay is ON. Pin set as Input=Relay is OFF (5V Relay controlled by 3.3V Arduino)
pin 8 is Red TX LED next to RF out SMA connector. Used as StatusIndicator to display when transmission occurs.
For Arduino Pro Mini 328 8MHz
Version History:
-------------------------------------
0.51 First Beta for Serial API
0.51 Expanded the Serial API and changed all information messages to {MIN} messages
0.52 [DGF] API updates will change SignalGen output freq if running.
0.54 Added TX status updates when sending CCM status to improve the PC client display.
0.55 Added support for factory data in EEPROM, stores TCXO and hardware info.
0.56 Changed 80 TX freq to new standard of 3.570,100MHz
0.57 Split Firmware in to two separate version for the WSPR-TX_ LP1 and LP4 products and changed the "Product_Model" from Factory EEPROM data to constant
0.58 Fixed Frequency information output calculation errors when Freq=<1MHz
0.59 Fixed dim Red TX LED, (Pin was not set to output by setup routine)
0.60 Fixed wrong TX Freq on 10,12 and 15 Band
*/
#include <EEPROM.h>
#include <TinyGPS++.h> //TinyGPS++ library by Mikal Hart https://github.com/mikalhart/TinyGPSPlus
#include <JTEncode.h> //JTEncode by NT7S https://github.com/etherkit/JTEncode
#include <SoftwareSerial.h>
// Data structures
enum E_Band
{
LF2190m = 0,
LF630m = 1,
HF160m = 2,
HF80m = 3,
HF40m = 4,
HF30m = 5,
HF20m = 6,
HF17m = 7,
HF15m = 8,
HF12m = 9,
HF10m = 10,
HF6m = 11,
VHF4m = 12,
VHF2m = 13,
UHF70cm = 14,
UHF23cm = 15
};
enum E_Mode
{
WSPRBeacon ,
SignalGen,
Idle
};
enum E_LocatorOption {
Manual,
GPS
};
struct S_WSPRData
{
char CallSign[7]; //Radio amateur Call Sign, zero terminated string can be four to six char in length + zero termination
E_LocatorOption LocatorOption; //If transmitted Maidenhead locator is based of GPS location or if it is using MaidneHead4 variable.
char MaidenHead4[5]; //Maidenhead locator, must be 4 chars and a zero termination
uint8_t TXPowerdBm; //Power data in dBm min=0 max=60
};
struct S_GadgetData
{
char Name[40]; //Optional Name of the device.
E_Mode StartMode; //What mode the Gadget should go to after boot.
S_WSPRData WSPRData; //Data needed to transmit a WSPR packet.
bool TXOnBand [16]; //Arraycount corresponds to the Enum E_Band, True =Transmitt Enabled, False = Transmitt disabled on this band
unsigned long TXPause; //Number of seconds to pause after having transmitted on all enabled bands.
uint64_t GeneratorFreq;//Frequency for when in signal Generator mode. Freq in centiHertz.
};
struct S_FactoryData
{
uint8_t HW_Version; // Hardware version
uint8_t HW_Revision; // Hardware revision
uint32_t RefFreq; //The frequency of the Reference Oscillator in Hz, usually 26000000
};
//Constants
#define I2C_START 0x08
#define I2C_START_RPT 0x10
#define I2C_SLA_W_ACK 0x18
#define I2C_SLA_R_ACK 0x40
#define I2C_DATA_ACK 0x28
#define I2C_WRITE 0b11000000
#define I2C_READ 0b11000001
#define SI5351A_H
#define SI_CLK0_CONTROL 16 // Register definitions
#define SI_CLK1_CONTROL 17
#define SI_CLK2_CONTROL 18
#define SI_SYNTH_PLL_A 26
#define SI_SYNTH_PLL_B 34
#define SI_SYNTH_MS_0 42
#define SI_SYNTH_MS_1 50
#define SI_SYNTH_MS_2 58
#define SI_PLL_RESET 177
#define SI_R_DIV_1 0b00000000 // R-division ratio definitions
#define SI_R_DIV_2 0b00010000
#define SI_R_DIV_4 0b00100000
#define SI_R_DIV_8 0b00110000
#define SI_R_DIV_16 0b01000000
#define SI_R_DIV_32 0b01010000
#define SI_R_DIV_64 0b01100000
#define SI_R_DIV_128 0b01110000
#define SI_CLK_SRC_PLL_A 0b00000000
#define SI_CLK_SRC_PLL_B 0b00100000
#define WSPR_FREQ23cm 129650150000ULL //23cm 1296.501,500MHz (Overtone, not implemented)
#define WSPR_FREQ70cm 43230150000ULL //70cm 432.301,500MHz (Overtone, not implemented)
#define WSPR_FREQ2m 14449000000ULL //2m 144.490,000MHz //Not working. No decode in bench test with WSJT-X decoding Software
#define WSPR_FREQ4m 7009250000ULL //4m 70.092,500MHz //Slightly lower output power
#define WSPR_FREQ6m 5029450000ULL //6m 50.294,500MHz //Slightly lower output power
#define WSPR_FREQ10m 2812610000ULL //10m 28.126,100MHz
#define WSPR_FREQ12m 2492610000ULL //12m 24.926,100MHz
#define WSPR_FREQ15m 2109610000ULL //15m 21.096.100MHz
#define WSPR_FREQ17m 1810610000ULL //17m 18.106,100MHz
#define WSPR_FREQ20m 1409710000ULL //20m 14.097,100MHz
#define WSPR_FREQ30m 1014020000ULL //30m 10.140,200MHz
#define WSPR_FREQ40m 704010000ULL //40m 7.040,100MHz
#define WSPR_FREQ80m 357010000ULL //80m 3.570,100MHz
#define WSPR_FREQ160m 183810000ULL //160m 1.838,100MHz
#define WSPR_FREQ630m 47570000ULL //630m 475.700kHz
#define WSPR_FREQ2190m 13750000ULL //2190m 137.500kHz
#define Product_Model 1011 // Product number. WSPR-TX_LP1=1011
#define FactorySpace true
#define UserSpace false
#define UMesCurrentMode 1
#define UMesLocator 2
#define UMesTime 3
#define UMesGPSLock 4
#define UMesNoGPSLock 5
#define UMesFreq 6
#define UMesTXOn 7
#define UMesTXOff 8
// Hardware defines
#define StatusLED 4 //Yellow LED next to Green Power LED
#define Relay1 5
#define Relay2 6
#define Relay3 7
#define TransmitLED 8 //Red LED next to RF out SMA that will turn on when Transmitting
const char softwareversion[] = "Beta 0.60" ; //Version of this program, sent to serialport at startup
//Global Variables
S_GadgetData GadgetData; //Create a datastructure that holds all relevant data for a WSPR Beacon
S_FactoryData FactoryData; //Create a datastructure that holds information of the hardware
E_Mode CurrentMode; //What mode are we in, WSPR, signal-gen or nothing
//bool TXOnBand [13]; //Arraycount corresponds to the Enum E_Band, True =Transmitt Enabled, False = Transmitt disabled on this band E.g to transmitt on 40m and 20m set TXOnBand[4] and TXOnBand[6] to true.
int CurrentBand = 0; //Keeps track on what band we are currently tranmitting on
const uint8_t SerCMDLength = 50; //Max number of char on a command in the SerialAPI
void i2cInit();
uint8_t i2cSendRegister(uint8_t reg, uint8_t data);
uint8_t i2cReadRegister(uint8_t reg, uint8_t *data);
//uint8_t WSPRBandHopCount = 0; //Keep tracks on what band we transmitted on
uint8_t tx_buffer[171];
uint64_t freq; //Holds the Output frequency when we are in signal generator mode or in WSPR mode
int GPSH; //GPS Hours
int GPSM; //GPS Minutes
int GPSS; //GPS Seconds
// Class instantiation
JTEncode jtencode;
// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial GPSSerial(2, 3); //GPS Serial port, RX on pin 2, TX on pin 3
void si5351aOutputOff(uint8_t clk);
void si5351aSetFrequency(uint32_t frequency);
void setup()
{
//Initialize the serial ports, The hardware port is used for communicating with a PC.
//The Soft Serial is for communcating with the GPS
Serial.begin (9600); //USB Serial port
Serial.setTimeout(2000);
GPSSerial.begin(9600); //Internal GPS Serial port
bool i2c_found;
// Use the Red LED as a Transmitt indicator and the Yellow LED as Status indicator
Serial.print(F("{MIN} ZachTek WSPR-TX_LP1 standard firmware version "));
Serial.println(softwareversion);
pinMode(StatusLED, OUTPUT);
pinMode(TransmitLED, OUTPUT);
pinMode(Relay1, INPUT);//Set Relay1 as Input to deactivate the relay
pinMode(Relay2, INPUT);//Set Relay1 as Input to deactivate the relay
pinMode(Relay3, INPUT);//Set Relay1 as Input to deactivate the relay
for (int i = 0; i <= 15; i++) {
digitalWrite(StatusLED, HIGH);
delay (50);
digitalWrite(StatusLED, LOW);
delay (50);
}
//Read all the Factory data from EEPROM at position 400
if (LoadFromEPROM(FactorySpace)) //Read all Factory data from EEPROM
{
}
else //No data was found in EEPROM, set some defaults
{
FactoryData.HW_Version = 1; // Hardware version
FactoryData.HW_Revision = 5; // Hardware revision
FactoryData.RefFreq = 26000000;//Reference Oscillator frequency
Serial.println(F("{MIN} No factory data found for Model # and Reference frequency, guessing on values"));
}
if (LoadFromEPROM(UserSpace)) //Read all UserSpace data from EEPROM at position 0
{
CurrentMode = GadgetData.StartMode;
GadgetData.WSPRData.CallSign[6] = 0;//make sure Call sign is null terminated in case of incomplete data saved
GadgetData.WSPRData.MaidenHead4[4] = 0; //make sure Maidenhead locator is null terminated in case of incomplete data saved
}
else //No data was found in EEPROM, set some defaults
{
CurrentMode = Idle;
GadgetData.Name[0] = 'W'; GadgetData.Name[1] = 'S'; GadgetData.Name[2] = 'P'; GadgetData.Name[3] = 'R';
GadgetData.Name[4] = ' '; GadgetData.Name[5] = 'T'; GadgetData.Name[6] = 'X'; GadgetData.Name[7] = 0;
GadgetData.StartMode = Idle;
GadgetData.WSPRData.CallSign[0] = 'A'; GadgetData.WSPRData.CallSign[1] = 'A'; GadgetData.WSPRData.CallSign[2] = '0';
GadgetData.WSPRData.CallSign[3] = 'A'; GadgetData.WSPRData.CallSign[4] = 'A'; GadgetData.WSPRData.CallSign[5] = 'A';
GadgetData.WSPRData.CallSign[6] = 0;
GadgetData.WSPRData.LocatorOption = GPS;
GadgetData.WSPRData.MaidenHead4[0] = 'A'; GadgetData.WSPRData.MaidenHead4[1] = 'A';
GadgetData.WSPRData.MaidenHead4[2] = '0'; GadgetData.WSPRData.MaidenHead4[3] = '0';
GadgetData.WSPRData.MaidenHead4[4] = 0;
GadgetData.WSPRData.TXPowerdBm = 23;
for (int i = 0; i <= 16; i++)
{
GadgetData.TXOnBand [i] = false;
}
GadgetData.TXPause = 120; //Number of minutes to pause after transmisson
GadgetData.GeneratorFreq = 1000000000;
Serial.println(F("{MIN} No user data was found, setting default values"));
}
i2cInit();
//si5351aSetFrequency(2000000000ULL);
si5351aOutputOff(SI_CLK0_CONTROL);
random(RandomSeed());
//if (CurrentMode == SignalGen) DoSignalGen();
//if (CurrentMode == WSPRBeacon) DoWSPR();
//if (CurrentMode == Idle) DoIdle();
switch (CurrentMode) {
case SignalGen :
DoSignalGen();
break;
case WSPRBeacon:
DoWSPR();
break;
case Idle:
DoIdle();
break;
}
}
void loop()
{
DoSerialHandling();
delay (100);
}
//Serial API commands and data decoding
void DecodeSerialCMD(const char * InputCMD) {
char CharInt[13];
bool EnabDisab;
if ((InputCMD[0] == '[') && (InputCMD[4] == ']')) { //A Command,Option or Data input
if (InputCMD[1] == 'C') { //Commmand
//Current Mode
if ((InputCMD[2] == 'C') && (InputCMD[3] == 'M')) {
if (InputCMD[6] == 'S') { //Set option
if (InputCMD[8] == 'S') {
DoSignalGen();
}
if (InputCMD[8] == 'W') {
DoWSPR();
}
if (InputCMD[8] == 'N') {
DoIdle ();
}
}//Set Current Mode
else //Get
{
SendAPIUpdate (UMesCurrentMode);
}//Get Current Mode
}//CurrentMode
//Store Current configuration data to EEPROM
if ((InputCMD[2] == 'S') && (InputCMD[3] == 'E')) {
if (InputCMD[6] == 'S') { //Set option
SaveToEEPROM(UserSpace);
Serial.println(F("{MIN} User data saved"));
}
}
exit;
}
if (InputCMD[1] == 'O') {//Option
//TX Pause
if ((InputCMD[2] == 'T') && (InputCMD[3] == 'P')) {
if (InputCMD[6] == 'S') { //Set option
CharInt[0] = InputCMD[8]; CharInt[1] = InputCMD[9]; CharInt[2] = InputCMD[10];
CharInt[3] = InputCMD[11]; CharInt[4] = InputCMD[12]; CharInt[5] = 0;
GadgetData.TXPause = atoi(CharInt);
}
else //Get Option
{
Serial.print (F("{OTP} "));
if (GadgetData.TXPause < 10000) Serial.print ("0");
if (GadgetData.TXPause < 1000) Serial.print ("0");
if (GadgetData.TXPause < 100) Serial.print ("0");
if (GadgetData.TXPause < 10) Serial.print ("0");
Serial.println (GadgetData.TXPause);
}
}//TX Pause
//StartMode
if ((InputCMD[2] == 'S') && (InputCMD[3] == 'M')) {
if (InputCMD[6] == 'S') { //Set option
if (InputCMD[8] == 'S') {
GadgetData.StartMode = SignalGen;
}
if (InputCMD[8] == 'W') {
GadgetData.StartMode = WSPRBeacon;
}
if (InputCMD[8] == 'N') {
GadgetData.StartMode = Idle;
}
}//Set Start Mode
else //Get
{
Serial.print ("{OSM} ");
switch (GadgetData.StartMode) {
case Idle:
Serial.println ("N");
break;
case WSPRBeacon:
Serial.println ("W");
break;
case SignalGen:
Serial.println ("S");
break;
}
}//Get Start Mode
}//StartMode
//Band TX enable
if ((InputCMD[2] == 'B') && (InputCMD[3] == 'D')) {
if (InputCMD[6] == 'S') { //Set option
CharInt[0] = InputCMD[8]; CharInt[1] = InputCMD[9]; CharInt[2] = 0; CharInt[3] = 0;
EnabDisab = false;
if (InputCMD[11] == 'E') EnabDisab = true;
GadgetData.TXOnBand [atoi(CharInt)] = EnabDisab ; //Enable or disable on this band
}//Set Band TX enable
else //Get
{
//Send out 16 lines, one for each band
for (int i = 0; i <= 15; i++) {
Serial.print (F("{OBD} "));
if (i < 10) Serial.print (F("0"));
Serial.print (i);
if (GadgetData.TXOnBand[i]) {
Serial.println (F(" E"));
}
else
{
Serial.println (F(" D"));
}
}//for
}//Get Band TX enable
}//Band TX enable
//Location Option
if ((InputCMD[2] == 'L') && (InputCMD[3] == 'C')) {
if (InputCMD[6] == 'S') { //Set Location Option
if (InputCMD[8] == 'G') {
GadgetData.WSPRData.LocatorOption = GPS;
}
if (InputCMD[8] == 'M') {
GadgetData.WSPRData.LocatorOption = Manual;
}
}//Set Location Option
else //Get Location Option
{
Serial.print ("{OLC} ");
if (GadgetData.WSPRData.LocatorOption == GPS)
{
Serial.println ("G");
}
else
{
Serial.println ("M");
}
}//Get Location Option
}//Location Option
exit;
}//All Options
//Data
if (InputCMD[1] == 'D') {
//Callsign
if ((InputCMD[2] == 'C') && (InputCMD[3] == 'S')) {
if (InputCMD[6] == 'S') { //Set option
for (int i = 0; i <= 5; i++) {
GadgetData.WSPRData.CallSign[i] = InputCMD[i + 8];
}
GadgetData.WSPRData.CallSign[6] = 0;
}
else //Get
{
Serial.print (F("{DCS} "));
Serial.println (GadgetData.WSPRData.CallSign);
}
}//Callsign
//Locator
if ((InputCMD[2] == 'L') && (InputCMD[3] == '4')) {
if (InputCMD[6] == 'S') { //Set option
for (int i = 0; i <= 3; i++) {
GadgetData.WSPRData.MaidenHead4[i] = InputCMD[i + 8];
}
GadgetData.WSPRData.MaidenHead4[4] = 0;
}
else //Get
{
Serial.print (F("{DL4} "));
Serial.println (GadgetData.WSPRData.MaidenHead4);
}
}//Locator
//Name
if ((InputCMD[2] == 'N') && (InputCMD[3] == 'M')) {
if (InputCMD[6] == 'S') { //Set option
for (int i = 0; i <= 38; i++) {
GadgetData.Name[i] = InputCMD[i + 8];
}
GadgetData.Name[39] = 0;
}
else //Get
{
Serial.print (F("{DNM} "));
Serial.println (GadgetData.Name);
}
}//Name
//Power data
if ((InputCMD[2] == 'P') && (InputCMD[3] == 'D')) {
if (InputCMD[6] == 'S') { //Set option
CharInt[0] = InputCMD[8]; CharInt[1] = InputCMD[9]; CharInt[2] = 0; CharInt[3] = 0;
GadgetData.WSPRData.TXPowerdBm = atoi(CharInt);
}
else //Get
{
Serial.print (F("{DPD} "));
if (GadgetData.WSPRData.TXPowerdBm < 10) Serial.print ("0");
Serial.println (GadgetData.WSPRData.TXPowerdBm);
}
}//Power Data
//Generator Frequency
if ((InputCMD[2] == 'G') && (InputCMD[3] == 'F')) {
if (InputCMD[6] == 'S') { //Set option
for (int i = 0; i <= 11; i++) {
CharInt[i] = InputCMD[i + 8];
}
CharInt[12] = 0;
GadgetData.GeneratorFreq = StrTouint64_t(CharInt);
if (CurrentMode == SignalGen) DoSignalGen();
}
else //Get
{
Serial.print (F("{DGF} "));
Serial.println (uint64ToStr(GadgetData.GeneratorFreq, true));
}
}//Generator Frequency
exit;
}//Data
//Factory
if (InputCMD[1] == 'F') {
//Product model Number
if ((InputCMD[2] == 'P') && (InputCMD[3] == 'N')) {
if (InputCMD[6] == 'G')
{ //Get option
Serial.print (F("{FPN} "));
if (Product_Model < 10000) Serial.print ("0");
Serial.println (Product_Model);
}
}//Product model Number
//Hardware Version
if ((InputCMD[2] == 'H') && (InputCMD[3] == 'V')) {
if (InputCMD[6] == 'S') { //Set option
CharInt[0] = InputCMD[8]; CharInt[1] = InputCMD[9]; CharInt[2] = InputCMD[10];
CharInt[3] = 0;
FactoryData.HW_Version = atoi(CharInt);
}//Set
else //Get Option
{
Serial.print (F("{FHV} "));
if (FactoryData.HW_Version < 100) Serial.print ("0");
if (FactoryData.HW_Version < 10) Serial.print ("0");
Serial.println (FactoryData.HW_Version);
}
}//Hardware Version
//Hardware Revision
if ((InputCMD[2] == 'H') && (InputCMD[3] == 'R')) {
if (InputCMD[6] == 'S') { //Set option
CharInt[0] = InputCMD[8]; CharInt[1] = InputCMD[9]; CharInt[2] = InputCMD[10];
CharInt[3] = 0;
FactoryData.HW_Revision = atoi(CharInt);
}//Set
else //Get Option
{
Serial.print (F("{FHR} "));
if (FactoryData.HW_Revision < 100) Serial.print ("0");
if (FactoryData.HW_Revision < 10) Serial.print ("0");
Serial.println (FactoryData.HW_Revision);
}
}//Hardware Revision
//Reference Oscillator Frequency
if ((InputCMD[2] == 'R') && (InputCMD[3] == 'F')) {
if (InputCMD[6] == 'S') { //Set option
for (int i = 0; i <= 8; i++) {
CharInt[i] = InputCMD[i + 8];
}
CharInt[9] = 0;
FactoryData.RefFreq = StrTouint64_t(CharInt);
}
else //Get
{
Serial.print (F("{FRF} "));
Serial.println (uint64ToStr(FactoryData.RefFreq, true));
}
}//Reference Oscillator Frequency
//Store Current Factory configuration data to EEPROM
if ((InputCMD[2] == 'S') && (InputCMD[3] == 'E')) {
if (InputCMD[6] == 'S') { //Set option
SaveToEEPROM(FactorySpace);
Serial.println(F("{MIN} Factory data saved"));
}
}
exit;
}//Factory
}
}
uint64_t StrTouint64_t (String InString)
{
uint64_t y = 0;
for (int i = 0; i < InString.length(); i++) {
char c = InString.charAt(i);
if (c < '0' || c > '9') break;
y *= 10;
y += (c - '0');
}
return y;
}
String uint64ToStr (uint64_t p_InNumber, boolean p_LeadingZeros)
{
char l_HighBuffer[7]; //6 digits + null terminator char
char l_LowBuffer[7]; //6 digits + null terminator char
char l_ResultBuffer [13]; //12 digits + null terminator char
String l_ResultString = "";
uint8_t l_Digit;
sprintf(l_HighBuffer, "%06lu", p_InNumber / 1000000L); //Convert high part of 64bit unsigned integer to char array
sprintf(l_LowBuffer, "%06lu", p_InNumber % 1000000L); //Convert low part of 64bit unsigned integer to char array
l_ResultString = l_HighBuffer;
l_ResultString = l_ResultString + l_LowBuffer; //Copy the 2 part result to a string
if (!p_LeadingZeros) //If leading zeros should be removed
{
l_ResultString.toCharArray(l_ResultBuffer, 13);
for (l_Digit = 0; l_Digit < 12; l_Digit++ )
{
if (l_ResultBuffer[l_Digit] == '0')
{
l_ResultBuffer[l_Digit] = ' '; // replace zero with a space character
}
else
{
break; //We have found all the leading Zeros, exit loop
}
}
l_ResultString = l_ResultBuffer;
l_ResultString.trim();//Remove all leading spaces
}
return l_ResultString;
}
//Parts from NickGammon Serial Input example
//http://www.gammon.com.au/serial
void DoSerialHandling()
{
static char SerialLine[SerCMDLength]; //A single line of incoming serial command and data
static uint8_t input_pos = 0;
char InChar;
while (Serial.available () > 0)
{
//Serial.println ("Serial availabe");
InChar = Serial.read ();
switch (InChar)
{
case '\n': // end of text
SerialLine [input_pos] = 0; // terminating null byte
// terminator reached, process Command
DecodeSerialCMD (SerialLine);
//Serial.println("New Line");
// reset buffer for next time
input_pos = 0;
break;
case '\r': // discard carriage return
break;
default:
// keep adding if not full ... allow for terminating null byte
if (input_pos < (SerCMDLength - 1))
SerialLine [input_pos++] = InChar;
break;
} // end of switch
} // end of processIncomingByte
}
void DoSignalGen ()
{
CurrentMode = SignalGen;
freq = GadgetData.GeneratorFreq;
si5351aSetFrequency(freq);
digitalWrite(StatusLED, HIGH);
SendAPIUpdate (UMesCurrentMode);
SendAPIUpdate (UMesFreq);
}
void DoIdle ()
{
CurrentMode = Idle;
digitalWrite(StatusLED, LOW);
si5351aOutputOff(SI_CLK0_CONTROL);
SendAPIUpdate (UMesCurrentMode);
}
void DoWSPR ()
{
// static double Lat;
// static double Lon;
CurrentMode = WSPRBeacon;
if (NoBandEnabled ()) {
Serial.println (F("{MIN} Warning, configuration error, tranmission is not enabled on any band!"));
}
else
{
NextFreq();
freq = freq + (100ULL * random (-100, 100)); //modify TX frequency with a random value beween -100 and +100 Hz
si5351aOutputOff(SI_CLK0_CONTROL);
SendAPIUpdate (UMesCurrentMode);
//LOOP HERE FOREVER OR UNTIL INTERRUPTED BY A SERIAL COMMAND
while (!Serial.available()) { //Do until incoming serial command
while (GPSSerial.available()) {
if (Serial.available()) {// If serialdata was received on control port then abort and handle command
DoIdle(); //Return to ideling
return;
}
gps.encode(GPSSerial.read());
if (gps.location.isUpdated())
{
GPSH = gps.time.hour();
GPSM = gps.time.minute();
GPSS = gps.time.second();
//Lat = gps.location.lat();
//Lon = gps.location.lng();
if (GadgetData.WSPRData.LocatorOption == GPS) { //If GPS should update the Maidenhead locator
//calcLocator (Lat, Lon);
calcLocator (gps.location.lat(), gps.location.lng());
//Serial.print (F("GPS calculated Maidenhead locator is "));
}
else
{
// Serial.print (F("Maidenhead locator manually set to "));
}
// if (SetWSPRFreq())
//Serial.println(GadgetData.WSPRData.MaidenHead4);
//Serial.println (F("Waiting for start of even minute to begin a new WSPR transmission block"));
if ((GPSS == 0) && ((GPSM % 2) == 0))//If second is zero at even minute then start WSPR transmission
{
set_tx_buffer();// Encode the message in the transmit buffer
SendWSPRBlock ();
if (LastFreq ())
{
smartdelay(GadgetData.TXPause * 1000UL); //Pause for some time to give a duty cycle on the transmit. 2000=100%, 20000=50%, 130000=33%
}
NextFreq();
freq = freq + (100ULL * random (-100, 100)); //modify TX frequency with a random value beween -100 and +100 Hz
smartdelay(3000);
// Serial.println (F("Waiting for start of even minute to start a new WSPR transmission block"));
}
else //Dubble-blink to indicate waiting for top of minute
{
SendAPIUpdate(UMesGPSLock);
SendAPIUpdate(UMesTime);
SendAPIUpdate(UMesLocator);
LEDBlink();
LEDBlink();
smartdelay(200);
}
}
if (gps.location.isValid())
{
//
}
else
{
//singelblink to indicate waiting for GPS Lock
LEDBlink();
SendAPIUpdate(UMesNoGPSLock);
smartdelay(400);
}
}
}
}//As this routine will run for ever if not interuppted by serial port, indicate end of routine by letting DoIdle routine send status message
DoIdle(); //Return to ideling
}
// Loop through the string, transmitting one character at a time.
void SendWSPRBlock()
{
uint8_t i;
unsigned long startmillis;
unsigned long endmillis;
boolean TXEnabled = true;
// Send WSPR for two minutes
digitalWrite(StatusLED, HIGH);
if ((GadgetData.WSPRData.CallSign[0] == 'A') && (GadgetData.WSPRData.CallSign[1] == 'A') && (GadgetData.WSPRData.CallSign[2] == '0') && (GadgetData.WSPRData.CallSign[3] == 'A') && (GadgetData.WSPRData.CallSign[4] == 'A') && (GadgetData.WSPRData.CallSign[5] == 'A')) //Do not actually key the transmitter if the callsign has not been changed from the default one AA0AAA
{
Serial.println(F("{MIN} Callsign is not changed from the default one, Transmit is disabled"));
Serial.println(F("{MIN} Set your Callsign to enable transmission"));
TXEnabled = false;
}
startmillis = millis();
for (i = 0; i < 162; i++) //162 WSPR symbols to transmitt
{
endmillis = startmillis + ((i + 1) * (unsigned long) 683) ; // Delay value for WSPR delay is 683 milliseconds
uint64_t tonefreq;
tonefreq = freq + ((tx_buffer[i] * 146)); //~1.46 Hz Tone spacing in centiHz
if (TXEnabled) si5351aSetFrequency(tonefreq);
//wait untill tone is transmitted for the correct amount of time
while ((millis() < endmillis) && (!Serial.available())) ;//Until time is up or there is serial data received on the controll Serial port
{
//Do nothing, just wait
}
if (Serial.available()) {// If serialdata was received on Controll port then abort and handle command
break;
}
}
// Switches off Si5351a output
si5351aOutputOff(SI_CLK0_CONTROL);
digitalWrite(StatusLED, LOW);
Serial.println(F("TX Off"));
}
void set_tx_buffer()
{
// Clear out the transmit buffer
memset(tx_buffer, 0, sizeof(tx_buffer));
jtencode.wspr_encode(GadgetData.WSPRData.CallSign, GadgetData.WSPRData.MaidenHead4, GadgetData.WSPRData.TXPowerdBm, tx_buffer);
}
//Maidenhead code from Ossi Väänänen https://ham.stackexchange.com/questions/221/how-can-one-convert-from-lat-long-to-grid-square
void calcLocator(double lat, double lon) {
int o1, o2, o3;
int a1, a2, a3;
double remainder;
// longitude
remainder = lon + 180.0;
o1 = (int)(remainder / 20.0);
remainder = remainder - (double)o1 * 20.0;
o2 = (int)(remainder / 2.0);
//remainder = remainder - 2.0 * (double)o2;
//o3 = (int)(12.0 * remainder);
// latitude
remainder = lat + 90.0;
a1 = (int)(remainder / 10.0);
remainder = remainder - (double)a1 * 10.0;
a2 = (int)(remainder);
//remainder = remainder - (double)a2;
//a3 = (int)(24.0 * remainder);
GadgetData.WSPRData.MaidenHead4[0] = (char)o1 + 'A';
GadgetData.WSPRData.MaidenHead4[1] = (char)a1 + 'A';
GadgetData.WSPRData.MaidenHead4[2] = (char)o2 + '0';
GadgetData.WSPRData.MaidenHead4[3] = (char)a2 + '0';
GadgetData.WSPRData.MaidenHead4[4] = 0;
}
// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartdelay(unsigned long ms)
{
long TimeLeft;
unsigned long EndTime = ms + millis();
do
{
while (GPSSerial.available()) gps.encode(GPSSerial.read()); //If GPS data available - process it
TimeLeft = EndTime - millis();
if ((TimeLeft > 1000) && ((TimeLeft % 1000) < 20)) {
//Send API update every second
Serial.print (F("{MPS} "));
Serial.println (TimeLeft / 1000);
}
} while ((TimeLeft > 0) && (!Serial.available())) ; //Until time is up or there is serial data received
//Serial.println (F("{MPS} 0"));
}
uint8_t i2cStart()
{
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT))) ;
return (TWSR & 0xF8);
}
void i2cStop()
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
while ((TWCR & (1 << TWSTO))) ;
}
uint8_t i2cByteSend(uint8_t data)
{
TWDR = data;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT))) ;
return (TWSR & 0xF8);
}
uint8_t i2cByteRead()
{
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT))) ;
return (TWDR);
}
uint8_t i2cSendRegister(uint8_t reg, uint8_t data)
{
uint8_t stts;
stts = i2cStart();
if (stts != I2C_START) return 1;
stts = i2cByteSend(I2C_WRITE);
if (stts != I2C_SLA_W_ACK) return 2;
stts = i2cByteSend(reg);
if (stts != I2C_DATA_ACK) return 3;
stts = i2cByteSend(data);
if (stts != I2C_DATA_ACK) return 4;
i2cStop();
return 0;
}
uint8_t i2cReadRegister(uint8_t reg, uint8_t *data)
{
uint8_t stts;
stts = i2cStart();
if (stts != I2C_START) return 1;
stts = i2cByteSend(I2C_WRITE);
if (stts != I2C_SLA_W_ACK) return 2;
stts = i2cByteSend(reg);
if (stts != I2C_DATA_ACK) return 3;
stts = i2cStart();
if (stts != I2C_START_RPT) return 4;
stts = i2cByteSend(I2C_READ);
if (stts != I2C_SLA_R_ACK) return 5;
*data = i2cByteRead();
i2cStop();
return 0;
}
// Init TWI (I2C)
//
void i2cInit()
{
TWBR = 92;
TWSR = 0;
TWDR = 0xFF;
PRR = 0;
}
//
// Set up specified PLL with mult, num and denom
// mult is 15..90
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
//
void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
{
uint32_t P1; // PLL config register P1
uint32_t P2; // PLL config register P2
uint32_t P3; // PLL config register P3
P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;
i2cSendRegister(pll + 0, (P3 & 0x0000FF00) >> 8);
i2cSendRegister(pll + 1, (P3 & 0x000000FF));
i2cSendRegister(pll + 2, (P1 & 0x00030000) >> 16);
i2cSendRegister(pll + 3, (P1 & 0x0000FF00) >> 8);
i2cSendRegister(pll + 4, (P1 & 0x000000FF));
i2cSendRegister(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 &