Tuesday 14 June 2016

Part 13: Implementing a workable keyboard


Foreword

In the previous blog we managed to simulate a single kepress. In this blog we will write code to actually interface with your physical keyboard.

By the end of this blog, you would be able to enter simple Basic programs and run it.

Trapping keys


We would like to encapsulate all the keyboard functionality into a class of its own. So, let us start with the plumbing first.

We first create a file Keyboard.js, hosting our keyboard class:

function keyboard() {
}

As with all our important classes we need to include this file in the head tag of index.html and create an instance of this class:

  ...
  <head>
    <title>6502 Emulator From Scratch</title>
    <script src="Memory.js"></script>
    <script src="Cpu.js"></script>
    <script src="Video.js"></script>
    <script src="Keyboard.js"></script>
  </head>
  ...
    <script language="JavaScript">
      var mykeyboard = new keyboard();
      var mymem = new memory(postInit);
      var mycpu = new cpu(mymem);
  ...




We are more or less done with the plumbing. Lets move on to implement the meat of our keyboard class.

We start off the implementation by asking the question: Does JavaScript provide us with a way to capture key strokes?

JavaScript indeed provide us with two events called, onkeydown and onkeyup. The names speaks for itself. When you press a key down, an onkeydown event will fire. When you release a key, an onkeyup event will fire.

The best place to define these two events in our emulator is within the body tag in our index.html:

<body onkeydown="mykeyboard.onkeydown(event)" onkeyup="mykeyboard.onkeyup(event)">

This assumes our keyboard class has the method onkeydown and onkeyup defined. Lets go about and implement skeletons for these two methods in our keyboard class:

function keyboard() {

 this.onkeydown = function(event) {
 }

 this.onkeyup = function(event) {
 }
}

Now the question on what code these two methods should contain. Basically what should happen is that in our onkeydown method we should store the keyevent for our emulator when it needs it. Obviously, in the onkeyup event we should remove the stored event since it would no longer be applicable for our emulator.

So, what do we need to store of the event? The event actually contains a very useful property that we can store, called keyCode. keyCode has the unshifted ASCII code of the key that fired the event. This means, for instance, if you hold down the "one key", it will register the code 49. If you are pressing the "one key" while holding the shift key, it will also register the code 49. All your alphabet letters will register the capital version of the applicable letter, irrespective if the shift key was down or not.

The unshifted ASCII code is perfect for our needs. If there is a shift key involved our scanning process will pick it up as two separate keypresses.

We will store all the keydown events within an array. With all this in mind, our keyboard class will look as follows:

function keyboard() {

  var keyarray = [];

  this.onkeydown = function(event) {
    if (event.keyCode == 32)
      event.preventDefault();

    if (keyarray.indexOf(event.keyCode) == -1)
      keyarray.push(event.keyCode);
  }

  this.onkeyup = function(event) {
    var ind = keyarray.indexOf(event.keyCode);
    keyarray.splice(ind,1);
  }

}

Let me quickly explain all the code. Obviously our array that stores the events is called keyarray which we start off as an empty array.

Next, lets zoom into the method onkeydown. The testing of keyCode 32 looks a bit odd, so let me explain. In my Firefox browser I actually found that pressing the space bar causes the browser window to scroll, so my emulator would indeed register a space, but I had to scroll back each time I typed a space. This if statement would fix that behaviour.

You will also see that after the keCode 32 check, I don't add the keyCode straight away to keyarray. This was also due to another side effect I found. While holding in a key, repeating keydown events fire. From the JavaScript documentation I found that this is mainly happening in Linux, but to be save, we would rather check if the code already exist in the array before adding it.

Finally, if we are sure the keyCode doesn't already exist in the array, we add it via a push. Push appends the keyCode to the end of the array, effectively growing the array by one element. That is one cool feature of JavaScript: Dynamic Arrays.

Next, lets discuss the onkeyUp event. In this event we remove the keyCode we previously added via the onkeydown event. The first line we ask JavaScript to find at which element number the relevant keyCode lives. We then remove the element with splice giving it the position and ordering it to only remove one element.

Emulating keypresses

Up to this point in time we are in a position to always know which key is down at any point in time. What remains to be done is to transfer this knowledge in way that our KERNEL ROM will understand. That is, giving the KERNEL what it wants when it reads memory location DC01h. We do this by implementing the following method in out keyboard class:

  this.getColumnByte = function(rowByte) {
    var rowArray = [0,0,0,0,0,0,0,0];
    rowByte = ~rowByte;
    rowByte = rowByte & 0xff;
    var i;
    for (i = 0; i < keyarray.length; i++) {
      var scanCode = getScanCode(keyarray[i]);
      var rowNum = (scanCode & 0x38) >> 3;
      rowArray[rowNum] = rowArray[rowNum] | (1 << (scanCode & 7));
    }
    var resultByte = 0;
    for (i = 0; i < 8; i++) {
      var currentRowEnabled = ((1 << i) & rowByte) != 0;
      if (currentRowEnabled)
        resultByte = resultByte | rowArray[i];
    }
    resultByte = ~resultByte;
    resultByte = resultByte & 0xff;
    return resultByte;
  }


As you can see we are passing a parameter rowByte to this method. rowByte is effectively the contents of memory location DC00h.

