Monday, 17 December 2018

Redirecting USB keystrokes to C64

Foreword

In the previous post we managed to catch the scan codes of keys pressed on a USB keyboard.

In this post we will be redirecting these keystrokes to our C64 module so we can have some meaningful interaction with our C64 module.

The Plan of Action

Let us start by refreshing our minds a bit.

A couple of posts ago we implemented two slave registers which we mapped into memory space at locations 43c0_0000 and 43c0_0004.

Combining these two slave registers we have 64 bits in which each of these bits represents a key on the C64 keyboards. The ARM can toggle the bits in these registers and in effect simulate key presses within our C64 module.

All it will take from us is to take the USB keyboard scan codes we receive from the keyboard, and converting it to C64 key scan codes and we have a working implementation.

Starting simple

Let us start by implementing a mapping between USB and C64 keyboard for just four keys: A, B, C, D.

The USB scan codes for these keys are as follows:


  • A -> 4
  • B -> 5
  • C -> 6
  • D -> 7
The corresponding scan codes for these keys on a C64 is as follows:

  • A -> 0xa
  • B -> 0x1c
  • C -> 0x14
  • D -> 0x12
We can create a quick mapping function for these keys:

u32 mapUsbToC64(int usbCode) {
 if (usbCode == 0x4) {
  return 0xa;
 } else if (usbCode == 0x5) {
  return 0x1c;
 } else if (usbCode == 0x6) {
  return 0x14;
 } else if (usbCode == 0x7) {
  return 0x12;
 }
}


We will invoke this method within our state_machine method where are printing the USB scancodes to the console:

...
  if (!(Xil_In32(qTDAddressCheck + 8) & 0x80)) {
   u32 word0 = Xil_In32(0x305000);
   u32 word1 = Xil_In32(0x305004);
   if (word0 == 0)
    Xil_Out32(0x43c00000, 0);
   else {
    u32 bit = mapUsbToC64((word0 >> 16) & 0xff);
    bit = 1 << bit;
    Xil_Out32(0x43c00000, bit);
   }
...

Here we set the corresponding bit in the slave register according to the returned c64 scan code.


Here is a demonstration of our code in action:



Our mapUsbToC64 can now just be extended to cover the other keys.

As it stands, our current implementation only support the first 32 c64 scancodes. So let us quickly do some changes to cover the full 64 scancodes:

...
  if (!(Xil_In32(qTDAddressCheck + 8) & 0x80)) {
   u32 word0 = Xil_In32(0x305000);
   u32 word1 = Xil_In32(0x305004);
   if (word0 == 0) {
    Xil_Out32(0x43c00000, 0);
    Xil_Out32(0x43c00004, 0);
   } else {
    u32 bit = mapUsbToC64((word0 >> 16) & 0xff);
    //bit = 1 << bit;
    u32 c64Word0 = 0;
    u32 c64Word1 = 0;
    if (bit < 32) {
     c64Word0 = 1 << bit;
    } else {
     c64Word1 = 1 << (bit - 32);
    }

    Xil_Out32(0x43c00000, c64Word0);
    Xil_Out32(0x43c00004, c64Word1);
   }
   printf("%x %x\n",word0, word1);
...

So, if the scancode is less than 32 we set the appropriate bit at address 0x43c0_0000. For scancodes bigger than 32 we set the appropriate bit at address 0x43c0_0004.

Implementing simultaneous key presses

Up to this point in time we are only able to deal with one key press at a time. This becomes an issue when we want to type double quotes (") on the C64, which require pressing the shift and the 2 key simultaneously.

In this section we will deal with simultaneous key presses.

Luckily from the USB side we are provided with enough information to determine if more than one key is pressed simultaneously. Each byte from the 8 bytes returned in the USB report descriptor represent a key that is currently been pressed. The exception to the rule is modifier keys, like Shift and Control. The status of all the modifier keys is contained within a single byte, where is bit corresponds to a modifier key.

We start off by creating a method we are sending the 8 USB bytes and returning the values we need to assign to addresses 0x43c0_0000 and 0x43c0_0004 respectively:

void getC64Words(u32 usbWord0, u32 usbWord1, u32 *c64Word0, u32 *c64Word1) {

}

We implement two loops for looping through both USB words:

void getC64Words(u32 usbWord0, u32 usbWord1, u32 *c64Word0, u32 *c64Word1) {
  *c64Word0 = 0;
  *c64Word1 = 0;

  usbWord0 = usbWord0 >> 16;

  for (int i = 0; i < 2; i++) {
   int current = usbWord0 & 0xff;
   if (current != 0) {
     int scanCode = mapUsbToC64(current);
        if (scanCode < 32) {
     *c64Word0 = *c64Word0 | (1 << scanCode);
     } else {
     *c64Word1 = *c64Word1 | (1 << (scanCode - 32));
     }

   }

   usbWord0 = usbWord0 >> 8;
  }

  for (int i = 0; i < 4; i++) {
   int current = usbWord1 & 0xff;
   if (current != 0) {
     int scanCode = mapUsbToC64(current);
        if (scanCode < 32) {
     *c64Word0 = *c64Word0 | (1 << scanCode);
     } else {
     *c64Word1 = *c64Word1 | (1 << (scanCode - 32));
     }

   }

   usbWord1 = usbWord1 >> 8;
  }

}


You will see that for the first USB word we are discarding the first two bytes. This is because the first byte is the byte mask for the modifier keys and the second byte is reserved.

Talking about the modifier keys. It would be nice to implement the shift key in order to type the double quotation (") in our C64 module. So let us do that quickly:

void getC64Words(u32 usbWord0, u32 usbWord1, u32 *c64Word0, u32 *c64Word1) {
  *c64Word0 = 0;
  *c64Word1 = 0;

  if (usbWord0 & 2) {
   *c64Word0 = 0x8000;
  }
...
}

In the USB report the left shift key is bit 2 of the modifier byte. So this is why we are masking off this bit.

Let us now do a test run. In the following video I write a very simple basic program and run it:


This conclude this post.

In Summary

In this post we integrated the USB keyboard with our C64 module.

We then tested everything by writing a very simple basic program and running it.

Till next time!

No comments:

Post a Comment