forked from g0orx/SDTVer050.0
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathProcessV12.cpp
1181 lines (1101 loc) · 48.7 KB
/
ProcessV12.cpp
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
#ifndef BEENHERE
#include "SDT.h"
#endif
#ifdef V12HWR // changes are so extensive for calibration in V12, this file supports only V12
// Updates to DoReceiveCalibration() and DoXmitCalibrate() functions by . July 20, 2023
// Updated PlotCalSpectrum() function to clean up graphics. August 3, 2023
// Major clean-up of calibration. August 16, 2023
#define RX_STATE 0
#define TX_STATE 1
#define TX_STATE_RX_PHASE 2
#define TX_STATE_TX_PHASE 3
#define GAIN_COARSE_MAX 1.3
#define GAIN_COARSE_MIN 0.7
#define PHASE_COARSE_MAX 0.2
#define PHASE_COARSE_MIN -0.2
#define GAIN_COARSE_STEP2_N 4
#define PHASE_COARSE_STEP2_N 4
#define GAIN_FINE_N 5
#define PHASE_FINE_N 5
static int stateMachine;
static bool FFTupdated;
static int val;
static int corrChange;
static float correctionIncrement; //AFP 2-7-23
static int userScale, userZoomIndex, userXmtMode;
static int transmitPowerLevelTemp;
static char strBuf[100];
static long userCurrentFreq;
static long userCenterFreq;
static long userTxRxFreq;
static long userNCOFreq;
static float adjdB;
#ifdef QUADFFT
/*****
Purpose: DrawIQBalancePlotContainer
Parameter list:
float IQfreqStart, IQfreqStop, long numIQPoints
Return value;
void
*****/
static int IQfreqStart_kHz,IQfreqStop_kHz,numIQPoints;
static float frmax;
static uint32_t index_of_max;
//static bool corrections_calculated = false;
float r,i;
static int delay_R = 0;
static float scale_R = 1.0;
void DrawIQBalancePlotContainer() {
tft.writeTo(L1);
tft.fillRect(0, 0, 799, 479, RA8875_BLACK);
tft.writeTo(L2);
tft.fillRect(0, 0, 799, 479, RA8875_BLACK);
#define xstart 50
#define ystart 50
#define xend 750
#define yend 400
#define Nxticks 10
#define Nyticks 10
#define xpixels_per_tick ((float)(xend-xstart))/Nxticks
#define ypixels_per_tick ((float)(yend-ystart))/Nyticks
#define xpixels_per_kHz ((xend-xstart)/((float)(IQfreqStop_kHz-IQfreqStart_kHz))) // -100 kHz to +100 kHz
#define ypixels_per_deg ((yend-ystart)/(100.0-(-100.0))) // -100 deg to +100 deg
#define ypixels_per_unit ((yend-ystart)/(1.5-(0.5))) // 1.5 to 0.5
#define kHz_per_tick (((float) xpixels_per_tick) / (xpixels_per_kHz))
#define deg_per_tick (((float) ypixels_per_tick) / (ypixels_per_deg))
#define unit_per_tick (((float) ypixels_per_tick) / (ypixels_per_unit))
//============== Draw lines on Layer 2
// Top-most horizontal, includes tick
tft.drawFastHLine(xstart-10, ystart, xend-xstart+10, RA8875_GREEN);
// Bottom-most horizontal, includes tick
tft.drawFastHLine(xstart-10, yend, xend-xstart+10, RA8875_GREEN);
// Major horizontal gridlines
tft.setFontScale((enum RA8875tsize)0);
for (int i=1; i<10; i++){
// Left ticks
tft.drawFastHLine(xstart-10, ystart+i*ypixels_per_tick, 10, RA8875_GREEN);
tft.setCursor(10, ystart+ypixels_per_tick * i - CHAR_HEIGHT/2);
tft.setTextColor(RA8875_WHITE);
tft.print(100 - i*deg_per_tick,0);
// Right ticks
tft.drawFastHLine(xend, ystart+i*ypixels_per_tick, 10, RA8875_GREEN);
tft.setCursor(xend + 15, ystart+ypixels_per_tick * i - CHAR_HEIGHT/2);
tft.setTextColor(RA8875_YELLOW);
tft.print(1.5 - i*unit_per_tick,1);
// grid line
tft.drawFastHLine(xstart, ystart+i*ypixels_per_tick, xend-xstart, tft.Color565(50, 50, 50));
}
// Left-most vertical, includes tick
tft.drawFastVLine(xstart, ystart, yend-ystart+10, RA8875_GREEN);
// Right-most vertical, includes tick
tft.drawFastVLine(xend, ystart, yend-ystart+10, RA8875_GREEN);
tft.setTextColor(RA8875_WHITE);
tft.setFontScale((enum RA8875tsize)0);
for (int k = 1; k < 10; k++) { // Draw Freq axis tick marks and numbers
tft.drawFastVLine(xstart + xpixels_per_tick * k, yend, 10, RA8875_GREEN);
tft.setCursor(xstart-13 + xpixels_per_tick * k, yend+15);
tft.print(IQfreqStart_kHz + k * kHz_per_tick, 1);
tft.drawFastVLine(xstart + xpixels_per_tick * k, ystart, yend-ystart, tft.Color565(50, 50, 50));
}
tft.setFontScale((enum RA8875tsize)0);
tft.setCursor(350, 440);
tft.print("kHz");
tft.setTextColor(RA8875_YELLOW);
tft.setCursor(16, 20);
tft.print("Deg");
tft.setTextColor(RA8875_YELLOW);
tft.setCursor(xend+5, 20);
tft.print("|I/Q|");
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_YELLOW);
tft.setCursor(270, 15);
tft.print("T41 IQ Balance");
tft.writeTo(L1);
}
void CalculateIQCorrectionValues(){
for (int i=0; i<numIQPoints; i++){
// The image frequency is in the bin mirrored around the center:
// Bin 0's image is in bin 2047
// Bin 1's image is in bin 2046
// image = 2047 - i
gCorrIQ[i] = 1.0 / gErrorIQ[numIQPoints-1-i];
float pcorrect = PI/2;
if (i >= numIQPoints/2){
pcorrect = -PI/2;
}
float pcorrection = -1*(pcorrect-pErrorIQ[numIQPoints-1-i]);
pCorrIQr[i] = cos(pcorrection);
pCorrIQi[i] = sin(pcorrection);
}
}
void calculate750HzCalibration(){
// Calculate the values at 750 Hz for the XMit calibration routine to use
if (bands[currentBand].mode == DEMOD_LSB) {
Clk2SetFreq = (centerFreq + IFFreq - 750)* SI5351_FREQ_MULT;
} else {
Clk2SetFreq = (centerFreq + IFFreq + 750)* SI5351_FREQ_MULT;
}
si5351.set_freq(Clk2SetFreq, SI5351_CLK2);
MyDelay(10);
Q_in_L.clear();
Q_in_R.clear();
MeasureIandQFFT();
arm_max_f32(Imag,1024,&frmax,&index_of_max);
scale_R = Imag[index_of_max]/Qmag[index_of_max];
float perr;
if (bands[currentBand].mode == DEMOD_LSB) {
// Tone is at -750 Hz, so we expect phase difference to be +90 deg
perr = PI/2.0 - IQphase[index_of_max];
} else {
// Tone is at +750 Hz, so we expect phase difference to be -90 deg
perr = -1*PI/2.0 - IQphase[index_of_max];
}
// Calculate the delay in number of samples that is closest to the needed
// phase shift at 750 Hz
delay_R = (int)(round(192000.0 * perr / (2*PI*750.0) ));
sprintf(strBuf,"delay=%d,scale=%4.3f\n",delay_R,scale_R);
Serial.println(strBuf);
}
#endif
/*****
Purpose: Set up prior to IQ calibrations. Revised function. AFP 07-13-24
These things need to be saved here and restored in the prologue function:
Vertical scale in dB (set to 10 dB during calibration)
Zoom, set to 1X in receive and 4X in transmit calibrations.
Transmitter power, set to 5W during both calibrations.
Parameter List:
int setZoom (This parameter should be 0 for receive (1X) and 2 (4X) for transmit)
Return value:
void
*****/
void CalibratePreamble(int setZoom) {
calOnFlag = 1;
corrChange = 0;
correctionIncrement = 0.01; //AFP 2-7-23
IQCalType = 0;
radioState = CW_TRANSMIT_STRAIGHT_STATE; //
userXmtMode = xmtMode; // Store the user's mode setting. July 22, 2023
userZoomIndex = spectrum_zoom; // Save the zoom index so it can be reset at the conclusion. August 12, 2023
spectrum_zoom = setZoom; // spectrum_zoom is used in Process.cpp
zoomIndex = setZoom - 1;
userCurrentFreq = currentFreq;
userTxRxFreq = TxRxFreq;
userNCOFreq = NCOFreq;
userCenterFreq = centerFreq;
transmitPowerLevelTemp = transmitPowerLevel;
TxRxFreq = centerFreq;
currentFreq = TxRxFreq;
NCOFreq = 0L;
tft.clearScreen(RA8875_BLACK); //AFP07-13-24
ButtonZoom(); // must call ButtonZoom because it configures the FFT!
ResetTuning();
tft.writeTo(L1); // Always exit function in L1. August 15, 2023
DrawBandWidthIndicatorBar();
ShowSpectrumdBScale();
DrawFrequencyBarValue();
ShowFrequency();
tft.writeTo(L2); // Erase the bandwidth bar. August 16, 2023
tft.clearMemory();
tft.writeTo(L1);
tft.setFontScale((enum RA8875tsize)0);
tft.setTextColor(RA8875_GREEN);
tft.setCursor(550, 160);
tft.print("Dir Freq - Gain/Phase");
tft.setCursor(550, 175);
tft.print("User1 - Incr");
tft.setTextColor(RA8875_CYAN);
tft.fillRect(350, 125, 100, tft.getFontHeight(), RA8875_BLACK);
tft.fillRect(0, 272, 517, 399, RA8875_BLACK); // Erase waterfall. August 14, 2023
tft.setCursor(400, 125);
tft.print("dB");
tft.setCursor(350, 110);
tft.print("Incr= ");
tft.setCursor(400, 110);
tft.print(correctionIncrement, 3);
userScale = currentScale; // Remember user preference so it can be reset when done.
currentScale = 1; // Set vertical scale to 10 dB during calibration.
updateDisplayFlag = 0;
xrState = RECEIVE_STATE;
T41State = CW_RECEIVE;
modeSelectInR.gain(0, 1);
modeSelectInL.gain(0, 1);
modeSelectInExR.gain(0, 0);
modeSelectInExL.gain(0, 0);
modeSelectOutL.gain(0, 1);
modeSelectOutR.gain(0, 1);
modeSelectOutL.gain(1, 0);
modeSelectOutR.gain(1, 0);
modeSelectOutExL.gain(0, 1);
modeSelectOutExR.gain(0, 1);
xrState = TRANSMIT_STATE;
digitalWrite(RXTX, HIGH); // Turn on transmitter.
ShowTransmitReceiveStatus();
ShowSpectrumdBScale();
}
/*****
Purpose: Shut down calibtrate and clean up after IQ calibrations. Revised function. AFP 07-13-24
Parameter List:
void
Return value:
void
*****/
void CalibratePost() {
digitalWrite(RXTX, LOW); // Turn off the transmitter.
digitalWrite(CAL, CAL_OFF);
SetRF_InAtten(currentRF_InAtten);
SetRF_OutAtten(currentRF_OutAtten);
updateDisplayFlag = 0;
xrState = RECEIVE_STATE;
T41State = CW_RECEIVE;
Q_in_L.clear();
Q_in_R.clear();
currentFreq = userCurrentFreq;
TxRxFreq = userTxRxFreq;
NCOFreq = userNCOFreq;
centerFreq = userCenterFreq;
xrState = RECEIVE_STATE;
calibrateFlag = 0; // this was set when the Calibration menu option was selected
calFreqShift = 0;
currentScale = userScale; // Restore vertical scale to user preference.
//ShowSpectrumdBScale();
xmtMode = userXmtMode; // Restore the user's floor setting. July 27, 2023
transmitPowerLevel = transmitPowerLevelTemp; // Restore the user's transmit power level setting. August 15, 2023
EEPROMWrite(); // Save calibration numbers and configuration. August 12, 2023
zoomIndex = userZoomIndex - 1;
spectrum_zoom = userZoomIndex;
ButtonZoom(); // Restore the user's zoom setting. Note that this function also modifies spectrum_zoom.
EEPROMWrite(); // Save calibration numbers and configuration. August 12, 2023
tft.writeTo(L2); // Clear layer 2. July 31, 2023
tft.clearMemory();
tft.writeTo(L1); // Exit function in layer 1. August 3, 2023
RedrawDisplayScreen();
IQChoice = 5; // this is the secondary menu choice. equivalent to Cancel
calOnFlag = 0;
mainMenuWindowActive = 0;
radioState = CW_RECEIVE_STATE; //
SetFreq(); // Return Si5351 to normal operation mode.
lastState = 1111; // This is required due to the function deactivating the receiver. This forces a pass through the receiver set-up code. October 16, 2023
return;
}
/*====
Purpose: Combined input/ output for the purpose of calibrating the receive IQ
Parameter List:
void
Return value:
void
*****/
void tuneCalParameter(int indexStart, int indexEnd, float increment, float *IQCorrectionFactor, char prompt[]){
float adjMin = 100;
int adjMinIndex = 0;
float correctionFactor = *IQCorrectionFactor;
for (int i = indexStart; i < indexEnd; i++){
*IQCorrectionFactor = correctionFactor + i*increment;
FFTupdated = false;
while (!FFTupdated){
adjdB = ShowSpectrum2();
}
if ((stateMachine == TX_STATE_TX_PHASE) | (stateMachine == TX_STATE_RX_PHASE)){
// Get two updated FFTs to avoid the same where the audio samples
// span a change in the correction factor
FFTupdated = false;
while (!FFTupdated){
adjdB = ShowSpectrum2();
}
} else {
adjdB = ShowSpectrum2();
}
//Serial.println(String(i)+","+String(adjdB));
if (adjdB < adjMin){
adjMin = adjdB;
adjMinIndex = i;
}
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_WHITE);
tft.fillRect(250, 0, 285, CHAR_HEIGHT, RA8875_BLACK); // Increased rectangle size to full erase value. KF5N August 12, 2023
tft.setCursor(257, 1);
tft.print(prompt);
tft.setCursor(440, 1);
tft.print(*IQCorrectionFactor, 3);
}
*IQCorrectionFactor = correctionFactor + adjMinIndex*increment;
/*sprintf(strBuf,"Min (%d, %4.3f) = %2.1f",
adjMinIndex,
*IQCorrectionFactor,
adjMin);
Serial.println(strBuf);*/
}
void autotune(float *amp, float *phase, float gain_coarse_max, float gain_coarse_min,
float phase_coarse_max, float phase_coarse_min,
int gain_coarse_step2_N, int phase_coarse_step2_N,
int gain_fine_N, int phase_fine_N, bool phase_first){
if (phase_first){
// Step 2: phase changes in 0.01 steps from -0.2 to 0.2. Find the minimum.
int phaseStepsCoarseN = (int)((phase_coarse_max-phase_coarse_min)/0.01/2);
*phase = 0.0;
//Serial.print("Step 2: ");
tuneCalParameter(-phaseStepsCoarseN, phaseStepsCoarseN+1, 0.01, phase,(char *)"IQ Phase");
// Step 1: Gain in 0.01 steps from 0.5 to 1.5
int gainStepsCoarseN = (int)((gain_coarse_max-gain_coarse_min)/0.01/2);
*amp = 1.0;
//Serial.print("Step 1: ");
tuneCalParameter(-gainStepsCoarseN, gainStepsCoarseN+1, 0.01, amp,(char *)"IQ Gain");
} else {
// Step 1: Gain in 0.01 steps from 0.5 to 1.5
int gainStepsCoarseN = (int)((gain_coarse_max-gain_coarse_min)/0.01/2);
*amp = 1.0;
//Serial.print("Step 1: ");
tuneCalParameter(-gainStepsCoarseN, gainStepsCoarseN+1, 0.01, amp,(char *)"IQ Gain");
// Step 2: phase changes in 0.01 steps from -0.2 to 0.2. Find the minimum.
int phaseStepsCoarseN = (int)((phase_coarse_max-phase_coarse_min)/0.01/2);
*phase = 0.0;
//Serial.print("Step 2: ");
tuneCalParameter(-phaseStepsCoarseN, phaseStepsCoarseN+1, 0.01, phase,(char *)"IQ Phase");
}
// Step 3: Gain in 0.01 steps from 4 steps below previous minimum to 4 steps above
//Serial.print("Step 3: ");
tuneCalParameter(-gain_coarse_step2_N, gain_coarse_step2_N+1, 0.01, amp,(char *)"IQ Gain");
// Step 4: phase in 0.01 steps from 4 steps below previous minimum to 4 steps above
//Serial.print("Step 4: ");
tuneCalParameter(-phase_coarse_step2_N, phase_coarse_step2_N+1, 0.01, phase,(char *)"IQ Phase");
// Step 5: gain in 0.001 steps 10 steps below to 10 steps above
//Serial.print("Step 5: ");
tuneCalParameter(-gain_fine_N, gain_fine_N+1, 0.001, amp,(char *)"IQ Gain");
// Step 6: phase in 0.001 steps 10 steps below to 10 steps above
//Serial.print("Step 6: ");
tuneCalParameter(-phase_fine_N, phase_fine_N+1, 0.001, phase,(char *)"IQ Phase");
}
void DoReceiveCalibrate() {
stateMachine = RX_STATE; // what calibration step are we in
int task = -1; // captures the button presses
int lastUsedTask = -2;
tft.setTextColor(RA8875_CYAN);
CalibratePreamble(0);
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_CYAN);
tft.setCursor(550, 300);
tft.print("Receive I/Q ");
tft.setCursor(550, 350);
tft.print("Calibrate");
// CLK0/1 will be set to centerFreq + IFFreq
SetFreq();
Clk2SetFreq = (centerFreq +2*IFFreq)* SI5351_FREQ_MULT;
si5351.output_enable(SI5351_CLK2, 1);
si5351.set_freq(Clk2SetFreq, SI5351_CLK2);
digitalWrite(XMIT_MODE, XMIT_CW);
digitalWrite(CW_ON_OFF, CW_ON);
digitalWrite(CAL, CAL_ON);
uint8_t out_atten = 60;
uint8_t in_atten = 60;
uint8_t previous_out_atten = out_atten;
uint8_t previous_in_atten = in_atten;
SetRF_InAtten(in_atten);
SetRF_OutAtten(out_atten);
zoomIndex = 0;
calTypeFlag = 0; // RX cal
bool calState = true;
bool changeOutAtten = true; // let's you change in_atten when false
while (true) {
val = ReadSelectedPushButton();
if (val != BOGUS_PIN_READ) {
val = ProcessButtonPress(val);
if (val != lastUsedTask && task == -100) task = val;
else task = BOGUS_PIN_READ;
}
adjdB = ShowSpectrum2(); // this is where the IQ data processing is applied
val = ReadSelectedPushButton();
if (val != BOGUS_PIN_READ) {
val = ProcessButtonPress(val);
if (val != lastUsedTask && task == -100) task = val;
else task = BOGUS_PIN_READ;
}
switch (task) {
case (CAL_AUTOCAL):{
// Run through the autocal routine
if (stateMachine == RX_STATE){
autotune(&IQAmpCorrectionFactor[currentBand], &IQPhaseCorrectionFactor[currentBand],
GAIN_COARSE_MAX, GAIN_COARSE_MIN,
PHASE_COARSE_MAX, PHASE_COARSE_MIN,
GAIN_COARSE_STEP2_N, PHASE_COARSE_STEP2_N,
GAIN_FINE_N, PHASE_FINE_N, false);
}
break;
}
case (CAL_TOGGLE_OUTPUT):{
// Toggle the transmit signal between the CAL line and the RF output
if (calState){
calState = false;
} else {
calState = true;
}
digitalWrite(CAL, calState);
break;}
// Toggle gain and phase
case CAL_CHANGE_TYPE:{
IQCalType = !IQCalType;
break;}
case CAL_CHANGE_INC: {
corrChange = !corrChange;
if (corrChange == 1) {
correctionIncrement = 0.001; //AFP 2-7-23
} else { //if (corrChange == 0) // corrChange is a toggle, so if not needed JJP 2/5/23
correctionIncrement = 0.01; //AFP 2-7-23
}
tft.setFontScale((enum RA8875tsize)0);
tft.fillRect(400, 110, 50, tft.getFontHeight(), RA8875_BLACK);
tft.setCursor(400, 110);
tft.print(correctionIncrement, 3);
break;}
case MENU_OPTION_SELECT:{
tft.fillRect(SECONDARY_MENU_X, MENUS_Y, EACH_MENU_WIDTH + 35, CHAR_HEIGHT, RA8875_BLACK);
EEPROMData.IQAmpCorrectionFactor[currentBand] = IQAmpCorrectionFactor[currentBand];
EEPROMData.IQPhaseCorrectionFactor[currentBand] = IQPhaseCorrectionFactor[currentBand];
IQChoice = 6;
break;}
case (CAL_TOGGLE_ATTENUATOR):{
changeOutAtten = ! changeOutAtten;
break;}
default:
break;
} // End switch
if (task != -1) lastUsedTask = task; // Save the last used task.
task = -100; // Reset task after it is used.
if (IQCalType == 0) { // AFP 2-11-23
IQAmpCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQAmpCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Gain");
} else {
IQPhaseCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQPhaseCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Phase");
}
// Adjust the value of the TX attenuator:
if (changeOutAtten){
out_atten = GetFineTuneValueLive(0,63,out_atten,1,(char *)"Out Atten");
} else {
in_atten = GetFineTuneValueLive(0,63,in_atten,1,(char *)"In Atten");
}
// Update via I2C if the attenuation value changed
if (out_atten != previous_out_atten){
SetRF_OutAtten(out_atten);
previous_out_atten = out_atten;
}
if (in_atten != previous_in_atten){
SetRF_InAtten(in_atten);
previous_in_atten = in_atten;
}
if (val == MENU_OPTION_SELECT) {
break;
}
}
si5351.output_enable(SI5351_CLK2, 0);
CalibratePost();
}
/*****
Purpose: Combined input/ output for the purpose of calibrating the transmit IQ
Parameter List:
void
Return value:
void
*****/
void DoXmitCalibrate() {
stateMachine = TX_STATE_RX_PHASE;
int task = -1;
int lastUsedTask = -2;
// Set the frequency of the transmit: remove the IF offset
CalibratePreamble(4); //16x zoom
centerFreq = centerFreq - IFFreq;
SetFreq();
corrChange = 0;
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_CYAN);
tft.setCursor(550, 300);
tft.print("Transmit I/Q ");
tft.setCursor(550, 350);
tft.print("Calibrate RX");
IQChoice = 3;
uint8_t out_atten = 60;
uint8_t previous_atten = out_atten;
SetRF_InAtten(out_atten);
SetRF_OutAtten(out_atten);
calTypeFlag = 1;
IQEXChoice = 0;
bool calState = true;
updateDisplayFlag = 1;
// For the receive chain calibration portion of transmit cal use CLK2
// CLK0/1 will be set to centerFreq + IFFreq
SetFreq();
if (bands[currentBand].mode == DEMOD_LSB) {
Clk2SetFreq = (centerFreq + IFFreq - 750)* SI5351_FREQ_MULT;
} else {
Clk2SetFreq = (centerFreq + IFFreq + 750)* SI5351_FREQ_MULT;
}
si5351.output_enable(SI5351_CLK2, 1);
si5351.set_freq(Clk2SetFreq, SI5351_CLK2);
digitalWrite(XMIT_MODE, XMIT_CW);
digitalWrite(CW_ON_OFF, CW_ON);
digitalWrite(CAL, CAL_ON);
#ifdef QUADFFT
calculate750HzCalibration();
#endif
ShowTransmitReceiveStatus();
// Switch to transmit path cal upon entering loop
task = CAL_TOGGLE_TX_STATE;
while (true) {
adjdB = ShowSpectrum2();
val = ReadSelectedPushButton();
if (val != BOGUS_PIN_READ) {
val = ProcessButtonPress(val);
if (val != lastUsedTask && task == -100) task = val;
else task = BOGUS_PIN_READ;
}
switch (task) {
case (CAL_TOGGLE_TX_STATE):{
switch (stateMachine){
case (TX_STATE_RX_PHASE):{
stateMachine = TX_STATE_TX_PHASE;
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_WHITE);
tft.fillRect(550-7, 350-1, 800-550, CHAR_HEIGHT, RA8875_BLACK);
tft.setCursor(550, 350);
tft.print("Calibrate TX");
out_atten = 40;
SetRF_InAtten(30);
SetRF_OutAtten(out_atten);
digitalWrite(XMIT_MODE, XMIT_SSB);
digitalWrite(CW_ON_OFF, CW_OFF);
si5351.output_enable(SI5351_CLK2, 0);
break;
}
case (TX_STATE_TX_PHASE):{
stateMachine = TX_STATE_RX_PHASE;
tft.setFontScale((enum RA8875tsize)1);
tft.setTextColor(RA8875_WHITE);
tft.fillRect(550-7, 350-1, 800-550, CHAR_HEIGHT, RA8875_BLACK);
tft.setCursor(550, 350);
tft.print("Calibrate RX");
out_atten = 60;
SetRF_InAtten(out_atten);
SetRF_OutAtten(out_atten);
digitalWrite(XMIT_MODE, XMIT_CW);
digitalWrite(CW_ON_OFF, CW_ON);
si5351.output_enable(SI5351_CLK2, 1);
#ifdef QUADFFT
calculate750HzCalibration();
#endif
break;
}
}
break;
}
case (CAL_AUTOCAL):{
if (stateMachine == TX_STATE_RX_PHASE){
autotune(&IQXRecAmpCorrectionFactor[currentBand], &IQXRecPhaseCorrectionFactor[currentBand],
GAIN_COARSE_MAX, GAIN_COARSE_MIN,
1.0, -1.0,
8, 8,
GAIN_FINE_N, PHASE_FINE_N, true);
}
if (stateMachine == TX_STATE_TX_PHASE){
autotune(&IQXAmpCorrectionFactor[currentBand], &IQXPhaseCorrectionFactor[currentBand],
GAIN_COARSE_MAX, GAIN_COARSE_MIN,
PHASE_COARSE_MAX, PHASE_COARSE_MIN,
GAIN_COARSE_STEP2_N, PHASE_COARSE_STEP2_N,
GAIN_FINE_N, PHASE_FINE_N, false);
}
break;
}
case (CAL_TOGGLE_OUTPUT):{
// Toggle the transmit signal between the CAL line and the RF output
if (calState){
calState = false;
} else {
calState = true;
}
digitalWrite(CAL, calState);
break;}
// Toggle gain and phase
case (CAL_CHANGE_TYPE):{
IQEXChoice = !IQEXChoice; //IQEXChoice=0, Gain IQEXChoice=1, Phase
break;}
// Toggle increment value
case (CAL_CHANGE_INC):{ //
corrChange = !corrChange;
if (corrChange == 1) { // Toggle increment value
correctionIncrement = 0.001; // AFP 2-11-23
} else {
correctionIncrement = 0.01; // AFP 2-11-23
}
tft.setFontScale((enum RA8875tsize)0);
tft.fillRect(400, 110, 50, tft.getFontHeight(), RA8875_BLACK);
tft.setCursor(400, 110);
tft.print(correctionIncrement, 3);
break;}
case (MENU_OPTION_SELECT):{ // Save values and exit calibration.
tft.fillRect(SECONDARY_MENU_X, MENUS_Y, EACH_MENU_WIDTH + 35, CHAR_HEIGHT, RA8875_BLACK);
EEPROMData.IQXRecAmpCorrectionFactor[currentBand] = IQXRecAmpCorrectionFactor[currentBand];
EEPROMData.IQXRecPhaseCorrectionFactor[currentBand] = IQXRecPhaseCorrectionFactor[currentBand];
EEPROMData.IQXAmpCorrectionFactor[currentBand] = IQXAmpCorrectionFactor[currentBand];
EEPROMData.IQXPhaseCorrectionFactor[currentBand] = IQXPhaseCorrectionFactor[currentBand];
tft.fillRect(SECONDARY_MENU_X, MENUS_Y, EACH_MENU_WIDTH + 35, CHAR_HEIGHT, RA8875_BLACK);
IQChoice = 6; // AFP 2-11-23
break;}
default:
break;
} // end switch
// Need to remember the last used task;
if (task != -1) lastUsedTask = task; // Save the last used task.
task = -100; // Reset task after it is used.
// Read encoder and update values.
if (IQEXChoice == 0) {
switch (stateMachine){
case TX_STATE_TX_PHASE:{
IQXAmpCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQXAmpCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Gain X");
break;
}
case TX_STATE_RX_PHASE:{
IQXRecAmpCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQXRecAmpCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Gain R");
break;
}
}
} else {
switch (stateMachine){
case TX_STATE_TX_PHASE:{
IQXPhaseCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQXPhaseCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Phase X");
break;
}
case TX_STATE_RX_PHASE:{
IQXRecPhaseCorrectionFactor[currentBand] = GetEncoderValueLive(-2.0, 2.0, IQXRecPhaseCorrectionFactor[currentBand], correctionIncrement, (char *)"IQ Phase R");
break;
}
}
}
// Adjust the value of the TX attenuator:
out_atten = GetFineTuneValueLive(0,63,out_atten,1,(char *)"Out Atten");
// Update via I2C if the attenuation value changed
if (out_atten != previous_atten){
SetRF_OutAtten(out_atten);
previous_atten = out_atten;
}
if (val == MENU_OPTION_SELECT) {
break;
}
} // end while
CalibratePost();
// Clean up and exit to normal operation.
}
#ifdef QUADFFT
void DoIQCalibrate() {
CalibratePreamble(0);
uint8_t out_atten = 60;
uint8_t previous_atten = out_atten;
SetRF_InAtten(out_atten);
SetRF_OutAtten(out_atten);
SetFreq();
IQfreqStart_kHz = -96;
IQfreqStop_kHz = +96;
numIQPoints = 512;
digitalWrite(XMIT_MODE, XMIT_CW);
digitalWrite(CW_ON_OFF, CW_ON);
digitalWrite(CAL, CAL_ON);
// Now, step through frequency
si5351.output_enable(SI5351_CLK2, 1);
int FREQ_OFFSET = 4688; // 100*( FFT RBW / 2 ) = 100 * (96000/1024 / 2)
while (true){
DrawIQBalancePlotContainer();
Serial.println("-----------------------");
Serial.println("i,Clk2SetFreq,I-Q phase,I/Q amp");
for (int i=0; i<numIQPoints; i++){
int offset_dHz = IQfreqStart_kHz*1000*SI5351_FREQ_MULT
+i*(((IQfreqStop_kHz-IQfreqStart_kHz)*1000*SI5351_FREQ_MULT)/numIQPoints);
Clk2SetFreq = (long long)offset_dHz + (long long)((centerFreq + IFFreq)*SI5351_FREQ_MULT); //+ FREQ_OFFSET;
si5351.set_freq(Clk2SetFreq, SI5351_CLK2);
MyDelay(10); // probably unnecessary
// Let's get into the correct configuration
Q_in_L.clear();
Q_in_R.clear();
MeasureIandQFFT();
arm_max_f32(Imag,1024,&frmax,&index_of_max);
gErrorIQ[i] = Imag[index_of_max]/Qmag[index_of_max];
pErrorIQ[i] = IQphase[index_of_max];
float xx_kHz = ((float)offset_dHz)/(100.0*1000.0) ;
sprintf(strBuf,"%d,%lld,%3.2f,%4.3f\n",
i,
Clk2SetFreq,
pErrorIQ[i]*180.0/PI,
gErrorIQ[i]);
Serial.print(strBuf);
if ((IQphase[index_of_max]*180.0/PI > -100) & (IQphase[index_of_max]*180.0/PI < 100)){
tft.fillCircle(xstart + (int16_t)(xpixels_per_kHz*(-1*IQfreqStart_kHz + xx_kHz )),
ystart + (int16_t)(ypixels_per_deg*(100.0 - IQphase[index_of_max]*180.0/PI )),
1, RA8875_LIGHT_GREY);
}
tft.fillCircle(xstart + (int16_t)(xpixels_per_kHz*(-1*IQfreqStart_kHz + xx_kHz )),
ystart + (int16_t)(ypixels_per_unit*(1.5 - Imag[index_of_max]/Qmag[index_of_max] )),
1, RA8875_YELLOW);
}
val = ReadSelectedPushButton();
if (val != BOGUS_PIN_READ) { // Any button press??
val = ProcessButtonPress(val); // Use ladder value to get menu choice
if (val == MENU_OPTION_SELECT) { // Yep. Make a choice??
tft.fillRect(SECONDARY_MENU_X, MENUS_Y, EACH_MENU_WIDTH + 35, CHAR_HEIGHT, RA8875_BLACK);
tft.fillRect(250-250, 0, 285+250, CHAR_HEIGHT+1, RA8875_BLACK);
calibrateFlag = 0;
IQChoice = 6;
break;
}
}
}
si5351.output_enable(SI5351_CLK2, 0);
CalibratePost();
}
#endif
/*****
Purpose: Signal processing for the purpose of calibrating the transmit IQ
Parameter List:
void
Return value:
void
*****/
void ProcessIQData2() {
float bandOutputFactor = 0.5;
float rfGainValue; // AFP 2-11-23
float recBandFactor[NUMBER_OF_BANDS] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
/******************************************************
* Configure the audio transmit queue
******************************************************/
// Generate I and Q for the transmit calibration.
// Only configure it if it hasn't been configured before
if ((stateMachine == TX_STATE_TX_PHASE) ){
// We only have to configure the DAC output if we're in the TX phase of the TX configuration
// Use pre-calculated sin & cos instead of Hilbert
// Sidetone = 750 Hz. Sampled at 24 kHz
arm_scale_f32(cosBuffer2, bandOutputFactor, float_buffer_L_EX, 256);
arm_scale_f32(sinBuffer2, bandOutputFactor, float_buffer_R_EX, 256);
// We apply the correction factor with a varying sign here because this is how we
// select the sideband to be transmitted
if (bands[currentBand].mode == DEMOD_LSB) {
arm_scale_f32(float_buffer_L_EX, + IQXAmpCorrectionFactor[currentBand], float_buffer_L_EX, 256);
IQXPhaseCorrection(float_buffer_L_EX, float_buffer_R_EX, IQXPhaseCorrectionFactor[currentBand], 256);
} else {
if (bands[currentBand].mode == DEMOD_USB) {
arm_scale_f32(float_buffer_L_EX, - IQXAmpCorrectionFactor[currentBand], float_buffer_L_EX, 256);
IQXPhaseCorrection(float_buffer_L_EX, float_buffer_R_EX, IQXPhaseCorrectionFactor[currentBand], 256);
}
}
// interpolation-by-2, 24KHz effective sample rate coming in, 48 kHz going out
arm_fir_interpolate_f32(&FIR_int1_EX_I, float_buffer_L_EX, float_buffer_LTemp, 256);
arm_fir_interpolate_f32(&FIR_int1_EX_Q, float_buffer_R_EX, float_buffer_RTemp, 256);
// interpolation-by-4, 48KHz effective sample rate coming in, 192 kHz going out
arm_fir_interpolate_f32(&FIR_int2_EX_I, float_buffer_LTemp, float_buffer_L_EX, 512);
arm_fir_interpolate_f32(&FIR_int2_EX_Q, float_buffer_RTemp, float_buffer_R_EX, 512);
}
/******************************************************
* Load transmit values into output buffers and read in data from the receive channel buffers.
* Teensy Audio Library stores ADC data in two buffers size=128, Q_in_L and Q_in_R as initiated from the audio lib.
* Then the buffers are read into two arrays sp_L and sp_R in blocks of 128 up to N_BLOCKS. The arrarys are
* of size BUFFER_SIZE * N_BLOCKS. BUFFER_SIZE is 128.
* N_BLOCKS = FFT_LENGTH / 2 / BUFFER_SIZE * (uint32_t)DF; // should be 16 with DF == 8 and FFT_LENGTH = 512
* BUFFER_SIZE*N_BLOCKS = 2048 samples
******************************************************/
// are there at least N_BLOCKS buffers in each channel available ?
if ((uint32_t)Q_in_L.available() > N_BLOCKS + 0 && (uint32_t)Q_in_R.available() > N_BLOCKS + 0) {
if ((stateMachine == TX_STATE_TX_PHASE) ){
// Revised I and Q calibration signal generation using large buffers.
q15_t q15_buffer_LTemp[2048];
q15_t q15_buffer_RTemp[2048];
Q_out_L_Ex.setBehaviour(AudioPlayQueue::NON_STALLING);
Q_out_R_Ex.setBehaviour(AudioPlayQueue::NON_STALLING);
arm_float_to_q15(float_buffer_L_EX, q15_buffer_LTemp, 2048);
arm_float_to_q15(float_buffer_R_EX, q15_buffer_RTemp, 2048);
Q_out_L_Ex.play(q15_buffer_LTemp, 2048);
Q_out_R_Ex.play(q15_buffer_RTemp, 2048);
Q_out_L_Ex.setBehaviour(AudioPlayQueue::ORIGINAL);
Q_out_R_Ex.setBehaviour(AudioPlayQueue::ORIGINAL);
}
for (unsigned i = 0; i < N_BLOCKS; i++) {
sp_L1 = Q_in_R.readBuffer(); // this is not a mistake. L and R must have got switched at
sp_R1 = Q_in_L.readBuffer(); // some point and the calibration process incorporates it
/********************************************************************************** AFP 12-31-20
Using arm_Math library, convert to float one buffer_size.
Float_buffer samples are now standardized from > -1.0 to < 1.0
**********************************************************************************/
arm_q15_to_float(sp_L1, &float_buffer_L[BUFFER_SIZE * i], BUFFER_SIZE); // convert int_buffer to float 32bit
arm_q15_to_float(sp_R1, &float_buffer_R[BUFFER_SIZE * i], BUFFER_SIZE); // convert int_buffer to float 32bit
Q_in_L.freeBuffer();
Q_in_R.freeBuffer();
}
rfGainValue = pow(10, (float)rfGainAllBands / 20); //AFP 2-11-23
arm_scale_f32(float_buffer_L, rfGainValue, float_buffer_L, BUFFER_SIZE * N_BLOCKS); //AFP 2-11-23
arm_scale_f32(float_buffer_R, rfGainValue, float_buffer_R, BUFFER_SIZE * N_BLOCKS); //AFP 2-11-23
#ifdef QUADFFT
// Apply an initial phase delay and scaling derived from the IQ balance measurement
// when in transmit mode
if ((stateMachine == TX_STATE_TX_PHASE) | (stateMachine == TX_STATE_RX_PHASE)){
arm_scale_f32(float_buffer_R, scale_R, float_buffer_R, BUFFER_SIZE * N_BLOCKS);
// Apply a shift-by-N to float_buffer_R
if (delay_R > 0){
for (int k=0;k<delay_R;k++){
float_buffer_RTemp[k] = float_buffer_R[BUFFER_SIZE * N_BLOCKS - (delay_R-k)];
}
for (int k=0;k<(BUFFER_SIZE * N_BLOCKS - delay_R);k++){
float_buffer_RTemp[delay_R+k] = float_buffer_R[k];
}
for (int k=0;k<(BUFFER_SIZE * N_BLOCKS);k++){
float_buffer_R[k] = float_buffer_RTemp[k];
}
}
if (delay_R < 0){
for (int k=0;k<(-1*delay_R);k++){
float_buffer_RTemp[BUFFER_SIZE * N_BLOCKS - (-1*delay_R-k)] = float_buffer_R[k];
}
for (int k=0;k<(BUFFER_SIZE * N_BLOCKS + delay_R);k++){
float_buffer_RTemp[k] = float_buffer_R[k-delay_R];
}
for (int k=0;k<(BUFFER_SIZE * N_BLOCKS);k++){
float_buffer_R[k] = float_buffer_RTemp[k];
}
}
}
#endif
/********************************************************************************** AFP 12-31-20
Scale the data buffers by the RFgain value defined in bands[currentBand] structure
**********************************************************************************/
arm_scale_f32(float_buffer_L, recBandFactor[currentBand], float_buffer_L, BUFFER_SIZE * N_BLOCKS); //AFP 2-11-23
arm_scale_f32(float_buffer_R, recBandFactor[currentBand], float_buffer_R, BUFFER_SIZE * N_BLOCKS); //AFP 2-11-23
// Receive amplitude correction
if ((stateMachine == TX_STATE_TX_PHASE) | (stateMachine == TX_STATE_RX_PHASE)){
// Apply the special calibration parameters that apply only here
arm_scale_f32(float_buffer_L, -IQXRecAmpCorrectionFactor[currentBand], float_buffer_L, BUFFER_SIZE * N_BLOCKS);
IQPhaseCorrection(float_buffer_L, float_buffer_R, IQXRecPhaseCorrectionFactor[currentBand], BUFFER_SIZE * N_BLOCKS);
} else {
arm_scale_f32(float_buffer_L, -IQAmpCorrectionFactor[currentBand], float_buffer_L, BUFFER_SIZE * N_BLOCKS); //AFP 04-14-22
IQPhaseCorrection(float_buffer_L, float_buffer_R, IQPhaseCorrectionFactor[currentBand], BUFFER_SIZE * N_BLOCKS);
}
if (spectrum_zoom == SPECTRUM_ZOOM_1) {
// This is executed during receive cal
CalcZoom1Magn();
FFTupdated = true;
} else {
// This is used during transmit cal
ZoomFFTExe(BUFFER_SIZE * N_BLOCKS);
if (zoom_sample_ptr == 0) {
FFTupdated = true;
/*for (int i=0; i<SPECTRUM_RES; i++){
sprintf(strBuf,"%d,%5.4f,%5.4f\n",
i,
FFT_ring_buffer_x[i],
FFT_ring_buffer_y[i]);
Serial.print(strBuf);
}*/
}
}
}
}
/*****
Purpose: Show Spectrum display modified for IQ calibration.
This is similar to the function used for normal reception, however, it has
been simplified and streamlined for calibration.
Parameter list:
void
Return value;
void
*****/
float ShowSpectrum2()
{
int x1 = 0;
float adjdB = 0.0;
pixelnew[0] = 0;
pixelnew[1] = 0;
pixelold[0] = 0;
pixelold[1] = 0;
// This is the "spectra scanning" for loop. During calibration, only small areas of the spectrum need to be examined.
// If the entire 512 wide spectrum is used, the calibration loop will be slow and unresponsive.
// The scanning areas are determined by receive versus transmit calibration, and LSB or USB. Thus there are 4 different scanning zones.
// All calibrations use a 0 dB reference signal and an "undesired sideband" signal which is to be minimized relative to the reference.
// Thus there is a target "bin" for the reference signal and another "bin" for the undesired sideband.
// The target bin locations are used by the for-loop to sweep a small range in the FFT. A maximum finding function finds the peak signal strength.
/*************************************
ProcessIQData2 performs an N-point (SPECTRUM_RES = 512) FFT on the data in float_buffer_L and
float_buffer_R when they fill up. The data in float_buffer_L and float_buffer_R are sampled at
192000 Hz. The length of the float_buffer_L and float_buffer_R buffers is BUFFER_SIZE * N_BLOCKS
= 128*16 = 2048, of which the FFT only uses the first 512 points.
N_BLOCKS = FFT_LENGTH / 2 / BUFFER_SIZE * (uint32_t)DF
= 512 / 2 / 128 * 8
= 16
Therefore the bin width of each FFT bin is SAMPLE_RATE / FFT_LEN = 192000 / 512 = 375 Hz.
The frequency of the middle bin is centerFreq + IFFreq and our spectrum spans
(centerFreq + IFFreq - SAMPLE_RATE/2) to (centerFreq + IFFreq + SAMPLE_RATE/2).
So the equation for bin number n given frequency Clk2SetFreq is:
n = (Clk2SetFreq - Clk1SetFreq)/375 + 256
= (Clk2SetFreq - (centerFreq + IFFreq))/375 + 256
In receive cal mode, we set Clk2SetFreq to centerFreq + 2*IFFreq
So we expect the desired tone to appear in bin
n_tone = IFFreq/375 + 256
while the undesired image product will be at
n_image= -IFFreq/375 + 256
Which are, given IFFreq = 48000:
n_tone = 384
n_image= 128
*********************************************/
int cal_bins[2] = { 0, 0 };
int capture_bins; // Sets the number of bins to scan for signal peak.
if (calTypeFlag == 0) {
capture_bins = 10;
cal_bins[0] = 384;