forked from ramapcsx2/gbs-control
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgbs-control.ino
6549 lines (6028 loc) · 216 KB
/
gbs-control.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
#include <Wire.h>
#include "ntsc_240p.h"
#include "pal_240p.h"
#include "ntsc_feedbackclock.h"
#include "pal_feedbackclock.h"
#include "ntsc_1280x720.h"
#include "ntsc_1280x1024.h"
#include "ntsc_1920x1080.h"
#include "pal_1280x720.h"
#include "pal_1280x1024.h"
#include "pal_1920x1080.h"
#include "presetMdSection.h"
#include "presetDeinterlacerSection.h"
#include "presetHdBypassSection.h"
#include "ofw_RGBS.h"
#include "tv5725.h"
#include "framesync.h"
#include "osd.h"
typedef TV5725<GBS_ADDR> GBS;
#if defined(ESP8266) // select WeMos D1 R2 & mini in IDE for NodeMCU! (otherwise LED_BUILTIN is mapped to D0 / does not work)
#include <ESP8266WiFi.h>
#include "FS.h"
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include "PersWiFiManager.h"
#include <ESP8266mDNS.h> // mDNS library for finding gbscontrol.local on the local network
//#define HAVE_PINGER_LIBRARY // ESP8266-ping library to aid debugging WiFi issues, install via Arduino library manager
#ifdef HAVE_PINGER_LIBRARY
#include <Pinger.h>
#include <PingerResponse.h>
unsigned long pingLastTime;
#endif
// WebSockets library by Markus Sattler
// to install: "Sketch" > "Include Library" > "Manage Libraries ..." > search for "websockets" and install "WebSockets for Arduino (Server + Client)"
#include <WebSocketsServer.h>
const char* ap_ssid = "gbscontrol";
const char* ap_password = "qqqqqqqq";
// change device_hostname_full and device_hostname_partial to rename the device
// (allows 2 or more on the same network)
const char* device_hostname_full = "gbscontrol.local";
const char* device_hostname_partial = "gbscontrol"; // for MDNS
//
const char* ap_info_string = "(WiFi) AP mode (SSID: gbscontrol, pass 'qqqqqqqq'): Access 'gbscontrol.local' in your browser";
ESP8266WebServer server(80);
DNSServer dnsServer;
WebSocketsServer webSocket(81);
PersWiFiManager persWM(server, dnsServer);
#ifdef HAVE_PINGER_LIBRARY
Pinger pinger; // pinger global object to aid debugging WiFi issues
#endif
#define DEBUG_IN_PIN D6 // marked "D12/MISO/D6" (Wemos D1) or D6 (Lolin NodeMCU)
// SCL = D1 (Lolin), D15 (Wemos D1) // ESP8266 Arduino default map: SCL
// SDA = D2 (Lolin), D14 (Wemos D1) // ESP8266 Arduino default map: SDA
#define LEDON \
pinMode(LED_BUILTIN, OUTPUT); \
digitalWrite(LED_BUILTIN, LOW)
#define LEDOFF \
digitalWrite(LED_BUILTIN, HIGH); \
pinMode(LED_BUILTIN, INPUT)
// fast ESP8266 digitalRead (21 cycles vs 77), *should* work with all possible input pins
// but only "D7" and "D6" have been tested so far
#define digitalRead(x) ((GPIO_REG_READ(GPIO_IN_ADDRESS) >> x) & 1)
#else // Arduino
#define LEDON \
pinMode(LED_BUILTIN, OUTPUT); \
digitalWrite(LED_BUILTIN, HIGH)
#define LEDOFF \
digitalWrite(LED_BUILTIN, LOW); \
pinMode(LED_BUILTIN, INPUT)
#define DEBUG_IN_PIN 11
// fastest, but non portable (Uno pin 11 = PB3, Mega2560 pin 11 = PB5)
//#define digitalRead(x) bitRead(PINB, 3)
#include "fastpin.h"
#define digitalRead(x) fastRead<x>()
//#define HAVE_BUTTONS
#define INPUT_PIN 9
#define DOWN_PIN 8
#define UP_PIN 7
#define MENU_PIN 6
#endif
// feed the current measurement, get back the moving average
uint8_t getMovingAverage(uint8_t item)
{
static const uint8_t sz = 16;
static uint8_t arr[sz] = { 0 };
static uint8_t pos = 0;
arr[pos] = item;
if (pos < (sz - 1)) {
pos++;
}
else {
pos = 0;
}
uint16_t sum = 0;
for (uint8_t i = 0; i < sz; i++) {
sum += arr[i];
}
return sum >> 4; // for array size 16
}
//
// Sync locking tunables/magic numbers
//
struct FrameSyncAttrs {
static const uint8_t debugInPin = DEBUG_IN_PIN;
static const uint32_t lockInterval = 60 * 16; // every 60 frames. good range for this: 30 to 90 (milliseconds)
static const int16_t syncCorrection = 2; // Sync correction in scanlines to apply when phase lags target
static const int32_t syncTargetPhase = 90; // Target vsync phase offset (output trails input) in degrees
};
typedef FrameSyncManager<GBS, FrameSyncAttrs> FrameSync;
struct MenuAttrs {
static const int8_t shiftDelta = 4;
static const int8_t scaleDelta = 4;
static const int16_t vertShiftRange = 300;
static const int16_t horizShiftRange = 400;
static const int16_t vertScaleRange = 100;
static const int16_t horizScaleRange = 130;
static const int16_t barLength = 100;
};
typedef MenuManager<GBS, MenuAttrs> Menu;
// runTimeOptions holds system variables
struct runTimeOptions {
uint8_t presetVlineShift;
uint8_t videoStandardInput; // 0 - unknown, 1 - NTSC like, 2 - PAL like, 3 480p NTSC, 4 576p PAL
uint8_t phaseSP;
uint8_t phaseADC;
uint8_t currentLevelSOG;
uint8_t thisSourceMaxLevelSOG;
uint8_t syncLockFailIgnore;
uint8_t applyPresetDoneStage;
uint8_t continousStableCounter;
uint8_t noSyncCounter;
uint8_t failRetryAttempts;
uint8_t presetID;
uint8_t HPLLState;
boolean isInLowPowerMode;
boolean clampPositionIsSet;
boolean coastPositionIsSet;
boolean inputIsYpBpR;
boolean syncWatcherEnabled;
boolean outModeHdBypass;
boolean printInfos;
boolean sourceDisconnected;
boolean webServerEnabled;
boolean webServerStarted;
boolean allowUpdatesOTA;
boolean enableDebugPings;
boolean autoBestHtotalEnabled;
boolean videoIsFrozen;
boolean forceRetime;
boolean motionAdaptiveDeinterlaceActive;
boolean deinterlaceAutoEnabled;
boolean scanlinesEnabled;
boolean boardHasPower;
boolean presetIsPalForce60;
boolean syncTypeCsync;
} rtos;
struct runTimeOptions *rto = &rtos;
// userOptions holds user preferences / customizations
struct userOptions {
uint8_t presetPreference; // 0 - normal, 1 - feedback clock, 2 - customized, 3 - 1280x720, 4 - 1280x1024, 5 - 1920x1080, 10 - bypass
uint8_t presetSlot;
uint8_t enableFrameTimeLock;
uint8_t frameTimeLockMethod;
uint8_t enableAutoGain;
uint8_t wantScanlines;
uint8_t wantOutputComponent;
uint8_t deintMode;
uint8_t wantVdsLineFilter;
uint8_t wantPeaking;
uint8_t preferScalingRgbhv;
uint8_t PalForce60;
uint8_t overscan;
} uopts;
struct userOptions *uopt = &uopts;
// remember adc options across presets
struct adcOptions {
uint8_t r_gain;
uint8_t g_gain;
uint8_t b_gain;
uint8_t r_off;
uint8_t g_off;
uint8_t b_off;
} adcopts;
struct adcOptions *adco = &adcopts;
char globalCommand; // Serial / Web Server commands
//uint8_t globalDelay; // used for dev / debug
#if defined(ESP8266)
// serial mirror class for websocket logs
class SerialMirror : public Stream {
size_t write(const uint8_t *data, size_t size) {
#if defined(ESP8266)
webSocket.broadcastTXT(data, size);
#endif
Serial.write(data, size);
return size;
}
size_t write(uint8_t data) {
#if defined(ESP8266)
webSocket.broadcastTXT(&data);
#endif
Serial.write(data);
return 1;
}
int available() {
return 0;
}
int read() {
return -1;
}
int peek() {
return -1;
}
void flush() { }
};
SerialMirror SerialM;
#else
#define SerialM Serial
#endif
static uint8_t lastSegment = 0xFF;
static inline void writeOneByte(uint8_t slaveRegister, uint8_t value)
{
writeBytes(slaveRegister, &value, 1);
}
static inline void writeBytes(uint8_t slaveRegister, uint8_t* values, uint8_t numValues)
{
if (slaveRegister == 0xF0 && numValues == 1) {
lastSegment = *values;
}
else
GBS::write(lastSegment, slaveRegister, values, numValues);
}
void copyBank(uint8_t* bank, const uint8_t* programArray, uint16_t* index)
{
for (uint8_t x = 0; x < 16; ++x) {
bank[x] = pgm_read_byte(programArray + *index);
(*index)++;
}
}
void zeroAll()
{
// turn processing units off first
writeOneByte(0xF0, 0);
writeOneByte(0x46, 0x00); // reset controls 1
writeOneByte(0x47, 0x00); // reset controls 2
// zero out entire register space
for (int y = 0; y < 6; y++)
{
writeOneByte(0xF0, (uint8_t)y);
for (int z = 0; z < 16; z++)
{
uint8_t bank[16];
for (int w = 0; w < 16; w++)
{
bank[w] = 0;
}
writeBytes(z * 16, bank, 16);
}
}
}
void loadHdBypassSection() {
uint16_t index = 0;
uint8_t bank[16];
writeOneByte(0xF0, 1);
for (int j = 3; j <= 5; j++) { // start at 0x30
copyBank(bank, presetHdBypassSection, &index);
writeBytes(j * 16, bank, 16);
}
}
void loadPresetDeinterlacerSection() {
uint16_t index = 0;
uint8_t bank[16];
writeOneByte(0xF0, 2);
for (int j = 0; j <= 3; j++) { // start at 0x00
copyBank(bank, presetDeinterlacerSection, &index);
writeBytes(j * 16, bank, 16);
}
}
void loadPresetMdSection() {
uint16_t index = 0;
uint8_t bank[16];
writeOneByte(0xF0, 1);
for (int j = 6; j <= 7; j++) { // start at 0x60
copyBank(bank, presetMdSection, &index);
writeBytes(j * 16, bank, 16);
}
bank[0] = pgm_read_byte(presetMdSection + index);
bank[1] = pgm_read_byte(presetMdSection + index + 1);
bank[2] = pgm_read_byte(presetMdSection + index + 2);
bank[3] = pgm_read_byte(presetMdSection + index + 3);
writeBytes(8 * 16, bank, 4); // MD section ends at 0x83, not 0x90
}
// programs all valid registers (the register map has holes in it, so it's not straight forward)
// 'index' keeps track of the current preset data location.
void writeProgramArrayNew(const uint8_t* programArray, boolean skipMDSection)
{
uint16_t index = 0;
uint8_t bank[16];
uint8_t y = 0;
GBS::PAD_SYNC_OUT_ENZ::write(1);
GBS::DAC_RGBS_PWDNZ::write(0); // no DAC
// should only be possible if previously was in RGBHV bypass, then hit a manual preset switch
if (rto->videoStandardInput == 15) {
rto->videoStandardInput = 0;
}
rto->outModeHdBypass = 0; // the default at this stage
if (GBS::ADC_INPUT_SEL::read() == 0) {
//if (rto->inputIsYpBpR == 0) SerialM.println("rto->inputIsYpBpR was 0, fixing to 1");
rto->inputIsYpBpR = 1; // new: update the var here, allow manual preset loads
}
else {
//if (rto->inputIsYpBpR == 1) SerialM.println("rto->inputIsYpBpR was 1, fixing to 0");
rto->inputIsYpBpR = 0;
}
uint8_t reset46 = GBS::RESET_CONTROL_0x46::read(); // for keeping these as they are now
uint8_t reset47 = GBS::RESET_CONTROL_0x47::read();
for (; y < 6; y++)
{
writeOneByte(0xF0, (uint8_t)y);
switch (y) {
case 0:
for (int j = 0; j <= 1; j++) { // 2 times
for (int x = 0; x <= 15; x++) {
if (j == 0 && x == 4) {
// keep DAC off
bank[x] = pgm_read_byte(programArray + index) & ~(1 << 0);
}
else if (j == 0 && x == 6) {
bank[x] = reset46;
}
else if (j == 0 && x == 7) {
bank[x] = reset47;
}
else if (j == 0 && x == 9) {
// keep sync output off
bank[x] = pgm_read_byte(programArray + index) | (1 << 2);
}
else {
// use preset values
bank[x] = pgm_read_byte(programArray + index);
}
index++;
}
writeBytes(0x40 + (j * 16), bank, 16);
}
copyBank(bank, programArray, &index);
writeBytes(0x90, bank, 16);
break;
case 1:
for (int j = 0; j <= 2; j++) { // 3 times
copyBank(bank, programArray, &index);
writeBytes(j * 16, bank, 16);
}
if (!skipMDSection) {
loadPresetMdSection();
}
break;
case 2:
loadPresetDeinterlacerSection();
break;
case 3:
for (int j = 0; j <= 7; j++) { // 8 times
copyBank(bank, programArray, &index);
writeBytes(j * 16, bank, 16);
}
// blank out VDS PIP registers, otherwise they can end up uninitialized
for (int x = 0; x <= 15; x++) {
writeOneByte(0x80 + x, 0x00);
}
break;
case 4:
for (int j = 0; j <= 5; j++) { // 6 times
copyBank(bank, programArray, &index);
writeBytes(j * 16, bank, 16);
}
break;
case 5:
for (int j = 0; j <= 6; j++) { // 7 times
for (int x = 0; x <= 15; x++) {
bank[x] = pgm_read_byte(programArray + index);
if (index == 322) { // s5_02 bit 6+7 = input selector (only bit 6 is relevant)
if (rto->inputIsYpBpR)bitClear(bank[x], 6);
else bitSet(bank[x], 6);
}
//if (index == 324) { // s5_04 reset for ADC REF init
// bank[x] = 0x00;
//}
if (index == 382) { // s5_3e
bitSet(bank[x], 5); // SP_DIS_SUB_COAST = 1
}
if (index == 407) { // s5_57
bitSet(bank[x], 0); // SP_NO_CLAMP_REG = 1
}
index++;
}
writeBytes(j * 16, bank, 16);
}
break;
}
}
// 640x480 RGBHV scaling mode
if (rto->videoStandardInput == 14) {
GBS::GBS_OPTION_SCALING_RGBHV::write(1);
rto->videoStandardInput = 3;
}
}
void setResetParameters() {
SerialM.println("<reset>");
rto->videoStandardInput = 0;
rto->videoIsFrozen = false;
rto->applyPresetDoneStage = 0;
rto->presetVlineShift = 0;
rto->sourceDisconnected = true;
rto->outModeHdBypass = 0;
rto->clampPositionIsSet = 0;
rto->coastPositionIsSet = 0;
rto->continousStableCounter = 0;
rto->noSyncCounter = 0;
rto->isInLowPowerMode = false;
rto->currentLevelSOG = 2;
rto->thisSourceMaxLevelSOG = 31; // 31 = auto sog has not (yet) run
rto->failRetryAttempts = 0;
rto->HPLLState = 0;
rto->motionAdaptiveDeinterlaceActive = false;
rto->scanlinesEnabled = false;
rto->syncTypeCsync = false;
adco->r_gain = 0;
adco->g_gain = 0;
adco->b_gain = 0;
// clear temp storage
GBS::ADC_UNUSED_64::write(0); GBS::ADC_UNUSED_65::write(0);
GBS::ADC_UNUSED_66::write(0); GBS::ADC_UNUSED_67::write(0);
GBS::GBS_PRESET_CUSTOM::write(0);
GBS::GBS_PRESET_ID::write(0);
GBS::OUT_SYNC_CNTRL::write(0); // no H / V sync out to PAD
GBS::DAC_RGBS_PWDNZ::write(0); // disable DAC
setAndUpdateSogLevel(rto->currentLevelSOG);
GBS::RESET_CONTROL_0x46::write(0x00); // all units off
GBS::RESET_CONTROL_0x47::write(0x00);
GBS::GPIO_CONTROL_00::write(0x67); // most GPIO pins regular GPIO
GBS::GPIO_CONTROL_01::write(0x00); // all GPIO outputs disabled
GBS::DAC_RGBS_PWDNZ::write(0); // disable DAC (output)
GBS::PLL648_CONTROL_01::write(0x00); // VCLK(1/2/4) display clock // needs valid for debug bus
GBS::IF_SEL_ADC_SYNC::write(1); // ! 1_28
GBS::PLLAD_VCORST::write(1); // reset = 1
GBS::PLL_ADS::write(1); // When = 1, input clock is from ADC ( otherwise, from unconnected clock at pin 40 )
GBS::PLL_CKIS::write(0); // PLL use OSC clock
GBS::PLL_MS::write(2); // fb memory clock can go lower power
GBS::PAD_CONTROL_00_0x48::write(0x2b); //disable digital inputs, enable debug out pin
GBS::PAD_CONTROL_01_0x49::write(0x1f); //pad control pull down/up transistors on
loadHdBypassSection(); // 1_30 to 1_55
loadPresetMdSection(); // 1_60 to 1_83
setAdcParametersGainAndOffset();
GBS::SP_PRE_COAST::write(9); // was 0x07 // need pre / poast to allow all sources to detect
GBS::SP_POST_COAST::write(18); // was 0x10 // ps2 1080p 18
GBS::SP_CS_CLP_ST::write(8); // define it to something at start
GBS::SP_CS_CLP_SP::write(16);
GBS::ADC_CLK_PA::write(0); // 5_00 0/1 PA_ADC input clock = PLLAD CLKO2
GBS::ADC_INPUT_SEL::write(1); // 1 = RGBS / RGBHV adc data input
GBS::SP_EXT_SYNC_SEL::write(0); // connect HV input ( 5_20 bit 3 )
//GBS::ADC_TR_RSEL::write(2); // 5_04 // ADC_TR_RSEL = 2
GBS::ADC_TR_RSEL::write(0);
GBS::ADC_TA_CTRL_05_BIT1::write(1); // 5_05 1 // minor SOG clamp effect
//GBS::ADC_TEST_0C::write(2); // 5_0c 2
GBS::ADC_TEST_0C::write(0);
GBS::ADC_TEST_0C_BIT4::write(1); // 5_0c 4
GBS::SP_NO_CLAMP_REG::write(1);
GBS::ADC_SOGEN::write(1);
GBS::ADC_POWDZ::write(1); // ADC on
GBS::PLLAD_ICP::write(0); // lowest charge pump current
GBS::PLLAD_FS::write(0); // low gain (have to deal with cold and warm startups)
GBS::PLLAD_5_16::write(0x1f);
GBS::PLLAD_MD::write(0x700);
resetPLL(); // cycles PLL648
delay(2);
resetPLLAD(); // same for PLLAD
GBS::PLL_VCORST::write(1); // reset on
GBS::PLLAD_CONTROL_00_5x11::write(0x01); // reset on
resetDebugPort();
GBS::RESET_CONTROL_0x47::write(0x16);
GBS::INTERRUPT_CONTROL_01::write(0xff); // enable interrupts
GBS::INTERRUPT_CONTROL_00::write(0xff); // reset irq status
GBS::INTERRUPT_CONTROL_00::write(0x00);
GBS::RESET_CONTROL_0x47::write(0x16); // decimation off
GBS::PAD_SYNC_OUT_ENZ::write(0); // sync output enabled, will be low (HC125 fix)
rto->clampPositionIsSet = 0; // some functions override these, so make sure
rto->coastPositionIsSet = 0;
rto->continousStableCounter = 0;
}
void OutputComponentOrVGA() {
boolean isCustomPreset = GBS::GBS_PRESET_CUSTOM::read();
if (uopt->wantOutputComponent) {
SerialM.println("Output Format: Component");
GBS::VDS_SYNC_LEV::write(0x80); // 0.25Vpp sync (leave more room for Y)
GBS::VDS_CONVT_BYPS::write(1); // output YUV
GBS::OUT_SYNC_CNTRL::write(0); // no H / V sync out to PAD
// patch up some presets
uint8_t id = GBS::GBS_PRESET_ID::read();
if (!isCustomPreset) {
if (id == 0x02 || id == 0x12 || id == 0x01 || id == 0x11) { // 1280x1024, 1280x960 presets
set_vtotal(1090); // 1080 is enough lines to trick my tv into "1080p" mode
if (id == 0x02 || id == 0x01) { // 60
GBS::IF_VB_SP::write(2); // GBS::IF_VB_SP::read() - 16 // better fix this
GBS::IF_VB_ST::write(0); // GBS::IF_VB_ST::read() - 16
GBS::VDS_HS_SP::write(10);
}
else { // 50
GBS::VDS_DIS_HB_ST::write(GBS::VDS_DIS_HB_ST::read() - 70);
GBS::VDS_HSCALE::write(724);
GBS::IF_VB_SP::write(2); // GBS::IF_VB_SP::read() - 18
GBS::IF_VB_ST::write(0); // GBS::IF_VB_ST::read() - 18
}
rto->forceRetime = true;
}
}
}
else {
GBS::VDS_SYNC_LEV::write(0);
GBS::VDS_CONVT_BYPS::write(0); // output RGB
GBS::OUT_SYNC_CNTRL::write(1); // H / V sync out enable
}
if (!isCustomPreset) {
if (rto->inputIsYpBpR == true) {
applyYuvPatches();
}
else {
applyRGBPatches();
}
}
}
void applyComponentColorMixing() {
GBS::VDS_Y_GAIN::write(0x64); // 3_35
GBS::VDS_UCOS_GAIN::write(0x19); // 3_36
GBS::VDS_VCOS_GAIN::write(0x19); // 3_37
GBS::VDS_Y_OFST::write(0xfe); // 3_3a
GBS::VDS_U_OFST::write(0x01); // 3_3b
}
void applyYuvPatches() {
GBS::ADC_RYSEL_R::write(1); // midlevel clamp red
GBS::ADC_RYSEL_B::write(1); // midlevel clamp blue
GBS::ADC_RYSEL_G::write(0); // gnd clamp green
GBS::IF_MATRIX_BYPS::write(1);
GBS::DEC_MATRIX_BYPS::write(1); // ADC
GBS::IF_AUTO_OFST_U_RANGE::write(1);
GBS::IF_AUTO_OFST_V_RANGE::write(1);
GBS::IF_AUTO_OFST_PRD::write(0); // 0 = by line, 1 = by frame
GBS::IF_AUTO_OFST_EN::write(0); // not too reliable yet
// colors
GBS::VDS_Y_GAIN::write(0x7f); // 3_25 0x80
GBS::VDS_UCOS_GAIN::write(0x1c); // 3_26 blue
GBS::VDS_VCOS_GAIN::write(0x27); // 3_27 red
GBS::VDS_Y_OFST::write(0xfc); // 3_3a // fe
GBS::VDS_U_OFST::write(0x00); // 3_3b
GBS::VDS_V_OFST::write(0x00); // 3_3c
// wii test on gbs with red offset issue:
//GBS::VDS_Y_GAIN::write(0x7b); // 0x80 = 0 // 7b
//GBS::VDS_VCOS_GAIN::write(0x26); // 3_37 red // wii 240p suite test: 26
//GBS::VDS_UCOS_GAIN::write(0x1c); // 3_36 blue
//GBS::VDS_Y_OFST::write(0x00); // 0 3_3a
//GBS::VDS_U_OFST::write(0xfd); // 0 3_3b
//GBS::VDS_V_OFST::write(0xfd); // 0 3_3c
if (uopt->wantOutputComponent) {
applyComponentColorMixing();
}
}
void applyRGBPatches() {
GBS::ADC_RYSEL_R::write(0); // gnd clamp red
GBS::ADC_RYSEL_B::write(0); // gnd clamp blue
GBS::ADC_RYSEL_G::write(0); // gnd clamp green
GBS::DEC_MATRIX_BYPS::write(0); // 5_1f 2 = 1 for YUV / 0 for RGB
GBS::IF_AUTO_OFST_U_RANGE::write(1);
GBS::IF_AUTO_OFST_V_RANGE::write(1);
GBS::IF_AUTO_OFST_PRD::write(0); // 0 = by line, 1 = by frame
GBS::IF_AUTO_OFST_EN::write(0); // not too reliable yet
GBS::IF_MATRIX_BYPS::write(1);
// colors
GBS::VDS_Y_GAIN::write(0x80); // 0x80 = 0
GBS::VDS_VCOS_GAIN::write(0x28); // red
GBS::VDS_UCOS_GAIN::write(0x1c); // blue
GBS::VDS_USIN_GAIN::write(0x00); // 3_38
GBS::VDS_VSIN_GAIN::write(0x00); // 3_39
GBS::VDS_Y_OFST::write(0x00); // 3_3a 0xfe
GBS::VDS_U_OFST::write(0x00); // 3_3b 0x01
GBS::VDS_V_OFST::write(0x00); // 3_3c 0x01
if (uopt->wantOutputComponent) {
applyComponentColorMixing();
}
}
void setAdcParametersGainAndOffset() {
GBS::ADC_ROFCTRL::write(0x3f);
GBS::ADC_GOFCTRL::write(0x3f);
GBS::ADC_BOFCTRL::write(0x3f);
GBS::ADC_RGCTRL::write(0x7f); // 7b
GBS::ADC_GGCTRL::write(0x7f); // 7b
GBS::ADC_BGCTRL::write(0x7f); // 7b
}
void updateHSyncEdge() {
static uint8_t printHS = 0, printVS = 0;
uint8_t syncStatus = GBS::STATUS_16::read();
if ((syncStatus & 0x02) != 0x02) // if !hs active
{
//SerialM.println("(SP) can't detect sync edge");
}
else
{
if ((syncStatus & 0x01) == 0x00)
{
if (printHS != 1) { SerialM.println("(SP) HS active low"); }
printHS = 1;
if (rto->videoStandardInput == 15) {
GBS::SP_CS_P_SWAP::write(1); // 5_3e 0 // bad in Component input mode, good in RGBHV
}
GBS::SP_HS_PROC_INV_REG::write(0); // 5_56 5
//GBS::SP_HS2PLL_INV_REG::write(1); //5_56 1
}
else
{
if (printHS != 2) { SerialM.println("(SP) HS active high"); }
printHS = 2;
if (rto->videoStandardInput == 15) {
GBS::SP_CS_P_SWAP::write(0); // 5_3e 0
}
GBS::SP_HS_PROC_INV_REG::write(1); // 5_56 5
//GBS::SP_HS2PLL_INV_REG::write(0); //5_56 1
}
if (rto->syncTypeCsync == false)
{
if ((syncStatus & 0x04) == 0x00)
{
if (printVS != 1) { SerialM.println("(SP) VS active low"); }
printVS = 1;
GBS::SP_VS_PROC_INV_REG::write(0); // 5_56 6
}
else
{
if (printVS != 2) { SerialM.println("(SP) VS active high"); }
printVS = 2;
GBS::SP_VS_PROC_INV_REG::write(1); // 5_56 6
}
}
}
}
void setSpParameters() {
writeOneByte(0xF0, 5);
GBS::SP_SOG_P_ATO::write(0); // 5_20 disable sog auto polarity // hpw can be > ht, but auto is worse
GBS::SP_JITTER_SYNC::write(0);
// H active detect control
writeOneByte(0x21, 0x18); // SP_SYNC_TGL_THD H Sync toggle times threshold 0x20; lower than 5_33(not always); 0 to turn off (?) 0x18 for 53.69 system @ 33.33
writeOneByte(0x22, 0x0F); // SP_L_DLT_REG Sync pulse width different threshold (little than this as equal)
writeOneByte(0x23, 0x00); // UNDOCUMENTED range from 0x00 to at least 0x1d
writeOneByte(0x24, 0x40); // SP_T_DLT_REG H total width different threshold rgbhv: b // range from 0x02 upwards
writeOneByte(0x25, 0x00); // SP_T_DLT_REG
writeOneByte(0x26, 0x04); // SP_SYNC_PD_THD H sync pulse width threshold // from 0(?) to 0x50 // in yuv 720p range only to 0x0a!
writeOneByte(0x27, 0x00); // SP_SYNC_PD_THD
writeOneByte(0x2a, 0x0F); // SP_PRD_EQ_THD How many legal lines as valid; scales with 5_33 (needs to be below)
// V active detect control
// these 4 have no effect currently test string: s5s2ds34 s5s2es24 s5s2fs16 s5s31s84 | s5s2ds02 s5s2es04 s5s2fs02 s5s31s04
writeOneByte(0x2d, 0x03); // SP_VSYNC_TGL_THD V sync toggle times threshold // at 5 starts to drop many 0_16 vsync events
writeOneByte(0x2e, 0x00); // SP_SYNC_WIDTH_DTHD V sync pulse width threshod
writeOneByte(0x2f, 0x02); // SP_V_PRD_EQ_THD How many continue legal v sync as valid // at 4 starts to drop 0_16 vsync events
writeOneByte(0x31, 0x2f); // SP_VT_DLT_REG V total different threshold
// Timer value control
writeOneByte(0x33, 0x3a); // SP_H_TIMER_VAL H timer value for h detect (was 0x28)
writeOneByte(0x34, 0x05); // SP_V_TIMER_VAL V timer for V detect // 0_16 vsactive
// Sync separation control
if (rto->videoStandardInput == 0) GBS::SP_DLT_REG::write(0x70); // 5_35 130 too much for ps2 1080i, 0xb0 for 1080p
else if (rto->videoStandardInput <= 4) GBS::SP_DLT_REG::write(0x130); // would be best to measure somehow
else if (rto->videoStandardInput <= 6) GBS::SP_DLT_REG::write(0x110);
else if (rto->videoStandardInput == 7) GBS::SP_DLT_REG::write(0x70);
else GBS::SP_DLT_REG::write(0x70);
GBS::SP_H_PULSE_IGNOR::write(0x02); // filter very short pulses, test with MS mode vs ps2 1080p (0x13 vs 0x05)
// leave out pre / post coast here
GBS::SP_H_TOTAL_EQ_THD::write(10); // 5_3a range from 0x03 to xxx
// test NTSC: s5s3bs11 s5s3fs09 s5s40s0b
// test PAL: s5s3bs11 s5s3fs38 s5s40s3c
GBS::SP_SDCS_VSST_REG_H::write(0);
GBS::SP_SDCS_VSSP_REG_H::write(0);
GBS::SP_SDCS_VSST_REG_L::write(12); // 5_3f test with bypass mode: t0t4ft7 t0t4bt2 t5t56t4 t5t11t3
GBS::SP_SDCS_VSSP_REG_L::write(11); // 5_40 // was 11
GBS::SP_CS_HS_ST::write(0); // 5_45
GBS::SP_CS_HS_SP::write(0x40); // 5_47 720p source needs ~20 range, may be necessary to adjust at runtime, based on source res
writeOneByte(0x49, 0x00); // retime HS start for RGB+HV rgbhv: 20
writeOneByte(0x4a, 0x00); //
writeOneByte(0x4b, 0x44); // retime HS stop rgbhv: 50
writeOneByte(0x4c, 0x00); //
writeOneByte(0x51, 0x02); // 0x00 rgbhv: 2
writeOneByte(0x52, 0x00); // 0xc0
writeOneByte(0x53, 0x06); // 0x05 rgbhv: 6
writeOneByte(0x54, 0x00); // 0xc0
if (rto->videoStandardInput != 15 && (GBS::GBS_OPTION_SCALING_RGBHV::read() != 1)) {
GBS::SP_CLAMP_MANUAL::write(0); // 0 = automatic on/off possible
GBS::SP_CLP_SRC_SEL::write(0); // clamp source 1: pixel clock, 0: 27mhz // was 1 but the pixel clock isn't available at first
GBS::SP_NO_CLAMP_REG::write(1); // 5_57_0 unlock clamp
GBS::SP_SOG_MODE::write(1);
GBS::SP_H_CST_ST::write(0x20); // 5_4d
GBS::SP_H_CST_SP::write(0x100); // 5_4f // how low (high) may this go? source dependant
GBS::SP_DIS_SUB_COAST::write(1); // auto coast initially off (vsync H pulse coast)
GBS::SP_HCST_AUTO_EN::write(0);
}
GBS::SP_HS_REG::write(1); // 5_57 7
GBS::SP_HS_PROC_INV_REG::write(0); // no SP sync inversion
GBS::SP_VS_PROC_INV_REG::write(0); //
writeOneByte(0x58, 0x05); //rgbhv: 0
writeOneByte(0x59, 0x00); //rgbhv: 0
writeOneByte(0x5a, 0x01); //rgbhv: 0 // was 0x05 but 480p ps2 doesnt like it
writeOneByte(0x5b, 0x00); //rgbhv: 0
writeOneByte(0x5c, 0x03); //rgbhv: 0
writeOneByte(0x5d, 0x02); //rgbhv: 0 // range: 0 - 0x20 (how long should clamp stay off)
}
// Sync detect resolution: 5bits; comparator voltage range 10mv~320mv.
// -> 10mV per step; if cables and source are to standard (level 6 = 60mV)
void setAndUpdateSogLevel(uint8_t level) {
rto->currentLevelSOG = level & 0x1f;
GBS::ADC_SOGCTRL::write(level);
delay(8); togglePhaseAdjustUnits(); delay(8);
setAndLatchPhaseSP(); delay(2); setAndLatchPhaseADC();
delay(2); latchPLLAD();
GBS::INTERRUPT_CONTROL_00::write(0xff); // reset irq status
GBS::INTERRUPT_CONTROL_00::write(0x00);
Serial.print("sog: "); Serial.println(rto->currentLevelSOG);
}
// in operation: t5t04t1 for 10% lower power on ADC
// also: s0s40s1c for 5% (lower memclock of 108mhz)
// for some reason: t0t45t2 t0t45t4 (enable SDAC, output max voltage) 5% lower done in presets
// t0t4ft4 clock out should be off
// s4s01s20 (was 30) faster latency // unstable at 108mhz
// both phase controls off saves some power 506ma > 493ma
// oversample ratio can save 10% at 1x
// t3t24t3 VDS_TAP6_BYPS on can save 2%
// Generally, the ADC has to stay enabled to perform SOG separation and thus "see" a source.
// It is possible to run in low power mode.
// Function should not further nest, so it can be called in syncwatcher
void goLowPowerWithInputDetection() {
GBS::OUT_SYNC_CNTRL::write(0); // no H / V sync out to PAD
GBS::DAC_RGBS_PWDNZ::write(0); // direct disableDAC()
//zeroAll();
setResetParameters(); // includes rto->videoStandardInput = 0
setSpParameters();
delay(100);
rto->isInLowPowerMode = true;
SerialM.println("Scanning inputs for sources ...");
LEDOFF;
}
void optimizeSogLevel() {
if (rto->boardHasPower == false) // checkBoardPower is too invasive now
{
return;
}
if (rto->videoStandardInput == 15 || GBS::SP_SOG_MODE::read() != 1) {
rto->thisSourceMaxLevelSOG = 14;
return;
}
if (rto->thisSourceMaxLevelSOG != 31) {
if (rto->thisSourceMaxLevelSOG >= 4) {
rto->currentLevelSOG = rto->thisSourceMaxLevelSOG;
} else {
rto->currentLevelSOG = 14; // max level was < 4, so better restart search
}
} else {
rto->currentLevelSOG = 14;
}
setAndUpdateSogLevel(rto->currentLevelSOG);
GBS::ADC_TEST_0C_BIT4::write(1); // ignore previous filter setting
//boolean coastWasEnabled = !!GBS::SP_DIS_SUB_COAST::read();
//GBS::SP_DIS_SUB_COAST::write(1);
//resetSyncProcessor(); //delay(400);
resetModeDetect();
delay(100);
//unfreezeVideo();
delay(160);
while (rto->currentLevelSOG >= 0) {
uint8_t syncGoodCounter = 0;
unsigned long timeout = millis();
while ((millis() - timeout) < 370) {
if ((getStatus16SpHsStable() == 1) && (GBS::STATUS_SYNC_PROC_HLOW_LEN::read() > 10))
{
syncGoodCounter++;
if (syncGoodCounter >= 30) { break; }
}
}
if (syncGoodCounter >= 30) {
delay(180); // to recognize video mode
if (getVideoMode() != 0) {
syncGoodCounter = 0;
delay(20);
for (int a = 0; a < 50; a++) {
syncGoodCounter++;
if (GBS::STATUS_SYNC_PROC_HLOW_LEN::read() < 10) {
syncGoodCounter = 0;
break;
}
}
if (syncGoodCounter >= 49) {
//Serial.print(" @SOG "); Serial.print(rto->currentLevelSOG);
//Serial.print(" STATUS_00: ");
//uint8_t status00 = GBS::STATUS_00::read();
//Serial.println(status00, HEX);
break; // found, exit
}
else {
//Serial.print(" inner test failed syncGoodCounter: "); Serial.println(syncGoodCounter);
}
}
else { // getVideoMode() == 0
//Serial.print("sog-- syncGoodCounter: "); Serial.println(syncGoodCounter);
}
}
else { // syncGoodCounter < 40
//Serial.print("outer test failed syncGoodCounter: "); Serial.println(syncGoodCounter);
}
// first attempt toggling sog filter (5_0c 4)
if (GBS::ADC_TEST_0C_BIT4::read() == 1) {
GBS::ADC_TEST_0C_BIT4::write(0);
//Serial.println("filt off, back to test");
continue; // back to test
}
GBS::ADC_TEST_0C_BIT4::write(1);
if (rto->currentLevelSOG >= 2) {
rto->currentLevelSOG -= 2;
setAndUpdateSogLevel(rto->currentLevelSOG);
delay(180); // time for sog to settle
}
else { break; } // level = 0, break
}
rto->thisSourceMaxLevelSOG = rto->currentLevelSOG;
//if (coastWasEnabled) {
// GBS::SP_DIS_SUB_COAST::write(0);
//}
}
void switchSyncProcessingMode(uint8_t mode) {
if (mode) {
GBS::SP_PRE_COAST::write(0);
GBS::SP_POST_COAST::write(0);
GBS::ADC_SOGEN::write(0);
GBS::SP_SOG_MODE::write(0);
GBS::SP_CLAMP_MANUAL::write(1);
GBS::SP_NO_COAST_REG::write(1);
}
else {
SerialM.println("todo..");
}
}
// GBS boards have 2 potential sync sources:
// - RCA connectors
// - VGA input / 5 pin RGBS header / 8 pin VGA header (all 3 are shared electrically)
// This routine looks for sync on the currently active input. If it finds it, the input is returned.
// If it doesn't find sync, it switches the input and returns 0, so that an active input will be found eventually.
uint8_t detectAndSwitchToActiveInput() { // if any
uint8_t currentInput = GBS::ADC_INPUT_SEL::read();
unsigned long timeout = millis();
while (millis() - timeout < 450) {
delay(10);
handleWiFi();
//uint8_t videoMode = getVideoMode();
boolean stable = getStatus16SpHsStable();
if (/*(videoMode > 0) && */stable)
{
currentInput = GBS::ADC_INPUT_SEL::read();
SerialM.print("Activity detected, input: ");
if(currentInput == 1) SerialM.println("RGB");
else SerialM.println("Component");
if (currentInput == 1) { // RGBS or RGBHV
boolean vsyncActive = 0;
unsigned long timeOutStart = millis();
while (!vsyncActive && ((millis() - timeOutStart) < 250)) { // vsync test
vsyncActive = GBS::STATUS_SYNC_PROC_VSACT::read();
handleWiFi(); // wifi stack
delay(1);
}
if (!vsyncActive) { // then do RGBS check
uint16_t testCycle = 0;
timeOutStart = millis();
while ((millis() - timeOutStart) < 6000)
{
delay(2);
if (getVideoMode() > 0) {
return 1;
}
testCycle++;
// post coast 18 can mislead occasionally (SNES 239 mode)
// but even then it still detects the video mode pretty well
if ((testCycle % 180) == 0) {
rto->currentLevelSOG += 2;
if (rto->currentLevelSOG >= 16) { rto->currentLevelSOG = 0; }
setAndUpdateSogLevel(rto->currentLevelSOG);
// if, after 160 testCycles at default sog level it didn't sync,
// assume thisSourceMaxLevelSOG is low
rto->thisSourceMaxLevelSOG = rto->currentLevelSOG;
}
}
rto->currentLevelSOG = rto->thisSourceMaxLevelSOG = 8;
setAndUpdateSogLevel(rto->currentLevelSOG);
return 1; //anyway, let later stage deal with it
}
// if VSync is active, it's RGBHV or RGBHV with CSync on HS pin
if (vsyncActive) {
SerialM.println("VSync: present");
boolean hsyncActive = 0;
timeOutStart = millis();
while (!hsyncActive && millis() - timeOutStart < 400) {
hsyncActive = GBS::STATUS_SYNC_PROC_HSACT::read();
handleWiFi(); // wifi stack
delay(1);
}
if (hsyncActive) {
SerialM.println("HSync: present");
// The HSync pin is setup to detect CSync, if present (SOG mode on, coasting setup, debug bus setup, etc)
GBS::SP_SOG_SRC_SEL::write(1); // but this may be needed (HS pin as SOG source)
short decodeSuccess = 0;
for (int i = 0; i < 2; i++)
{
// no success if: no signal at all (returns 0.0f), no embedded VSync (returns ~18.5f)
if (getSourceFieldRate(1) > 40.0f) decodeSuccess++; // properly decoded vsync from 40 to xx Hz
}
if (decodeSuccess == 2) { rto->syncTypeCsync = true; }