-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathArduino-Saturn-Controller.ino
More file actions
177 lines (159 loc) · 5.42 KB
/
Arduino-Saturn-Controller.ino
File metadata and controls
177 lines (159 loc) · 5.42 KB
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
/* Arduino Saturn Controller
* Author: Ryan Myers <ryan.p.myers@gmail.com>
*
* Copyright (c) 2021 Ryan Myers <https://ryanmyers.ca>
*
* GNU GENERAL PUBLIC LICENSE
* Version 3, 29 June 2007
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
volatile uint8_t OUTPUTS[4];
void setup()
{
//Setup controller pins Up, Down, Left, Right, A, B, C
// BCAUDLR-;
DDRB &= ~B11111110; //Set them up as inputs
PORTB |= B11111110; //Enable internal pull-ups
//Setup controller pin L
// -L------;
DDRC &= ~B01000000; //Set it up as input
PORTC |= B01000000; //Enable internal pull-ups
//Setup controller pins Z Y X and R as inputs
// Z--YXR--
DDRD &= ~B10011100; //Set them up as inputs
PORTD |= B10011100; //Enable internal pull-ups
//Setup controller pin Start
// -S------;
DDRE &= ~B01000000; //Set it up as input
PORTE |= B01000000; //Enable internal pull-ups
//Setup Saturn select pins S0 (TH) and S1 (TR)
// ------10
DDRD &= ~B00000011; //Set them up as inputs
PORTD |= B00000011; //Enable internal pull-ups
//Setup Saturn data pins D0, D1, D2, D3, and TL ACK
// 0123--T-
DDRF |= B11110010; //Set them up as outputs
PORTF |= B11110010; //Set them HIGH by default
// Interrupt 0 for clock (PD0, pin 3) (TH S0 on Saturn)
EICRA &= ~(bit(ISC00) | bit (ISC01)); // Clear existing flags of interrupt 0
EICRA |= bit (ISC00); // Set interrupt on rising and falling
// Interrupt 1 for clock (PD1, pin 2) (TR S1 on Saturn)
EICRA &= ~(bit(ISC10) | bit (ISC11)); // Clear existing flags of interrupt 1
EICRA |= bit (ISC10); // Set interrupt on rising and falling
// Enable both interrupts
EIMSK |= bit(INT0) | bit(INT1);
//Default outputs to high which means unpressed.
OUTPUTS[0] = B11110010; //ZYXR
OUTPUTS[1] = B11110010; //UDLR
OUTPUTS[2] = B11110010; //BCAS
OUTPUTS[3] = B00110010; //L
}
//Interrupt when Saturn S0 (TH) pin changes
ISR (INT0_vect, ISR_NAKED)
{
//Ideally this is all I would be able to do, but it's too slow
//PORTF = OUTPUTS[PIND & 0B00000011];
//This is *very* close to good enough
asm volatile (
"sbis %[rPIND], 0 \n"
"rjmp __TH0 \n"
"sbis %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT1] \n" //OUTPUT 1
"rjmp __TH1 \n"
"__TH0: \n"
"sbis %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT0] \n" //OUTPUT 0
"sbic %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT2] \n" //OUTPUT 2
"reti \n"
"__TH1: \n"
"sbis %[rPIND], 1 \n"
"reti \n"
"out %[rPORTF], %[OUTPUT3] \n" //OUTPUT 3
"reti \n"
:
:
[OUTPUT0]"r"(OUTPUTS[0]),
[OUTPUT1]"r"(OUTPUTS[1]),
[OUTPUT2]"r"(OUTPUTS[2]),
[OUTPUT3]"r"(OUTPUTS[3]),
[rPIND]"I"(_SFR_IO_ADDR(PIND)),
[rPORTF]"I"(_SFR_IO_ADDR(PORTF))
:
);
}
/**
* Interrupt when Saturn S1 (TR) pin changes
* This code is identical to the above with just different label names.
* I purposefully only used ASM that doesn't affect SREG so we didn't need to
* do any PUSH and POPS on any registers.
* This is *just* not fast enough because the OUTPUTS first get loaded into registers
* and that uses 8 cycles.
*/
ISR (INT1_vect, ISR_NAKED)
{
//Ideally this is all I would be able to do, but it's too slow
//PORTF = OUTPUTS[PIND & 0B00000011];
//This is *very* close to good enough
asm volatile (
"sbis %[rPIND], 0 \n"
"rjmp __TH0v2 \n"
"sbis %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT1] \n"
"rjmp __TH1v2 \n"
"__TH0v2: \n"
"sbis %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT0] \n"
"sbic %[rPIND], 1 \n"
"out %[rPORTF], %[OUTPUT2] \n"
"reti \n"
"__TH1v2: \n"
"sbis %[rPIND], 1 \n"
"reti \n"
"out %[rPORTF], %[OUTPUT3] \n"
"reti \n"
:
:
[OUTPUT0]"r"(OUTPUTS[0]),
[OUTPUT1]"r"(OUTPUTS[1]),
[OUTPUT2]"r"(OUTPUTS[2]),
[OUTPUT3]"r"(OUTPUTS[3]),
[rPIND]"I"(_SFR_IO_ADDR(PIND)),
[rPORTF]"I"(_SFR_IO_ADDR(PORTF))
:
);
}
/**
* During the main loop, just continuosly update the values of the outputs so they're ready when the interrupts fire.
*/
void loop()
{
//0:0 ZYXR--T-
//PIND = Z--YXR--
OUTPUTS[0] = ((PIND & B10000010) | ((PIND & B00011100) << 2)) | B00000010;
//0:1 UDLR--T-
//PINB = ---UDLR-
OUTPUTS[1] = ((PINB & B00011110) << 3) | B00000010;
//1:0 BCAS--T-
//PINB = BCA-----
//PINE = -S------
OUTPUTS[2] = ((PINB & B11100000) | ((PINE & B01000000) >> 2)) | B00000010;
//1:1 001L--T-
//PINC = -L------
//For this one in particular we need to set 001L according to the documentation here (page 97):
//https://cdn.preterhuman.net/texts/gaming_and_diversion/CONSOLES/sega/ST-169-R1-072694.pdf
OUTPUTS[3] = (((PINC & B01000000) >> 2) | B00100010);
}