-
Notifications
You must be signed in to change notification settings - Fork 2
/
SMPEmu.ino
250 lines (203 loc) · 7.06 KB
/
SMPEmu.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
// Elektronika SMP emulator
// by Genjitsu Labs, 2018
// version 0.0.2
#include <avr/pgmspace.h>
#include <avr/sleep.h>
/* Connect as follows:
* (see http://www.pisi.com.pl/piotr433/mk90cahe.htm for reference)
*
* Pin 2 Vcc of calculator into +5V of Arduino (when PC not connected to arduino!)
* Pin 3 CLK of calculator into pin 4 via R=220 Ohm
* Pin 4 DAT of calculator into pin 5 via R=270 Ohm
* ^^^^^^^ this helps in case there is a time when the calculator is listening but
* arduino is still in input mode, otherwise there is a risk of high current thru
* the ICs and it can burn your computer or arduino
* Pin 5 SEL of calculator into pin 3 of Arduino
* Pin 6 GND of calculator into GND of Arduino
*/
word current_address = 0x0; // 1-word address register of the SMP
boolean is_locked = false; // when the cartridge is locked, r/w instructions are ignored
byte current_command = 0x0;
/* Allowed commands:
* 0x00 Read status
* 0x10 Read postdecrement (unused in MK90)
* 0x20 Erase postdecrement (used in INIT of MK90)
* 0x80 Lock
* 0x90 Unlock
* 0xA0 Write address
* 0xB0 Read address (unused in MK90)
* 0xC0 Write postincrement
* 0xD0 Read postincrement
* 0xE0 Write postdecrement (unused in MK90)
*/
#define WAIT_CLOCK_FALL while( (PIND & B00010000) != 0x0 )
#define WAIT_CLOCK_RISE while( (PIND & B00010000) == 0x0 )
#define IS_SELECTED ( (PIND & B00001000) == 0x0 )
#define IS_NOT_SELECTED ( (PIND & B00001000) != 0x0 )
// Read a byte from input
byte do_get_byte() {
// Switch DATA pin 5 to INPUT
DDRD = B11000111;
byte current_byte = 0x0;
// Data written to the cartridge change at rising edges of the CLOCK pulses, and are shifted-in on falling edges of the CLOCK pulses beginning with the most significant bit.
for(byte i=8; i!=0; --i) {
// Reading 8 times
WAIT_CLOCK_FALL;
// shift left 1 bit
current_byte <<= 1;
if( (PIND & B00100000) > 0x0 )
current_byte += 1; // set last bit to 1 if DATA high
WAIT_CLOCK_RISE;
}
return current_byte;
}
// Send a byte to data pin
void do_send_byte(byte toSend) {
// Switch DATA pin 5 to OUTPUT
DDRD = B11100111;
byte current_byte = toSend;
// Data read from the cartridge change at falling edges of the CLOCK pulses, and are sampled at rising edges of the CLOCK pulses.
for(byte i=8; i!=0; --i) {
// Sending 8 times
WAIT_CLOCK_FALL;
// set output high if last bit is 1
if( (current_byte & 0x80) > 0x0 )
PORTD |= B00100000;
else
PORTD &= B11011111;
// shift left 1 bit
current_byte <<= 1;
WAIT_CLOCK_RISE;
}
// Switch DATA pin 5 back to INPUT (see issue #5)
DDRD = B11000111;
}
extern const unsigned char cart_image[];
// Gets executed when SELECT is pulled low
void recvCommand() {
// Read command
current_command = do_get_byte();
// Now accept the data
switch(current_command) {
case 0x00: // query status
// TODO: Implement password errors counter?
do_send_byte( is_locked ? 0x01 : 0x00 );
break;
case 0xA0: // set address
{
byte hi = do_get_byte();
byte lo = do_get_byte();
current_address = (hi << 8) + lo;
}
break;
case 0xB0: // read address
{
byte hi = (byte) (current_address >> 8) & 0xFF;
byte lo = (byte) current_address & 0xFF;
do_send_byte(hi);
do_send_byte(lo);
}
break;
case 0xD0: // read postincrement
if( is_locked ) return;
// If i suppose right, we should be giving out data until we have been unselected
while( IS_SELECTED ) {
byte current_byte = pgm_read_byte(cart_image + current_address);
do_send_byte(current_byte);
++current_address;
}
break;
case 0xC0: // write postincrement
if(is_locked) return;
while( IS_SELECTED ) {
byte current_byte = do_get_byte();
// TODO: actual write
++current_address;
}
break;
case 0x10: // read postdecrement
if( is_locked ) return;
// If i suppose right, we should be giving out data until we have been unselected
while( IS_SELECTED ) {
byte current_byte = pgm_read_byte(cart_image +current_address);
do_send_byte(current_byte);
--current_address;
}
break;
case 0xE0: // write postdecrement
if( is_locked ) return;
case 0x20: // erase postdecrement
if( current_address == 0xFFFF || current_command == 0xE0 ) {
while( IS_SELECTED ) {
byte current_byte = do_get_byte();
// TODO: actual write
--current_address;
}
}
// the 0x20 command is ignored if address is not FFFFh, tho.
break;
case 0x80: // Lock
// Well you asked for it
is_locked = true;
break;
case 0x90: // Unlock
if( ! is_locked ) return; // Ignore attempts to unlock if not locked
if( current_address == 0x0000 ) {
// Unlocking basically takes 7 bytes of data input, and if they match bytes at 0000h...0007h
while( IS_SELECTED && current_address <= 0x0007 ) {
byte current_byte = do_get_byte();
byte reference_byte = pgm_read_byte(cart_image[current_address]);
if ( current_byte != reference_byte ) {
return; // if password won't match, why bother processing further?
}
++current_address;
}
// OK so we've made it up to here without mismatching a single byte in the password
if ( IS_SELECTED ) {
// Let's not unlock if password entry was just canceled right :p
is_locked = false;
}
}
break;
// ---------------------------------------
// Genjitsu SMP Commandset Extensions 0.2
// ---------------------------------------
case 0xF0: // List files
{
// TODO: build a string of file names and send them
do_send_byte('T');
do_send_byte('O');
do_send_byte('D');
do_send_byte('O');
do_send_byte(0x0);
do_send_byte(0xFF);
}
break;
case 0xF1: // Mount image
{
// TODO: receive file name
}
break;
case 0xF2:
{
}
break;
default: // unknown command
break;
}
while( IS_SELECTED ); // wait until SELECT line is untriggered, see issue #13
}
void setup() {
pinMode(13, OUTPUT);
// SELECT pin 3 always listens
// So does the CLOCK 4, as the calculator is the clock master
// DATA pin 5 listen by default
PORTD = B11111111;
DDRD = B11000111;
// remove interrupts
cli();
}
void loop() {
recvCommand();
}
#include "pdpboot.h" // this needs to be on the bottom, to avoid code entering the Far address space