-
Notifications
You must be signed in to change notification settings - Fork 2
/
radioCxV2.ino
1998 lines (1639 loc) · 65.5 KB
/
radioCxV2.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
/*
* --- RadioUno ---
* Multi-mode radio controller and synthesizer using an Arduino Uno
* and si5351 oscillator. Also used is an MCP23008 I/O expander
* for RF filter and hardware control of modules.
* (C) 2018,2019 Kurt Theis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*
* THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
* APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
* HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
* OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
* IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
* ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*
*
* Please see the LICENCE file in the archive for more information.
*
* k theis <[email protected]> Initial Upload 11/2018
*
* Ver2 - has 16x2 LCD, a mode/RIT button, a channel/vfo store/recall
* button, a rotary (mechanical) encoder with a button. The button steps
* thru tuning steps from 10hz thru 100 khz depending on long or short pushes.
*
* There is a MCP23008 I2C port expander that controls relays for band filters,
* cw/ssb tx/rx control and usb/lsb relay control for the receiver.
*
* The oscillator is a Si5351 used for Tx and Rx.
*
* Note: there is no provision for a frequency lockout. This will transmit
* across all frequencies. YOU are responsible to follow all laws in your
* region regarding what frequencies (if any) you can transmit on.
*
* Please Note: You can probably save some flash memory by tightening up many
* of these routines, but that would make it harder for a beginning programmer
* to understand. As long as there is available memory, I keep it verbose.
*
* TODO:
* redo the osc code. Some freqs I get weird noise I suspect might be caused by jitter etc.
* show ref power in all tx modes
* try a different Si5351 library (or write code to turn on/off indiv Si ports)
*
*/
/* Subroutines:
*
* updateBand() look at current frequency, send filter data to port expander
* updateFreq() show the current frequency and channel data on the LCD display
* updateMode() change register to one of cw-L, cw-U, lsb, ssb, update display
* and update the port expander for hardware control
* Save() Save frequency and mode to EEPROM based on channel #
* Recall() Read frequency and mode from EEPROM, update registers and display
* UpdateDcVolt() Read analog pin, divide by constant for true voltage, display on LCD
* changeFreq() Interrupt driven - read rotary encoder, change freq/channel/menu etc
* updateOsc() Read from freq and mode. Send display freq to osc0, derived rx to osc1
* menu() Menu sub-system. Control registers, run special functions
* setDefault() Initialize the EEPROM with default frequencies & settings
* wspr(freq) Transmit WSPR on (freq) Call and Grid are predefined before setup() is called.
* Power level is defined in the wspr() routine.
* txKey() key the transmitter (send 1 to GP5 of the MCP23008), set Tx freq to display
* txDekey() dekey the transmitter (send 0 to GP5 of the MCP23008), shift Tx freq to 1 MHz
* scan() Scan 100kc from current display freq (vfo or channel)
* showTune() Place cursor on digit being tuned for step size display
*/
/*
* Notes -
* At startup, the TX freq is set to 1 MHz. The Adafruit libraries I use for the
* Si5351 don't allow the individual clocks to be enabled/disabled. It's all
* or nothing. With the TX freq set to RX all the time, it's signal is heard on the receiver.
*
* (osc notes):
* Enabled desired outputs (see Register 3)
* ASSERT_STATUS(write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, enabled ? 0x00: 0xFF));
* This should enable/disable indiv clocks
* bit: 2 is CLK2_EN 1 is CLK1_EN 0 is CLK0_EN
* located in routine: Adafruit_SI5351::enableOutputs(bool enabled)
* (the etherkit libs seem more suited for this - see github.com/etherkit/Si5351Arduino)
*
*
*/
/* Operation:
*
* At start up, the status of VFO/CHAN is checked:
*
* At start up, if the CHANNEL/VFO button is NOT pressed, the radio will act normally.
* If the CHANNEL/VFO button IS pressed, you will jump into the MENU mode. This is used
* to configure various settings. To exit from the menu mode, turn off the radio. Turning
* the radio back on while NOT pressing the CHANNEL/VFO button will restore normal operation.
*
* If not pressed, the various registers are set up (some read from eeprom) and
* the oscillator is configured. The TX osc (and osc 2) are set to 1 MHz. This gets them
* out of the way of the receiver. The receiver oscillator is configured to 4 times the
* receive frequency. The Tayloe detector my receiver uses requires this. If you have
* a different receiver, your requirements will be different.
*
* After all the lines and registers are configured, the program enters a command loop where
* the control buttons, the rotary encoder and tx lines are constantly checked. The display
* is not updated unless external inputs change. This eliminates a lot of digital noise.
*
* All the control (input) lines tie to the processor directly. If using the port expander for
* input, it would have to be constantly polled and this would generate too much digital noise
* for the receiver to be useful.
*
* Many additional functions I wanted proved to be impossible to impliment due to RAM limitations.
* The stack and the heap would collide causing weird issues. If you add features be aware of this.
*
*
*
* UPDATES:
*
* 1/04/2019 Minor doc changes, added extensive licence info
* 1/03/2019 In setting grid square, start with current char under cursor
* 1/02/2019 Change memory allocation in beacon()
* 1/02/2019 Update comments (documentation changes)
* 1/01/2019 Changed encoder sw press to scq button. scq short - beacon/cq. Long/scan
* 12/31/2018 When in channel mode, encoder button press increments channel by 10
* 12/31/2018 Put static values for cw code and wspr in PROGMEM
* 12/31/2018 Cleaned up misc compiler warnings (none critical)
* 12/31/2018 Code considered useful enough to use. Still some things to be added (none necessary)
* 12/30/2018 Removed 2nd tx keyline, made it 3rd button for scan and cq option
* 12/30/2018 Wrote cw char send routine/beacon routine/cq send routine
*/
/****************************/
#define MINFREQ 2400000 // lowest operating frequency (hz)
#define MAXFREQ 16100000 // highest operating frequency (hz)
// EEPROM addresses 0-500 used for channel storage, 5 bytes/channel
#define gridAddr 504 // eeprom storage for grid square (4 bytes) (504-507)
#define SidetoneLow 508 // sidetone/cw offset frequency storage (2 bytes) (508-509)
#define SidetoneHi 509
#define CalLow 510 // EEPROM address for calibrate function (510-511)
#define CalHi 511 // (2 bytes)
#define DEBOUNCE 50 // debounce make/break switches
/****************************/
#define CALLSIGN "D0MMY" // Your call sign for wspr, cw, beacon. Change before use! Use UPPER CASE.
// grid square is set from menu and stored in eeprom
#define RFPOWER 13 // rf power used in wspr (values: 0,3,7,10,13,17,30,23,27,30,33,37,40,43)
/****************************/
#include <LiquidCrystal.h>
#include <stdlib.h>
#include <EEPROM.h>
#include <Wire.h> // for I2C port expander
#include <Adafruit_SI5351.h> // for clock osc
#include <avr/pgmspace.h> // probably not needed
#include <avr/io.h> // probably not needed
#include <string.h> // probably not needed
Adafruit_SI5351 clockgen = Adafruit_SI5351(); // you will need to install the Adafruit Si5351 libs
// lcd1 is Frequency Display (LCD D0 thru D3 are unused and left floating)
LiquidCrystal lcd1(10,13,6,7,8,9); // RS, e, D4, D5, D6, D7
// (original version used 2 lcd displays, lcd1 and lcd2)
/* define input lines (output lines are on the port expander, I2C & LCD) */
const int knobsw = 4; // digital pin D4 (encoder switch) White wire on mine
const int knob = 2; // digital pin D2 (encoder pulse) Brown wire on mine
const int knobDir = 5; // digital pin D5 (encoder direction) Blue wire on mine
const int dcVoltage = A0; // read dc voltage (thru 10K trimmer resistor) on A0
const int toneOut = A1; // audio tone out while in transmit on A1
const int refIn = A3; // dc reflected power input (used in transmit)
const int vc = 3; // pushbutton, vfo/channel and sto/rcl on D3
const int keyIn1 = 11; // key line (D11)
const int scq = 12; // Scan/Send CQ (D12)
const int mr = A2; // Mode/RIT
// used for WSPR, beacon and sending CQ message
const char call[] = CALLSIGN;
char cwMsg[] = " \0"; // initialize cw play message
// define variables
float VOLT; // read DC Voltage on pin A0
float freq; // main frequency in Hz
float ritFreq; // tx freq when rit is 1
byte rit; // 0, no rit. 1, rit active
float freqBak; // use this when in channel mode to hold the vfo freq
float STEP; // step size when tuning (in Hz)
int FREQFLAG = 0; // when HIGH update freq Display & Osc
int vfoChan = 0; // 0=vfo, 1=chan, 2=menuMode
int chan = 0; // channel number - 0-99
int menu_sel; // used in menu sub-system
float MULTI = 28.0; // multiplier (* XTAL) for PLL (used in transmit pll only)
float XTAL = 25.0; // Crystal frequency of PLL clock (MHz)
unsigned int SIDETONE; // sidetone frequency for tone out and CW offset
byte radioReg; // set radio filters, radio mode etc (for MCP23008)
int CALOFFSET; // calibrate setting for +/- 0 hz
#define MAXMODE 6 // set max number of modes
byte MODE;
const char mode[MAXMODE][6] = {"LSB ","USB ","CW-U","CW-L","WSPR","BECN"};
/* the following are constants used in sendCw() */
const PROGMEM char numCw[10][7] = {
"333330","133330","113330","111330","111130", // 0-4
"111110","311110","331110","333110","333310" // 5-9
};
const char chrCw[26][6] PROGMEM = {
"130","31110","31310","3110","10","11310", // a-f
"3310","11110","110","13330","3130","13110", // g-l
"330","310","3330","13310","33130","1310", // m-r
"1110","30","1130","11130","1330","31130","31330","33110" // s-z
};
/* the following are constants used in wspr */
const byte sync[] PROGMEM = {
1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0,
1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1,
0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0
};
/*****************************/
/* set up hardware ports etc */
/*****************************/
void setup() {
/* init LCD displays */
lcd1.begin(16,2); // initialize LCD display
/* set up input devices */
pinMode(knobsw, INPUT_PULLUP);
pinMode(knob, INPUT_PULLUP);
pinMode(knobDir, INPUT_PULLUP);
pinMode(vc, INPUT_PULLUP);
pinMode(keyIn1, INPUT_PULLUP);
pinMode(scq, INPUT_PULLUP);
pinMode(mr, INPUT_PULLUP);
analogReference(DEFAULT); // use 5vdc for this 5v arduino
/* debug code - enable when problems */
//Serial.begin(9600);
//Serial.print("Starting up\n\n");
/* set up I2C */
Wire.begin();
/* initialize port-expander */
Wire.beginTransmission(0x20); // select port expander
Wire.write(0x00); // select IODIRA of port expander
Wire.write(0x00); // PORT A all output
Wire.endTransmission(); // stop talking to port expander
/* initialize Si5351 oscillator */
if (clockgen.begin() != ERROR_NONE) {
lcd1.home();
lcd1.print(F("CLK ERR")); // if it doesn't start up, no reason to continue
while (true) continue; // so loop until power cycled
}
/* set up PLL B for rx freq range (12-120 MHz) */
clockgen.setupPLLInt(SI5351_PLL_B, 40); // PLL B is 1000 MHz (noisy at 900MHz)
/* set up PLL A for TX freq range (3-30 MHz) */
clockgen.setupPLLInt(SI5351_PLL_A, 28); // PLL A is 700 MHz (Using 25 MHz clock on Adafruit Si5351 breakout)
/* turn on the outputs */
clockgen.enableOutputs(true);
/* set interrupts last */
attachInterrupt(0, changeFreq, LOW); // set interrupt on knob going LOW
interrupts(); // allow interrupts (normally defaults on)
}
/***********************/
/***** SUBROUTINES *****/
/***********************/
/*********************************************************************/
/**** show tune speed by displaying underline cursor in freq area ****/
/*********************************************************************/
void showTune() {
extern float STEP;
extern int vfoChan;
if (vfoChan != 0) return;
if (STEP == 100000) {
lcd1.setCursor(8,0);
lcd1.cursor();
return;
}
if (STEP == 10000) {
lcd1.setCursor(9,0);
lcd1.cursor();
return;
}
if (STEP == 1000) {
lcd1.setCursor(10,0);
lcd1.cursor();
return;
}
if (STEP == 100) {
lcd1.setCursor(12,0);
lcd1.cursor();
return;
}
if (STEP == 10) {
lcd1.setCursor(13,0);
lcd1.cursor();
return;
}
return;
}
/********************************************/
/**** show the current frequency ****/
/********************************************/
void updateFreq() {
extern float freq;
extern int vfoChan;
if (vfoChan == 1) { // show channel number (ie CH 01 14250.50)
lcd1.home();
if (chan < 10) lcd1.print(F("CH 0")); // thats a '0' (zero)
if (chan > 9) lcd1.print(F("CH "));
lcd1.print(chan);
lcd1.print(F(" "));
// now show the frequency
}
if (vfoChan == 0) { // show frequency of VFO (ie VF 01 14250.00)
lcd1.home();
if (chan < 10) lcd1.print(F("VF 0")); // thats a '0' (zero), not an O (oh)
if (chan > 9) lcd1.print(F("VF "));
lcd1.print(chan);
lcd1.print(F(" "));
}
// now show the frequency
if ((freq/1000) < 10000) lcd1.print(F(" ")); // push display out, no leading 0
lcd1.print(freq/1000);
lcd1.print(F(" ")); // clean up from channel display
return;
}
/********************************************/
/**** Store vfo frequency/MODE to EEPROM ****/
/********************************************/
void Save() {
extern float freq;
extern int chan;
extern byte MODE;
int i;
int address = chan * 5; // store freq as (4 byte) long, mode as 1 byte
union u_tag
{
byte b[4];
float fval;
} u;
u.fval = freq;
for (i=0; i<4; i++) {
EEPROM.write(address+i, u.b[i]); // save float value of freq
}
EEPROM.write(address+4, MODE); // save mode value
return;
}
/****************************************/
/*** Recall from EEPROM to freq/MODE ****/
/****************************************/
void Recall() {
extern float freq;
extern int chan;
extern byte MODE;
int i;
int address = chan * 5; // recall freq as 4 byte values, mode as 1 byte
union u_tag
{
byte b[4];
float fval;
} u;
for (i=0; i<4; i++) {
u.b[i] = EEPROM.read(address+i);
}
freq = u.fval;
MODE = EEPROM.read(address+4); // get mode value
return;
}
/******************************/
/**** Display D.C. Voltage ****/
/******************************/
void updateDcVolt() {
// read analog pin, divide by constant for true voltage (I use a 10k multiturn pot)
extern float VOLT;
char buf[5];
VOLT = analogRead(dcVoltage)/42.0; // read DC voltage (use a float here)
// dtostrf(float var,str len, digits after decimal point, var to hold val)
dtostrf(VOLT,4,1,buf); // arduino can't handle floats (WTF? it has a c compiler)
// this stupid little routine takes 2K of memory!!
lcd1.setCursor(11,1);
lcd1.print(buf);
lcd1.setCursor(15,1);
lcd1.write("V"); // show as voltage
return;
}
/*****************************************************/
/**** INTERRUPT 0 ( rotary encoder turned cw/ccw) ****/
/*****************************************************/
/* read dir, change freq +/- by STEP size ****/
void changeFreq() {
extern float freq;
extern int FREQFLAG;
extern int menu_sel; // determines menu selection
if (vfoChan == 3) return; // in cal/sidetone/setGrid mode, vfochan = 3
if ((FREQFLAG==1) && (vfoChan<3)) { // wait until display updated before continuing
return;
}
delay(1); // deal with debounce using cheap encoders
if (digitalRead(knob) == HIGH) {
interrupts();
return;
}
noInterrupts(); // stop further ints while in this routine
if ((digitalRead(knobDir) == HIGH) && (digitalRead(knob) == LOW)) {
if (vfoChan == 2) { // in menu sub-system
menu_sel++;
if (menu_sel > 4) menu_sel = 0;
FREQFLAG = 1;
}
if (vfoChan == 0) { // in vfo mode - increment vfo freq
freq += STEP;
if (freq > MAXFREQ) freq = MAXFREQ; // freq limits
FREQFLAG = 1;
}
if (vfoChan == 1) { // in channel mode - increment channel number
chan += 1;
if (chan > 99) chan = 0;
Recall(); // read freq from EEPROM save in freq
FREQFLAG = 1;
}
while (digitalRead(knob)==LOW) continue;
interrupts(); // resume ints
return;
}
if ((digitalRead(knobDir) == LOW) && (digitalRead(knob) == LOW)) {
if (vfoChan == 2) { // in menu sub-system
menu_sel--;
if (menu_sel < 0) menu_sel = 4;
FREQFLAG = 1;
}
if (vfoChan == 0) { // in vfo mode - decrement vfo frequency
freq -= STEP;
if (freq < MINFREQ) freq = MINFREQ; // freq limits
FREQFLAG = 1;
}
if (vfoChan == 1) { // in channel mode - decrement channel number
chan -= 1;
if (chan < 0) chan = 99;
Recall(); // get the EEPROM channel freq
FREQFLAG = 1;
}
while (digitalRead(knob)==LOW) continue;
interrupts(); // resume ints
return;
}
// nothing happend (shouldn't really get here)
interrupts();
delay(1);
return;
}
/***************************************/
/* update the si5351 oscillator for RX */
/***************************************/
void updateOsc() {
extern unsigned int SIDETONE;
extern float freq, XTAL;
extern byte MODE; // 0=LSB, 1=USB, 2=CW-U, 3=CW-L, 4 and up USB
float VB, VALUE, DIV, VA, VINT;
float rxFreq; // rx freq = 4x freq, +/- ssb/cw offset, +/- caloffset
extern int CALOFFSET;
rxFreq = 0; // keep compiler from bitching about uninitialized vars
VB = 1000000.0;
// set up the receive frequency (no offset for ssb needed with qrp-labs rx with poly)
if (MODE == 0) rxFreq = freq; // LSB
if (MODE == 1) rxFreq = freq; // USB
if (MODE == 2) rxFreq = freq - (float)SIDETONE; // CW-L
if (MODE == 3) rxFreq = freq + (float)SIDETONE; // CW-U
if (MODE > 3) rxFreq = freq; // all else
rxFreq += CALOFFSET;
/* When rxFreq > MAXFREQ set rxFreq to 1 mhz. (Keep si5351 happy) */
if (rxFreq > MAXFREQ) rxFreq = 1000000; // channels can contain freq above MAXFREQ (ie. 6M beacon)
rxFreq *= 4.0; // using a tayloe detector, rx freq is 4X display freq
VALUE = rxFreq/VB;
DIV = XTAL * 40; // MULTI for PLL B is 40 (below that osc output is jittery
VALUE = DIV/VALUE; // above about 118 MHz)
VINT = (long)VALUE;
VA = (long)((VALUE - VINT) * VB);
clockgen.setupMultisynth(1, SI5351_PLL_B, VINT, VA, VB); // output on osc 1
return;
}
/*******************/
/* Update the Mode */
/*******************/
void updateMode() {
extern byte radioReg;
extern byte MODE;
/*
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
cw/ssb usb/lsb tx (band filter settings)
cw/ssb is 0 for cw, 1 for ssb
usb/lsb is 0 for lsb, 1 for usb
tx = 0 for RX, 1 for TX
*/
if (MODE == 0) { // LSB
radioReg &= B00000111; // clear current mode
radioReg |= B10000000; // set SSB-LSB
}
if (MODE == 1) { // USB
radioReg &= B00000111; // clear current mode
radioReg |= B11000000; // set SSB-USB
}
if (MODE == 2) { // CW-U
radioReg &= B00000111; // clear current mode
radioReg |= B01000000; // set CW-USB
}
if (MODE == 3) { // CW-L
radioReg &= B00000111; // clear current mode
radioReg |= B00000000; // set CW-LSB
}
if (MODE >= 4) { // wspr etc use USB
radioReg &= B00000111; // clear current mode
radioReg |= B11000000; // set SSB-USB
}
Wire.beginTransmission(0x20); // set up communication with port expander
Wire.write(0x09); // select GPIO pins
Wire.write(radioReg); // set band pins
Wire.endTransmission(); // done
/* update LCD display */
lcd1.setCursor(0,1); // row 1 pos 0
lcd1.print(mode[MODE]);
return;
}
/*************************************************************************/
/* update the band register for the TX low pass filters and rx filters */
/*************************************************************************/
void updateBand() {
extern float freq;
extern byte radioReg;
/*
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
cw/ssb usb/lsb tx 11-15 8-11 5-8 2-5
2-5 is a high pass at 3 mc, low pass at 5 mc
5-8 is a high pass at 5, low pass at 8 mc
8-11 is a high pass at 8 mc, low pass at 11 mc
11-15 is a high pass at 11 mc, low pass at 15 mc
cw/ssb is 0 for cw, 1 for ssb
usb/lsb is 0 for lsb, 1 for usb
tx = 0 for RX, 1 for TX
*/
if ((freq >= 2900000) && (freq < 5000000)) { // 2.9 mc thru 5 mc
radioReg &= B1110000; radioReg |= B00000001; // set band 1 line
}
if ((freq >= 5000000) && (freq < 8000000)) { // 5 mc thru 8 mc
radioReg &= B11110000; radioReg |= B00000010; // set band 2 line
}
if ((freq >= 8000000) && (freq < 11000000)) { // 8 mc thru 11 mc
radioReg &= B11110000; radioReg |= B00000100; // set band 3 line
}
if ((freq >= 11000000) && (freq < 15200000)) { // 11 mc thru 15.2 mc
radioReg &= B11110000; radioReg |= B00001000; // set band 4 line
}
Wire.beginTransmission(0x20); // set up communication with port expander
Wire.write(0x09); // select GPIO pins
Wire.write(radioReg); // set band pins
Wire.endTransmission(); // done
return;
}
/*******************************************/
/* MENU mode - set default, working values */
/*******************************************/
void menu() {
int i; // gp variable
int charPos; // used in setGrid, setCall
int charValue; // "" "" ""
extern int vfoChan;
extern int menu_sel;
// this is for the cal routine
extern int CALOFFSET;
int ByHi, ByLo;
extern int FREQFLAG;
float VALUE, DIV, VINT, VA, VB;
VB = 1000000.0;
// these next 2 are for grid square calcs
char gs[5];
char sChar[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
vfoChan = 2; // in menu mode
menu_sel = 0; // select menu item #
FREQFLAG = 0;
extern unsigned int SIDETONE;
SIDETONE = 700;
lcd1.home();
lcd1.print(F("Menu "));
while (digitalRead(vc) == LOW){
delay(100);
continue; // wait until vc released
}
delay(150);
lcd1.home();
lcd1.print(F("Select List "));
delay(500);
FREQFLAG = 1;
/*******************/
/**** menu loop ****/
/*******************/
while (true) {
/*****************/
/* calibrate VFO */
/*****************/
if (menu_sel == 0) {
if (FREQFLAG == 1) {
lcd1.home();
lcd1.print(F("Calibrate Osc "));
FREQFLAG = 0;
}
CALOFFSET = 0; // offset in Hz
if (digitalRead(vc) == LOW) { // wait for press on vc
while (digitalRead(vc) == LOW) continue; // wait until vc is released
delay(DEBOUNCE);
lcd1.clear();
lcd1.print(F("Rotate knob"));
lcd1.setCursor(0,1);
lcd1.print(CALOFFSET);
vfoChan = 3; // disable knob operation for menu selection
VALUE = (10000000.0 + CALOFFSET)/VB; // 10mc + offset in hz
DIV = XTAL * MULTI; VALUE = DIV/VALUE; VINT = (long)VALUE; VA = (long)((VALUE - VINT) * VB);
clockgen.setupMultisynth(2, SI5351_PLL_A, VINT,VA,VB); // output on osc 2
while (true) { // use knob to vary CALOFFSET
if ((digitalRead(knobDir) == HIGH) && (digitalRead(knob) == LOW)) {
CALOFFSET += 1;
VALUE = (10000000.0 + CALOFFSET)/VB; // 10mc + offset in hz
DIV = XTAL * MULTI; VALUE = DIV/VALUE; VINT = (long)VALUE; VA = (long)((VALUE - VINT) * VB);
clockgen.setupMultisynth(2, SI5351_PLL_A, VINT,VA,VB); // output on osc 2
lcd1.setCursor(0,1);
lcd1.print(CALOFFSET);
lcd1.print(F(" hz "));
while (digitalRead(knob) == 0) continue;
}
if ((digitalRead(knobDir) == LOW) && (digitalRead(knob) == LOW)) {
CALOFFSET -= 1;
VALUE = (10000000.0 + CALOFFSET)/VB; // 10mc + offset in hz
DIV = XTAL * MULTI; VALUE = DIV/VALUE; VINT = (long)VALUE; VA = (long)((VALUE - VINT) * VB);
clockgen.setupMultisynth(2, SI5351_PLL_A, VINT,VA,VB); // output on osc 2
lcd1.setCursor(0,1);
lcd1.print(CALOFFSET);
lcd1.print(F(" hz "));
while (digitalRead(knob) == 0) continue;
}
if (digitalRead(vc) == LOW) { // vc pressed - save value to eeprom
EEPROM.write(CalLow, highByte(CALOFFSET));
EEPROM.write(CalHi, lowByte(CALOFFSET));
lcd1.clear();
lcd1.print(F("Offset Saved"));
while(digitalRead(vc) == LOW) continue;
delay(DEBOUNCE);
vfoChan = 2; // re-enable knob for menu ops
FREQFLAG = 1; // to redraw menu choice
break;
}
}
}
continue;
}
/**********************************/
/* set default channels in EEPROM */
/**********************************/
if (menu_sel == 1) {
if (FREQFLAG == 1) {
lcd1.home();
lcd1.print(F("set defaults "));
FREQFLAG = 0;
}
if (digitalRead(vc) == 0) {
delay(100);
if (digitalRead(vc) == 0) {
lcd1.home();
lcd1.print(F("writing "));
while (digitalRead(vc) == LOW) continue;
delay(DEBOUNCE);
setDefault();
lcd1.home();
lcd1.print(F("done "));
delay(750);
FREQFLAG = 1; // to redraw menu choice
}
}
delay(DEBOUNCE);
continue;
}
/*********************/
/* set sidetone freq */
/*********************/
if (menu_sel == 2) {
if (FREQFLAG == 1) {
lcd1.home();
lcd1.print(F("Sidetone "));
FREQFLAG = 0;
}
if (digitalRead(vc) == 0) {
delay(100);
if (digitalRead(vc) == 0) {
ByLo = EEPROM.read(SidetoneLow);
ByHi = EEPROM.read(SidetoneHi);
SIDETONE = word(ByHi,ByLo);
tone(toneOut, SIDETONE);
vfoChan = 3;
lcd1.home();
lcd1.print(F("Rotate Knob "));
lcd1.setCursor(0,1);
lcd1.print(SIDETONE);
while (digitalRead(vc) == 0) continue;
delay(DEBOUNCE);
while(digitalRead(vc)==1) {
if ((digitalRead(knobDir) == HIGH) && (digitalRead(knob) == LOW)) {
SIDETONE += 1;
if (SIDETONE >= 1000) SIDETONE = 1000;
while (digitalRead(knob) == LOW) continue;
FREQFLAG = 1;
}
if ((digitalRead(knobDir) == LOW) && (digitalRead(knob) == LOW)) {
SIDETONE -= 1;
if (SIDETONE <= 500) SIDETONE = 500;
while (digitalRead(knob) == LOW) continue;
FREQFLAG = 1;
}
if (FREQFLAG == 1) {
tone(toneOut, SIDETONE);
lcd1.setCursor(0,1);
lcd1.print(SIDETONE);
lcd1.print(F(" ")); // remove trailing zero at 1000 hz
FREQFLAG = 0;
}
}
while (digitalRead(vc) == 0) continue;
delay(DEBOUNCE);
noTone(toneOut);
/* now save to eeprom */
EEPROM.write(SidetoneHi, highByte(SIDETONE));
EEPROM.write(SidetoneLow, lowByte(SIDETONE));
lcd1.clear();
lcd1.print(F("Saved Value "));
delay(700);
FREQFLAG = 1; // to redraw display
}
}
vfoChan = 2; // back to menu mode
delay(DEBOUNCE);
continue;
}
/**************************************/
/* set grid square (for beacon, wspr) */
/**************************************/
if (menu_sel == 3) {
if (FREQFLAG == 1) {
lcd1.home();
lcd1.print(F("Set Grid Square "));
FREQFLAG = 0;
}
if (digitalRead(vc) == 0) {
delay(100);
vfoChan = 3; // tell int to ignore knob rotation
if (digitalRead(vc) == 0) {
for (i=0; i<4; i++) // read current grid square from eeprom
gs[i] = EEPROM.read(gridAddr+i);
gs[i]='\0';
lcd1.home();
lcd1.print(F("Rotate Knob "));
while (digitalRead(vc) == LOW) continue;
delay(DEBOUNCE);
lcd1.setCursor(0,1); // 2nd row, char pos 0
lcd1.print(gs); // show current grid square from eeprom
charPos = 0;
lcd1.setCursor(charPos,1);
charValue = gs[0] - 'A' + 10; // set char under cursor, offset by numbers
lcd1.cursor(); // show cursor position
while (true) { // set grid, read VFO/CHAN, knob, knobsw to change values
if (digitalRead(vc) == 0) { // save grid, exit routine
for (i=0; i<4; i++) // save grid to eeprom
EEPROM.write(gridAddr+i,gs[i]);
lcd1.noCursor();
lcd1.home();
lcd1.print(F("Grid Saved "));
lcd1.setCursor(0,1);
lcd1.print(F(" "));
lcd1.home();
FREQFLAG = 1;
break;
}
if (digitalRead(knobsw) == 0) { // step to next char position
charPos++;
if (charPos == 4) charPos = 0;
if (charPos < 2) charValue = gs[charPos] - 'A' + 10; // set char under cursor, pos 0,1 alpha
if (charPos > 1) charValue = gs[charPos] - '0'; // set char under cursor; // pos 2,3 numeric
//charValue = 0;
lcd1.setCursor(charPos,1);
lcd1.cursor(); // show current position
while (digitalRead(knobsw) == 0) continue;
delay(DEBOUNCE);
}
if ((digitalRead(knob) == LOW) && (digitalRead(knobDir) == HIGH)) { // change char under cursor
charValue++;
if (charValue > 35) charValue = 0;
gs[charPos] = sChar[charValue];
lcd1.setCursor(charPos,1);
lcd1.print(gs[charPos]);
lcd1.setCursor(charPos,1);
lcd1.cursor(); // reset underline cursor
while (digitalRead(knob) == LOW) continue;
delay(DEBOUNCE);
}
if ((digitalRead(knob) == LOW) && (digitalRead(knobDir) == LOW)) { // change char under cursor
charValue--;
if (charValue < 0) charValue = 35;
gs[charPos] = sChar[charValue];
lcd1.setCursor(charPos,1);
lcd1.print(gs[charPos]);
lcd1.setCursor(charPos,1);
lcd1.cursor(); // reset underline cursor
while (digitalRead(knob) == LOW) continue;
delay(DEBOUNCE);
}
}
while (digitalRead(vc) == 0) continue; // wait until vc is released
vfoChan = 2; // back to menu mode
FREQFLAG = 1; // to redraw menu choice
}
}
delay(DEBOUNCE);
continue;
}
/*** Placeholder ***/
if (menu_sel == 4) {
if (FREQFLAG == 1) {