forked from amunters/bitx40
-
Notifications
You must be signed in to change notification settings - Fork 0
/
raduino_v1.29.ino
2529 lines (2193 loc) · 92.7 KB
/
raduino_v1.29.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
/**
Raduino_v1.29 for BITX40 - Allard Munters PE1NWL ([email protected])
This source file is under General Public License version 3.
Most source code are meant to be understood by the compilers and the computers.
Code that has to be hackable needs to be well understood and properly documented.
Donald Knuth coined the term Literate Programming to indicate code that is written be
easily read and understood.
The Raduino is a small board that includes the Arduino Nano, a 16x2 LCD display and
an Si5351a frequency synthesizer. This board is manufactured by Paradigm Ecomm Pvt Ltd.
To learn more about Arduino you may visit www.arduino.cc.
The Arduino works by first executing the code in a routine called setup() and then it
repeatedly keeps calling loop() forever. All the initialization code is kept in setup()
and code to continuously sense the tuning knob, the function button, transmit/receive,
etc is all in the loop() routine. If you wish to study the code top down, then scroll
to the bottom of this file and read your way up.
First we define all user parameters. The parameter values can be changed by the user
via SETTINGS menu, and are stored in EEPROM.
The parameters values will be set to initial 'factory' settings after each
version upgrade, or when the Function Button is kept pressed during power on.
It is also possible to manually edit the values below. After that, initialize the
settings to the new values by keeping the F-Button pressed during power on, or by
switching the CAL wire to ground.
*/
// *** USER PARAMETERS ***
#define MY_CALLSIGN "" // callsign here will display on line 2 when otherwise blank (tks Richard, VE3YSH)
#define FAST_TUNE_DELAY 300 // fast tuning step delay in ms (when tuning pot is at the upper or lower limit)(tks Bob, N4FV)
// tuning range parameters
#define MIN_FREQ 7000000UL // absolute minimum tuning frequency in Hz
#define MAX_FREQ 7300000UL // absolute maximum tuning frequency in Hz
#define TUNING_POT_SPAN 50 // tuning pot span in kHz [accepted range 10-500 kHz]
// recommended pot span for a 1-turn pot: 50kHz, for a 10-turn pot: 100 to 200kHz
// recommended pot span when radio is used mainly for CW: 10 to 25 kHz
// prevent out-of-band transmission depending on ham band allocation (out-of-band RX is still possible if PTTsense mod is installed)
// uncomment only ONE of the following 4 lines depending on your location:
//#define REGION 1 // ham band allocation for ITU Region 1 (Europe, Africa, Middle-East, former Sovjet Union: TX restricted to 7000-7200 kHz)
#define REGION 2 // ham band allocation for ITU Region 2 (Americas: TX restricted to 7000-7300 kHz)
//#define REGION 3 // ham band allocation for ITU Region 3 (Asia except FSU, Oceania, Australia: TX restricted to 7000-7200 kHz)
//#define REGION 0 // general coverage, TX unrestricted (not normally recommended)
// USB/LSB parameters
#define CAL_VALUE 1575 // Initial VFO calibration value: 180 ppm
#define OFFSET_USB 1500 // USB offset in Hz [accepted range -10000Hz to 10000Hz]
#define VFO_DRIVE_LSB 4 // VFO drive level in LSB mode in mA [accepted values 2,4,6,8 mA]
#define VFO_DRIVE_USB 8 // VFO drive level in USB mode in mA [accepted values 2,4,6,8 mA]
// CW parameters
#define CW_SHIFT 800 // RX shift in CW mode in Hz, equal to sidetone pitch [accepted range 200-1200 Hz]
#define SEMI_QSK true // whether we use semi-QSK (true) or manual PTT (false)
#define CW_TIMEOUT 350 // time delay in ms before radio goes back to receive [accepted range 10-1000 ms]
// CW keyer parameters
#define CW_KEY_TYPE 0 // type of CW-key (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug)
#define CW_SPEED 16 // CW keyer speed in words per minute [accepted range 1-50 WPM]
#define AUTOSPACE false // whether or not auto-space is enabled [accepted values: true or false]
#define DIT_DELAY 5 // debounce delay (ms) for DIT contact (affects the DIT paddle sensitivity)
#define DAH_DELAY 15 // debounce delay (ms) for DAH contact (affects the DAH paddle sensitivity)
// Capacitive touch keyer
#define CAP_SENSITIVITY 0 // capacitive touch keyer sensitivity 0 (OFF) [accepted range 0-25]
// frequency scanning parameters
#define SCAN_START 7100 // Scan start frequency in kHz [accepted range MIN_FREQ - MAX_FREQ, see above]
#define SCAN_STOP 7150 // Scan stop frequency in kHz [accepted range SCAN_START - MAX_FREQ, see above]
#define SCAN_STEP 1000 // Scan step size in Hz [accepted range 50Hz to 10000Hz]
#define SCAN_STEP_DELAY 500 // Scan step delay in ms [accepted range 0-2000 ms]
// Function Button
#define CLICK_DELAY 1500 // max time (in ms) between function button clicks
// RX-TX burst prevention
#define TX_DELAY 65 // delay (ms) to prevent spurious burst that is emitted when switching from RX to TX
// Roger Beep function
#define ROGER_BEEP false // whether roger beep is enabled (true) or disabled (false)
// all variables that will be stored in EEPROM are contained in this 'struct':
struct userparameters {
byte raduino_version; // version identifier
byte wpm = CW_SPEED; // CW keyer speed (words per minute)
int USB_OFFSET = OFFSET_USB; // VFO offset in Hz for USB mode
int cal = CAL_VALUE; // VFO calibration value
byte LSBdrive = VFO_DRIVE_LSB; // VFO drive level in LSB mode
byte USBdrive = VFO_DRIVE_USB; // VFO drive level in USB mode
bool semiQSK = SEMI_QSK; // whether semi QSK is ON or OFF
unsigned int POT_SPAN = TUNING_POT_SPAN; // tuning pot span (kHz)
unsigned int CW_OFFSET = CW_SHIFT; // RX shift (Hz) in CW mode, equal to side tone pitch
unsigned long vfoA = 7125000UL; // frequency of VFO A
unsigned long vfoB = 7125000UL; // frequency of VFO B
byte mode_A = 0; // operating mode of VFO A
byte mode_B = 0; // operating mode of VFO B
bool vfoActive = false; // currently active VFO (A = false, B = true)
bool splitOn = false; // whether SPLIT is ON or OFF
unsigned int scan_start_freq = SCAN_START; // scan start frequency (kHz)
unsigned int scan_stop_freq = SCAN_STOP; // scan stop frequency (kHz)
unsigned int scan_step_freq = SCAN_STEP; // scan step size (Hz)
unsigned int scan_step_delay = SCAN_STEP_DELAY; // scan step delay (ms)
unsigned int QSK_DELAY = CW_TIMEOUT; // word [accepted range 10-1000 ms]
byte key_type = CW_KEY_TYPE; // CW key type (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug)
unsigned long LOWEST_FREQ = MIN_FREQ; // absolute minimum dial frequency (Hz)
unsigned long HIGHEST_FREQ = MAX_FREQ; // absolute maximum dial frequency (Hz)
bool autospace = AUTOSPACE; // whether auto character space is ON or OFF
byte cap_sens = CAP_SENSITIVITY; // sensitivity of the touch keyer sensors
bool RogerBeep = ROGER_BEEP; // whether Roger Beep is enabled or disabled
};
// we can access each of the variables inside the above struct by "u.variablename"
struct userparameters u;
/**
Below are the libraries to be included for building the Raduino
The EEPROM library is used to store settings like the frequency memory, calibration data, etc.
*/
#include <EEPROM.h>
/**
The Wire.h library is used to talk to the Si5351 and we also declare an instance of
Si5351 object to control the clocks.
*/
#include <Wire.h>
/**
The PinChangeInterrupt.h library is used for handling interrupts
*/
#include <PinChangeInterrupt.h> // https://github.com/NicoHood/PinChangeInterrupt
/**
The main chip which generates upto three oscillators of various frequencies in the
Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet
from www.silabs.com although, strictly speaking it is not a requirement to understand this code.
We no longer use the standard SI5351 library because of its huge overhead due to many unused
features consuming a lot of program space. Instead of depending on an external library we now use
Jerry Gaffke's, KE7ER, lightweight standalone mimimalist "si5351bx" routines (see further down the
code). Here are some defines and declarations used by Jerry's routines:
*/
#define BB0(x) ((uint8_t)x) // Bust int32 into Bytes
#define BB1(x) ((uint8_t)(x>>8))
#define BB2(x) ((uint8_t)(x>>16))
#define SI5351BX_ADDR 0x60 // I2C address of Si5351 (typical)
#define SI5351BX_XTALPF 2 // 1:6pf 2:8pf 3:10pf
// If using 27mhz crystal, set XTAL=27000000, MSA=33. Then vco=891mhz
#define SI5351BX_XTAL 25000000 // Crystal freq in Hz
#define SI5351BX_MSA 35 // VCOA is at 25mhz*35 = 875mhz
// User program may have reason to poke new values into these 3 RAM variables
uint32_t si5351bx_vcoa = (SI5351BX_XTAL*SI5351BX_MSA); // 25mhzXtal calibrate
uint8_t si5351bx_rdiv = 0; // 0-7, CLK pin sees fout/(2**rdiv)
uint8_t si5351bx_drive[3] = {1, 1, 1}; // 0=2ma 1=4ma 2=6ma 3=8ma for CLK 0,1,2
uint8_t si5351bx_clken = 0xFF; // Private, all CLK output drivers off
/**
The Raduino board is the size of a standard 16x2 LCD panel. It has three connectors:
First, is an 8 pin connector that provides +5v, GND and six analog input pins that can also be
configured to be used as digital input or output pins. These are referred to as A0,A1,A2,
A3,A6 and A7 pins. The A4 and A5 pins are missing from this connector as they are used to
talk to the Si5351 over I2C protocol.
A0 A1 A2 A3 GND +5V A6 A7
BLACK BROWN RED ORANGE YELLOW GREEN BLUE VIOLET (same color coding as used for resistors)
Second is a 16 pin LCD connector. This connector is meant specifically for the standard 16x2
LCD display in 4 bit mode. The 4 bit mode requires 4 data lines and two control lines to work:
Lines used are : RESET, ENABLE, D4, D5, D6, D7
We include the library and declare the configuration of the LCD panel too
*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
/**
The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory.
We have to be very careful with variables that are declared inside the functions as they are
created in a memory region called the stack. The stack has just a few bytes of space on the Arduino
if you declare large strings inside functions, they can easily exceed the capacity of the stack
and mess up your programs.
We circumvent this by declaring a few global buffers as kitchen counters where we can
slice and dice our strings. These strings are mostly used to control the display or handle
the input and output from the USB port. We must keep a count of the bytes used while reading
the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable.
*/
char c[17], b[10], printBuff[2][17];
/**
We need to carefully pick assignment of pin for various purposes.
There are two sets of completely programmable pins on the Raduino.
First, on the top of the board, in line with the LCD connector is an 8-pin connector
that is largely meant for analog inputs and front-panel control. It has a regulated 5v output,
ground and six pins. Each of these six pins can be individually programmed
either as an analog input, a digital input or a digital output.
The pins are assigned as follows:
A0, A1, A2, A3, GND, +5V, A6, A7
pin 8 7 6 5 4 3 2 1 (connector P1)
BLACK BROWN RED ORANGE YELLW GREEN BLUE VIOLET
(while holding the board up so that back of the board faces you)
Though, this can be assigned anyway, for this application of the Arduino, we will make the following
assignment:
A0 (digital input) for sensing the PTT. Connect to the output of U3 (LM7805) of the BITX40.
This way the A0 input will see 0V (LOW) when PTT is not pressed, +5V (HIGH) when PTT is pressed.
A1 (digital input) is to connect to a straight key, or to the 'Dit' contact of a paddle keyer. Open (HIGH) during key up, switch to ground (LOW) during key down.
A2 (digital input) can be used for calibration by grounding this line (not required when you have the Function Button at A3)
A3 (digital input) is connected to a push button that can momentarily ground this line. This Function Button will be used to switch between different modes, etc.
A4 (already in use for talking to the SI5351)
A5 (already in use for talking to the SI5351)
A6 (analog input) is not currently used
A7 (analog input) is connected to a center pin of good quality 100K or 10K linear potentiometer with the two other ends connected to
ground and +5v lines available on the connector. This implements the tuning mechanism.
*/
#define PTT_SENSE (A0)
#define KEY (A1)
#define CAL_BUTTON (A2)
#define FBUTTON (A3)
#define ANALOG_TUNING (A7)
bool PTTsense_installed; //whether or not the PTT sense line is installed (detected automatically during startup)
/**
The second set of 16 pins on the bottom connector P3 have the three clock outputs and the digital lines to control the rig.
This assignment is as follows :
Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (connector P3)
+12V +12V CLK2 GND GND CLK1 GND GND CLK0 GND D2 D3 D4 D5 D6 D7
These too are flexible with what you may do with them, for the Raduino, we use them to :
output D2 - PULSE : is used for the capacitive touch keyer
input D3 - DAH : is connected to the 'Dah' contact of an paddle keyer (switch to ground).
input D4 - SPOT : is connected to a push button that can momentarily ground this line. When the SPOT button is pressed a sidetone will be generated for zero beat tuning.
output D5 - CW_TONE : Side tone output
output D6 - CW_CARRIER line : turns on the carrier for CW
output D7 - TX_RX line : Switches between Transmit and Receive in CW mode
*/
#define PULSE (2)
#define DAH (3)
#define SPOT (4)
#define CW_TONE (5)
#define CW_CARRIER (6)
#define TX_RX (7)
bool TXRX_installed = true; // whether or not the TX_RX mod (PTT bypass transistor) is installed (set automatically at startup)
/**
The Raduino supports two VFOs : A and B and receiver incremental tuning (RIT).
we define a variables to hold the frequency of the two VFOs, RIT, SPLIT
the rit offset as well as status of the RIT
To use this facility, wire up push button on A3 line of the control connector (Function Button)
*/
bool ritOn = false; // whether or not the RIT is on
int RIToffset = 0; // RIT offset (Hz)
int RIT = 0; // actual RIT offset that is applied during RX when RIT is on
int RIT_old;
byte region = REGION; // ham band allocation depending on ITU Region
bool outofband; // whether or not the frequency is outside the 40m ham band allocation
bool firstrun = true;
char clicks; // counter for function button clicks
bool locked = false; // whether or not the dial is locked
byte param;
/**
Raduino has 4 modes of operation:
*/
#define LSB (0)
#define USB (1)
#define CWL (2)
#define CWU (3)
/**
Raduino needs to keep track of current state of the transceiver. These are a few variables that do it
*/
unsigned long vfoA; // the frequency (Hz) of VFO A
unsigned long vfoB; // the frequency (Hz) of VFO B
byte mode = LSB; // mode of the currently active VFO
bool inTx = false; // whether or not we are in transmit mode
bool keyDown = false; // whether we have a key up or key down
unsigned long TimeOut = 0; // time in ms since last key down
volatile bool TXstart = false; // this flag will be set by the ISR as soon as the PTT is keyed
//some variables used for the autokeyer function:
bool keyeron = false; // will be true while auto-keying
unsigned long released = 0;
bool ditlatch = false;
bool dahlatch = false;
byte gap = 1; // space between elements (1) or characters (3)
unsigned long dit;
unsigned long dah;
unsigned long space = 0;
// some variable used by the capacitive touch keyer:
bool CapTouch_installed = true; // whether or not the capacitive touch modification is installed (detected automatically during startup)
byte base_sens_KEY; // base delay (in us) when DIT pad is NOT touched (measured automatically by touch sensor calibration routine)
byte base_sens_DAH; // base delay (in us) when DAH pad is NOT touched (measured automatically by touch sensor calibration routine)
bool capaKEY = false; // true when DIT pad is touched
bool capaDAH = false; // true when DAH pad is touched
/**
Tks Pavel CO7WT:
Use pointers for reversing the behaviour of the dit/dah paddles, this way we can simply
refer to them as two paddles regardless it's normal or reversed
Macros don't like pointers, so we create two byte vars to hold the values
**/
byte _key = KEY;
byte _dah = DAH;
byte *paddleDIT = &_key; // Paddle DIT bind to KEY
byte *paddleDAH = &_dah; // Paddle DAH bind to DAH
/** Tuning Mechanism of the Raduino
We use a linear pot that has two ends connected to +5 and the ground. the middle wiper
is connected to ANALOG_TUNNING pin. Depending upon the position of the wiper, the
reading can be anywhere from 0 to 1023.
If we want to use a multi-turn potentiometer covering 500 kHz and a step
size of 50 Hz we need 10,000 steps which is about 10x more than the steps that the ADC
provides. Arduino's ADC has 10 bits which results in 1024 steps only.
We can artificially expand the number of steps by a factor 10 by oversampling 100 times.
As a result we get 10240 steps.
The tuning control works in steps of 50Hz each for every increment between 0 and 10000.
Hence the turning the pot fully from one end to the other will cover 50 x 10000 = 500 KHz.
But if we use the standard 1-turn pot, then a tuning range of 500 kHz would be too much.
(tuning would become very touchy). In the SETTINGS menu we can limit the pot span
depending on the potentiometer used and the band section of interest. Tuning beyond the
limits is still possible by the fast 'scan-up' and 'scan-down' mode at the end of the pot.
At the two ends, that is, the tuning starts stepping up or down in 10 KHz steps.
To stop the scanning the pot is moved back from the edge.
*/
#define bfo_freq (11998700UL)
unsigned long baseTune = 7100000UL; // frequency (Hz) when tuning pot is at minimum position
int old_knob = 0;
int RXshift = 0; // the actual frequency shift that is applied during RX depending on the operation mode
unsigned long frequency; // the 'dial' frequency as shown on the display
int fine = 0; // fine tune offset (Hz)
/**
The raduino has multiple RUN-modes:
*/
#define RUN_NORMAL (0) // normal operation
#define RUN_CALIBRATE (1) // calibrate VFO frequency in LSB mode
#define RUN_DRIVELEVEL (2) // set VFO drive level
#define RUN_TUNERANGE (3) // set the range of the tuning pot
#define RUN_CWOFFSET (4) // set the CW offset (=sidetone pitch)
#define RUN_SCAN (5) // frequency scanning mode
#define RUN_SCAN_PARAMS (6) // set scan parameters
#define RUN_MONITOR (7) // frequency scanning mode
#define RUN_FINETUNING (8) // fine tuning mode
byte RUNmode = RUN_NORMAL;
// ************* SI5315 routines - tks Jerry Gaffke, KE7ER ***********************
// An minimalist standalone set of Si5351 routines.
// VCOA is fixed at 875mhz, VCOB not used.
// The output msynth dividers are used to generate 3 independent clocks
// with 1hz resolution to any frequency between 4khz and 109mhz.
// Usage:
// Call si5351bx_init() once at startup with no args;
// Call si5351bx_setfreq(clknum, freq) each time one of the
// three output CLK pins is to be updated to a new frequency.
// A freq of 0 serves to shut down that output clock.
// The global variable si5351bx_vcoa starts out equal to the nominal VCOA
// frequency of 25mhz*35 = 875000000 Hz. To correct for 25mhz crystal errors,
// the user can adjust this value. The vco frequency will not change but
// the number used for the (a+b/c) output msynth calculations is affected.
// Example: We call for a 5mhz signal, but it measures to be 5.001mhz.
// So the actual vcoa frequency is 875mhz*5.001/5.000 = 875175000 Hz,
// To correct for this error: si5351bx_vcoa=875175000;
// Most users will never need to generate clocks below 500khz.
// But it is possible to do so by loading a value between 0 and 7 into
// the global variable si5351bx_rdiv, be sure to return it to a value of 0
// before setting some other CLK output pin. The affected clock will be
// divided down by a power of two defined by 2**si5351_rdiv
// A value of zero gives a divide factor of 1, a value of 7 divides by 128.
// This lightweight method is a reasonable compromise for a seldom used feature.
void si5351bx_init() { // Call once at power-up, start PLLA
uint8_t reg; uint32_t msxp1;
Wire.begin();
i2cWrite(149, 0); // SpreadSpectrum off
i2cWrite(3, si5351bx_clken); // Disable all CLK output drivers
i2cWrite(183, ((SI5351BX_XTALPF << 6) | 0x12)); // Set 25mhz crystal load capacitance (tks Daniel KB3MUN)
msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional
uint8_t vals[8] = {0, 1, BB2(msxp1), BB1(msxp1), BB0(msxp1), 0, 0, 0};
i2cWriten(26, vals, 8); // Write to 8 PLLA msynth regs
i2cWrite(177, 0x20); // Reset PLLA (0x80 resets PLLB)
// for (reg=16; reg<=23; reg++) i2cWrite(reg, 0x80); // Powerdown CLK's
// i2cWrite(187, 0); // No fannout of clkin, xtal, ms0, ms4
}
void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz
uint32_t msa, msb, msc, msxp1, msxp2, msxp3p2top;
if ((fout < 500000) || (fout > 109000000)) // If clock freq out of range
si5351bx_clken |= 1 << clknum; // shut down the clock
else {
msa = si5351bx_vcoa / fout; // Integer part of vco/fout
msb = si5351bx_vcoa % fout; // Fractional part of vco/fout
msc = fout; // Divide by 2 till fits in reg
while (msc & 0xfff00000) {
msb = msb >> 1;
msc = msc >> 1;
}
msxp1 = (128 * msa + 128 * msb / msc - 512) | (((uint32_t)si5351bx_rdiv) << 20);
msxp2 = 128 * msb - 128 * msb / msc * msc; // msxp3 == msc;
msxp3p2top = (((msc & 0x0F0000) << 4) | msxp2); // 2 top nibbles
uint8_t vals[8] = { BB1(msc), BB0(msc), BB2(msxp1), BB1(msxp1),
BB0(msxp1), BB2(msxp3p2top), BB1(msxp2), BB0(msxp2)
};
i2cWriten(42 + (clknum * 8), vals, 8); // Write to 8 msynth regs
i2cWrite(16 + clknum, 0x0C | si5351bx_drive[clknum]); // use local msynth
si5351bx_clken &= ~(1 << clknum); // Clear bit to enable clock
}
i2cWrite(3, si5351bx_clken); // Enable/disable clock
}
void i2cWrite(uint8_t reg, uint8_t val) { // write reg via i2c
Wire.beginTransmission(SI5351BX_ADDR);
Wire.write(reg);
Wire.write(val);
Wire.endTransmission();
}
void i2cWriten(uint8_t reg, uint8_t *vals, uint8_t vcnt) { // write array
Wire.beginTransmission(SI5351BX_ADDR);
Wire.write(reg);
while (vcnt--) Wire.write(*vals++);
Wire.endTransmission();
}
// *********** End of Jerry's si5315bx routines *********************************************************
/**
Display Routine
This display routine prints a line of characters to the upper or lower line of the 16x2 display
linenmbr = 0 is the upper line
linenmbr = 1 is the lower line
*/
void printLine(char linenmbr, const char * const c) {
if (strcmp(c, printBuff[linenmbr])) { // only refresh the display when there was a change
lcd.setCursor(0, linenmbr); // place the cursor at the beginning of the selected line
lcd.print(c);
strcpy(printBuff[linenmbr], c);
for (byte i = strlen(c); i < 16; i++) { // add white spaces until the end of the 16 characters line is reached
lcd.print(' ');
}
}
}
/**
Building upon the previous routine,
update Display paints the first line as per current state of the radio
*/
void updateDisplay() {
// tks Jack Purdum W8TEE
// replaced fsprint commmands by str commands for code size reduction
memset(c, 0, sizeof(c));
memset(b, 0, sizeof(b));
if (locked || RUNmode == RUN_FINETUNING) {
ultoa((frequency + fine), b, DEC); // construct the frequency string
strcpy(c, "");
}
else {
ultoa((frequency + 50), b, DEC); // construct the frequency string
if (!u.vfoActive)
strcpy(c, "A "); // display which VFO is active (A or B)
else
strcpy(c, "B ");
}
byte p = strlen(b); // the length of the frequency string (<10 Mhz: 7, >10 MHz: 8)
strncat(c, &b[0], p - 6); // display the megahertzes
strcat(c, ".");
strncat(c, &b[p - 6], 3); // display the kilohertzes
strcat(c, ".");
if (locked || RUNmode == RUN_FINETUNING)
strncat(c, &b[p - 3], 3); // display the frequency at 1 Hz precision
else
strncat(c, &b[p - 3], 1); // display the frequency at 100 Hz precision
switch (mode) { // show the operating mode
case LSB:
strcat(c, " LSB");
break;
case USB:
strcat(c, " USB");
break;
case CWL:
strcat(c, " CWL");
break;
case CWU:
strcat(c, " CWU");
break;
}
if (inTx && !outofband) // show the state (TX, SPLIT, or nothing)
strcat(c, " TX");
else if (u.splitOn)
strcat(c, " SP");
c[16] = '\0'; // cut off any excess characters (string length is max 16 postions)
printLine(0, c); // finally print the constructed string to the first line of the display
}
// routine to generate a bleep sound (FB menu)
void bleep(int pitch, int duration, byte repeat) {
for (byte i = 0; i < repeat; i++) {
tone(CW_TONE, pitch);
delay(duration);
noTone(CW_TONE);
delay(duration);
}
}
bool calbutton = false;
/**
To use calibration sets the accurate readout of the tuned frequency
To calibrate, follow these steps:
1. Tune in a LSB signal that is at a known frequency.
2. Now, set the display to show the correct frequency,
the signal will no longer be tuned up properly
3. Use the "LSB calibrate" option in the "Settings" menu (or Press the CAL_BUTTON line to the ground (pin A2 - red wire))
4. tune in the signal until it sounds proper.
5. Press the FButton (or Release CAL_BUTTON)
In step 4, when we say 'sounds proper' then, for a CW signal/carrier it means zero-beat
and for LSB it is the most natural sounding setting.
Calibration is an offset value that is added to the VFO frequency.
We store it in the EEPROM and read it in setup() when the Radiuno is powered up.
Then select the "USB calibrate" option in the "Settings" menu and repeat the same steps for USB mode.
*/
int shift, current_setting;
void calibrate() {
int knob = analogRead(ANALOG_TUNING); // get the current tuning knob position
if (RUNmode != RUN_CALIBRATE) {
if (mode == USB)
current_setting = u.USB_OFFSET;
else
current_setting = u.cal;
shift = current_setting - knob;
}
// The tuning knob gives readings from 0 to 1000
if (mode == USB) {
u.USB_OFFSET = constrain(knob + shift, -10000, 10000);
if (knob < 5 && u.USB_OFFSET > -10000)
shift = shift - 10;
else if (knob > 1020 && u.USB_OFFSET < 10000)
shift = shift + 10;
}
else {
u.cal = constrain(knob + shift, -10000, 10000);
if (knob < 5 && u.cal > -10000)
shift = shift - 10;
else if (knob > 1020 && u.cal < 10000)
shift = shift + 10;
}
// if Fbutton is pressed again (or when the CAL button is released), we save the setting
if (!digitalRead(FBUTTON) || (calbutton && digitalRead(CAL_BUTTON))) {
RUNmode = RUN_NORMAL;
bleep(600, 50, 2);
printLine(1, "--- SETTINGS ---");
shiftBase(); //align the current knob position with the current frequency
}
else {
// while offset adjustment is in progress, keep tweaking the
// frequency as read out by the knob, display the change in the second line
RUNmode = RUN_CALIBRATE;
if (mode == USB) {
setFrequency();
itoa(u.USB_OFFSET, b, DEC);
strcpy(c, "offset ");
strcat(c, b);
strcat(c, " Hz");
}
else {
si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + u.cal * 100L;
si5351bx_setfreq(2, bfo_freq - frequency);
ltoa(u.cal * 100L / 875, b, DEC);
strcpy(c, "corr ");
strcat(c, b);
strcat(c, " ppm");
}
printLine(1, c);
}
}
/**
The setFrequency is a little tricky routine, it works differently for USB and LSB
The BITX BFO is permanently set to lower sideband, (that is, the crystal frequency
is on the higher side slope of the crystal filter).
LSB: The VFO frequency is subtracted from the BFO. Suppose the BFO is set to exactly 12 MHz
and the VFO is at 5 MHz. The output will be at 12.000 - 5.000 = 7.000 MHz
USB: The BFO is subtracted from the VFO. Makes the LSB signal of the BITX come out as USB!!
Here is how it will work:
Consider that you want to transmit on 14.000 MHz and you have the BFO at 12.000 MHz. We set
the VFO to 26.000 MHz. Hence, 26.000 - 12.000 = 14.000 MHz. Now, consider you are whistling a tone
of 1 KHz. As the BITX BFO is set to produce LSB, the output from the crystal filter will be 11.999 MHz.
With the VFO still at 26.000, the 14 Mhz output will now be 26.000 - 11.999 = 14.001, hence, as the
frequencies of your voice go down at the IF, the RF frequencies will go up!
Thus, setting the VFO on either side of the BFO will flip between the USB and LSB signals.
In addition we add some offset to USB mode so that the dial frequency is correct in both LSB and USB mode.
The amount of offset can be set in the SETTING menu as part of the calibration procedure.
Furthermore we add/substract the sidetone frequency only when we receive CW, to assure zero beat
between the transmitting and receiving station (RXshift)
The desired sidetone frequency can be set in the SETTINGS menu.
*/
void setFrequency() {
outofband = false;
switch (region) {
case 1:
case 3:
if (frequency < 7000000UL || frequency > 7200000UL)
outofband = true;
break;
case 2:
if (frequency < 7000000UL || frequency > 7300000UL)
outofband = true;
break;
}
if (PTTsense_installed || !outofband) {
if (mode & 1) // if we are in UPPER side band mode
si5351bx_setfreq(2, (bfo_freq + frequency - RXshift + RIT + fine - u.USB_OFFSET));
else // if we are in LOWER side band mode
si5351bx_setfreq(2, (bfo_freq - frequency - RXshift - RIT - fine));
}
updateDisplay();
}
/**
The checkTX toggles the T/R line. If you would like to make use of RIT, etc,
you must connect pin A0 (black wire) via a 10K resistor to the output of U3
This is a voltage regulator LM7805 which goes on during TX. We use the +5V output
as a PTT sense line (to tell the Raduino that we are in TX).
*/
void checkTX() {
// We don't check for ptt when transmitting cw in semi-QSK mode
// as long as the TimeOut is non-zero, we will continue to hold the
// radio in transmit mode
if (TimeOut > 0 && u.semiQSK)
return;
if (digitalRead(PTT_SENSE) && !inTx) {
// go in transmit mode
si5351bx_setfreq(2, 0); // temporarily disable CLK2 to prevent spurious emission (tks Dave M0WID)
inTx = true;
RXshift = RIT = RIT_old = 0; // no frequency offset during TX
if (u.semiQSK) {
mode = mode & B11111101; // leave CW mode, return to SSB mode
if (!u.vfoActive) // if VFO A is active
u.mode_A = mode;
else // if VFO B is active
u.mode_B = mode;
}
delay(TX_DELAY); // wait till RX-TX burst is over
shiftBase(); // this will enable CLK2 again
updateDisplay();
if (u.splitOn) { // when SPLIT is on, swap the VFOs
swapVFOs();
}
}
if (!digitalRead(PTT_SENSE) && inTx) {
delay(50);
if (!digitalRead(PTT_SENSE)) {
// send roger beep before switching back to receive
// "Quindar Key" as used by NASA in the Apollo missions
// 2450 Hz tone with 250 ms duration at the end of each transmission
if (mode < 2 && u.RogerBeep) { // when we are in SSB mode and Roger Beep is enabled
digitalWrite(TX_RX, 1); // activate the PTT relay - keep the radio in transmit for a while
digitalWrite(CW_CARRIER, 1); // generate carrier
tone(CW_TONE, 2450); // generate sidetone
// temporarily shift TX frequency by 2450 Hz
if (mode & 1) // if we are in UPPER side band mode
si5351bx_setfreq(2, (bfo_freq + frequency + fine - u.USB_OFFSET + 2450));
else // if we are in LOWER side band mode
si5351bx_setfreq(2, (bfo_freq - frequency - fine + 2450));
delay(250);
digitalWrite(CW_CARRIER, 0); // stop generating carrier
noTone(CW_TONE); // stop generating sidetone
digitalWrite(TX_RX, 0); // release the PTT switch - move the radio back to receive
}
//go in receive mode
inTx = false;
setFrequency();
updateDisplay();
if (u.splitOn) { // when SPLIT was on, swap the VFOs back to original state
swapVFOs();
}
if (mode & 2) { // if we are in CW mode
RXshift = u.CW_OFFSET; // apply the frequency offset in RX
shiftBase();
}
}
}
}
/* CW is generated by unbalancing the mixer when the key is down.
During key down, the output CW_CARRIER is HIGH (+5V).
This output is connected via a 10K resistor to the mixer input. The mixer will
become unbalanced when CW_CARRIER is HIGH, so a carrier will be transmitted.
During key up, the output CW_CARRIER is LOW (0V). The mixer will remain balanced
and the carrrier will be suppressed.
The radio will go into CW mode automatically as soon as the key goes down, and
return to normal LSB/USB mode when the key has been up for some time.
There are three variables that track the CW mode
inTX : true when the radio is in transmit mode
keyDown : true when the CW is keyed down, you maybe in transmit mode (inTX true)
and yet between dots and dashes and hence keyDown could be true or false
TimeOut: Figures out how long to wait between dots and dashes before putting
the radio back in receive mode
When we transmit CW, we need to apply some offset (800Hz) to the TX frequency, in
order to keep zero-beat between the transmitting and receiving station. The shift
depends on whether upper or lower sideband CW is used:
In CW-U (USB) mode we must shift the TX frequency 800Hz up
In CW-L (LSB) mode we must shift the TX frequency 800Hz down
The default offset (CW_OFFSET) is 800Hz, the default timeout (QSK_DELAY) is 350ms.
The user can change these in the SETTINGS menu.
*/
void checkCW() {
if (!keyDown && ((u.cap_sens == 0 && !digitalRead(KEY)) || (u.cap_sens != 0 && capaKEY) || (u.key_type > 0 && ((u.cap_sens == 0 && !digitalRead(DAH)) || (u.cap_sens != 0 && capaDAH))))) {
keyDown = true;
if (u.semiQSK) {
mode = mode | 2; // if semiQSK is on, switch to CW
}
if (u.key_type > 0 && mode & 2) { // if mode is CW and if keyer is enabled
keyeron = true;
released = 0;
// put the paddles pointers in the correct position
if (u.key_type & 1) {
// paddle not reversed
paddleDAH = &_dah;
paddleDIT = &_key;
}
else {
// paddle reversed
paddleDAH = &_key;
paddleDIT = &_dah;
}
if ((u.cap_sens == 0 && !digitalRead(*paddleDIT)) || (u.cap_sens != 0 && capaKEY))
dit = millis();
if ((u.cap_sens == 0 && !digitalRead(*paddleDAH)) || (u.cap_sens != 0 && capaDAH))
dah = millis();
}
if (!inTx && u.semiQSK) { // switch to transmit mode if we are not already in it
si5351bx_setfreq(2, 0); // temporarily disable CLK2 to prevent spurious emission (tks Dave M0WID)
digitalWrite(TX_RX, 1); // key the PTT - go in transmit mode
inTx = true;
if (!u.vfoActive) // if VFO A is active
u.mode_A = mode;
else // if VFO B is active
u.mode_B = mode;
RXshift = RIT = RIT_old = 0; // no frequency offset during TX
delay(TX_DELAY); // wait till RX-TX burst is over
if (u.splitOn) // when SPLIT is on, swap the VFOs
swapVFOs(); // this will also enable CLK2 again
else
shiftBase(); // this will also enable CLK2 again
dit = dit + TX_DELAY + 7; // delay the initial dit
dah = dah + TX_DELAY + 7; // delay the initial dah
}
}
//keep resetting the timer as long as the key is down
if (keyDown)
TimeOut = millis() + u.QSK_DELAY;
//if the key goes up again after it's been down
if (keyDown && ((u.cap_sens == 0 && digitalRead(KEY)) || (u.cap_sens != 0 && !capaKEY))) {
keyDown = false;
TimeOut = millis() + u.QSK_DELAY;
}
// if we are in semi-QSK mode and have a keyup for a "longish" time (QSK_DELAY value in ms)
// then go back to RX
if (!keyeron && TimeOut > 0 && inTx && TimeOut < millis() && u.semiQSK) {
inTx = false;
TimeOut = 0; // reset the CW timeout counter
RXshift = u.CW_OFFSET; // apply the frequency offset in RX
shiftBase();
if (u.splitOn) // then swap the VFOs back when SPLIT was on
swapVFOs();
digitalWrite(TX_RX, 0); // release the PTT switch - move the radio back to receive
delay(10); //give the relays a few ms to settle the T/R relays
}
if (u.key_type == 0 && keyDown && mode & B00000010) {
digitalWrite(CW_CARRIER, 1); // generate carrier
tone(CW_TONE, u.CW_OFFSET); // generate sidetone
}
else if (u.key_type == 0 && digitalRead(SPOT) == HIGH) {
digitalWrite(CW_CARRIER, 0); // stop generating the carrier
noTone(CW_TONE); // stop generating the sidetone
}
}
void keyer() {
static bool FBpressed = false;
static bool SPOTpressed = false;
if (!digitalRead(FBUTTON)) // Press and release F-Button to increase keyer speed
FBpressed = true;
if (FBpressed && digitalRead(FBUTTON) && u.wpm < 50) {
FBpressed = false;
u.wpm++;
}
if (!digitalRead(SPOT)) // Press and release SPOT button to reduce keyer speed
SPOTpressed = true;
if (SPOTpressed && digitalRead(SPOT) && u.wpm > 1) {
SPOTpressed = false;
u.wpm--;
}
if (u.key_type > 2) { // bug mode
if ((u.cap_sens == 0 && !digitalRead(*paddleDAH)) || (u.cap_sens != 0 && capaDAH))
dah = millis();
else
dah = 0;
}
unsigned long element = 1200UL / u.wpm;
if (space == 0 && (millis() - dit < element || millis() - dah < 3 * element)) {
digitalWrite(CW_CARRIER, 1); // generate carrier
tone(CW_TONE, u.CW_OFFSET); // generate sidetone
keyDown = true;
}
else {
digitalWrite(CW_CARRIER, 0); // stop generating the carrier
noTone(CW_TONE); // stop generating the sidetone
if (space == 0) {
space = millis();
}
if (millis() - space > gap * element) {
if (dit < dah) {
if (ditlatch || (u.cap_sens == 0 && !digitalRead(*paddleDIT)) || (u.cap_sens != 0 && capaKEY)) {
dit = millis();
keyeron = true;
ditlatch = false;
keyDown = true;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
else {
if (dahlatch || (u.cap_sens == 0 && !digitalRead(*paddleDAH)) || (u.cap_sens != 0 && capaDAH)) {
dah = millis();
keyeron = true;
dahlatch = false;
keyDown = true;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
else {
if (u.autospace)
gap = 3; // autospace - character gap is 3 elements
keyeron = true;
keyDown = true;
if (millis() - space > gap * element) {
keyeron = false;
keyDown = false;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
}
}
}
else {
if (dahlatch || (u.cap_sens == 0 && !digitalRead(*paddleDAH)) || (u.cap_sens != 0 && capaDAH)) {
dah = millis();
keyeron = true;
dahlatch = false;
keyDown = true;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
else {
if (ditlatch || (u.cap_sens == 0 && !digitalRead(*paddleDIT)) || (u.cap_sens != 0 && capaKEY)) {
dit = millis();
keyeron = true;
ditlatch = false;
keyDown = true;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
else {
if (u.autospace)
gap = 3; // autospace - character gap is 3 elements
keyeron = true;
keyDown = true;
if (millis() - space > gap * element) {
keyeron = false;
keyDown = false;
gap = 1; //standard gap between elements
space = 0;
released = 0;
}
}
}
}
}
}
if (released == 0) {
if (space == 0 && millis() - dit < element && ((u.cap_sens == 0 && digitalRead(*paddleDIT)) || (u.cap_sens != 0 && !capaKEY)))
released = millis();
if (space == 0 && millis() - dah < 3 * element && ((u.cap_sens == 0 && digitalRead(*paddleDAH)) || (u.cap_sens != 0 && !capaDAH)))
released = millis();
if (space > 0 && ((u.cap_sens == 0 && digitalRead(*paddleDIT)) || (u.cap_sens != 0 && !capaKEY)) && ((u.cap_sens == 0 && digitalRead(*paddleDAH)) || (u.cap_sens != 0 && !capaDAH)))
released = millis();
}
if (u.cap_sens == 0) { // if standard paddle is used
if (released > 0 && millis() - released > DIT_DELAY && !digitalRead(*paddleDIT)) { // DIT_DELAY optimized timing characteristics - tks Hidehiko, JA9MAT
ditlatch = true;
dahlatch = false;
}
else if (space > 0 && released > 0 && millis() - released > DAH_DELAY && !digitalRead(*paddleDAH)) { // DAH_DELAY optimized timing characteristics - tks Hidehiko, JA9MAT