├── .hgignore
├── libmaplecommon.S
├── libmaple.h
├── pulsedtr.py
├── vmu_image.py
├── README.md
├── pgmtotxt.py
├── vmu_dump.py
├── astroboy.txt
├── megaman.txt
├── Makefile
├── arduino-maple.c
├── vmu_flash.py
├── libmaple16.S
├── libmaple22.S
├── maple.py
└── DreamcastMapleBusConnected.svg
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 | app.elf
3 | app.hex
4 | *.o
5 | Session.vim
6 |
7 |
--------------------------------------------------------------------------------
/libmaplecommon.S:
--------------------------------------------------------------------------------
1 | ; Common functions for 16- and 22-bit program counter devices.
2 |
--------------------------------------------------------------------------------
/libmaple.h:
--------------------------------------------------------------------------------
1 | #define ON 1
2 | #define OFF 0
3 |
4 | void debug(int on);
5 | void maple_tx_raw(unsigned char *buf, unsigned char length);
6 | unsigned char *maple_rx_raw(unsigned char *buf, short skip_amt);
7 | void maple_timer_test();
8 |
--------------------------------------------------------------------------------
/pulsedtr.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import time
3 | import sys
4 |
5 | if len(sys.argv) != 2:
6 | print("Please specify a port, e.g. %s /dev/ttyUSB0" % sys.argv[0])
7 | sys.exit(-1)
8 |
9 | ser = serial.Serial(sys.argv[1])
10 | ser.setDTR(1)
11 | time.sleep(0.5)
12 | ser.setDTR(0)
13 | ser.close()
14 |
15 |
--------------------------------------------------------------------------------
/vmu_image.py:
--------------------------------------------------------------------------------
1 | """
2 | Display an image on the VMU.
3 |
4 | Image must be a text file containing 32 lines, each 48 characters long. Each
5 | character represents a pixel -- x for black and space for white.
6 | """
7 | import sys
8 | import argparse
9 |
10 | import maple
11 |
12 | def main():
13 | parser = argparse.ArgumentParser()
14 | parser.add_argument('-p', '--port', default=maple.PORT)
15 | parser.add_argument('filename')
16 | args = parser.parse_args()
17 |
18 | if args.filename == '-':
19 | image = maple.load_image(sys.stdin)
20 | else:
21 | image = maple.load_image(args.filename)
22 |
23 | bus = maple.MapleProxy(args.port)
24 | bus.deviceInfo(maple.ADDRESS_CONTROLLER)
25 | bus.deviceInfo(maple.ADDRESS_PERIPH1)
26 | bus.writeLCD(maple.ADDRESS_PERIPH1, image)
27 |
28 | if __name__ == '__main__':
29 | main()
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Wiring
2 | ------
3 | You need to connect the red and white wires (clock and data lines) to two separate ports. The short
4 | explanation is this reduces the amount of bit twiddling required when reading, resulting in pretty
5 | consistent reads on an 16MHz Arduino. [The blog post](https://code.lardcave.net/2018/10/15/1/) has a
6 | longer explanation.
7 |
8 | I used a breadboard for this, but you could also just solder an extra wire on.
9 |
10 | * Red wire:
11 | PORTB0 (Pin 8 on Duemilanove)
12 | PORTC2 (Analogue Pin 2 on Duemilanove)
13 | * White wire:
14 | PORTB1 (Pin 9 on Duemilanove)
15 | PORTC3 (Analogue Pin 3 on Duemilanove)
16 | * Blue wire: +5V
17 | * GND (unshielded): GND
18 |
19 | Handy diagram:
20 |
21 |
22 |
23 | Converting images
24 | -----------------
25 | To display images using the vmu_image program, produce a 48x32 text file with 'x' where you want the set pixels to be. I use imagmagick and go through pgm using this pipeline, which is not pretty but works:
26 |
27 | convert mypic.png pgm: |python3 pgmtotxt.py - >mypic.txt
28 |
--------------------------------------------------------------------------------
/pgmtotxt.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | def read_pgm(filename):
4 | if hasattr(filename, 'readline'):
5 | handle = filename
6 | closeme = False
7 | else:
8 | handle= open(filename, 'rb')
9 | closeme = True
10 |
11 | format = handle.readline().decode('ascii')
12 | dims = handle.readline().decode('ascii')
13 | levels = handle.readline().decode('ascii')
14 | data = handle.readline()
15 |
16 | if closeme:
17 | handle.close()
18 |
19 | return format, dims, levels, data
20 |
21 | def main(pathname):
22 | if pathname == '-':
23 | pathname = sys.stdin.buffer
24 |
25 | format, dims, levels, data = read_pgm(pathname)
26 |
27 | dims = dims.split(' ', 1)
28 | width, height = int(dims[0]), int(dims[1])
29 |
30 | levels = int(levels)
31 |
32 | if levels > 255:
33 | raise NotImplementedError()
34 |
35 | threshold = levels // 2
36 |
37 | for idx, b in enumerate(data):
38 | sys.stdout.write('x' if b < threshold else ' ')
39 | if idx % width == width - 1:
40 | sys.stdout.write('\n')
41 |
42 | if __name__ == '__main__':
43 | main(sys.argv[1])
44 |
--------------------------------------------------------------------------------
/vmu_dump.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import maple
4 | import argparse
5 |
6 | LAST_BLOCK = 255
7 |
8 | def read_vmu(port, start_block=0, end_block=LAST_BLOCK):
9 | bus = maple.MapleProxy(port)
10 |
11 | # Quick bus enumeration
12 | bus.deviceInfo(maple.ADDRESS_CONTROLLER)
13 | bus.deviceInfo(maple.ADDRESS_PERIPH1)
14 |
15 | bus.getCond(maple.ADDRESS_CONTROLLER, maple.FN_CONTROLLER)
16 | bus.getCond(maple.ADDRESS_PERIPH1, maple.FN_CLOCK)
17 | bus.getMemInfo(maple.ADDRESS_PERIPH1)
18 |
19 | for block_num in range(start_block, end_block + 1):
20 | sys.stdout.write(chr(13) + chr(27) + '[K' + 'Reading block %d of 255' % (block_num,))
21 | sys.stdout.flush()
22 | data = bus.readFlash(maple.ADDRESS_PERIPH1, block_num, 0)
23 | yield data
24 |
25 | def main():
26 | parser = argparse.ArgumentParser()
27 | parser.add_argument('-p', '--port', default=maple.PORT)
28 | parser.add_argument('filename')
29 | args = parser.parse_args()
30 |
31 | open_mode = 'wb'
32 | start_block = 0
33 |
34 | if os.path.exists(args.filename):
35 | size = os.stat(args.filename).st_size
36 | if size % 512 == 0:
37 | print('Extending previous file')
38 | open_mode = 'ab'
39 | start_block = size // 512
40 |
41 | with open(args.filename, open_mode) as handle:
42 | for block in read_vmu(args.port, start_block=start_block):
43 | handle.write(block)
44 | handle.flush()
45 |
46 | if __name__ == '__main__':
47 | main()
48 |
49 |
--------------------------------------------------------------------------------
/astroboy.txt:
--------------------------------------------------------------------------------
1 | xxxxxxxxxxx x
2 | xxxxxxxxxxxxxx x
3 | xxxxxxxxxxxxxxxxx x
4 | xxxxxxxxxxxxxxxxxxxx
5 | xxxxxxxxxxxxxxxxxxxxxxx
6 | xxxxxxxxxxxxxxxxxxxxxxxxx
7 | xxxxxxxxxxxxxxxxxxxxxxxxxxx
8 | xxxxxxxxxxxxxxxxxxxxxxxxxxx
9 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
10 | xxxx xxxxxxx xxxxxxxxxxxxxxxxx
11 | xx xxxxxx xxxxxxxxxxxxxxxx
12 | xx xx xxxx xxxxxxxxxxxxxxxx
13 | xx x x xx xxx xxxxxxxxx xxxx
14 | xx x x x x xxxxxxxxx xxx
15 | xx x x x x xxxxxxxx xxxxx
16 | xx x x xxxxxxxxx xxxx
17 | x xx x xxxxxxx xxx
18 | x xxxxx xxxx xxxxxxx xxx
19 | x xxxxx xxxxx xxxxxxxxxxxx
20 | x x xxx x xxxx xxxxxxxxxxxxx
21 | x x xx x xx xxxxxxxxxxxxxxxxxxxx
22 | x x xx x x xx x xxxxxxxxxxxxxxxxxx
23 | x x xxxx x xxxx xxxxxxxxxxxxxxxxx
24 | x x xxx x xxx xxxxxxxxxxxxxxxx
25 | x x xxx x xxx xxxxxxxxxxxxxxxxxx
26 | x x x xxx xxxxxxxxxxxxxxx
27 | x x x x xxxxxxxxxxxxxx
28 | x x x x x xxxx xx
29 | x xxx x x xx xx x
30 | x xxx x x
31 | x xx xxx x
32 | x x x x x
33 |
--------------------------------------------------------------------------------
/megaman.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | xxxxxxxx
5 | xxxxxxxxxx
6 | xxxxx xxxxx
7 | xxxxxxx xxxxxxx
8 | xxxxxxxx xxxxxxxx
9 | xxxxxxxxx xxxxxxxxx
10 | xxxxxxxxxx xxxxxxxxxx
11 | xxxxxxxxxxxxxxxxxxxxxxxxxx
12 | xxxxxxxxxxx xxxxxxxxxxx
13 | xxxxxxxxxxx xxxxxxxxxxx
14 | xxxxxxxxxxx xxxxxxxxxxx
15 | xxxxxxxxxxx xxxxxxxxxxx
16 | x xxxx xxxxxxxx xxxx x
17 | x xxxx xxxxxxxx xxxx x
18 | x xx xxxx xx x
19 | x xx xxxx xxxx xx x
20 | x xx xxxx xxxx xx x
21 | x xx xxxx xxxx xx x
22 | x xx xxxx xxxx xx x
23 | xxxx xxxx
24 | xx xxxxxxxxxxxx xx
25 | xxx xxxxxxxxxxxx xxx
26 | xxx xxxxxxxxxx xxx
27 | xxx xxxxxxxx xxx
28 | xxxx xxxx
29 | xxx xxx
30 | xxxxxxxx
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for building small AVR executables, supports C and C++ code
2 | # Author: Kiril Zyapkov
3 | # Hacked up by nfd
4 |
5 | # Settings for atmega328
6 | MMCU = atmega328
7 | MCU = m328p
8 | AVRDUDE_PROGRAMMER = stk500v1
9 | UPLOAD_SPEED = -b 57600
10 | PORT = /dev/tty.usbserial-A700ekGi
11 | DEBUG_LED_BIT = 5
12 | CHIP_SPECIFIC_ASM=libmaple16.S
13 |
14 | # Settings for atmega2560. NB upload speed is autodetected.
15 | #MMCU = atmega2560
16 | #AVRDUDE_PROGRAMMER = stk600
17 | #MCU = m2560
18 | #UPLOAD_SPEED=
19 | #PORT = /dev/tty.usbmodemfa131
20 | #DEBUG_LED_BIT = 7
21 | #CHIP_SPECIFIC_ASM=libmaple22.S
22 |
23 | # Other settings that hopefully won't need changing much follow.
24 | SOURCE_DIRS = .
25 | # This probably can't be changed without modifying libmaple.S.
26 | F_CPU = 16000000UL
27 | BUILD_DIR = build
28 |
29 | CFLAGS = -Wall -g2 -gstabs -Os -fpack-struct -fshort-enums -ffunction-sections \
30 | -fdata-sections -ffreestanding -funsigned-char -funsigned-bitfields \
31 | -mmcu=$(MMCU) -DF_CPU=$(F_CPU) -DDEBUG_LED_BIT=$(DEBUG_LED_BIT)
32 |
33 | CXXFLAGS = -Wall -g2 -gstabs -Os -fpack-struct -fshort-enums -ffunction-sections \
34 | -fdata-sections -ffreestanding -funsigned-char -funsigned-bitfields \
35 | -fno-exceptions -mmcu=$(MMCU) -DF_CPU=$(F_CPU) -DDEBUG_LED_BIT=$(DEBUG_LED_BIT)
36 |
37 | LDFLAGS = -Os -Wl,-gc-sections -mmcu=$(MMCU) #-Wl,--relax
38 |
39 | CC = avr-gcc
40 | CXX = avr-g++
41 | OBJCOPY = avr-objcopy
42 | OBJDUMP = avr-objdump
43 | AR = avr-ar
44 | SIZE = avr-size
45 |
46 | SRC = $(wildcard *.c)
47 |
48 | CXXSRC = $(wildcard *.cpp)
49 |
50 | ASMSRC = libmaplecommon.S $(CHIP_SPECIFIC_ASM)
51 |
52 | OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o) $(CXXSRC:%.cpp=$(BUILD_DIR)/%.o) $(ASMSRC:%.S=$(BUILD_DIR)/%.o)
53 |
54 | DEPS = $(OBJ:%.o=%.d)
55 |
56 | $(BUILD_DIR)/%.o: ./%.c
57 | $(CC) $(CFLAGS) -c $< -o $@
58 |
59 | $(BUILD_DIR)/%.o: ./%.S
60 | $(CC) $(CFLAGS) -c $< -o $@
61 |
62 | $(BUILD_DIR)/%.o: ./%.cpp
63 | $(CXX) $(CXXFLAGS) -c $< -o $@
64 |
65 | all: app.hex printsize
66 |
67 | #$(TARGET).a: $(OBJ)
68 | # $(AR) rcs $(TARGET).a $?
69 |
70 | app.elf: $(OBJ)
71 | $(CXX) $(LDFLAGS) $(OBJ) -o $@
72 |
73 | $(BUILD_DIR)/%.d: ./%.c
74 | mkdir -p $(dir $@)
75 | $(CC) $(CFLAGS) -MM -MF $@ $<
76 |
77 | $(BUILD_DIR)/%.d: ./%.cpp
78 | mkdir -p $(dir $@)
79 | $(CXX) $(CXXFLAGS) -MM -MF $@ $<
80 |
81 | #$(TARGET).elf: $(TARGET).a
82 | # $(CXX) $(LDFLAGS) $< -o $@
83 |
84 | app.hex: app.elf
85 | $(OBJCOPY) -R .eeprom -O ihex $< $@
86 |
87 | clean:
88 | $(RM) $(BUILD_DIR)/*
89 | rm app.*
90 |
91 | printsize:
92 | avr-size --format=avr --mcu=$(MMCU) app.elf
93 |
94 |
95 |
96 | # Programming support using avrdude. Settings and variables.
97 | #PORT = /dev/ttyUSB0
98 | AVRDUDE_PORT = $(PORT)
99 | AVRDUDE_WRITE_FLASH = -U flash:w:app.hex
100 | #AVRDUDE_FLAGS = -V -F -C \app\arduino-0021\hardware\tools\avr\etc\avrdude.conf
101 | AVRDUDE_FLAGS = -V -F \
102 | -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) \
103 | $(UPLOAD_SPEED)
104 | #
105 | # Program the device.
106 | INSTALL_DIR = \app\arduino-0021
107 | AVRDUDE = avrdude
108 | upload: app.hex
109 | python3 pulsedtr.py $(PORT)
110 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH)
111 |
112 |
--------------------------------------------------------------------------------
/arduino-maple.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "libmaple.h"
7 |
8 | // Serial port setup
9 | #define BAUD 57600
10 | #define USE_2X 1
11 | #include
12 |
13 | FILE uart;
14 |
15 | static void uart_putchar(uint8_t c) {
16 | if (c == '\n') {
17 | loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
18 | UDR0 = '\r';
19 | }
20 | loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
21 | UDR0 = c;
22 | }
23 |
24 | static int stdio_uart_putchar(char c, FILE *handle)
25 | {
26 | (void) handle;
27 |
28 | uart_putchar(c);
29 | return 0;
30 | }
31 |
32 | static uint8_t uart_getchar(void) {
33 | loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
34 | return UDR0;
35 | }
36 |
37 | static uint16_t uart_getshort(void) {
38 | return (uint16_t)(uart_getchar()) | (((uint16_t)uart_getchar()) << 8);
39 | }
40 |
41 | static int stdio_uart_getchar(FILE *handle)
42 | {
43 | (void) handle;
44 | return (int)uart_getchar();
45 | }
46 |
47 | static void uart_init(void) {
48 | UBRR0H = UBRRH_VALUE;
49 | UBRR0L = UBRRL_VALUE;
50 |
51 | #if USE_2X
52 | UCSR0A |= _BV(U2X0);
53 | #else
54 | UCSR0A &= ~(_BV(U2X0));
55 | #endif
56 |
57 | UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
58 | UCSR0B = _BV(RXEN0) | _BV(TXEN0); /* Enable RX and TX */
59 |
60 | fdev_setup_stream(&uart, stdio_uart_putchar, stdio_uart_getchar, _FDEV_SETUP_RW);
61 | stdout = &uart;
62 | stdin = &uart;
63 | }
64 |
65 | struct maplepacket {
66 | unsigned char data_len; /* Bytes: header, data, and checksum */
67 | unsigned short data_len_rx; /* For Rx -- didn't realise we could get up to 512 bytes */
68 | unsigned short recv_skip; /* 6-cycle durations to skip when receiving */
69 |
70 | unsigned char data[1536]; /* Our maximum packet size */
71 | } packet;
72 |
73 | void setup()
74 | {
75 | // Initialise serial port
76 | uart_init();
77 |
78 | // Maple bus data pins as output -- NB needs corresponding changes in libMaple.S
79 | DDRB = 0xff;
80 |
81 | // Secondary pins are always tristate (floating and input).
82 | DDRC = 0x0;
83 | PORTC = 0x0;
84 |
85 | //puts("Hi there \n");
86 | packet.recv_skip = 0;
87 | }
88 |
89 | unsigned char compute_checksum(unsigned char data_bytes)
90 | {
91 | unsigned char *ptr = packet.data;
92 | int count;
93 | unsigned char checksum = 0;
94 |
95 | for(count = 0; count < data_bytes + 4; count ++) {
96 | checksum ^= *ptr;
97 | ptr++;
98 | }
99 | return checksum;
100 | }
101 |
102 | /* Turn logic-analyser-style reads into a bit sequence. */
103 | void debittify()
104 | {
105 | // TODO -- done in Python currently.
106 | }
107 |
108 | bool
109 | maple_transact()
110 | {
111 | unsigned char *rx_buf_end;
112 | //unsigned char *rx_buf_ptr;
113 |
114 | // debug
115 | /*
116 | packet.header[0] = 0; // Number of additional words in frame
117 | packet.header[1] = 0; // Sender address = Dreamcast
118 | packet.header[2] = 1; //(1 << 5); // Recipient address = main peripheral on port 0
119 | packet.header[3] = 1; // Command = request device information
120 | packet.data[0] = compute_checksum(0);
121 | packet.data_len = 5;
122 | */
123 |
124 | /*packet.header[0] = (192 + 4) / 4; // Number of additional words in frame
125 | packet.header[1] = 0; // Sender address = Dreamcast
126 | packet.header[2] = 1;
127 | packet.header[3] = 12; // block write
128 | packet.data[0] = 0x4; // LCD
129 | packet.data[1] = 0; // partition
130 | packet.data[2] = 0; // phase
131 | packet.data[3] = 0; // block number
132 | packet.data[4] = 0xff;
133 | packet.data[5] = 0x0f;
134 | packet.data[6] = 0xff;
135 | packet.data[192 + 4] = compute_checksum(192 + 4);
136 | packet.data_len = 4 + 192 + 4 + 1;
137 | */
138 |
139 | maple_tx_raw(packet.data, packet.data_len);
140 | rx_buf_end = maple_rx_raw(packet.data, packet.recv_skip);
141 |
142 | packet.data_len_rx = (rx_buf_end - packet.data);
143 |
144 | // TODO debittify here rather than in Python: it's simpler in C and
145 | // significantly reduces transfer time.
146 |
147 | return true;
148 |
149 | // debug
150 | /*Serial.print("All done \n");
151 | Serial.println((int)(rx_buf_end - (&(packet.header[0]))), HEX);
152 | rx_buf_ptr = (&(packet.header[0]));
153 | while(rx_buf_ptr <= rx_buf_end) {
154 | Serial.println(*rx_buf_ptr, HEX);
155 | rx_buf_ptr ++;
156 | }
157 | */
158 | }
159 |
160 | /* Read packet to send from the controller. */
161 | bool
162 | read_packet(void)
163 | {
164 | /* First byte: #bytes in packet (including header and checksum)*/
165 | packet.data_len = uart_getchar();
166 | packet.recv_skip = uart_getshort();
167 | if(packet.data_len > 0) {
168 | unsigned char *data = packet.data;
169 | int i;
170 | for(i = 0; i < packet.data_len; i++) {
171 | *data = uart_getchar();
172 | data ++;
173 | }
174 | return true;
175 | } else {
176 | return false;
177 | }
178 | }
179 |
180 | bool
181 | packet_dest_is_maple(void)
182 | {
183 | return packet.data_len > 0;
184 | }
185 |
186 | void
187 | send_packet(void)
188 | {
189 | uart_putchar((packet.data_len_rx & 0xff00) >> 8);
190 | uart_putchar(packet.data_len_rx & 0xff);
191 | if(packet.data_len_rx) {
192 | int i;
193 | uint8_t *data = packet.data;
194 | for (i = 0; i < packet.data_len_rx; i++) {
195 | uart_putchar(data[i]);
196 | }
197 | }
198 | }
199 |
200 | void main() __attribute__ ((noreturn));
201 | void main(void) {
202 | setup();
203 |
204 | // maple_transact(); for (;;) ;
205 | debug(1);
206 |
207 | #if 0
208 | while(1) {
209 | puts("before timer test\n");
210 | _delay_ms(500);
211 | maple_timer_test();
212 | puts("after timer test\n");
213 | _delay_ms(500);
214 | }
215 | #endif
216 |
217 | for (;;) {
218 | //debug(0);
219 | read_packet();
220 | debug(0);
221 |
222 | if(packet_dest_is_maple()) {
223 | maple_transact();
224 | //debug(1);
225 | send_packet();
226 | } else {
227 | // Debug
228 | uart_putchar(1);
229 | }
230 | debug(1);
231 | }
232 | }
233 |
234 |
--------------------------------------------------------------------------------
/vmu_flash.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import time
4 | import struct
5 | import argparse
6 |
7 | import maple
8 |
9 | WRITE_SIZE = 128
10 | BLOCK_SIZE = 512
11 | DIRECTORY_BLOCK_IDX = 253
12 | DIRECTORY_BLOCK_RANGE = (241, 253)
13 | FAT_BLOCK_IDX = 254
14 | ROOT_BLOCK_IDX = 255
15 |
16 | class ImageError(Exception):
17 | pass
18 |
19 | def write_vmu(fs_image, port):
20 | bus = maple.MapleProxy(port)
21 |
22 | # Quick bus enumeration
23 | bus.deviceInfo(maple.ADDRESS_CONTROLLER)
24 | bus.deviceInfo(maple.ADDRESS_PERIPH1)
25 | bus.getMemInfo(maple.ADDRESS_PERIPH1)
26 |
27 | print("Writing %d blocks..." % (len(fs_image)))
28 | for block_num in sorted(fs_image.keys()):
29 | print(block_num)
30 | #orig_data = bus.readFlash(maple.ADDRESS_PERIPH1, block_num, 0)
31 |
32 | target_data = fs_image[block_num]
33 | assert len(target_data) == BLOCK_SIZE
34 |
35 | for phase_num in range(BLOCK_SIZE // WRITE_SIZE):
36 | #print block_num, phase_num
37 | data = target_data[phase_num * WRITE_SIZE : (phase_num + 1) * WRITE_SIZE]
38 | bus.writeFlash(maple.ADDRESS_PERIPH1, block_num, phase_num, data)
39 |
40 | bus.writeFlashComplete(maple.ADDRESS_PERIPH1, block_num)
41 |
42 | def read_vmu():
43 | bus = maple.MapleProxy()
44 |
45 | # Quick bus enumeration
46 | bus.deviceInfo(maple.ADDRESS_CONTROLLER)
47 | bus.deviceInfo(maple.ADDRESS_PERIPH1)
48 |
49 | bus.getCond(maple.ADDRESS_CONTROLLER, maple.FN_CONTROLLER)
50 | bus.getCond(maple.ADDRESS_PERIPH1, maple.FN_CLOCK)
51 | bus.getMemInfo(maple.ADDRESS_PERIPH1)
52 |
53 | print(bus.readFlash(maple.ADDRESS_PERIPH1, 0, 0))
54 |
55 |
56 | def pad_to_block_size(data):
57 | modulus = len(data) % BLOCK_SIZE
58 |
59 | if modulus != 0:
60 | data += b'\x00' * (BLOCK_SIZE - modulus)
61 |
62 | return data
63 |
64 | def dump_hex(block):
65 | for i in range(0, len(block), 16):
66 | chunk = block[i : i + 16]
67 | sys.stdout.write('%04x ' % (i,))
68 | for j, b in enumerate(chunk):
69 | sys.stdout.write(format(b, '02x') + ' ')
70 | if j == 7:
71 | sys.stdout.write(' ')
72 |
73 | sys.stdout.write(' ' * (16 - len(chunk)) + ' ')
74 | if len(chunk) < 9:
75 | sys.stdout.write(' ')
76 |
77 | for b in chunk:
78 | sys.stdout.write(chr(b) if 32 <= b < 127 else '.')
79 |
80 | sys.stdout.write('\n')
81 | sys.stdout.write('\n')
82 |
83 |
84 | def read_vmu_dump(fn):
85 | """
86 | Return a blocksize-padded image.
87 | """
88 | with open(fn, 'rb') as h:
89 | image_data = h.read()
90 |
91 | return pad_to_block_size(image_data)
92 |
93 | def construct_8_3_filename(filename):
94 | base, ext = os.path.splitext(os.path.basename(filename.upper()).replace(' ', '_'))
95 | base = base[:8]
96 | ext = ext[-3:]
97 |
98 | return ('%-8s%-3s' % (base, ext)).encode('ascii') + b'\x00'
99 |
100 | def construct_fs_image(filename, data):
101 | """
102 | Construct a file system image consisting of a dict mapping block number to block.
103 | """
104 | fs_image = {}
105 |
106 | bcd_date = [0x20, 0x18, 0x10, 0x08, 0x23, 0x03, 0x00, 0x00] # BCD encoded date
107 |
108 | idx = 0
109 | while idx < len(data):
110 | fs_image[idx // BLOCK_SIZE] = data[idx : idx + BLOCK_SIZE]
111 | idx += BLOCK_SIZE
112 |
113 | if DIRECTORY_BLOCK_IDX not in fs_image and FAT_BLOCK_IDX not in fs_image and ROOT_BLOCK_IDX not in fs_image:
114 | # Construct a directory block
115 | data_length_blocks = len(data) // BLOCK_SIZE
116 |
117 | filename = construct_8_3_filename(filename)
118 |
119 | dir_entry = bytes([
120 | 0xcc, # file type: game
121 | 0x0, # no copy protect
122 | 0x0, 0x0 # first block
123 | ]) + filename + bytes(
124 | bcd_date + [
125 | data_length_blocks & 0xff,
126 | data_length_blocks >> 8,
127 | 1 # header offset within file (in blocks) -- nb only valid for games
128 | ])
129 |
130 | fs_image[DIRECTORY_BLOCK_IDX] = pad_to_block_size(dir_entry)
131 |
132 | dump_hex(fs_image[DIRECTORY_BLOCK_IDX])
133 |
134 | # construct the FAT block
135 | fat_list = [0xfffc] * 256 # empty space initially
136 |
137 | # add the game...
138 | for i in range(len(data) // BLOCK_SIZE):
139 | fat_list[i] = i + 1
140 | fat_list[(len(data) // BLOCK_SIZE) - 1] = 0xfffa
141 |
142 | # add the system blocks
143 | for i in range(DIRECTORY_BLOCK_RANGE[0] + 1, DIRECTORY_BLOCK_RANGE[1] + 1):
144 | fat_list[i] = i - 1
145 | fat_list[DIRECTORY_BLOCK_RANGE[0]] = 0xfffa
146 | fat_list[FAT_BLOCK_IDX] = 0xfffa
147 | fat_list[ROOT_BLOCK_IDX] = 0xfffa
148 |
149 | fat_bytes = b''.join(struct.pack('
4 |
5 | ; reg usage faq: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage
6 | ; essentially we can play with regs 18 to 27 and 30 and 31.
7 |
8 | .global debug
9 | .global maple_tx_raw
10 | .global maple_rx_raw
11 | .global maple_timer_test
12 |
13 | #define tmp1 r18
14 | #define tmp2 r19
15 | #define tmp3 r20
16 | #define rport r21
17 |
18 | ; Maple1 is on bit 0 of PORTMAPLE -- mask of 1:
19 | #define bitmaple1 0
20 | #define maskmaple1 1
21 | ; maple5 is on bit 1 -- mask of 10:
22 | #define bitmaple5 1
23 | #define maskmaple5 2
24 | #define maskmaple 3
25 |
26 | #define bufsize_div_four 255
27 | #define bufsize_div_six 255
28 |
29 | #define PORTMAPLE1 PORTB
30 | #define PINMAPLE1 PINB
31 | #define DDRMAPLE1 DDRB
32 |
33 | #define PORTMAPLE2 PORTC
34 | #define PINMAPLE2 PINC
35 | #define DDRMAPLE2 DDRC
36 |
37 | #define IOM1 _SFR_IO_ADDR(PINMAPLE1)
38 | #define IOM2 _SFR_IO_ADDR(PINMAPLE2)
39 |
40 | .macro maple_high pin
41 | SBI _SFR_IO_ADDR(PORTMAPLE1), \pin
42 | .endm
43 |
44 | .macro maple_low pin
45 | CBI _SFR_IO_ADDR(PORTMAPLE1), \pin
46 | .endm
47 |
48 | .macro debug_pin_high
49 | SBI _SFR_IO_ADDR(PORTD), 7
50 | .endm
51 |
52 | .macro debug_pin_low
53 | CBI _SFR_IO_ADDR(PORTD), 7
54 | .endm
55 |
56 | .macro debug_pin_flip
57 | IN tmp1, _SFR_IO_ADDR(PORTD)
58 | ANDI tmp1, 0x80
59 | BREQ 1f
60 | debug_pin_low
61 | RJMP 2f
62 | 1: debug_pin_high
63 | 2:
64 | .endm
65 |
66 | .macro delayquarter
67 | nop
68 | nop
69 | nop
70 | nop
71 | .endm
72 |
73 | .macro threenops
74 | nop
75 | nop
76 | nop
77 | .endm
78 |
79 | ; Delay half a uS. CALL takes 4 cycles, RET takes 4 cycles,
80 | ; 8 cycles is half a microsecond at 16MHz, voila.
81 | delayhalf:
82 | ret
83 |
84 | maple_tx_start:
85 | maple_high bitmaple1
86 | maple_high bitmaple5
87 | delayquarter
88 | ; Start of sequence: pin 1 goes low, pin 5 goes high.
89 | maple_low bitmaple1
90 | maple_high bitmaple5
91 | delayquarter
92 | ; Toggle pin 5 four times: 1
93 | maple_low bitmaple5
94 | delayquarter
95 | maple_high bitmaple5
96 | delayquarter
97 | ; 2
98 | maple_low bitmaple5
99 | delayquarter
100 | maple_high bitmaple5
101 | delayquarter
102 | ; 3
103 | maple_low bitmaple5
104 | delayquarter
105 | maple_high bitmaple5
106 | delayquarter
107 | ; 4
108 | maple_low bitmaple5
109 | delayquarter
110 | maple_high bitmaple5
111 | delayquarter
112 | ; End of sequence: pin 1 goes high, pin 5 goes low.
113 | maple_high bitmaple1
114 | delayquarter
115 | maple_low bitmaple5
116 | delayquarter
117 | ret
118 |
119 | maple_tx_end:
120 | ; Start of sequence: pin 1 is high, pin 5 is low.
121 | maple_high bitmaple1
122 | maple_low bitmaple5
123 | delayquarter
124 | ; Pin 5 goes high for a short time.
125 | maple_high bitmaple5
126 | delayquarter
127 | ; Pin 5 goes low
128 | maple_low bitmaple5
129 | delayquarter
130 | ; Pin 1 goes low for 2 cycles
131 | maple_low bitmaple1
132 | RCALL delayhalf
133 | ; Pin 1 goes high for 2 cycles
134 | maple_high bitmaple1
135 | RCALL delayhalf
136 | ; Pin 1 goes low for 2 cycles
137 | maple_low bitmaple1
138 | RCALL delayhalf
139 | ; Pin 1 goes high for 2 cycles.
140 | maple_high bitmaple1
141 | RCALL delayhalf
142 | ; End of sequence: pin 5 goes high
143 | ; moved to rx
144 | ;maple_high bitmaple5
145 | ret
146 |
147 |
148 | maple_tx_phase1:
149 | BST r18, 7 ; Read the bit
150 | BRTC 1f ; Is it set?
151 | maple_high bitmaple5 ; Yes, put the bit on the wire
152 | 1:
153 | maple_low bitmaple1 ; New data is ready
154 | delayquarter ; Wait for other end to notice
155 | maple_high bitmaple5 ; Phase complete
156 | ret
157 |
158 | maple_tx_phase2:
159 | BST r18, 7 ; Read the bit
160 | BRTC 1f ; Is it set?
161 | maple_high bitmaple1 ; Set it
162 | 1:
163 | maple_low bitmaple5 ; Notify
164 | delayquarter ; Wait for other end to notice
165 | maple_high bitmaple1 ; Phase complete
166 | ret
167 |
168 | ; r22: length
169 | ; r23: unused
170 | ; r24: low order bit of buffer pointer
171 | ; r25: high order bit of buffer pointer
172 | maple_tx_data:
173 | MOVW r26, r24 ; X register <- the buffer
174 |
175 | _maple_tx_next_byte:
176 | LD r18, X+ ; Next byte of data into r18
177 |
178 | ; put the byte on the bus one bit at a time.
179 | RCALL maple_tx_phase1 ; bit 7
180 | LSL r18
181 | RCALL maple_tx_phase2 ; bit 6
182 | LSL r18
183 | RCALL maple_tx_phase1 ; bit 5
184 | LSL r18
185 | RCALL maple_tx_phase2 ; bit 4
186 | LSL r18
187 | RCALL maple_tx_phase1 ; bit 3
188 | LSL r18
189 | RCALL maple_tx_phase2 ; bit 2
190 | LSL r18
191 | RCALL maple_tx_phase1 ; bit 1
192 | LSL r18
193 | RCALL maple_tx_phase2 ; bit 0
194 |
195 | DEC r22 ; Are we done yet?
196 | BRNE _maple_tx_next_byte ; No, get the next character
197 |
198 | ret
199 |
200 | ; r22: length (1 byte)
201 | ; r23: unused
202 | ; r24: low order bit of buffer pointer
203 | ; r25: high order bit of buffer pointer
204 |
205 | ; Returns pointer to final character stored in r25:r24
206 | maple_tx_raw:
207 | ; Set primary pins to output for transmission
208 | SBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple1
209 | SBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple5
210 |
211 | RCALL maple_tx_start
212 | RCALL maple_tx_data
213 | RCALL maple_tx_end
214 |
215 | ; Tx over: set pins to input and enable pull-up resistors. (Done in rx)
216 | ;SBI _SFR_IO_ADDR(PORTMAPLE1), bitmaple1
217 | ;CBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple1
218 | ; SBI _SFR_IO_ADDR(PORTMAPLE1), bitmaple5
219 | ; CBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple5
220 |
221 | ret
222 |
223 | .macro read_four_samples
224 | IN r18, IOM2 ; ----51-- ------51 ----51-- r18 is third read (after loop). order: r19, r20, r18
225 | OR r18, r19 ; ----5151 r18 = none, none, third, first
226 | SWAP r18 ; 5151---- r18 = third, first, none, none
227 | IN r19, IOM1 ; 5151---- ------51
228 | OR r18, r20 ; 515151-- r18 = third, first, second, none
229 | OR r18, r19 ; 51515151 r18 = third, first, second, fourth
230 | IN r19, IOM1 ; 51515151 ------51 r19 is new first
231 | ST X+, r18 ; -------- ------51
232 | IN r20, IOM2 ; -------- ------51 ----51-- r20 is new second
233 | .endm
234 |
235 | ; r22:23 -- skip_amt
236 | ; r24:25 -- pointer to buffer
237 | maple_rx_raw:
238 | MOVW r26, r24 ; X register <- the buffer
239 | PUSH r28 ; save Y register (avr-gcc frame pointer)
240 | PUSH r29
241 | MOVW r28, r22 ; Y register <- the amount of 2-sample cycles to skip
242 | MOVW r30, r24
243 | LDI r30, bufsize_div_six ; low byte of Z <- space remaining div 6
244 |
245 | LDI r18, 0 ; clear r18
246 |
247 | ; Tell the device it can start sending data. (this is from the tx lead-out)
248 | maple_high bitmaple5 ; done implicitly by enabling pull-ups below
249 | SBI _SFR_IO_ADDR(PORTMAPLE1), bitmaple1
250 | CBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple1
251 | SBI _SFR_IO_ADDR(PORTMAPLE1), bitmaple5
252 | CBI _SFR_IO_ADDR(DDRMAPLE1), bitmaple5
253 |
254 | ; start sequence: wait for bitmaple1 to go low
255 | 1: SBIC _SFR_IO_ADDR(PINMAPLE1), bitmaple1
256 | RJMP 1b
257 |
258 | ; wait for bitmaple1 to go high again
259 | 2: SBIS _SFR_IO_ADDR(PINMAPLE1), bitmaple1
260 | RJMP 2b
261 |
262 | ; TODO is a NOP required here? Hard to tell.
263 |
264 | 3:
265 | ; lead-in: read_four_samples expects r18 and r20 to each contain one sample.
266 | ; This also incorporates cycle skipping if desired, by passing in the number of cycles to skip,
267 | ; divided by 6 (which is the number of cycles in the loop). We take one sample every three cycles,
268 | ; so there's no way to go out of sync with a bizarre skip value.
269 |
270 | IN r19, IOM1
271 | SBIW r28, 1 ; subtract 1 from amount to skip
272 | IN r20, IOM2
273 | BRPL 3b ; if we're still skipping, skip some more.
274 | NOP ; BRPL not taken takes one cycle -- wait one more before starting the read loop.
275 | 4:
276 | ; main read loop, unrolled 6x to read 6 bytes.
277 |
278 | ; read first byte
279 | read_four_samples
280 |
281 | ; read second byte:
282 | NOP
283 | NOP
284 | read_four_samples
285 |
286 | ; read third byte:
287 | NOP
288 | NOP
289 | read_four_samples
290 |
291 | ; read fourth byte:
292 | NOP
293 | NOP
294 | read_four_samples
295 |
296 | ; read fifth byte:
297 | NOP
298 | NOP
299 | read_four_samples
300 |
301 | ; read final byte
302 | DEC r30 ; have we run out of space?
303 | BREQ _maple_rx_end ; yes, stop writing
304 | read_four_samples
305 |
306 | ; lead-out: 2-cycle jump to read the next 4 bytes.
307 | RJMP 4b
308 |
309 | _maple_rx_end:
310 | ; debug_pin_high
311 | ;POP r18 ;
312 | ;STS TIMSK0, r18 ; re-enable Arduino interrupts
313 | POP r29 ; restore Y register (avr-gcc frame pointer)
314 | POP r28
315 |
316 | MOVW r24, r26 ; end of buffer <- X register
317 |
318 | ret
319 |
320 | ; Turn the led on if r24 (first int parameter) is 1, off if r24 is 0.
321 | debug:
322 | IN r18, _SFR_IO_ADDR(PORTB)
323 | bst r24, 0 ; first parameter: on or off?
324 | bld r18, DEBUG_LED_BIT ; bit 5 on duemilanove, bit 7 on the mega
325 | out _SFR_IO_ADDR(PORTB), r18 ; on port B
326 | ret
327 |
328 |
--------------------------------------------------------------------------------
/libmaple22.S:
--------------------------------------------------------------------------------
1 | ; Functions for Maple bus support on devices with 22-bit program counters (Mega)
2 |
3 | #include
4 |
5 | ; reg usage faq: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage
6 | ; essentially we can play with regs 18 to 27 and 30 and 31.
7 |
8 | .global debug
9 | .global maple_tx_raw
10 | .global maple_rx_raw
11 | .global maple_timer_test
12 | .global TIMER2_OVF_vect
13 |
14 | #define intsave r31
15 | #define intwork r20
16 | #define tmp1 r18
17 | #define tmp2 r19
18 | #define rport2 r21
19 | #define rport r30
20 |
21 | ; Maple1 is on bit 0 of PORTMAPLE -- mask of 1:
22 | #define bitmaple1 0
23 | #define maskmaple1 1
24 | ; maple5 is on bit 1 -- mask of 10:
25 | #define bitmaple5 1
26 | #define maskmaple5 2
27 |
28 | #define PORTMAPLE PORTB
29 | #define PINMAPLE PINB
30 | #define DDRMAPLE DDRB
31 |
32 | .macro maple_high pin
33 | SBI _SFR_IO_ADDR(PORTMAPLE), \pin
34 | .endm
35 |
36 | .macro maple_low pin
37 | CBI _SFR_IO_ADDR(PORTMAPLE), \pin
38 | .endm
39 |
40 | .macro debug_pin_high
41 | SBI _SFR_IO_ADDR(PORTD), 7
42 | .endm
43 |
44 | .macro debug_pin_low
45 | CBI _SFR_IO_ADDR(PORTD), 7
46 | .endm
47 |
48 | .macro debug_pin_flip
49 | IN tmp1, _SFR_IO_ADDR(PORTD)
50 | ANDI tmp1, 0x80
51 | BREQ 1f
52 | debug_pin_low
53 | RJMP 2f
54 | 1: debug_pin_high
55 | 2:
56 | .endm
57 |
58 | .macro delayquarter
59 | nop
60 | nop
61 | nop
62 | nop
63 | .endm
64 |
65 | .macro delayhalf
66 | nop
67 | nop
68 | nop
69 | nop
70 | nop
71 | nop
72 | nop
73 | nop
74 | .endm
75 |
76 | maple_tx_start:
77 | maple_high bitmaple1
78 | maple_high bitmaple5
79 | delayquarter
80 | ; Start of sequence: pin 1 goes low, pin 5 goes high.
81 | maple_low bitmaple1
82 | maple_high bitmaple5
83 | delayquarter
84 | ; Toggle pin 5 four times: 1
85 | maple_low bitmaple5
86 | delayquarter
87 | maple_high bitmaple5
88 | delayquarter
89 | ; 2
90 | maple_low bitmaple5
91 | delayquarter
92 | maple_high bitmaple5
93 | delayquarter
94 | ; 3
95 | maple_low bitmaple5
96 | delayquarter
97 | maple_high bitmaple5
98 | delayquarter
99 | ; 4
100 | maple_low bitmaple5
101 | delayquarter
102 | maple_high bitmaple5
103 | delayquarter
104 | ; End of sequence: pin 1 goes high, pin 5 goes low.
105 | maple_high bitmaple1
106 | delayquarter
107 | maple_low bitmaple5
108 | delayquarter
109 | ret
110 |
111 | maple_tx_end:
112 | ; Start of sequence: pin 1 is high, pin 5 is low.
113 | maple_high bitmaple1
114 | maple_low bitmaple5
115 | delayquarter
116 | ; Pin 5 goes high for a short time.
117 | maple_high bitmaple5
118 | delayquarter
119 | ; Pin 5 goes low
120 | maple_low bitmaple5
121 | delayquarter
122 | ; Pin 1 goes low for 2 cycles
123 | maple_low bitmaple1
124 | delayhalf
125 | ; Pin 1 goes high for 2 cycles
126 | maple_high bitmaple1
127 | delayhalf
128 | ; Pin 1 goes low for 2 cycles
129 | maple_low bitmaple1
130 | delayhalf
131 | ; Pin 1 goes high for 2 cycles.
132 | maple_high bitmaple1
133 | delayhalf
134 | ; End of sequence: pin 5 goes high
135 | maple_high bitmaple5
136 | ret
137 |
138 |
139 | maple_tx_phase1:
140 | BST r18, 7 ; Read the bit
141 | BRTC 1f ; Is it set?
142 | maple_high bitmaple5 ; Yes, put the bit on the wire
143 | 1:
144 | maple_low bitmaple1 ; New data is ready
145 | delayquarter ; Wait for other end to notice
146 | maple_high bitmaple5 ; Phase complete
147 | ret
148 |
149 | maple_tx_phase2:
150 | BST r18, 7 ; Read the bit
151 | BRTC 1f ; Is it set?
152 | maple_high bitmaple1 ; Set it
153 | 1:
154 | maple_low bitmaple5 ; Notify
155 | delayquarter ; Wait for other end to notice
156 | maple_high bitmaple1 ; Phase complete
157 | ret
158 |
159 | ; r22: length
160 | ; r23: unused
161 | ; r24: low order bit of buffer pointer
162 | ; r25: high order bit of buffer pointer
163 | maple_tx_data:
164 | MOVW r26, r24 ; X register <- the buffer
165 |
166 | _maple_tx_next_byte:
167 | LD r18, X+ ; Next byte of data into r18
168 |
169 | ; put the byte on the bus one bit at a time.
170 | RCALL maple_tx_phase1 ; bit 7
171 | LSL r18
172 | RCALL maple_tx_phase2 ; bit 6
173 | LSL r18
174 | RCALL maple_tx_phase1 ; bit 5
175 | LSL r18
176 | RCALL maple_tx_phase2 ; bit 4
177 | LSL r18
178 | RCALL maple_tx_phase1 ; bit 3
179 | LSL r18
180 | RCALL maple_tx_phase2 ; bit 2
181 | LSL r18
182 | RCALL maple_tx_phase1 ; bit 1
183 | LSL r18
184 | RCALL maple_tx_phase2 ; bit 0
185 |
186 | DEC r22 ; Are we done yet?
187 | BRNE _maple_tx_next_byte ; No, get the next character
188 |
189 | ret
190 |
191 | ; r22: length (1 byte)
192 | ; r23: unused
193 | ; r24: low order bit of buffer pointer
194 | ; r25: high order bit of buffer pointer
195 |
196 | ; Returns pointer to final character stored in r25:r24
197 | maple_tx_raw:
198 | ; Set pins to output for transmission
199 | SBI _SFR_IO_ADDR(DDRMAPLE), bitmaple1
200 | SBI _SFR_IO_ADDR(DDRMAPLE), bitmaple5
201 |
202 |
203 | RCALL maple_tx_start
204 | RCALL maple_tx_data
205 | RCALL maple_tx_end
206 |
207 | ; Tx over: set pins to input and enable pull-up resistors.
208 | SBI _SFR_IO_ADDR(PORTMAPLE), bitmaple1
209 | SBI _SFR_IO_ADDR(PORTMAPLE), bitmaple5
210 | CBI _SFR_IO_ADDR(DDRMAPLE), bitmaple1
211 | CBI _SFR_IO_ADDR(DDRMAPLE), bitmaple5
212 |
213 | ret
214 |
215 | ; Watchdog timer
216 | .macro timer_init
217 | CLI
218 |
219 | STS TIMSK2, r1 ; Normal mode (no PWM)
220 |
221 | STS TCCR2A, r1 ; Normal mode (no PWM)
222 |
223 | LDI r18, 1 << CS22 | 1 << CS21 | 1 << CS20
224 | STS TCCR2B, r18 ; Timer increments every 1024 cycles
225 | ; and overflows every 1024 * 256 cycles,
226 | ; or about 61 times a second at 16MHz
227 |
228 | STS TCNT2, r1 ; Timer count
229 |
230 | LDI r18, 1 ; TIFR2[0] -> 1 to clear
231 | STS TIFR2, r18 ; overflow irq flag
232 |
233 | SEI
234 | .endm
235 |
236 | .macro timer_on
237 | LDI r18, 1 << TOIE2
238 | STS TIMSK2, r18
239 | .endm
240 |
241 | .macro timer_off
242 | STS TIMSK2, r1
243 | .endm
244 |
245 | .macro timer_reset
246 | STS TCNT2, r1 ; Count register -> 0
247 | .endm
248 |
249 | ; Reading
250 | .macro _rx_read clockbit
251 | ; Version 1:
252 | ; Worst-case scenario here: the port is set halfway through the IN -- total
253 | ; cost is most of the IN, the BST, 2 cycles for the BRTS, plus the next IN,
254 | ; for a total of 5 cycles, which is sometimes out of tolerance.
255 | 1: IN rport, _SFR_IO_ADDR(PINMAPLE) ; 1
256 | BST rport, \clockbit ; 1
257 | BRTS 1b ; 1 (if fall through)
258 |
259 | ; Version 2:
260 | ; This works a little better because by the time we detect the falling edge
261 | ; we already have the value.
262 | ;1: IN rport, _SFR_IO_ADDR(PINMAPLE)
263 | ; SBIC _SFR_IO_ADDR(PINMAPLE), \clockbit
264 | ; RJMP 1b
265 | .endm
266 |
267 | .macro _rx_store reg srcbit destbit
268 | BST \reg, \srcbit ; 1
269 | BLD r18, \destbit ; 1
270 | ;1: IN \reg, _SFR_IO_ADDR(PINMAPLE)
271 | ; BST \reg, \srcbit
272 | ; BRTC 1b
273 | .endm
274 |
275 | maple_rx_raw:
276 | MOVW r26, r24 ; X register <- the buffer
277 |
278 | ; Set up our bailout routine: r25:24 point to an address to "reti" to.
279 | ldi r25, hh8(pm(_maple_rx_raw_watchdog))
280 | ldi r24, hi8(pm(_maple_rx_raw_watchdog))
281 | ldi r23, lo8(pm(_maple_rx_raw_watchdog))
282 |
283 | LDS r18, TIMSK0 ; disable Arduino interrupts
284 | PUSH r18 ; to re-enable later
285 | STS TIMSK0, r1
286 |
287 | timer_init ; Set up our watchdog timer
288 |
289 | ; Tell the device it can start sending data. (this is from the tx lead-out)
290 | ; maple_high bitmaple5
291 |
292 | ; Wait for a start sequence
293 | timer_on ; rx will start soon
294 |
295 | 1: IN rport2, _SFR_IO_ADDR(PINMAPLE)
296 | BST rport2, bitmaple1 ; maple1
297 | BRTC 1b ; must be high initially
298 |
299 | 2: IN rport2, _SFR_IO_ADDR(PINMAPLE)
300 | BST rport2, bitmaple1
301 | BRTS 2b ; must be low to continue
302 |
303 | 3: IN rport2, _SFR_IO_ADDR(PINMAPLE)
304 | BST rport2, bitmaple1 ; maple1
305 | BRTC 3b ; must be high now
306 |
307 | _rx_read bitmaple1
308 | _rx_loop:
309 | _rx_store rport bitmaple5 7
310 | _rx_read bitmaple5
311 | _rx_store rport bitmaple1 6
312 | _rx_read bitmaple1
313 | _rx_store rport bitmaple5 5
314 | _rx_read bitmaple5
315 | _rx_store rport bitmaple1 4
316 | _rx_read bitmaple1
317 | timer_reset ; pat the watchdog
318 | _rx_store rport bitmaple5 3
319 | _rx_read bitmaple5
320 | _rx_store rport bitmaple1 2
321 | _rx_read bitmaple1
322 | _rx_store rport bitmaple5 1
323 | _rx_read bitmaple5
324 | _rx_store rport bitmaple1 0 ; 2
325 | ST X+, r18
326 | _rx_read bitmaple1
327 | RJMP _rx_loop
328 |
329 | _maple_rx_raw_watchdog: ; Watchdog bailed us out. We're done!
330 | ; debug_pin_high
331 | POP r18 ;
332 | STS TIMSK0, r18 ; re-enable Arduino interrupts
333 |
334 | MOVW r24, r26 ; end of buffer <- X register
335 |
336 | ret
337 |
338 | ; Watchdog timer interrupt routine
339 | ; When this fires, it returns to r25:r24, effectively doing
340 | ; a longjmp().
341 | TIMER2_OVF_vect:
342 | IN intsave, _SFR_IO_ADDR(SREG) ; Save registers;
343 |
344 | POP intwork ; Remove the old return address
345 | POP intwork ; all three words
346 | POP intwork ;
347 | PUSH r23 ; Store the new return address
348 | PUSH r24 ; all three words
349 | PUSH r25 ;
350 | STS TIMSK2, r1 ; Disable the timer
351 |
352 | OUT _SFR_IO_ADDR(SREG), intsave ; Restore registers
353 | reti
354 |
355 | maple_timer_test:
356 | ; Set up our bailout routine: r25:24 point to an address to "reti" to.
357 | ; broken for 22 bit pc
358 | ldi r25, hh8(pm(maple_timer_test_watchdog))
359 | ldi r24, hi8(pm(maple_timer_test_watchdog))
360 | ldi r23, lo8(pm(maple_timer_test_watchdog))
361 |
362 | ; Set up the timer
363 | timer_init
364 | timer_on
365 |
366 | 1:
367 | nop
368 | rjmp 1b
369 |
370 | maple_timer_test_watchdog:
371 | ; Timer got us!
372 | timer_off
373 |
374 | ret;
375 |
376 | ; Turn the led on if r24 (first int parameter) is 1, off if r24 is 0.
377 | debug:
378 | IN r18, _SFR_IO_ADDR(PORTB)
379 | bst r24, 0 ; first parameter: on or off?
380 | bld r18, DEBUG_LED_BIT ; bit 5 on duemilanove, bit 7 on the mega
381 | out _SFR_IO_ADDR(PORTB), r18 ; on port B
382 | ret
383 |
384 |
--------------------------------------------------------------------------------
/maple.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # copy of large chunks of maple.py for debug / testing purposes.
3 | import sys
4 | import struct
5 | import select
6 | import serial
7 | import time
8 | import argparse
9 | import collections
10 |
11 | PORT='/dev/tty.usbserial-A700ekGi' # OS X (or similar)
12 | FN_CONTROLLER = 1
13 | FN_MEMORY_CARD = 2
14 | FN_LCD = 0x4
15 | FN_CLOCK = 0x8
16 | FN_MICROPHONE = 0x10
17 | FN_AR_GUN = 0x20
18 | FN_KEYBOARD = 0x40
19 | FN_LIGHT_GUN = 0x80
20 | FN_PURU_PURU = 0x100
21 | FN_MOUSE = 0x200
22 |
23 | FN_CODE_MAP = {FN_CONTROLLER: 'CONTROLLER', FN_MEMORY_CARD: "MEMORY_CARD",
24 | FN_LCD: "LCD", FN_CLOCK: "CLOCK", FN_MICROPHONE: "MICROPHONE",
25 | FN_AR_GUN: "AR_GUN", FN_KEYBOARD: "KEYBOARD", FN_LIGHT_GUN: "LIGHT_GUN",
26 | FN_PURU_PURU: "PURU_PURU", FN_MOUSE: "MOUSE"}
27 |
28 | # Device commands
29 | CMD_INFO = 0x01
30 | CMD_INFO_EXT = 0x02
31 | CMD_RESET = 0x03
32 | CMD_SHUTDOWN = 0x04
33 | CMD_INFO_RESP = 0x05
34 | CMD_INFO_EXT_RESP = 0x06
35 | CMD_ACK_RESP = 0x07
36 | CMD_XFER_RESP = 0x08
37 | CMD_GET_COND = 0x09
38 | CMD_GET_MEMINFO = 0x0A
39 | CMD_READ = 0x0B
40 | CMD_WRITE = 0x0C
41 | CMD_WRITE_COMPLETE = 0x0D
42 | CMD_SET_COND = 0x0E
43 | CMD_NO_RESP = 0xFF
44 | CMD_UNSUP_FN_RESP = 0xFE
45 | CMD_UNKNOWN_RESP = 0xFD
46 | CMD_RESEND_RESP = 0xFC
47 | CMD_FILE_ERR_RESP = 0xFB
48 |
49 |
50 | # Hardcoded recipient addresses.
51 | # Controller, main peripheral, port A
52 | ADDRESS_CONTROLLER = 1 << 5
53 | # Controller, first sub-peripheral, port A
54 | ADDRESS_PERIPH1 = 1
55 | # Dreamcast, magic value, port A
56 | ADDRESS_DC = 0
57 |
58 | # At least this many trailing samples must have both pins high in order for the receive to be
59 | # considered complete.
60 | IDLE_SAMPLES_INDICATING_COMPLETION = 8
61 |
62 | SKIP_LOOP_LENGTH = 2 # size is given in samples
63 |
64 | # Safety factor for skip loop to give us a chance to align subsequences -- turns out not to be needed
65 | RX_SKIP_SAFETY_FACTOR = 0 # samples
66 |
67 | # Number of samples stored per byte.
68 | RAW_SAMPLES_PER_BYTE = 4
69 |
70 | log = print
71 |
72 | def debug_hex(packet):
73 | def ascii(b):
74 | return ' %c' % (b,) if 32 <= b <= 127 else '%02x' % (b,)
75 |
76 | #display = ['%02x %c ' % (item, ascii(item)) for item in packet]
77 | #return ''.join(display)
78 | return ''.join(ascii(item) for item in packet)
79 |
80 | def debug_txt(packet):
81 | return bytes([c if int(c) >= ord(' ') and int(c) <= ord('z') else ord('.') for c in packet])
82 |
83 | def print_header(data):
84 | words = data[0]
85 | sender = data[1]
86 | recipient = data[2]
87 | command = data[3]
88 | print("Command %x sender %x recipient %x length %x" % (command, recipient, sender, words))
89 |
90 | def swapwords(s):
91 | swapped = []
92 | while s:
93 | swapped.append(s[:4][-1::-1])
94 | s = s[4:]
95 | return b''.join(swapped)
96 |
97 | def get_command(data):
98 | if data:
99 | return data[3]
100 | else:
101 | return None
102 |
103 | def decode_func_codes(code):
104 | names = []
105 | for candidate in sorted(FN_CODE_MAP.keys()):
106 | if code & candidate:
107 | names.append(FN_CODE_MAP[candidate])
108 |
109 | return names
110 |
111 | BUTTONS = ["C", "B", "A", "START", "UP", "DOWN", "LEFT", "RIGHT",
112 | "Z", "Y", "X", "D", "UP2", "DOWN2", "LEFT2", "RIGHT2"]
113 | def print_controller_info(data):
114 | print_header(data)
115 | data = data[4:] # Header
116 | data = data[4:] # Func
117 | data = data[:-1] # CRC
118 | data = swapwords(data)
119 | buttons = struct.unpack("= 2:
269 | sys.stdout.write('%s ' % ('^c' if debug_bits_list[i][1] == 1 else 'c^'))
270 | else:
271 | sys.stdout.write(' ')
272 |
273 | sys.stdout.write('\n')
274 |
275 | for i in range(offset, end):
276 | if len(debug_bits_list[i]) == 3:
277 | character = debug_bits_list[i][2]
278 | sys.stdout.write(' %c ' % (character,) if 32 <= character < 128 else '%02x ' % (character,))
279 | else:
280 | sys.stdout.write(' ')
281 |
282 | sys.stdout.write('\n')
283 |
284 | #print('debit', '{0:08b}{1:08b}{2:08b}{3:08b}'.format(output[0], output[1], output[2], output[3]))
285 | print('bitcount', bitcount)
286 |
287 | # the recv was completed if at least the last IDLE_SAMPLES_INDICATING_COMPLETION samples
288 | # are all '11'.
289 | recv_completed = num_samples_all_high >= IDLE_SAMPLES_INDICATING_COMPLETION
290 |
291 | if recv_completed:
292 | # TODO: why?
293 | output = output[:-1]
294 |
295 | num_samples = (len(bitstring) * RAW_SAMPLES_PER_BYTE) - samples_this_byte
296 | return DecodedRx(result=bytes(output), num_samples=num_samples, completed=recv_completed)
297 |
298 | def align_messages(prev, current):
299 | return prev + current
300 |
301 | def calculate_recv_skip(samples_so_far):
302 | " Calculate the amount to skip forward. "
303 | samples_to_skip = max(0, samples_so_far - RX_SKIP_SAFETY_FACTOR)
304 | return samples_to_skip // SKIP_LOOP_LENGTH
305 |
306 | class MapleProxy(object):
307 | def __init__(self, port=PORT):
308 | log("connecting to %s" % (port))
309 | self.handle = serial.Serial(port, 57600, timeout = 1)
310 |
311 | total_sleep = 0
312 | while total_sleep < 5:
313 | print("are you there?")
314 | self.handle.write(b'\x00\x00\x00') # are-you-there
315 | result = self.handle.read(1)
316 | if result == b'\x01':
317 | break
318 | time.sleep(0.5)
319 | total_sleep += 0.5
320 | else:
321 | raise Exception()
322 |
323 | print("maple proxy detected")
324 |
325 | def __del__(self):
326 | if hasattr(self, 'handle'):
327 | self.handle.close()
328 |
329 | def deviceInfo(self, address, debug_filename=None):
330 | # cmd 1 = request device information
331 | info_bytes = self.transact(CMD_INFO, address, b'', debug_write_filename=debug_filename, allow_repeats=True)
332 | if not info_bytes:
333 | print("No device found at address:")
334 | print(hex(address))
335 | return False
336 |
337 | #print info_bytes, len(info_bytes)
338 | print_header(info_bytes[:4])
339 | info_bytes = info_bytes[4:] # Strip header
340 | print("Device information:")
341 | print("raw:", debug_hex(swapwords(info_bytes)), len(info_bytes))
342 | func, func_data_0, func_data_1, func_data_2, product_name,\
343 | product_license =\
344 | struct.unpack("HH", info_bytes[108:112])
346 | print("Functions :", ', '.join(decode_func_codes(func)))
347 | print("Periph 1 :", hex(func_data_0))
348 | print("Periph 2 :", hex(func_data_1))
349 | print("Periph 3 :", hex(func_data_2))
350 | #print "Area :", ord(area_code)
351 | #print "Direction? :", ord(connector_dir)
352 | print("Name :", debug_txt(swapwords(product_name)))
353 | print("License :", debug_txt(swapwords(product_license)))
354 | # These are in tenths of a milliwatt, according to the patent:
355 | print("Power :", standby_power)
356 | print("Power max :", max_power)
357 | return True
358 |
359 | def readFlash(self, address, block, phase):
360 | addr = (0 << 24) | (phase << 16) | block
361 | cmd = struct.pack("H", num_bytes)[0]
476 | raw_response = self.handle.read(num_bytes)
477 | if debug_write_filename:
478 | with open(debug_write_filename, 'wb') as h:
479 | h.write(raw_response)
480 |
481 | response = debittify(raw_response)
482 | if prev_response and prev_response.result == response.result:
483 | break
484 |
485 | return response
486 |
487 | def compute_checksum(self, data):
488 | checksum = 0
489 | for datum in data:
490 | checksum ^= datum
491 | return checksum
492 |
493 | def debug_dump(filename):
494 | with open(filename, 'rb') as h:
495 | raw_data = h.read()
496 |
497 | result = debittify(raw_data)
498 | print("raw:", debug_hex(swapwords(result)), len(result))
499 |
500 | def test():
501 | parser = argparse.ArgumentParser()
502 | parser.add_argument('-p', '--port', default=None)
503 | parser.add_argument('-d', '--debug-prefix', default=None)
504 | args = parser.parse_args()
505 |
506 | if args.port:
507 | bus = MapleProxy(args.port)
508 |
509 | # Nothing will work before you do a deviceInfo on the controller.
510 | # I guess this forces the controller to enumerate its devices.
511 | debug_filename = '%s-controller' % (args.debug_prefix,) if args.debug_prefix else None
512 | found_controller = bus.deviceInfo(ADDRESS_CONTROLLER, debug_filename=debug_filename)
513 | if not found_controller:
514 | print("Couldn't find controller.")
515 | #return
516 |
517 | debug_filename = '%s-vmu' % (args.debug_prefix,) if args.debug_prefix else None
518 | found_vmu = bus.deviceInfo(ADDRESS_PERIPH1, debug_filename=debug_filename)
519 | else:
520 | debug_dump(args.debug_prefix + '-controller')
521 |
522 | if __name__ == '__main__':
523 | test()
524 |
--------------------------------------------------------------------------------
/DreamcastMapleBusConnected.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
3382 |
--------------------------------------------------------------------------------