-
Notifications
You must be signed in to change notification settings - Fork 10
/
APC.ino
executable file
·2673 lines (2536 loc) · 155 KB
/
APC.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 <SdFat.h> // APC.ino is the operating system for the Arduino Pinball Controller
SdFat SD;
#include <SPI.h>
#include "Arduino.h"
#include "Sound.h"
#define UpDown 53 // arduino pin connected to the auto/up - manual/Down button
#define Blanking 22 // arduino pin to control the blanking signal
#define VolumePin 13 // arduino pin to control the sound volume
#define SwMax 72 // number of existing switches (max. 72)
#define LampMax 64 // number of existing lamps (max. 64)
#define DispColumns 16 // Number of columns of the used display unit
#define AllSelects 938447360 // mask for all port C pins belonging to select signals except special switch Sel13 and HW_ext latch select Sel14
#define Sel5 2097152 // mask for the Sel5 select signal
#define HwExtSels 606077440 // mask for all Hw extension port select signals
#define Sel14 8192 // mask for the Sel14 select signal
#define Sel13 16384 // mask for the Sel13 select signal
#define AllData 510
#define HwExtStackPosMax 20 // size of the HwExtBuffer
const char APC_Version[6] = "01.02"; // Current APC version - includes the other INO files also
void HandleBoolSetting(bool change);
void HandleTextSetting(bool change);
void HandleDisplaySetting(bool change);
void HandleNumSetting(bool change);
void HandleVolumeSetting(bool change);
void RestoreDefaults(bool change);
void ExitSettings(bool change);
void watchdogSetup(void){ }
byte (*PinMameException)(byte, byte);
const byte AlphaUpper[128] = {0,0,0,0,0,0,0,0,107,21,0,0,0,0,0,0,0,0,0,0,64,191,64,21,0,0,64,4,0,0,0,40, // Blank $ * + - / for upper row alphanumeric displays
63,0,6,0,93,4,15,4,102,4,107,4,123,4,14,0,127,4,111,4,0,0,0,0,0,136,65,4,0,34,0,0,0,0, // 0 1 2 3 4 5 6 7 8 9 < = > and fill bytes
126,4,15,21,57,0,15,17,121,4,120,4,59,4,118,4,0,17,23,0,112,136,49,0,54,10,54,130,63,0, // Pattern A B C D E F G H I J K L M N O
124,4,63,128,124,132,107,4,8,17,55,0,48,40,54,160,0,170,0,26,9,40,0,0,0,0,0,0,0,0,1,0}; // Pattern P Q R S T U V W X Y Z _
const byte AlphaLower[128] = {0,0,0,0,0,0,0,0,182,35,0,0,0,0,0,0,0,0,0,0,2,247,2,35,0,0,2,2,0,0,0,80, // Blank $ * + - / for lower row alphanumeric displays
252,0,136,0,122,2,184,2,142,2,182,2,246,2,152,0,254,2,190,2,0,0,0,0,0,68,0,0,0,144,0,0,0,0, // 0 1 2 3 4 5 6 7 8 9 < > and fill bytes
222,2,184,35,116,0,184,33,118,2,86,2,244,2,206,2,0,33,232,0,70,68,100,0,204,192,204,132,252,0, // Pattern A B C D E F G H I J K L M N O
94,2,252,4,94,6,182,2,16,33,236,0,68,80,204,20,0,212,0,224,48,80,0,0,0,0,0,0,0,0,32,0}; // Pattern P Q R S T U V W X Y Z _
const byte NumLower[118] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Blank and fill bytes for lower row numeric displays
252,0,136,0,122,0,186,0,142,0,182,0,246,0,152,0,254,0,190,0,0,0,0,0,70,0,34,0,138,0,184,0,0,0, // 0 1 2 3 4 5 6 7 8 9 = and fill bytes
222,0,230,0,116,0,234,0,118,0,86,0,246,0,206,0,68,0,232,0,206,0,100,0,194,0,194,0,226,0, // Pattern A B C D E F G H I J K L M N O
94,0,252,0,66,0,182,0,84,0,236,0,236,0,236,0,206,0,78,0,122,0}; // Pattern P Q R S T U V W X Y Z
const byte LeftCredit[118] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Blank and fill bytes for the left credit (numeric)
63,0,6,0,93,0,79,0,102,0,107,0,123,0,14,0,127,0,111,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0 1 2 3 4 5 6 7 8 9 and fill bytes
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Pattern A B C D E F G H I J K L M N O
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // Pattern P Q R S T U V W X Y Z
const byte RightCredit[118] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Blank and fill bytes for the right credit (numeric)
252,0,136,0,122,0,186,0,142,0,182,0,246,0,152,0,254,0,190,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0 1 2 3 4 5 6 7 8 9 and fill bytes
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Pattern A B C D E F G H I J K L M N O
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // Pattern P Q R S T U V W X Y Z
const int LampPeriod = 2; // Lamp cycle period in ms
const byte NoLamps[8] = {0,0,0,0,0,0,0,0};
const byte AllLamps[8] = {255,255,255,255,255,255,255,255};
// system variables
unsigned int IntBuffer; // general purpose buffer
byte ByteBuffer = 0; // general purpose buffer
byte ByteBuffer2 = 0; // general purpose buffer
byte ByteBuffer3 = 0; // general purpose buffer
byte i = 0; // general purpose counter
byte IRQerror = 0; // general purpose counter
bool SDfound = false; // SD card present?
volatile byte SwitchStack = 0; // determines which switch events stack is active
byte ChangedSw[2][30]; // two stacks of switches with pending events
byte SwEvents[2]; // contains the number of pending switch events in each stack
uint32_t SwitchRows[10] = {29425756,29425756,29425756,29425756,29425756,29425756,29425756,29425756,29425756,29425756};// stores the status of all switch rows
const byte *DispRow1; // determines which patterns are to be shown (2 lines with 16 chars each)
const byte *DispRow2;
const byte *DispPattern1; // points to the correct character patterns for this kind of display
const byte *DispPattern2;
byte DisplayUpper[32]; // changeable display buffer
byte DisplayLower[32];
byte DisplayUpper2[32]; // second changeable display buffer
byte DisplayLower2[32];
const uint16_t *LEDpatDuration; // sets the time until the next LED pattern is shown
const byte *LEDpattern; // determines which LED pattern is to be shown (only active with 'LED lamps' = 'Additional' and depends on the 'No of LEDs' setting)
const byte *LEDpointer; // Pointer to the LED flow to be shown
void (*LEDreturn)(byte); // Pointer to the procedure to be executed after the LED flow has been shown
const byte *LampPattern; // determines which lamp pattern is to be shown (depends on the 'Backbox Lamps' setting)
const byte *LampBuffer;
byte LampColumns[8]; // stores the status of all lamp columns
bool StrobeLightsOn; // Indicates that the playfield lamps are strobing
const struct LampPat *PatPointer; // Pointer to the lamp flow to be shown
struct LampPat { // a lamp pattern sequence played by ShowLampPatterns
uint16_t Duration;
byte Pattern[8];};
struct LampFlow { // defines a series of lamp patterns shown by AttractLampCycle
uint16_t Repeat;
const struct LampPat *FlowPat;};
short FlowRepeat = 0; // number of repeats for the LampFlow
void (*LampReturn)(byte); // Pointer to the procedure to be executed after the lamp flow has been shown
byte BlinkTimers = 0; // number of active blink timers
byte BlinkTimer[65]; // timer used for blinking lamps
byte BlinkingNo[65]; // number of lamps using this BlinkTimer
unsigned int BlinkPeriod[65]; // blink period for this timer
byte BlinkingLamps[65][65]; // [BlinkTimer] used by these [lamps]
byte SolMax = 24; // maximum number of solenoids
volatile bool SolChange = false; // Indicates that the state of a solenoid has to be changed
byte SolRecycleTime[22]; // recycle time for each solenoid
byte SolRecycleTimers[22]; // stores the numbers of the recycle timers for each solenoid
bool C_BankActive = false; // A/C relay currently doing C bank?
byte SolWaiting[256][2]; // list of waiting A/C solenoid requests
byte ACselectRelay = 0; // solenoid number of the A/C select relay
byte ActSolSlot = 0; // currently processed slot in SolWaiting
byte NextSolSlot = 0; // next free slot in SolWaiting
byte SettingsRepeatTimer = 0; // number of the timer of the key repeat function in the settings function
byte BallWatchdogTimer = 0; // number of the ball watchdog timer
byte CheckReleaseTimer = 0; // number of the timer for the ball release check
volatile bool BlockPrioTimers = false; // blocks the prio timer interrupt while timer properties are being changed
byte ActivePrioTimers = 0; // Number of active prio timers
unsigned int PrioTimerValue[9]; // Timer value
byte PrioTimerArgument[9];
void (*PrioTimerEvent[9])(byte); // pointers to the procedures to be executed on the prio timer event
volatile bool BlockTimers = false; // blocks the timer interrupt while timer properties are being changed
byte ActiveTimers = 0; // Number of active timers
unsigned int TimerValue[64]; // Timer values
volatile byte TimerStack = 0; // determines which timer events stack is active
byte RunOutTimers[2][30]; // two stacks of timers with pending events
byte TimerEvents[2]; // contains the number of pending timer events in each stack
byte TimerArgument[64];
void (*TimerEvent[64])(byte); // pointers to the procedures to be executed on the timer event
void (*Switch_Pressed)(byte); // Pointer to current behavior mode for activated switches
void (*Switch_Released)(byte); // Pointer to current behavior mode for released switches
char EnterIni[3];
byte HwExt_Buf[20][2]; // ringbuffer for bytes to be send to the HW_ext interface (first bytes specifies the select line to be activated
byte HwExtIRQpos = 0; // next buffer position for the interrupt to work on
byte HwExtBufPos = 0; // next buffer position to be written to
uint16_t *SoundBuffer; // buffers sound data from SD to be processed by interrupt
uint16_t *MusicBuffer; // buffers music data from SD to be processed by interrupt
uint16_t *Buffer16b; // 16bit pointer to the audio DAC buffer
uint32_t *Buffer32b; // 32bit pointer to the audio DAC buffer
byte MBP = 0; // Music Buffer Pointer - next block to write to inside of the music buffer
byte MusicIRpos = 0; // next block of the music buffer to be read from the interrupt
byte StopMusic = 0; // last music buffer block with music data
bool StartMusic = false; // music startup active -> filling music buffer
bool PlayingMusic = false; // StartMusic done -> continuously playing music
byte MusicVolume = 0; // 0 = max volume
File MusicFile; // file handle for the music file (SD)
byte AfterMusicPending = 0; // indicates an after music event -> 0 - no event, 1 - event pending, 2 - event is blocked by StopPlayingMusic
void (*AfterMusic)(byte) = 0; // program to execute after music file has ended
byte MusicPriority = 0; // stores the priority of the music file currently being played
byte SBP = 0; // Sound Buffer Pointer - next block to write to inside of the music buffer
byte SoundIRpos = 0; // next block of the sound buffer to be read from the interrupt
byte StopSound = 0; // last sound buffer block with sound data
bool StartSound = false; // sound startup active -> filling sound buffer
bool PlayingSound = false; // StartSound done -> continuously playing sound
File SoundFile; // file handle for the sound file (SD)
byte AfterSoundPending = 0; // indicates an after sound event -> 0 - no event, 1 - event pending, 2 - event is blocked by StopPlayingSound
void (*AfterSound)(byte) = 0; // program to execute after sound file has ended
byte SoundPriority = 0; // stores the priority of the sound file currently being played
bool SoundPrio = false; // indicates which channel has to be processed first
const char TestSounds[3][15] = {{"MUSIC.SND"},{"SOUND.SND"},0};
byte APC_settings[64]; // system settings to be stored on the SD
byte game_settings[64]; // game settings to be stored on the SD
byte *SettingsPointer; // points to the settings being changed
const char *SettingsFileName; // points to the settings file currently being edited
const unsigned long SwitchMask[8] = {65536, 16777216, 8388608, 4194304, 64, 16, 8, 4};
uint16_t SwDrvMask = 2; // mask for switch row select
byte SolBuffer[4]; // stores the state of the solenoid latches + solenoid_exp board
bool OnBoardCom = false; // on board Pi detected?
struct SettingTopic { // one topic of a list of settings
char Text[17]; // display text
void (*EventPointer)(bool); // Pointer to the subroutine to process this topic
const char *TxTpointer; // if text setting -> pointer to a text array
byte LowerLimit; // if num setting -> lower limit of selection value
byte UpperLimit;}; // if text setting -> amount of text entries -1 / if num setting -> upper limit of selection value
const char APC_set_file_name[13] = "APC_SET.BIN";
const byte APC_defaults[64] = {0,3,3,1,2,0,0,0, // system default settings
64,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0};
#define DisplayType 0 // which display is used?
#define ActiveGame 1 // Select the active game
#define NofBalls 2 // Balls per game
#define FreeGame 3 // Free game mode?
#define ConnType 4 // Remote control mode?
#define DimInserts 5 // Reduce lighting time of playfield lamps by 50%
#define Volume 6 // Volume of the speaker
#define LEDsetting 7 // Setting for the APC_LED_EXP board
#define NumOfLEDs 8 // The length of the LED stripe. Setting is only effective when 'Additional' is selected as 'LED lamps' setting
#define SolenoidExp 9 // Solenoid expander present?
#define DebugMode 10 // debug mode enabled?
#define BackboxLamps 11 // Column of backbox lamps
const char TxTGameSelect[5][17] = {{" BASE CODE "},{" BLACK KNIGHT "},{" PINBOT "},{"REMOTE CONTROL "},{" TUTORIAL "}};
const char TxTLEDSelect[4][17] = {{" NO LEDS "},{" ADDITIONAL "},{"PLAYFLD ONLY "},{"PLAYFLDBACKBOX "}};
const char TxTDisplaySelect[9][17] = {{"4 ALPHA+CREDIT "},{" SYS11 PINBOT "},{" SYS11 F-14 "},{" SYS11 BK2K "},{" SYS11 TAXI "},{" SYS11 RIVERBOAT"},{" DATA EAST 2X16 "},{"123456123456 "},{"12345671234567 "}};
const char TxTConType[3][17] = {{" OFF "},{" ONBOARD "},{" USB "}};
const char TxTLampColSelect[3][17] = {{" COLUMN1 "},{" COLUMN8 "},{" NONE "}};
const struct SettingTopic APC_setList[15] = {
{"DISPLAY TYPE ",HandleDisplaySetting,&TxTDisplaySelect[0][0],0,8},
{" ACTIVE GAME ",HandleTextSetting,&TxTGameSelect[0][0],0,4},
{" NO OF BALLS ",HandleNumSetting,0,1,5},
{" FREE GAME ",HandleBoolSetting,0,0,0},
{"CONNECT TYPE ",HandleTextSetting,&TxTConType[0][0],0,2},
{" DIM INSERTS ",HandleBoolSetting,0,0,0},
{"SPEAKER VOLUME ",HandleVolumeSetting,0,0,255},
{" LED LAMPS ",HandleTextSetting,&TxTLEDSelect[0][0],0,3},
{" NO OF LEDS ",HandleNumSetting,0,1,192},
{"SOL EXP BOARD ",HandleBoolSetting,0,0,0},
{" DEBUG MODE ",HandleBoolSetting,0,0,0},
{"BACKBOX LAMPS ",HandleTextSetting,&TxTLampColSelect[0][0],0,2},
{"RESTOREDEFAULT ",RestoreDefaults,0,0,0},
{" EXIT SETTNGS ",ExitSettings,0,0,0},
{"",NULL,0,0,0}};
struct GameDef {
const struct SettingTopic *GameSettingsList; // points to the settings definition of the current game
byte *GameDefaultsPointer; // points to the default settings of the selected game
const char *GameSettingsFileName; // points to the name of the settings file for the selected game
const char *HighScoresFileName; // contains the name of the high scores file for the selected game
void (*AttractMode)(); // Pointer to Attract mode of current game
const unsigned int *SolTimes; // Default activation times of solenoids
};
struct GameDef GameDefinition; // invoke a game definition structure
const struct SettingTopic *SettingsList; // points to the current settings menu list
// game variables
bool BlockOuthole = false; // blocks the outhole as long as the previous ball loss is not handled completely
byte LockedBalls[5]; // locked balls of all players (starting from 1)
byte InLock = 0; // number of balls currently in lock
byte Multiballs = 1; // balls on playfield (same as multiball point multiplier)
byte NoPlayers = 0; // number of players
byte Player = 0; // current player
byte Coins = 0; // inserted coins
byte Ball = 0; // No of current ball
byte ExBalls = 0; // No of extra balls
byte Bonus = 1; // bonus points of the current player
byte BonusMultiplier = 1; // current bonus multiplier
unsigned int Points[5]; // points of all players (starting from 1)
struct HighScores {
unsigned int Scores[4] = {4000000, 3000000, 2000000, 1000000}; // all time high scores
char Initials[12] = {49,49,49,50,50,50,51,51,51,52,52,52};};
struct HighScores HallOfFame;
byte AppByte; // general purpose application buffer
byte AppByte2; // general purpose application buffer
byte AppByte3; // general purpose application buffer
bool AppBool = false; // general purpose application bool
void setup() {
pinMode(Blanking, OUTPUT); // initialize blanking pin
pinMode(VolumePin, OUTPUT); // initialize volume PWM pin
pinMode(2, INPUT); // pin for onboard PI detection
digitalWrite(Blanking, LOW); // and activate the blanking
digitalWrite(VolumePin, HIGH); // set volume to 0
pinMode(UpDown, INPUT); // initialize Up/Down pin
//Serial.begin(115200); // needed for USB and serial communication
SPI.begin(); // needed for SD card handling
REG_PIOC_PER = 871363582; // set required Port C pins to controlled In/Out
REG_PIOC_PUDR = 871363582; // disable Pull-ups
REG_PIOC_OER = 871363582; // set pins to outputs
REG_PIOA_PER = 29950044; // set required Port A pins to controlled In/Out
REG_PIOA_PUDR = 29425756; // disable Pull-ups
REG_PIOA_ODR = 29425756; // set pins to inputs
REG_PIOA_OER = 524288; // set pin 19 to output (DisBlank)
REG_PIOD_PER = 47; // set required Port D pins to controlled In/Out
REG_PIOD_PUDR = 47; // disable Pull-ups
REG_PIOD_ODR = 32; // set RX3 to input
REG_PIOD_OER = 15; // set pins to outputs
REG_PIOD_CODR = 15; // clear strobe select signals
REG_PIOC_CODR = AllSelects; // clear all select signals
REG_PIOC_SODR = AllData; // set all pins of the HW_ext bus to high to suppress sounds from Sys7 sound boards
REG_PIOC_SODR = Sel13 + Sel14; // set the special switch select (Sel13 - low active) and the HW_ext_latch (Sel14)
REG_PIOC_CODR = Sel14; // deselect the HW_ext_latch
REG_PIOC_CODR = AllSelects + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = AllData - SwDrvMask; // put select pattern on data bus
REG_PIOC_SODR = 32768; // use Sel12
REG_PIOC_CODR = AllSelects + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = 16777216; // use Sel2
REG_PIOC_CODR = AllSelects + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = 8388608; // use Sel3
REG_PIOC_CODR = AllSelects + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = 4194304; // use Sel4
MusicBuffer = (uint16_t *) malloc(2048 * 2);
SoundBuffer = (uint16_t *) malloc(2048 * 2);
for (i=0; i<8; i++) { // initialize lamp status
LampColumns[i+1] = 0; }
for (i=0; i< 8; i++) { // initialize switch input pins
pinMode(54 + i, INPUT); }
g_Sound.begin(44100, 100); // initialize sound
Buffer32b = g_Sound.next; // fill first part of audio DAC buffer with silence
for (i=0; i<64; i++) {
*Buffer32b = 402655232; // 2048 on both channels and the channel 2 tag
Buffer32b++;}
g_Sound.next = Buffer32b;
g_Sound.enqueue();
pmc_set_writeprotect(false); // enable writing to pmc registers
pmc_enable_periph_clk(ID_TC7); // enable TC7
TC_Configure(/* clock */TC2,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
TC_SetRC(TC2, 1, 656); // set counter value to achieve an interrupt period of 1ms
TC_Start(TC2, 1);
TC2->TC_CHANNEL[1].TC_IER=TC_IER_CPCS; // IER = interrupt enable register
TC2->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS; // IDR = interrupt disable register
NVIC_EnableIRQ(TC7_IRQn); // enable interrupt
delay(1000);
watchdogEnable(2000); // set watchdog to 2s
DispPattern1 = AlphaUpper; // use character definitions for alphanumeric displays
DispPattern2 = AlphaLower;
DispRow1 = DisplayUpper; // use the standard display buffer
DispRow2 = DisplayLower;
LampPattern = NoLamps;
Switch_Pressed = DummyProcess;
Switch_Released = DummyProcess;
digitalWrite(Blanking, HIGH); // Release the blanking
if (SD.begin(52, SD_SCK_MHZ(20))) { // look for an SD card and set max SPI clock to 20MHz
WriteUpper("SD CARD FOUND ");
SDfound = true;}
else {
WriteUpper(" NO SD CARD ");}
Init_System();}
void Init_System() {
if (SDfound) { // SD card found?
File Settings = SD.open(APC_set_file_name); // look for system settings
if (!Settings) {
WriteLower("NO SYS SETTNGS "); // if no system settings
for(i=0;i<64;i++) { // use default settings
APC_settings[i] = APC_defaults[i];}}
else { // if system settings found
Settings.read(&APC_settings, sizeof APC_settings);} // read them
Settings.close();}
else { // no SD card?
for(i=0;i<64;i++) { // use default settings
APC_settings[i] = APC_defaults[i];}}
if (APC_settings[DisplayType] && APC_settings[DisplayType] != 3 && APC_settings[DisplayType] != 6) { // display with numerical lower row
DispPattern2 = NumLower;} // use patterns for num displays
else {
DispPattern2 = AlphaLower;}
if (APC_settings[DisplayType] < 7) { // non BCD display
WriteLower("APC REV ");
*(DisplayLower+24) = DispPattern2[2*(APC_Version[0]-32)];
*(DisplayLower+25) = DispPattern2[2*(APC_Version[0]-32)+1];
*(DisplayLower+26) = DispPattern2[2*(APC_Version[1]-32)] | 1; // Comma
*(DisplayLower+27) = DispPattern2[2*(APC_Version[1]-32)+1] | 8; // Dot
*(DisplayLower+28) = DispPattern2[2*(APC_Version[3]-32)];
*(DisplayLower+29) = DispPattern2[2*(APC_Version[3]-32)+1];
*(DisplayLower+30) = DispPattern2[2*(APC_Version[4]-32)];
*(DisplayLower+31) = DispPattern2[2*(APC_Version[4]-32)+1];}
else { // BCD display
*(DisplayLower+16) = ConvertNumLower((byte) 10,(byte) *(DisplayLower+24));
*(DisplayLower+18) = ConvertNumLower((byte) 10,(byte) *(DisplayLower+24));
*(DisplayLower+20) = ConvertNumLower((byte) APC_Version[0]-48,(byte) *(DisplayLower+24));
*(DisplayLower+22) = ConvertNumLower((byte) APC_Version[1]-48,(byte) *(DisplayLower+26));
*(DisplayLower+23) = 128; // Comma
*(DisplayLower+24) = ConvertNumLower((byte) APC_Version[3]-48,(byte) *(DisplayLower+28));
*(DisplayLower+26) = ConvertNumLower((byte) APC_Version[4]-48,(byte) *(DisplayLower+30));
*(DisplayLower+28) = ConvertNumLower((byte) 10,(byte) *(DisplayLower+24));
*(DisplayLower+30) = ConvertNumLower((byte) 10,(byte) *(DisplayLower+24));}
delay(2000);
Init_System2(0);}
void Init_System2(byte State) { // state = 0 will restore the settings if no card is found
switch(APC_settings[ActiveGame]) { // init calls for all valid games
case 0:
BC_init();
break;
case 1:
BK_init();
break;
case 2:
PB_init();
break;
case 3:
USB_init();
break;
case 4:
TT_init();
break;
default:
WriteUpper("NO GAMESELECTD ");
while (true) {}
}
if (APC_settings[DisplayType] && APC_settings[DisplayType] != 3 && APC_settings[DisplayType] != 6) { // display with numerical lower row
DispPattern2 = NumLower;} // use patterns for num displays
else {
DispPattern2 = AlphaLower;}
if (SDfound) {
File HighScore = SD.open(GameDefinition.HighScoresFileName);
if (!HighScore) {
WriteUpper("NO HIGHSCORES ");}
else {
HighScore.read(&HallOfFame, sizeof HallOfFame);}
HighScore.close();
File Settings = SD.open(GameDefinition.GameSettingsFileName);
if (!Settings) {
WriteLower("NO GAMESETTNGS ");
if (!State) { // only initialize the settings in the boot up run
for(i=0;i<64;i++) {
game_settings[i] = *(GameDefinition.GameDefaultsPointer+i);}}}
else {
Settings.read(&game_settings, sizeof game_settings);}
Settings.close();}
else {
if (!State) { // only initialize the settings in the boot up run
for(i=0;i<64;i++) {
game_settings[i] = *(GameDefinition.GameDefaultsPointer+i);}}}
digitalWrite(VolumePin,HIGH); // turn off the digital volume control
if (APC_settings[SolenoidExp]) { // solenoid exp board selected?
REG_PIOC_SODR = Sel14; // enable the HW_ext_latch
SolMax = 33;} // enable 8 more solenoids
else {
SolMax = 25;}
if (APC_settings[LEDsetting]) {
REG_PIOC_SODR = Sel14;} // enable the HW_ext_latch
if (State) {
if(APC_settings[LEDsetting]) { // LEDs selected?
LEDinit();} // set them up
ActivateTimer(2000, 0, EnterAttractMode);}
else {
delay(2000);
if(APC_settings[LEDsetting]) { // LEDs selected?
LEDinit();} // set them up
EnterAttractMode(0);}}
void EnterAttractMode(byte Event) { // Enter the attract mode from a timer
UNUSED(Event);
GameDefinition.AttractMode();}
void TC7_Handler() { // interrupt routine - runs every ms
TC_GetStatus(TC2, 1); // clear status
watchdogReset(); // reset system watchdog
static byte SwDrv = 0; // switch driver being accessed at the moment
static byte DispCol = 0; // display column being illuminated at the moment
static byte LampCol = 0; // lamp column being illuminated at the moment
static byte LampWait = 1; // counter for lamp waiting time until next column is applied
static uint16_t LampColMask = 2; // mask for lamp column select
const uint32_t HwExtSelMask[5] = {2097152, 536870912, 512, 67108864, 8192}; // mask for sel5, sel6, sel7, SPI_CS1, Sel14
int i; // general purpose counter
uint32_t Buff;
uint16_t c;
if (APC_settings[DebugMode]) { // Show number of active timers if debug mode is on
if (APC_settings[DisplayType] == 7) { // Sys6 display
*(DisplayLower+28) = ConvertNumUpper((byte) ActiveTimers,(byte) *(DisplayLower+28));}
else if (APC_settings[DisplayType] == 8) { // Sys7 display
*(DisplayLower) = ConvertNumUpper((byte) ActiveTimers,(byte) *(DisplayLower));}
else { // Sys11 display
*(DisplayLower) = RightCredit[32 + 2 * ActiveTimers];}} // show the number of active timers
if (APC_settings[DimInserts] || (LampWait == LampPeriod)) { // if inserts have to be dimmed or waiting time has passed
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = 268435456;} // use Sel0 to disable column driver outputs at half time
// Display columns
if (APC_settings[DisplayType] < 7) { // Sys11 display
if (APC_settings[DisplayType] == 3 || APC_settings[DisplayType] == 5) // BK2K or Riverboat type display with inverted segments
REG_PIOC_SODR = AllData;
else // all other Sys11 displays
REG_PIOC_CODR = AllData;
REG_PIOC_SODR = 983040;} // use Sel8 - 11
else { // Sys7 - 9 display
REG_PIOC_SODR = AllData;
REG_PIOC_SODR = 131072; // use Sel10
REG_PIOC_CODR = AllData;
REG_PIOC_SODR = 65536;} // use Sel11
if (DispCol == (DispColumns-1)) { // last display column reached?
DispCol = 0;} // start again from column 0
else {
DispCol++;} // prepare for next column
REG_PIOD_CODR = 15; // clear strobe select signals
REG_PIOD_SODR = DispCol; // set display column
// Switches
i = 0;
Buff = REG_PIOA_PDSR;
while ((SwitchRows[SwDrv] != (Buff & 29425756)) && (SwEvents[SwitchStack] < 30)) { // as long as something is different at the switch port
if (Buff & SwitchMask[i]) { // scan the switch port bit by bit
if (!(SwitchRows[SwDrv] & SwitchMask[i])) { // different from the stored switch state?
SwitchRows[SwDrv] = SwitchRows[SwDrv] | SwitchMask[i]; // then change it
SwEvents[SwitchStack]++; // increase the number of pending switch events
c = 0;
while (ChangedSw[SwitchStack][c] && (c<29)) { // look for a free slot
c++;}
ChangedSw[SwitchStack][c] = SwDrv*8+i+1;}} // store the switch number to be processed in the main loop
else {
if (SwitchRows[SwDrv] & SwitchMask[i]) { // different from the stored switch state?
SwitchRows[SwDrv] = SwitchRows[SwDrv] & (29425756 - SwitchMask[i]); // then change it
SwEvents[SwitchStack]++; // increase the number of pending switch events
c = 0;
while (ChangedSw[SwitchStack][c] && (c<29)) { // look for a free slot
c++;}
ChangedSw[SwitchStack][c] = SwDrv*8+i+1;}} // store the switch number to be processed in the main loop
i++;}
SwDrvMask = SwDrvMask<<1; // and the corresponding select pattern
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
if (SwDrv < 7) {
REG_PIOC_SODR = AllData - SwDrvMask; // put select pattern on data bus
SwDrv++; // next switch driver
REG_PIOC_SODR = 32768;} // use Sel12
else {
if (SwDrv == 7) {
REG_PIOC_SODR = AllData;
SwDrv++; // next switch driver
REG_PIOC_SODR = 32768; // use Sel12
REG_PIOC_CODR = 16384;} // enable Sel13
else {
if (((bool)(SwitchRows[9] & 65536) != (bool)(REG_PIOB_PDSR & 16384)) && (SwEvents[SwitchStack] < 30)) { // check state of the Up/Down button
if (SwitchRows[9] & 65536) {
SwitchRows[9] = SwitchRows[9] & (29425756 - 65536);}
else {
SwitchRows[9] = SwitchRows[9] | 65536;}
SwEvents[SwitchStack]++; // increase the number of pending switch events
c = 0;
while (ChangedSw[SwitchStack][c] && (c<30)) { // look for a free slot
c++;}
ChangedSw[SwitchStack][c] = 73;}
SwDrvMask = 2;
REG_PIOC_SODR = AllData - SwDrvMask; // put select pattern on data bus
SwDrv = 0;
REG_PIOC_SODR = 32768+16384;}} // use Sel12 and disable Sel13
// Priority Timer
if (ActivePrioTimers) { // do not execute if activate or kill procedure is currently running
byte Buffer = ActivePrioTimers;
byte x = 1;
while (Buffer) { // Number of prio timers to process (active timers)
if (PrioTimerValue[x]) { // Timer active?
Buffer--; // Decrease number of timers to process
PrioTimerValue[x]--; // Decrease timer value
if (!PrioTimerValue[x]) { // Timer run out?
void (*TimerBuffer)(byte) = PrioTimerEvent[x];
if (!TimerBuffer) {
IRQerror = 40;}
else {
PrioTimerEvent[x] = 0;
TimerBuffer(PrioTimerArgument[x]);} // execute timer procedure
if (!ActivePrioTimers) {
IRQerror = 41;}
else {
ActivePrioTimers--;}}} // reduce number of active timers
x++;}} // increase timer counter
// Timer
if (!BlockTimers) { // do not execute if activate or kill procedure is currently running
byte Buffer = ActiveTimers;
byte x = 1;
while (Buffer) { // Number of timers to process (active timers)
if (TimerValue[x]) { // Timer active?
Buffer--; // Decrease number of timers to process
TimerValue[x]--; // Decrease timer value
if (!TimerValue[x]) { // Timer run out?
c = 0;
while (RunOutTimers[TimerStack][c]) {
c++;}
RunOutTimers[TimerStack][c] = x;
TimerEvents[TimerStack]++; // increase the number of pending timer events
if (!ActiveTimers) { // number of active timers already 0?
IRQerror = 9;} // that's wrong
else {
ActiveTimers--;}}} // reduce number of active timers
x++;}} // increase timer counter
// Solenoids
if (SolChange) { // is there a solenoid state to be changed?
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
c = SolBuffer[0];
REG_PIOC_SODR = c<<1;
REG_PIOC_SODR = 16777216; // select first latch
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
c = SolBuffer[1];
REG_PIOC_SODR = c<<1;
REG_PIOC_SODR = 8388608; // select second latch
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
c = SolBuffer[2];
REG_PIOC_SODR = c<<1;
REG_PIOC_SODR = 4194304;} // select third latch
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
// Display segments
if (APC_settings[DisplayType] == 3 || APC_settings[DisplayType] == 5) { // 2x16 alphanumeric display with inverted segments
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
byte Buf = ~(*(DispRow1+2*DispCol));
REG_PIOC_SODR = Buf<<1; // set 1st byte of the display pattern for the upper row
REG_PIOC_SODR = 524288; // use Sel8
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
Buf = ~(*(DispRow1+2*DispCol+1));
REG_PIOC_SODR = Buf<<1; // set 2nd byte of the display pattern for the upper row
REG_PIOC_SODR = 262144; // use Sel9
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
Buf = ~(*(DispRow2+2*DispCol));
REG_PIOC_SODR = Buf<<1; // set 1st byte of the display pattern for the lower row
REG_PIOC_SODR = 131072; // use Sel10
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
Buf = ~(*(DispRow2+2*DispCol+1));
REG_PIOC_SODR = Buf<<1; // set 1st byte of the display pattern for the lower row
REG_PIOC_SODR = 65536;} // use Sel11
else {
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
REG_PIOC_SODR = *(DispRow1+2*DispCol)<<1; // set 1st byte of the display pattern for the upper row
REG_PIOC_SODR = 524288; // use Sel8
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
REG_PIOC_SODR = *(DispRow1+2*DispCol+1)<<1; // set 2nd byte of the display pattern for the upper row
REG_PIOC_SODR = 262144; // use Sel9
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
REG_PIOC_SODR = *(DispRow2+2*DispCol)<<1; // set 1st byte of the display pattern for the lower row
REG_PIOC_SODR = 131072; // use Sel10
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals except HwExtSels and the data bus
REG_PIOC_SODR = *(DispRow2+2*DispCol+1)<<1; // set 1st byte of the display pattern for the lower row
REG_PIOC_SODR = 65536;} // use Sel11
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
// Lamps
if (APC_settings[LEDsetting] < 2) { // matrix lamps active?
if (LampWait == LampPeriod) { // Waiting time has passed
LampCol++; // prepare for next lamp column
LampColMask = LampColMask<<1;
if (APC_settings[BackboxLamps]) { // backbox lamps not in first column
if (LampCol == 8){ // max column exceeded?
LampCol = 0;
LampColMask = 2;}
if (LampCol == 7 && APC_settings[BackboxLamps] == 1){ // max column reached?
c = LampColumns[LampCol];} // last column is from LampColumns
else { // game has no backbox lamps
c = *(LampPattern+LampCol);}} // all other columns are referenced via LampPattern
else { // backbox lamps in first column
if (LampCol == 8){ // max column exceeded?
LampCol = 0;
LampColMask = 2;
c = LampColumns[LampCol];} // first column is from LampColumns
else {
c = *(LampPattern+LampCol);}} // all other columns are referenced via LampPattern
REG_PIOC_SODR = c<<1; // write lamp pattern
REG_PIOC_SODR = 33554432; // use Sel1
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
REG_PIOC_SODR = LampColMask;
LampWait = 1; // restart lamp waiting counter
REG_PIOC_SODR = 268435456;} // use Sel0
else { // waiting time has not yet passed
LampWait++;}} // increase wait counter
// Hardware extension interface
while (HwExtIRQpos != HwExtBufPos) { // for all bytes in the ringbuffer
HwExtIRQpos++; // go to next task
if (HwExtIRQpos >= HwExtStackPosMax) { // end of buffer reached?
HwExtIRQpos = 0;} // start from zero
REG_PIOC_CODR = AllSelects - HwExtSels + AllData; // clear all select signals and the data bus
c = HwExt_Buf[HwExtIRQpos][0]; // get data byte
REG_PIOC_SODR = c<<1; // and put it on the data bus
if (HwExt_Buf[HwExtIRQpos][1] & 128) { // rising select edge requested?
for (i=0;i<5;i++) { // for all HwExt selects
if (HwExt_Buf[HwExtIRQpos][1] & 1) { // is the corresponding bit set?
REG_PIOC_SODR = HwExtSelMask[i];} // generate a rising edge
HwExt_Buf[HwExtIRQpos][1] = HwExt_Buf[HwExtIRQpos][1]>>1;}} // shift to the next bit
else { // falling select edge requested
for (i=0;i<5;i++) { // for all HwExt selects
if (HwExt_Buf[HwExtIRQpos][1] & 1) { // is the corresponding bit set?
REG_PIOC_CODR = HwExtSelMask[i];} // generate a falling edge
HwExt_Buf[HwExtIRQpos][1] = HwExt_Buf[HwExtIRQpos][1]>>1;}}} // shift to the next bit
// Sound
if (g_Sound.next != g_Sound.running) {
if (PlayingMusic) {
if (PlayingSound) { // playing music and sound
Buffer16b = (uint16_t *) g_Sound.next; // get address of the next DAC buffer part to be filled
if (MusicVolume) { // reduce the music volume?
uint16_t Offset = 1024;
for (i=0; i<MusicVolume-1; i++) {
Offset = Offset + (512>>i);}
for (i=0; i<64; i++) {
*Buffer16b = (*(MusicBuffer+MusicIRpos*128+2*i) >> MusicVolume) + Offset; // write channel 1
Buffer16b++;
*Buffer16b = *(SoundBuffer+SoundIRpos*128+2*i) | 4096; // write channel 2
Buffer16b++;}}
else { // full volume
for (i=0; i<64; i++) {
*Buffer16b = *(MusicBuffer+MusicIRpos*128+2*i); // write channel 1
Buffer16b++;
*Buffer16b = *(SoundBuffer+SoundIRpos*128+2*i) | 4096; // write channel 2
Buffer16b++;}}
g_Sound.next = (uint32_t *) Buffer16b; // write back the updated address
g_Sound.enqueue();
MusicIRpos++; // increase the IRQ read pointer for the music buffer
if (MusicIRpos == 16) { // last buffer block read?
MusicIRpos = 0;} // start over
SoundIRpos++; // increase the IRQ read pointer for the sound buffer
if (SoundIRpos == 16) { // last buffer block read?
SoundIRpos = 0;} // start over
if (StopMusic) { // end of music file reached?
if (MusicIRpos == MBP) { // remaining music data played?
PlayingMusic = false; // stop playing music
MBP = 0; // reset write pointer
MusicIRpos = 0; // reset read pointer
StopMusic = 0; // mark stop sequence as completed
if (AfterMusic && AfterMusicPending != 2) { // anything to do after the music?
AfterMusicPending = 1;} // indicate now is the time to do so
else {
AfterMusicPending = 0;}}}
if (StopSound) { // end of sound file reached?
if (SoundIRpos == SBP) { // remaining sound data played?
PlayingSound = false; // stop playing sound
SBP = 0; // reset write pointer
SoundIRpos = 0; // reset read pointer
StopSound = 0; // mark stop sequence as completed
if (AfterSound && AfterSoundPending != 2) { // anything to do after the sound and not blocked by StopPlayingSound?
AfterSoundPending = 1;} // indicate now is the time to do so
else {
AfterSoundPending = 0;}}}}
else { // playing music only
Buffer16b = (uint16_t *) g_Sound.next; // same as above but music only
if (MusicVolume) { // reduce the music volume?
uint16_t Offset = 1024;
for (i=0; i<MusicVolume-1; i++) {
Offset = Offset + (512>>i);}
for (i=0; i<64; i++) {
*Buffer16b = (*(MusicBuffer+MusicIRpos*128+2*i) >> MusicVolume) + Offset;
Buffer16b++;
*Buffer16b = 6144; // 2048 | 4096
Buffer16b++;}}
else { // full volume
for (i=0; i<64; i++) {
*Buffer16b = *(MusicBuffer+MusicIRpos*128+2*i);
Buffer16b++;
*Buffer16b = 6144; // 2048 | 4096
Buffer16b++;}}
g_Sound.next = (uint32_t *) Buffer16b;
g_Sound.enqueue();
MusicIRpos++;
if (MusicIRpos == 16) {
MusicIRpos = 0;}
if (StopMusic) {
if (MusicIRpos == MBP) {
PlayingMusic = false;
MBP = 0;
MusicIRpos = 0;
StopMusic = 0;
if (AfterMusic && AfterMusicPending != 2) {
AfterMusicPending = 1;}
else {
AfterMusicPending = 0;}}}}}
else { // not playing music
if (PlayingSound) { // playing sound only
Buffer16b = (uint16_t *) g_Sound.next; // same as above but sound only
for (i=0; i<64; i++) {
*Buffer16b = 2048;
Buffer16b++;
*Buffer16b = *(SoundBuffer+SoundIRpos*128+2*i) | 4096;
Buffer16b++;}
g_Sound.next = (uint32_t *) Buffer16b;
g_Sound.enqueue();
SoundIRpos++;
if (SoundIRpos == 16) {
SoundIRpos = 0;}
if (StopSound) {
if (SoundIRpos == SBP) {
PlayingSound = false;
SBP = 0;
SoundIRpos = 0;
StopSound = 0;
if (AfterSound && AfterSoundPending != 2) {
AfterSoundPending = 1;}
else {
AfterSoundPending = 0;}}}}
else { // neither sound nor music
Buffer32b = g_Sound.next;
for (i=0; i<64; i++) {
*Buffer32b = 402655232; // 2048 on both channels and the channel tag
Buffer32b++;}
g_Sound.next = Buffer32b;
g_Sound.enqueue();}}}}
void loop() {
byte c = 0; // initialize counter
byte i = 0;
if (IRQerror) {
ErrorHandler(IRQerror,0,0);
IRQerror = 0;}
if (SwEvents[SwitchStack]) { // switch event pending?
SwitchStack = 1-SwitchStack; // switch to the other stack to avoid a conflict with the interrupt
while (SwEvents[1-SwitchStack]) { // as long as there are switch events to process
if (ChangedSw[1-SwitchStack][c]) { // pending switch event found?
SwEvents[1-SwitchStack]--; // decrease number of pending events
i = ChangedSw[1-SwitchStack][c]; // buffer the switch number
ChangedSw[1-SwitchStack][c] = 0; // clear the event
if (QuerySwitch(i)) { // process SET switches
Switch_Pressed(i);} // access the set switch handler
else { // process released switches
Switch_Released(i);}} // access the released switch handler
if (c < 29) { // number of pending events still in the allowed range?
c++;} // increase counter
else {
if (c > 29) {
ErrorHandler(21,0,c);}}}}
c = 0; // initialize counter
if (TimerEvents[TimerStack]) { // timer event pending?
TimerStack = 1-TimerStack; // switch to the other stack to avoid a conflict with the interrupt
while (TimerEvents[1-TimerStack]) { // as long as there are timer events to process
if (RunOutTimers[1-TimerStack][c]) { // number of run out timer found?
TimerEvents[1-TimerStack]--; // decrease number of pending events
i = RunOutTimers[1-TimerStack][c]; // buffer the timer number
void (*TimerBuffer)(byte) = TimerEvent[i]; // Buffer the event for this timer
if (!TimerBuffer) { // TimerEvent must be specified
ErrorHandler(20,0,c);}
RunOutTimers[1-TimerStack][c] = 0; // delete the timer from the list
TimerEvent[i] = 0; // delete the event to show the timer as free
TimerBuffer(TimerArgument[i]);} // call event procedure
c++;}} // increase search counter
if (SoundPrio) { // which channel has to be processed first?
if (!StopSound && (SBP != SoundIRpos)) { // still sound data to read?
ReadSound();} // read it
else { // no sound data?
if (AfterSoundPending == 1) { // is there an after sound event pending?
AfterSoundPending = 0; // reset the flag
if (AfterSound) { // really?
AfterSound(0);}} // call it
else { // no after sound event
if (!StopMusic && (MBP != MusicIRpos)) { // proceed with music
ReadMusic();}
else { // no music data?
if (AfterMusicPending == 1) { // is there an after music event pending?
AfterMusicPending = 0; // reset the flag
if (AfterMusic) { // really?
AfterMusic(0);}}}}}} // call it
else { // same as above but with the priority on music
if (!StopMusic && (MBP != MusicIRpos)) {
ReadMusic();}
else {
if (AfterMusicPending == 1) {
AfterMusicPending = 0;
if (AfterMusic) {
AfterMusic(0);}}
else {
if (!StopSound && (SBP != SoundIRpos)) {
ReadSound();}
else {
if (AfterSoundPending == 1) {
AfterSoundPending = 0;
if (AfterSound) {
AfterSound(0);}}}}}}
if ((APC_settings[ActiveGame] == 3) && (APC_settings[ConnType])) { // Remote mode?
if (APC_settings[ConnType] == 1) { // onboard Pi selected?
if (!OnBoardCom && (REG_PIOB_PDSR & 33554432)) { // onboard com off and Pi detected?
Serial3.begin(115200); // needed for onboard serial communication
OnBoardCom = true;}}
if(USB_Available()) {
USB_SerialCommand();}}} // use the first received byte as a command
void ReadMusic() { // read music data from SD
if (MusicFile.available() > 255) { // enough data remaining in file to fill one block?
MusicFile.read(MusicBuffer+MBP*128,2*128); // read one block
MBP++; // increase read pointer
if (MBP == 16) { // last block reached?
MBP = 0; // start over
if (!PlayingMusic) { // not already playing?
PlayingMusic = true; // start playing
StartMusic = false;}}} // stop init phase
else { // not enough data to fill one block
byte Avail = MusicFile.available(); // determine how much is left
MusicFile.read(MusicBuffer+MBP*128,Avail); // read it
for (i=0; i<128-Avail/2; i++) { // fill the rest with silence
*(MusicBuffer+MBP*128+Avail/2+i) = 2048;}
MBP++; // increase read pointer
if (MBP == 16) { // last block read?
MBP = 0;} // start over
StopMusic = MBP; // mark this block as being the last
MusicFile.close();} // close file
SoundPrio = true;} // switch priority to sound
void ReadSound() { // same as above but for the sound channel
if (SoundFile.available() > 255) {
SoundFile.read(SoundBuffer+SBP*128,2*128);
SBP++;
if (SBP == 16) {
SBP = 0;
if (!PlayingSound) {
PlayingSound = true;
StartSound = false;}}}
else {
byte Avail = SoundFile.available();
SoundFile.read(SoundBuffer+SBP*128,Avail);
for (i=0; i<128-Avail/2; i++) {
*(SoundBuffer+SBP*128+Avail/2+i) = 2048;}
SBP++;
if (SBP == 16) {
SBP = 0;}
StopSound = SBP;
SoundFile.close();}
SoundPrio = false;}
byte LEDhandling(byte Command, byte Arg) { // main LED handler
static bool PolarityFlag; // stores whether the select has to be triggered by the rising or falling edge
static byte ChangeSequence = 0; // indicator needed for change operations
static byte *LEDstatus; // points to the status memory of the LEDs
static const byte *LEDselected; // buffer to sync the change of an LEDpattern to the command execution
static byte NumOfLEDbytes = 8; // stores the length of the LEDstatus memory
static byte LengthOfSyncCycle = 3; // stores the length of the sync cycle in ms
static byte SpcCommandLength[8]; // length in bytes of commands to be send to the LED exp board
static byte SpcBuffer[20]; // command bytes to be send to the LED exp board
static byte BufferRead = 0; // read pointer for ringbuffer SpcBuffer
static byte BufferWrite = 0; // write pointer for ringbuffer SpcBuffer
static byte SpcWriteCount = 0; // points to the next command byte to be send to the LED exp board
static byte SpcReadCount = 0; // counter for bytes to transmit
static byte Timer = 0;
switch(Command) {
case 0: // stop LEDhandling
if (Timer) { // LEDhandling active?
ChangeSequence = 2; // one more sync needed
free(LEDstatus); // free memory
SpcBuffer[BufferWrite] = 196; // write stop command to ringbuffer
BufferWrite++; // increase write pointer
if (BufferWrite > 19) { // end of ringbuffer reached?
BufferWrite = 0;} // start over
if (BufferWrite == BufferRead) { // ringbuffer full?
return(1);} // terminate write attempt
byte i = 0;
while (SpcCommandLength[i]) { // look for a free slot
i++;}
if (i > 7) { // no more than 8 commands at a time
return(1);}
SpcCommandLength[i] = 1; // write length of command to buffer
SpcWriteCount = 0;} // reset number for command entry
break;
case 1: // init
if (APC_settings[LEDsetting] == 1) { // LEDsetting = Additional?
if (!Timer) {
NumOfLEDbytes = APC_settings[NumOfLEDs] / 8; // calculate the needed memory for LEDstatus
if (APC_settings[NumOfLEDs] % 8) {
NumOfLEDbytes++;}
LEDstatus = (byte *) malloc(NumOfLEDbytes); // allocate memory
for (byte i=0; i<NumOfLEDbytes; i++) { // and delete it
LEDstatus[i] = 0;}
LengthOfSyncCycle = APC_settings[NumOfLEDs] / 24; // calculate the required length of the sync cycle
if (APC_settings[NumOfLEDs] % 24) {
LengthOfSyncCycle++;}
SpcBuffer[BufferWrite] = 193; // write to ringbuffer
BufferWrite++; // increase write pointer
if (BufferWrite > 19) { // end of ringbuffer reached?
BufferWrite = 0;} // start over
if (BufferWrite == BufferRead) { // ringbuffer full?
return(1);} // terminate write attempt
SpcBuffer[BufferWrite] = NumOfLEDbytes; // write to ringbuffer
BufferWrite++; // increase write pointer
if (BufferWrite > 19) { // end of ringbuffer reached?
BufferWrite = 0;} // start over
if (BufferWrite == BufferRead) { // ringbuffer full?
return(1);} // terminate write attempt
byte i = 0;
while (SpcCommandLength[i]) { // look for a free slot
i++;}
if (i > 7) { // no more than 8 commands at a time
return(1);}
SpcCommandLength[i] = 2; // write length of command to buffer
SpcWriteCount = 0; // reset number for command entry
Timer = ActivateTimer(1, NumOfLEDbytes, LEDtimer);} // start main timer
LEDpattern = LEDstatus;} // show LEDstatus
else { // LEDsetting != Additional
NumOfLEDbytes = 8;
LengthOfSyncCycle = 3;
if (!Timer) {
Timer = ActivateTimer(1, 8, LEDtimer);}} // start main timer
break;
case 2: // timer call
if (Arg > NumOfLEDbytes + LengthOfSyncCycle + 6) { // Sync over
Arg = 0;} // start from the beginning
if (Arg < NumOfLEDbytes) { // the first cycles are for transmitting the status of the lamp matrix
byte LampData;
if (ChangeSequence) { // end sequence running
if (ChangeSequence > 1) {
ChangeSequence--;}
LampData = 0;}
else { // normal operation
if (APC_settings[LEDsetting] > 1) { // playfield LEDs selected?
if (!Arg){ // max column reached?
LampData = LampColumns[Arg];}
else {
LampData = *(LampPattern+Arg);}}
else { // additional LEDs selected
LampData = *(LEDselected+Arg);}}
if (PolarityFlag) { // data bus of LED_exp board works with toggling select
PolarityFlag = false;