Two major things are happening in this method: It starts/ends with negations (~), and the use of scan codes. I will spend some time now explaining each of these.

Firstly, why all these negations? In the previous blog we saw that with the keyboard matrix, a zero means activated and a one means the opposite. This logic makes bit operations very difficult. To deal with this, we negate the rowByte in the beginning of the method. From this point onwards a one means active and make bit operations easier. Before we exit the method, we negate the result so that the byte returned follows the zero-active logic.

Next, lets discuss scan codes. Firstly, what is a scan code? A scan code is way to refer to a particular key in the keyboard matrix with a single number, instead of numbers, row and column. Scan codes simplifies lookups.

So, how do you determine the scan code of a key? Let us have a look again at the keyboard matrix of the C64:



Bit#0
$01,$FE
Bit#1
$02,$FD
Bit#2
$04,$FB
Bit#3
$08,$F7
Bit#4
$10,$EF
Bit#5
$20,$DF
Bit#6
$40,$BF
Bit#7
$80,$7F
Bit#0
$01,$FE
Insert/DeleteReturncursor left/rightF7F1F3F5cursor up/down
Bit#1
$02,$FD
3WA4ZSEleft Shift
Bit#2
$04,$FB
5RD6CFTX
Bit#3
$08,$F7
7YG8BHUV
Bit#4
$10,$EF
9IJ0MKON
Bit#5
$20,$DF
+ (plus)PL– (minus). (period): (colon)@ (at), (comma)
Bit#6
$40,$BF
£ (pound)* (asterisk); (semicolon)Clear/Homeright Shift (Shift Lock)= (equal)↑ (up arrow)/ (slash)
Bit#7
$80,$7F
1← (left arrow)Control2SpaceCommodoreQRun/Stop

Scan code numbering start at the first row (bit 0). In that row it starts counting from 0 and count to 7. We then go to the next row and count from 8 to 15. This process is followed for every row.

To convert a scancode back to a row/column value is very easy. The least three bits of the scan code is the column number and the upper three bits is the row.

With all this background, we can discuss the getColumnByte method.

The method begins with declaring rowArray. Strickly speaking, this is a two dimensional array representing the keyboard matrix. The eight bits of each element gives you the second dimension.

We then loop through all elements of the keyarray. Before we can do anything useful with an element from the keyarray, we need to convert it to a scan code. The getScanCode method takes care of this for us. The getScanCode method is basically a big case statement returning a scanCode, given an ascii code.

The remaining part of the loop set the applicable bit in the rowarray matrix for the given scan code.

Next, the rowByte is examined to determine which rows are activated. The values of all active rows are taken from the rowArray and or'ed together. The result for this OR operation is the value we should pass back.

What remains to be done is to let the Memory class make use of the keyboard class. To do this we should first inject a keyboard instance into the memory class as follows:

function memory(allDownloadedCallback, keyboard)

{
  var mainMem = new Uint8Array(65536);
  var basicRom = new Uint8Array(8192);
  var kernalRom = new Uint8Array(8192);
  var charRom = new Uint8Array(4192);
  var outstandingDownloads = 3;
  var simulateKeypress = false;
  var keyboardInstance = keyboard;
...
}

Of course, we should adjust index.html as well:

    <script language="JavaScript">
      var mykeyboard = new keyboard();
      var mymem = new memory(postInit,mykeyboard);
      var mycpu = new cpu(mymem);
...

Finally, lets adjust the readMem method in the memory class:

  this.readMem = function (address) {
    if ((address >= 0xa000) & (address <=0xbfff))
      return basicRom[address & 0x1fff];
    else if ((address >= 0xe000) & (address <=0xffff))
      return kernalRom[address & 0x1fff];
    else if (address == 0xdc01) {
      return keyboardInstance.getColumnByte(mainMem[0xdc00]);
    }
    return mainMem[address];
  }

Given the emulated keyboard a Test Run

We are now ready to give give our emulated keyboard a Test run.

My test run did not start off so well. Initially my emulator didn't accept any keystrokes. After a bit of playing I actually found that you need to click on the emulator screen before it would accept any keystrokes.

It was as if our emulator web page was loosing focus for some reason.

Closer investigation yielded that the problem is caused when we click the run button. At that point the run button has focus. Part of the stuff that is happening when we click the run button, is to actually disable it. What solve this issue is to actually let the run button loose focus before disabling it:

      function startEm() {
        document.getElementById("btnStep").disabled = true;
        document.getElementById("btnRun").blur();
        document.getElementById("btnRun").disabled = true;
        document.getElementById("btnStop").disabled = false;
        var myBreak = document.getElementById("breakpoint");
        breakpoint = parseInt(myBreak.value, 16);
        running = true;
        myTimer = setInterval(runBatch, 20);
      }

One should now be able to enter some BASIC programs and run it. I must admit, I haven't implemented all the keys. So, at the moment for some basic programs you need to extend the getScanCode method. The keys that are implemented currently are as follows:


  • Letters
  • Digits
  • Space bar
  • shift key
  • Return key
Here is one of my sessions:


In Summary

In this blog we managed to interface to a physical keyboard. With this functionality we are now able to enter simple basic programs and run it.

In the next blog we will be exploring Tape emulation. This will be the blog in series of blogs where will attempt to emulate a game loaded from tape.

Till next time!

No comments:

Post a Comment