├── .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 | 13 | 15 | 18 | 23 | 24 | 27 | 32 | 33 | 36 | 41 | 42 | 45 | 50 | 51 | 54 | 59 | 60 | 63 | 68 | 69 | 72 | 77 | 78 | 81 | 86 | 87 | 90 | 95 | 96 | 99 | 104 | 105 | 108 | 113 | 114 | 117 | 122 | 123 | 126 | 131 | 132 | 135 | 140 | 141 | 144 | 149 | 150 | 153 | 158 | 159 | 162 | 167 | 168 | 171 | 176 | 177 | 180 | 185 | 186 | 189 | 194 | 195 | 198 | 203 | 204 | 207 | 212 | 213 | 216 | 221 | 222 | 225 | 230 | 231 | 234 | 239 | 240 | 243 | 248 | 249 | 252 | 257 | 258 | 261 | 266 | 267 | 270 | 275 | 276 | 279 | 284 | 285 | 288 | 293 | 294 | 297 | 302 | 303 | 306 | 311 | 312 | 315 | 320 | 321 | 324 | 329 | 330 | 333 | 338 | 339 | 342 | 347 | 348 | 351 | 356 | 357 | 360 | 365 | 366 | 369 | 374 | 375 | 378 | 383 | 384 | 387 | 392 | 393 | 396 | 401 | 402 | 405 | 410 | 411 | 414 | 419 | 420 | 423 | 428 | 429 | 432 | 437 | 438 | 441 | 446 | 447 | 450 | 455 | 456 | 459 | 464 | 465 | 468 | 473 | 474 | 477 | 482 | 483 | 486 | 491 | 492 | 495 | 500 | 501 | 504 | 509 | 510 | 513 | 518 | 519 | 522 | 527 | 528 | 531 | 536 | 537 | 540 | 545 | 546 | 549 | 554 | 555 | 558 | 563 | 564 | 567 | 572 | 573 | 576 | 581 | 582 | 585 | 590 | 591 | 594 | 599 | 600 | 603 | 608 | 609 | 612 | 617 | 618 | 621 | 626 | 627 | 630 | 635 | 636 | 639 | 644 | 645 | 648 | 653 | 654 | 657 | 662 | 663 | 666 | 671 | 672 | 675 | 680 | 681 | 684 | 689 | 690 | 693 | 698 | 699 | 702 | 707 | 708 | 711 | 716 | 717 | 720 | 725 | 726 | 729 | 734 | 735 | 738 | 743 | 744 | 747 | 752 | 753 | 756 | 761 | 762 | 765 | 770 | 771 | 774 | 779 | 780 | 783 | 788 | 789 | 792 | 797 | 798 | 801 | 806 | 807 | 810 | 815 | 816 | 819 | 824 | 825 | 828 | 833 | 834 | 837 | 842 | 843 | 846 | 851 | 852 | 855 | 860 | 861 | 864 | 869 | 870 | 873 | 878 | 879 | 882 | 887 | 888 | 891 | 896 | 897 | 900 | 905 | 906 | 909 | 914 | 915 | 918 | 923 | 924 | 927 | 932 | 933 | 936 | 941 | 942 | 945 | 950 | 951 | 954 | 959 | 960 | 963 | 968 | 969 | 972 | 977 | 978 | 981 | 986 | 987 | 990 | 995 | 996 | 999 | 1004 | 1005 | 1008 | 1013 | 1014 | 1017 | 1022 | 1023 | 1026 | 1031 | 1032 | 1035 | 1040 | 1041 | 1044 | 1049 | 1050 | 1053 | 1058 | 1059 | 1062 | 1067 | 1068 | 1071 | 1076 | 1077 | 1080 | 1085 | 1086 | 1089 | 1094 | 1095 | 1098 | 1103 | 1104 | 1107 | 1112 | 1113 | 1116 | 1121 | 1122 | 1125 | 1130 | 1131 | 1134 | 1139 | 1140 | 1143 | 1148 | 1149 | 1152 | 1157 | 1158 | 1161 | 1166 | 1167 | 1170 | 1175 | 1176 | 1179 | 1184 | 1185 | 1188 | 1193 | 1194 | 1197 | 1202 | 1203 | 1206 | 1211 | 1212 | 1215 | 1220 | 1221 | 1224 | 1229 | 1230 | 1233 | 1238 | 1239 | 1242 | 1247 | 1248 | 1251 | 1256 | 1257 | 1260 | 1265 | 1266 | 1269 | 1274 | 1275 | 1278 | 1283 | 1284 | 1287 | 1292 | 1293 | 1296 | 1301 | 1302 | 1305 | 1310 | 1311 | 1314 | 1319 | 1320 | 1323 | 1328 | 1329 | 1332 | 1337 | 1338 | 1341 | 1346 | 1347 | 1350 | 1355 | 1356 | 1359 | 1364 | 1365 | 1368 | 1373 | 1374 | 1377 | 1382 | 1383 | 1386 | 1391 | 1392 | 1395 | 1400 | 1401 | 1404 | 1409 | 1410 | 1413 | 1418 | 1419 | 1422 | 1427 | 1428 | 1431 | 1436 | 1437 | 1440 | 1445 | 1446 | 1449 | 1454 | 1455 | 1458 | 1463 | 1464 | 1467 | 1472 | 1473 | 1476 | 1481 | 1482 | 1485 | 1490 | 1491 | 1494 | 1499 | 1500 | 1503 | 1508 | 1509 | 1512 | 1517 | 1518 | 1521 | 1526 | 1527 | 1530 | 1535 | 1536 | 1539 | 1544 | 1545 | 1548 | 1553 | 1554 | 1557 | 1562 | 1563 | 1566 | 1571 | 1572 | 1575 | 1580 | 1581 | 1584 | 1589 | 1590 | 1593 | 1598 | 1599 | 1602 | 1607 | 1608 | 1611 | 1616 | 1617 | 1620 | 1625 | 1626 | 1629 | 1634 | 1635 | 1638 | 1643 | 1644 | 1647 | 1652 | 1653 | 1656 | 1661 | 1662 | 1665 | 1670 | 1671 | 1674 | 1679 | 1680 | 1683 | 1688 | 1689 | 1692 | 1697 | 1698 | 1701 | 1706 | 1707 | 1710 | 1715 | 1716 | 1719 | 1724 | 1725 | 1728 | 1733 | 1734 | 1737 | 1742 | 1743 | 1746 | 1751 | 1752 | 1755 | 1760 | 1761 | 1764 | 1769 | 1770 | 1773 | 1778 | 1779 | 1782 | 1787 | 1788 | 1791 | 1796 | 1797 | 1800 | 1805 | 1806 | 1809 | 1814 | 1815 | 1818 | 1823 | 1824 | 1827 | 1832 | 1833 | 1836 | 1841 | 1842 | 1845 | 1850 | 1851 | 1854 | 1859 | 1860 | 1863 | 1868 | 1869 | 1872 | 1877 | 1878 | 1881 | 1886 | 1887 | 1890 | 1895 | 1896 | 1899 | 1904 | 1905 | 1908 | 1913 | 1914 | 1917 | 1922 | 1923 | 1926 | 1931 | 1932 | 1935 | 1940 | 1941 | 1944 | 1949 | 1950 | 1951 | 1953 | 1959 | 1965 | 1971 | Auteur : Aurélien Colombet 1980 | 1986 | 1992 | Dreamcast controller maple bus 2001 | 2007 | 2013 | Fichier : 2022 | 2028 | 2034 | Date : 2043 | 2049 | 2055 | Folio : 1/1 2064 | 2070 | 2079 | 2085 | 2091 | 2097 | 2103 | 3.3V 2112 | GND 2121 | 2127 | 2133 | GND 2142 | 2148 | Vin 2157 | 2163 | ANALOG IN 2172 | POWER 2181 | RESET 2190 | 5V 2199 | 2205 | IO ref 2214 | 2220 | 2226 | 2232 | 2238 | 2244 | 2250 | 2256 | 2262 | 2268 | 2274 | 2280 | 2286 | 2292 | RX 2301 | 2307 | 2313 | 2319 | 2325 | 2331 | L 2340 | 2346 | 2352 | 2358 | 2364 | 2370 | 2376 | 2382 | 2388 | 2394 | 2400 | 2406 | 2412 | 2418 | 2424 | 2430 | 2436 | 2442 | 2448 | 2454 | 2460 | 2466 | 2472 | 2478 | 2484 | 2490 | 2496 | 2502 | 2508 | 2514 | 2520 | 2526 | 2532 | 2538 | 2544 | 2550 | 2556 | 2562 | A5 2571 | 2577 | A4 2586 | A3 2595 | 2601 | A0 2610 | 2616 | A2 2625 | 2631 | A1 2640 | 2646 | ~9 2655 | 12 2664 | ~10 2673 | TX->1 2682 | RX<-0 2691 | ~11 2700 | 8 2709 | 4 2718 | 7 2727 | ~3 2736 | 2 2745 | 13 2754 | DIGITAL (PWM~) 2763 | ~5 2772 | ~6 2781 | AREF 2790 | 2796 | 2802 | 2808 | 2814 | 2820 | 2826 | 2832 | 2838 | 2844 | 2850 | 2856 | 2862 | 2868 | 2874 | 2880 | 2886 | 2892 | 2898 | 2904 | 2910 | 2916 | 2922 | 2928 | 2934 | 2940 | 2946 | 2952 | 2958 | 2964 | 2970 | 2976 | 2982 | 2988 | 2994 | 3000 | 3006 | 3012 | GND 3021 | 3027 | 3033 | ARDUINO 3042 | 3048 | 3054 | 3060 | 3066 | UNO 3075 | 3081 | 3087 | 3093 | 3099 | 3105 | 3111 | 3117 | 3123 | 3129 | TX 3138 | AIMEL 3147 | 3153 | 3159 | 3165 | 3171 | 3177 | 3183 | 3189 | 3195 | MADE IN ITALY 3204 | 3210 | 3216 | 3222 | 3228 | 3234 | 3240 | 3246 | 3252 | 3258 | 3264 | 3270 | 3276 | 3282 | 3285 | male connector controller 3294 | 3295 | 3298 | ARDUINO 3307 | 3308 | 3314 | 3320 | 3326 | 3332 | 3338 | 3344 | 3350 | 3356 | 3362 | 3368 | 3374 | 3380 | 3381 | 3382 | --------------------------------------------------------------------------------