From 8807187ed130ba733caa7093b48cdaa0d2fc64f0 Mon Sep 17 00:00:00 2001 From: Charles Mangin Date: Wed, 20 Jul 2016 16:15:34 -0500 Subject: [PATCH] Update README.md --- README.md | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 520 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66b0dd3..ad87769 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,520 @@ -# gp2io -GP2IO board for Apple II, presented at KansasFest 2016 +=== +What is GP2IO? +=== + +My goal is to create an interface between Apple II and the library of peripherals and devices that exist for the modern hobbyist, a list that has exploded recently due to the popularity of Arduino, Raspberry Pi, and other programmable single board computers. + +These peripherals - displays, LED arrays, sensors, motor controllers and more – generally communicate via one of a handful of protocols like SPI, I2C or serial. Out of the box, an Apple II can't talk on these channels except serial. Even serial has its limits, though, since the Super Serial Card and later models with built-in serial ports use the older RS232 standard. This requires an adapter to convert the signal voltages to TTL for more modern UART serial, that's built into Arduino, Pi, etc. + +The "glue" between the Apple II and the newer devices is the GP2IO. This stands for "General Purpose Game Port I/O" + +GP2IO is based on sending serial data from an Apple II game port annunciator pin to an Atmel AVR microcontroller. An Arduino "sketch" on the AVR interprets the data and then acts on it accordingly – triggering LEDs, sending or receiving data to peripherals via I2C, serial, SPI, etc. + +Communication is bidirectional, as well. Data is received on the Apple II via reading data shifted into one of the paddle button pins from the AVR, similar to the way data is read through the cassette input. + + +Reference: + + ========= + Pushbutton 3 (GS) / NC 9 | * * | 8 Ground + Gm Ctrl 1 (Stick-1 Y) 10 | * * | 7 Gm Ctrl 2 (Stick-2 X) + Gm Ctrl 3 (Stick-2 Y) 11 | * * | 6 Gm Ctrl 0 (Stick-1 X) + Annunciator 3 12 | * * | 5 +5V (GS ONLY) / $C040 Strobe (II-IIe) + Annunciator 2 13 | * * | 4 Pushbutton 2 + Annunciator 1 14 | * * | 3 Pushbutton 1 + Annunciator 0 15 | * * | 2 Pushbutton 0 + No Connection 16 | * * | 1 +5V + ===| |=== + ^ + Notch on socket + (faces toward front of computer) + +For input, I use the joystick/paddle button pins on the game port. Setting these to TTL HIGH (pressed) or LOW (not pressed) and "bit banging" in rapid succession gives them the capacity to carry serial data. Elsewhere, this is referred to as the Track & Field Protocol (http://atomsandelectrons.com/blog/2010/04/apple-t/) + +The first two buttons addresses are shared with the Apple keys, so it's best not to fiddle with those, if your program uses the Apple keys for keyboard input, macros, or for the PEW PEW! + + PB0 / OPNAPPLE = $C061 ; open apple (command) key data (read) + PB1 / CLSAPPLE = $C062 ; closed apple (option) key data (read) + PB2 = $C063 ; game Pushbutton 2 (read) <- Our victim + + PB3 (GS only) = $C060 ; game Pushbutton 3 (read) + $C060 bit 7 = data from cassette on Apple II, II+, IIe + +Button 3 and the cassette share the same memory address $C060. So, we could toggle pin 9 on the game port (PB3) to load data directly into the memory address for the built-in cassette routines. But that pin is only on the GS. Poo. And the GS, having no cassette input, has no cassette routine in ROM. Double poo. + +Luckily, there are already examples of reading in data on those pins. + +I dug into [Michael Mahon's NadaNet](http://michaeljmahon.com/NadaNetPaper.html) implementation for inspiration, and found proof of speedy and reliable communication on the game port. This was encouraging. + + + +But, rather than require developers to implement NadaNet network packet reads just to get a few bytes into and out of an AVR, we can continue to take inspiration from the cassette routines. Using those as a starting point, I look for a transition from LOW to HIGH in pushbutton 1, then count the loops until the transition from HIGH to LOW. If it's a short time, the bit is zero, if it's longer, a one. + +=== + +The game port also has four output pins: Annunciators 0,1,2,3. Each has a softswitch and memory location for "setting" and "clearing", counterintuitively these are 0 and 1 respectively. For AVR comms, I'll refer to HIGH (1) and LOW (0). + + SETAN0 = $C058 ;Set annunciator-0 output to 0 + CLRAN0 = $C059 ;Set annunciator-0 output to 1 + + SETAN1 = $C05A ;Set annunciator-1 output to 0 + CLRAN1 = $C05B ;Set annunciator-1 output to 1 + + SETAN2 = $C05C ;Set annunciator-2 output to 0 + CLRAN2 = $C05D ;Set annunciator-2 output to 1 + + SETAN3 = $C05E ;Set annunciator-3 output to 0 + CLRAN3 = $C05F ;Set annunciator-3 output to 1 + +NOTE: You won't likely be able to use the GP2IO in this mode with a double hi-res program. Double hi-res is incompatible with the annunciators since it clobbers those memory locations: + + SETIOUDIS= $C07E ;enable DHIRES & disable $C058-5F (W) + CLRIOUDIS= $C07E ;disable DHIRES & enable $C058-5F (W) + + SETDHIRES= $C05E ;if IOUDIS Set, turn on double-hires + CLRDHIRES= $C05F ;if IOUDIS Set, turn off double-hires + +Sorry. + +Other peripherals, like the ZIP Chip, use the annunciators' memory locations for setting configuration flags, so, timing matters aside, there are probably other incompatible setups as well. + +Sorry again. + + +=== +Sending data from Apple II to GP2IO +=== + +Considering it's available in ROM on a bare II or II+, repurposing the cassette output seemed like a good place to start. There's no handshaking (other than the user pressing Record on the tape recorder) and it requires minimal effort on the Apple II to send a stream of bytes. The source for the ROM cassette output, via + https://github.com/cmosher01/Apple-II-Source/blob/master/src/system/monitor/common/cassette.m4 + + TAPEOUT = $C020 + + HEADR LDY #$4B ;WRITE A*256 'LONG 1' + JSR ZERDLY ; HALF CYCLES + BNE HEADR ; (650 USEC EACH) + ADC #$FE + BCS HEADR ;THEN A 'SHORT 0' + LDY #$21 ; (400 USEC) + WRBIT JSR ZERDLY ;WRITE TWO HALF CYCLES + INY ; OF 250 USEC ('0') + INY ; OR 500 USEC ('0') + ZERDLY DEY + BNE ZERDLY + BCC WRTAPE ;Y IS COUNT FOR + LDY #$32 ; TIMING LOOP + ONEDLY DEY + BNE ONEDLY + WRTAPE LDY TAPEOUT + LDY #$2C + DEX + RTS + +Sending bytes from the Apple II via GP2IO is similar to writing to the cassette port. Instead of writing to the $C020 casette output for each bit, annunciator 1 is set to HIGH, then back to LOW after a short wait (zero) or longer wait (one). + +There is also a short "reset" transition from LOW to HIGH between bits, which are ignored by the AVR, to remove the necessity of keeping track of the ANN1 value between bits and bytes. + +I've also added in rudimentary handshaking with "Ready to Send" signals. Before sending, to tell the AVR to be ready to receive, I set annunciator 0 HIGH ("clear ANN0"), then LOW again when the byte is done. For speed, and since we have two-way communications, we can dispense with the long header write and sync bit of the cassette routine. + +Here is the source, which should be fairly relocatable (thanks, Mark Pilgrim): + +SENDBYTE +entry point $0303, or $0300 for sending single bit +uses $EF for storing outgoing byte + + 0300- A0 01 LDY #$01 ; load 1, for a 1 bit send - OR - + 0302- 2C BIT ; + 0303- A0 08 LDY #$08 ; load 8 for a full byte, loop counter + 0305- 85 EF STA $EF ; put byte in zero page for safe keeping + 0307- 8D 59 C0 STA $C059 ; annunciator 0 high, RTS + 030A- 26 EF ROL $EF ; rotate byte left, high/MSB out to carry (sendbit) + 030C- 90 03 BCC $0311 ; "branch on carry clear" - if carry/bit = 0, goto #311 (short loop) + 030E- A2 14 LDX #$14 ; if carry/bit = 1, load X with 20 (long loop) - OR - + 0310- 2C BIT + 0311- A2 0A LDX #$0A ; if carry/bit = 0, load X with 10 (short loop) + 0313- 8D 5B C0 STA $C05B ; set annunciator 1 HIGH + 0316- CA DEX ; decrement, countdown to setting ANN1 low (countdown) + 0317- D0 FD BNE $0316 ; if X > 0, keep counting (countdown) + 0319- 8D 5A C0 STA $C05A ; if done, set ANN1 LOW, countdown to getting next bit + 031C- A2 05 LDX #$05 ; reset counter for short "reset" transition. This lets the GP2IO stage the bit before the next interrupt + 031E- CA DEX ; count down again (countdown2) + 031F- D0 FD BNE $031E ; if X > 0, keep counting (countdown2) + 0321- 88 DEY ; decrement y, countdown bits sent + 0322- D0 E6 BNE $030A ; if y > 0, next bit (sendbit) + 0324- 8D 58 C0 STA $C058 ; annunciator 0 low, sending RTS OFF + 0327- 60 RTS ; return + + + ___---------------------___ RTS line goes high + ____--_-_--_--_-_--_-_-____ as Apple sends 10110100 (0xB4, ASCII 4) + + +On the GP2IO side, the AVR has two interrupts set on ANN0 and ANN1: as the RTS pin goes HIGH (rising), and on changes to the data pin. If the data pin changes state in less than 70 microseconds (.07 milliseconds) the bit is read as zero. Longer than 70 microseconds, a one. + + attachInterrupt(0, APPLERTS, RISING); // ANNUNCIATOR 0, Apple sending byte + attachInterrupt(1, RECEIVINGBITS, CHANGE); // ANNUNCIATOR 1, Apple sending bits + + void APPLERTS() { + // signal to start receiving bits from Apple II + bitCount = 0; + changeCount = 0; + returnByte = B00000000; + } + + void RECEIVINGBITS() + { + // ignore short "reset" transitions + currentMicros = micros(); + + if (changeCount % 2 == 1) { + + if ((currentMicros - lastMicros) > 70) { + + receivedBit = 1; + + } else { + + receivedBit = 0; + } + + byteArray[7 - bitCount] = receivedBit; + + bitCount++; + + + } + changeCount++; + + if (bitCount == 8) { // got a BYTE + receivedByte = arrayToByte(byteArray, 8); + PROCESSBYTE( byte(receivedByte) ); + TIMEOUTCLOCK = millis(); + } + + lastMicros = currentMicros; + + } + + + + + +=== +Practical example SENDKEY +=== +Read input from the keyboard, send each byte to GP2IO + + 20 1B FD JSR $FD1B ; wait for keyboard input, byte now in Accumulator + C9 9B CMP #$9B ; check for ESC + F0 0B BEQ $0812 ; return on ESC + 20 ED FD JSR $FDF0 ; print byte to screen, local echo + 09 80 ORA #$80 ; clear bit 7 (for "low" vs "high" ASCII, display purposes) + 20 03 03 JSR $0303 ; send the byte to GP2IO (SENDBYTE) + 4C 00 08 JMP $0800 ; repeat + 60 RTS ; return on ESC + + + + +=== +Processing Bytes on the GP2IO +=== + +The AVR starts in a null "standby" state, waiting for a "control" byte from the Apple II to set its function. Subsequent bytes after the function is set are the "message". The value of the first byte from the Apple determines the function according to this table: + +$01 Sets the RGB LED on the GP2IO to the color value of the next three bytes, in order Red, Green and Blue. Requires 3 byte message +$02 Sets the RGB LED to white, intensity based on the 1 byte message that follows (0-255). +$04 Write the following message to an internal buffer, for retrieval later. First byte is message length (0-255 bytes follow) +$08 Write the following message to UART serial (buffered). First byte is message length (0-255 bytes follow) +$10 Write the following 8 bytes to I2C bus. For the demo, this is connected to either an 8x8 LED matrix or a 4 character 7-segment display. Requires 8 byte message. +$20 SPI (not yet implemented) +$40 Query the buffer. Triggers the AVR to respond with one byte, containing the length of the current buffer. +$80 Send the buffer. Triggers the AVR to respond with the first N bytes of the buffer, where N is the byte following the $80 trigger. +$00 Debug mode. At the moment, simply echoes the bytes received from the Apple side into the serial buffer, and prints them to USB serial out. Also sends one byte from the GP2IO buffer to the Apple whenever the APPLECTS signal (Annunciator 2) goes high. + + +Each function, as outlined, expects a certain number of bytes per message. For example, to set an RGB LED requires three bytes - one each for Red, Green, and Blue values, plus the first $01 byte to set the function, for a total of four. + +The AVR then shuttles those bytes to the proper output, sends results back to the Apple, or waits for further instruction. + +After each message is done, the GP2IO returns to standby for the next function, so before setting the LED to a different color, you need to send the $01 control byte again. In case a message gets cut off, or something goes awry, the GP2IO will also go to standby if nothing is received for roughly 5 seconds. + +The LED on the GP2IO board goes to blue to indicate it has timed out or is in standby mode waiting for a control byte. + + +EXAMPLE: RGB LED Pulse white + + 0800- A9 FF LDA #$FF ; set accumulator = 255, starts with LED off after one INC + 0802- 48 PHA ; push FF to stack + 0803- A9 02 LDA #$02 ; set accumulator = 2, one byte LED mode + 0805- 20 03 03 JSR $0303 ; SENDBYTE, sets the mode (setmode) + 0808- 68 PLA ; pull current intensity from stack + 0809- 18 CLC ; clear carry + 080A- 69 01 ADC #$01 ; increment accumulator/intensity value, $FF rolls to $00 + 080C- 48 PHA ; push new intensity to stack + 080D- 20 03 03 JSR $0303 ; SENDBYTE, sets the LED intensity + 0810- 4C 03 08 JMP $0803 ; jump to (setmode) for next pass + + + +SLIGHTLY MORE COMPLEX EXAMPLE: RGB LED pulsing + +SETRGB: + one-shot, sets the LED with red value in $06, green at $07, blue at $08 + + 0800- A9 01 LDA #$01 ; set accumulator to 1, three byte RGB mode + 0802- 20 03 03 JSR $0303 ; $0303 = SENDBYTE routine + 0805- A5 06 LDA $06 ; read in red value from zero page + 0807- 20 03 03 JSR $0303 ; SENDBYTE + 080A- A5 07 LDA $07 ; read green value + 080C- 20 03 03 JSR $0303 ; SENDBYTE + 080F- A5 08 LDA $08 ; read blue value + 0811- 20 03 03 JSR $0303 ; SENDBYTE + 0814- 60 RTS ; return. + +CYCLERGB + + 0816- A9 00 LDA #$00 ; set accumulator to 0 + 0818- 85 06 STA $06 ; zero to red value, + 081A- 85 07 STA $07 ; zero green + 081C- 85 08 STA $08 ; zero blue + 081E- 20 00 08 JSR $0800 ; SETRGB + 0821- A5 06 LDA $06 ; load red value + 0823- 69 02 ADC #$02 ; add 2 to current red value + 0825- 85 06 STA $06 ; push it to zero page $06 + 0827- B0 F5 BCS $081E ; if still less than $FF, return and SETRGB + 0829- A5 07 LDA $07 ; else, red has rolled over, load green + 082B- 69 02 ADC #$02 ; increment green x2 + 082D- 85 07 STA $07 ; store green + 082F- B0 ED BCS $081E ; if less than $FF, return and SETRGB + 0831- A5 08 LDA $08 ; else, green has rolled over, load blue + 0833- 69 02 ADC #$02 ; increment blue + 0835- 85 08 STA $08 ; store blue + 0837- 4C 1E 08 JMP $081E ; return and SETRGB + 083A- 00 BRK + + + + + +=== +Receiving Data from the GP2IO to the Apple +=== + +The plan, again, is to mimic data coming in on the cassette port, but with the pushbutton signal going high and low instead of tones on the casette. About the cassette input: + + "Routines in the monitor scan bit 7 at location $CO6O every 12.8 microseconds looking for a transition. By measuring the time between transitions, the routines can distinguish between header, sync, 0, and 1 bits." + + From Apple II Circuit Description by Winston Gaylor + ©1983 by Howard and Sams & Co, Inc. + +Here's the source for those calls, again via + https://github.com/cmosher01/Apple-II-Source/blob/master/src/system/monitor/common/cassette.m4 + + TAPEOUT = $C020 + TAPEIN = $C060 + + RDBYTE LDX #$08 ;8 BITS TO READ + RDBYT2 PHA ;READ TWO TRANSITIONS + JSR RD2BIT ; (FIND EDGE) + PLA + ROL ;NEXT BIT + LDY #$3A ;COUNT FOR SAMPLES + DEX + BNE RDBYT2 + RTS + + RD2BIT JSR RDBIT + RDBIT DEY ;DECR Y UNTIL + LDA TAPEIN ; TAPE TRANSITION + EOR LASTIN + BPL RDBIT + EOR LASTIN + STA LASTIN + CPY #$80 ;SET CARRY ON Y + RTS + + + +Here is my GP2IO READ BYTE routines: + +$EF = byte as it's read +$EE = pushbutton 1 value stored for comparison +$C05D = set annunciator 2 = 1 +$C05C = set annunciator 2 = 0 +Entry point at $34A. Received byte is returned in Accumulator + +READCOMBINED + 034A- A2 09 LDX #$09 ; reading 8 bits requires 9 transitions. + 034C- A9 00 LDA #$00 ; clear the accumulator + 034E- 85 EF STA $EF ; $EF is staging for received byte, set 0 + 0350- 85 EE STA $EE ; $EE is staging for each bit, set 0 + 0352- 8D 5D C0 STA $C05D ; set ANN2 HIGH, indicate to AVR "Clear to Send" + 0355- A0 FF LDY #$FF ; start wait loop + 0357- C8 INY ; Increment Y - rolls over to 0 on first run (loopcount) + 0358- AD 62 C0 LDA $C062 ; check PB1 status hi/low + 035B- 29 80 AND #$80 ; clear 0-6 bits (just need bit 7, all others float) + 035D- C5 EE CMP $EE ; compare Accumulator 7 bit with $EE, previous PB2 value + 035F- D0 02 BNE $0363 ; if PB2 has changed state, store in $EE (bitchange) + 0361- F0 F4 BEQ $0357 ; bit hasn't changed yet, return to (loopcount) + 0363- 85 EE STA $EE ; (bitchange) + 0365- C0 44 CPY #$44 ; if the loop count is more than 68, bit is one. Bit is set in Carry + 0367- 26 EF ROL $EF ; rotate the new bit into $EF, our result byte. + 0369- CA DEX ; decrement X, our bit count + 036A- D0 E9 BNE $0355 ; if bit count is not yet full, loop back to (loopcount) + 036C- 8D 5C C0 STA $C05C ; if bit count is full, set ANN2 LOW, CTS off + 036F- A5 EF LDA $EF ; puts received byte into Accumulator + 0371- 60 RTS ; return with byte in Accumulator + + +And a demo: this reads one byte from the AVR output buffer to the screen. It gets one byte at a time, triggered by a keypress. The AVR will need to be ready to send one byte at a time when it sees ANN2 go HIGH ("SEND BUFFER CONTENT" mode, set by sending $80 as the function switch first). + +GETBYTEONKEY + 0800- 20 1B FD JSR $FD1B ; wait for keypress + 0803- 20 4A 03 JSR $034A ; READBYTE + 0806- 20 DA FD JSR $FDDA ; print hex to screen + 0809- 20 8B FD JSR $FD8B ; return carriage + 080C- 4C 00 08 JMP $0800 ; repeat + + +So, how to receive usefully large chunks of data at once? With no interrupts on the game port to work with on incoming data, and better things to do with your program than poll the game port constantly, we need to tell the AVR when to send, and how much data you're ready to store. + +The Apple sends a "Send Buffer Data" control byte (0x80) followed by another byte indicating how long the message should be (1-255 bytes). + +The GP2IO then sends the requested number of bytes from its internal output buffer, zero-padded if necessary. Future versions could optionally include a checksum. The checksum could be a simple AND of each byte, like what's done with the original cassette routine, or something more complicated. This is not yet implemented, and I leave that as an exercise for other programmers. + +Here is a buffered I/O routine for minimally interactive data transmission between the Apple II and a serial device connected to the GP2IO. + + 0800- 20 4D 08 JSR $084D ; start by querying the buffer length - is there anything waiting? + ; returns with buffer length in $08 + + 0804- A9 80 LDA #$80 ; load control byte $80 "send buffer bytes" + 0806- 20 03 03 JSR $0303 ; send control byte + 0809- A5 08 LDA $08 ; load buffer length (or however much buffer you want sent) + 080B- 20 03 03 JSR $0303 ; send message length byte + 080E- A9 28 LDA #$28 ; wait a moment (betweenbytes) + 0810- 20 A8 FC JSR $FCA8 ; wait + 0813- 20 4A 03 JSR $034A ; CTS - send byte + 0816- 09 80 ORA #$80 ; clear byte 7 (for ASCII) + 0818- 20 ED FD JSR $FDED ; display byte on screen + 081B- C6 08 DEC $08 ; count down bytes (countdownbuffer) + 081D- A5 08 LDA $08 ; load Accumulator with new buffer length + 0820- D0 EC BNE $080E ; if there's more to come, loop (betweenbytes) + 0822- 20 8E FD JSR $FD8E ; last byte. print CF/LF + 0825- 60 RTS ; return + +GETLINE + 0826- 20 6A FD JSR $FD6A ; get line of input + 0829- 8A TXA ; get input length into Accumulator + 082A- 85 07 STA $07 ; put input length into $07 + 082C- A9 00 LDA #$00 ; load zero into Accumulator + 082E- 85 06 STA $06 ; put zero into $06, keyboard buffer pointer + 0830- A9 04 LDA #$04 ; load Accumulator with control byte "write to buffer" + 0832- 20 03 03 JSR $0303 ; send control byte + 0835- A5 07 LDA $07 ; load input length from $07 + 0837- 20 03 03 JSR $0303 ; send message length + 083A- A6 06 LDX $06 ; load X with keyboard buffer pointer (keybuffer) + 083C- BD 00 02 LDA $0200,X ; load Accumulator with bytes from keyboard buffer (start at $200) + 083F- 29 7F AND #$7F ; clear high bit (for ASCII) + 0841- 20 03 03 JSR $0303 ; send byte + 0844- E6 06 INC $06 ; increment keyboard buffer pointer + 0846- A5 06 LDA $06 ; load buffer pointer into Accumulator + 0848- C5 07 CMP $07 ; compare with message length + 084A- D0 EE BNE $083A ; if not at end of message, loop to (keybuffer) + 084C- 60 RTS ; return + +QUERY BUFFER LENGTH +returns buffer length in $08 + + 084D- A9 40 LDA #$40 ; control byte for query buffer length + 084F- 20 03 03 JSR $0303 ; send control byte + 0852- 20 28 03 JSR $0328 ; wait routine - gives GP2IO time to receive bytes from serial + 0855- 8D 5C C0 STA $C05C ; set ANN2 to LOW (just in case it's floating high) + 0858- 20 4A 03 JSR $034A ; CTS - ready for response byte with buffer length + 085B- F0 F0 BEQ $084D ; if there's nothing in the buffer, loop until there is + 085D- 85 08 STA $08 ; put the byte in $08 + 085F- 60 RTS ; return + + + +Used Zero Page locations: + +06 07 08: SetRGB routine, Red Green Blue bytes + +EE: ReadBit +EF: ReadByte, SendByte + + + +=== +So, What Can It Do? +=== + +With a general purpose gateway to I2C, SPI and UART devices (among others), GP2IO lets the Apple II access a variety of different inputs and outputs, from sensors and alternate controllers to external data displays. + +Some use case ideas include: + - light indicator for disk activity, RWTS function + - trackstar-type indicator for bootloaders + - game status indicators - blink on collision, red for damage, etc + - get current internet time via NTP for real time clock function + + +=== +So, What Can't It Do? +=== + +The main limitation is the platforms the GP2IO can connect to. The IIc and IIc+ have no 16-pin game port, and the 9-pin game port on those models lacks connections to the annunciator pins. The GS has the 16-pin port, but the CPU speed makes the timing-based loops run much too quickly without modification. + +Those models also include serial on-board, so why not use serial connection? + +=== +So, Why Not Use A Serial Connection? +=== + +Seriously, why not? + +The GP2IO includes RS232 9-pin connector that's adapted on the board to UART levels, so yes, you can connect to it directly with any Apple II with serial. + +Did I forget to mention that? + + +=== +Big thanks to +=== + + - Mark Pilgrim for checking and optimizing my code, and helping out testing the various use cases. + + - David Schmidt for sanity checking my serial implementation and proof-of-concept. + + - KansasFest committee for allowing me to present, public beta test the GP2IO at KansasFest 2016. + + - Chris Torrence and Roger Wagner for Assembly Lines, both the book and searchable PDF. + + - Michael Mahon, for inventing such a nice wheel that I had to go and reinvent it. + + + +=== +The Inaugural, First Annual, and Probably Last, RetroConnector Mini-Hackfest +=== + +Each participant will be given a beta GP2IO board and available components as requested, on a first-come-first-served basis. Participants may keep the board, so long as he or she submits a completed entry, and agrees to post instructions publicly. Participants are encouraged to build or purchase additional hardware as needed, so long as they do not spend more than $50. The GP2IO board, its software and components can be altered, repurposed and hacked, but not outright destroyed. Okay, fine, you can destroy whatever you want, as long as your entry is cooler because of it. + +All entries must be documented or demonstrated to a degree they could be reproduced by someone of reasonable intelligence, and submitted or demonstrated to the judges prior to the Vendor Faire. The judges will have to decide amongst themselves what constitutes "reasonable intelligence." + +Entries will be judged on the following criteria: + - Is it cool? + - Does it do something the Apple II couldn't do before? + - Is it *really* cool? + - Originality. + - No, seriously. How cool is that? I'm angry I didn't think of it myself. + +Judges: + - Charles Mangin + - Mark Pilgrim + - Bill Martens + + + + +