I've been continuing work on my project of trying to use an Arduino Uno to simulate the xband keyboard. I bought an xband modem on ebay and I have some pins from the Arduino connected to a DB9 connector which is plugged into the Genesis. So far it's been quite an enlightening learning project (I've never done much with hardware and the concept of creating an empty do{ } while loop while waiting for pins to change was completely foreign to me).
After looking through the rom source code some more and reading about how the Sega Team Player adapter works, I've extrapolated the following:
- Specific types of communication between the Genesis and the keyboard are broken up into different types of transactions.
- TH is held low throughout the duration of a transaction.
- A transaction that communicates with the keyboard can be one of the three following types: Find, Write (to the keyboard, which includes 'almost' a complete Find transaction at the beginning), or Read (from the keyboard, which includes a complete Find transaction at the beginning).
The Genesis performs a Find transaction once every 2 seconds (see the _ControllerVBL and FindESKeyboard functions in controls.c)
A Find transaction consists of the following:
- When TH and TR are high, the keyboard should be broadcasting the first nibble of what the rom source code refers to as an identifying ID (%0011).
- The Genesis reads this nibble and then sets TH low to begin a transaction. (TH is held low for the rest of the transaction).
- The keyboard responds with the 2nd nibble of the identifying ID (%1100).
- The Genesis reads this nibble and then sets TR low, the keyboard responds with the 3rd nibble of the identifying ID (%0110), and the keyboard sets TL low.
- The Genesis reads this nibble and sets TR high, the keyboard responds with the 4th nibble of the identifying ID (%1001), and the keyboard sets TL high.
= The Genesis reads this nibble, and then aborts the transaction by setting TH high).
- The Genesis checks to see if the 4 nibbles it received match the kbID variable (kbID == 0x030C0609 per /Database/DBConstants.h) (note that each nibble is stored in a separate byte. The upper nibble of each byte is zero, and the lower nibble contains the value sent by the keyboard). 0x030C0609 = %0000 0011 0000 1100 0000 0110 0000 1001
- If kbID == 0x030C0609 the FindESKeyboard function returns true, otherwise it returns false.
A Read transaction (handled by the function ReadESKeyboard in controls.c) begins with everything described above, except the Genesis doesn't abort the transaction by setting TH high. After it confirms that kbID == 0x030C0609, it continues with the transaction.
The rest of the transaction consists of a common pattern (see the function GetHandshakeNibblePort2 in controls.c):
- The Genesis sets TR low. The keyboard will write a nibble of data to the data pins, and then set TL low to indicate that the data is ready to be read.
- The Genesis reads the data, and if it wants another nibble, it will set TR high. The keyboard will write another nibble of data to the data pins, and then set TL high to indicate that the data is ready to be read.
- The Genesis reads the data, and if it wants another nibble, it will set TR low. The keyboard will write another nibble of data to the data pins, and then set TL low to indicate that the data is ready to be read.
- The Genesis reads the data, and if it wants another nibble, it will set TR high. The keyboard will...
During this period, the keyboard will first send a nibble which indicates the total number of bytes it's about to send. Next it should send a data type byte (2 nibbles). If sending keycode data, both of these nibbles should have a value of zero (#define kESKeycodeData 0 in controls.c)
Next, the keyboard should send AT style scancodes. For instance, if the letter 'a' was pressed, the keyboard should send the byte 1C. If the letter 'a' was released, the keyboard should send two bytes, F0 followed by 1C (AT scancodes
available here).
Note: Total number of bytes should include the data type byte
When I wrote above that a Write transaction includes 'almost' a complete Find transaction, I was referring to this:
FindESKeyboard:
Code: Select all
...
readScan++;
hshkState = 0; // start flipping TR
*readScan++ = GetHandshakeNibblePort2(&hshkState); // 3rd nybble = local ID
*readScan = GetHandshakeNibblePort2(&hshkState); // 4th nybble = local ID
*reg |= kTH; // abort the transaction
for ( timeout = 0; timeout != 50; timeout++ ); // spin a bit
*reg = kTH + kTR; // make sure we leave with TH & TR hi
if ( *(ULong *) readBuf == kbID ) // found a good Eric Smith Keyboard
return ( true );
else
return ( false );
...
ReadESKeyboard:
Code: Select all
...
readScan++;
hshkState = 0; // start flipping TR
*readScan++ = GetHandshakeNibblePort2(&hshkState); // 3rd nybble = local ID
*readScan++ = GetHandshakeNibblePort2(&hshkState); // 4th nybble = local ID
if ( *(ULong *) readBuf == kbID ) // found a good Eric Smith Keyboard
{
len = GetHandshakeNibblePort2(&hshkState); // 5th nybble = BYTE count, 0-15
if (len)
...
WriteESKeyboard:
Code: Select all
...
readScan++;
hshkState = 0; // start flipping TR
*readScan++ = GetHandshakeNibblePort2(&hshkState); // 3rd nybble = local ID
if ( (*(ULong *) readBuf & 0xFFFFFF00) == (kbID & 0xFFFFFF00) ) // found a good Eric Smith Keyboard?
{
*reg &= 0xF0; // ensure data lines are 0
*(char *)kCtl2 |= kDataLines; // 4 data lines are outputs now
REFGLOBAL( controls, cmdTail )++;
REFGLOBAL( controls, cmdTail ) &= kKeybdCmdStatusFifoMask;
byteToSend = REFGLOBAL( controls, cmdBuf )[REFGLOBAL( controls, cmdTail )];
PutHandshakeNibblePort2(&hshkState, 0); // 4th nybble = 0 ==> I'm talking to him
PutHandshakeNibblePort2(&hshkState, 2); // 2 bytes follow; type & data
...
Notice that in the WriteESKeyboard function, the last nibble of the keyboard ID is ignored / not read.
Also regarding the WriteESKeyboard function, something I don't understand is how the keyboard knows that the Genesis is going to start sending information through the data lines and that it should begin treating them as inputs instead of outputs.
This project of trying to simulate the keyboard with an Arduino is proving to be quite difficult and I'm not sure if it's going to be possible. It's kind of a shot in the dark without having any debug information from the console / modem.
Here is what I've come up with so far for an Arduino sketch if anyone's interested. The sendScancodes() function is currently commented out everywhere and not being called.
The println() statements at the bottom of the identifyKeyboard() function are almost doing what I expect them to, but the results are inverted (
here's a video). I was expecting "continuing with transaction" to be printed about 15 times a second, and "transaction aborted" to be printed every 2 seconds.
Just the fact that the timing I'm seeing with the println statements seems to match what is described at the top of _ControllerVBL is somewhat promising.
Code: Select all
void _ControllerVBL( void )
{
register short whichRead = 0; // +1 read controls, -1 sample controller types
register long newPhase;
// to avoid sucking down too much CPU, we do a few things based on
// a phase counter. We read the controls 15 times per second (instead
// of 60), and every two seconds, we replace a controller read with
// a controller-type sample. This allows the user to change controllers
// and we'll figure it out.
newPhase = REFGLOBAL( controls, samplePhase ) - 1;
if (newPhase < 0) {
...