Sunday, 5 June 2016

Part 12: Simulating a key press

Foreword

In the previous Blog we managed to get the flashing cursor to work in our emulator.

In this blog we will start to tackle the emulation of the keyboard.

To start off, we will cover some theory on how the C64 is interfaced to the system. Afterwards, we will see if we can put the theory to the test and simulate a keypress in our emulator.

This blog will unfortunately not end off with the emulator integrating with your keyboard.  I feel that this blog already contains a lot of overwhelming technical detail, so I decided to leave the integration to a physical keyboard for the next Blog.

Enjoy!

Some Keyboard Theory

The keys on the C64 keyboard is arranged in a matrix that looks as follows:


To determine if a particular key is pressed, a process called keyboard scanning is followed.

With keyboard scanning a particular row is pulled low (logical 0) and each column is examined in turn to see which ones are pulled low. For each in key in that row that is pressed the associated column will register a zero.

Needles to say, the above process is repeated for each and every column.

How does the keyboard interface to the CIA chips? Again, let us visit a schematic of the C64 as we did in the previous post:

As you can see the keyboard connector connects to CIA 1 via port A and port B.

It is not very easy to map this diagram to the scan table earlier in this section, because of different numbering. The following diagram makes understanding the mapping easier:


I found this table at http://sta.c64.org/cbm64kbdlay.html

This table assigns a bit number to each row/column, which will make out task easier.

Simulating a key press

Next, let us see if we can simulate a keypress in our emulator.

I will simulate pressing the 8 key. From the scan-code table we see that key 8 is row bit 3 and column bit 3.

How do we simulate this? First let us see what is involved during a keyboard scan:

  • Kernel ROM code write the particular row to pull low to address DC00h
  • Kernel ROM reads address DC01h and determines which columns are pulled low.
So, we need to trick KERNEL ROM a bit when it accesses addresses DC00h and DC01h. From these two addresses we must actually check when a read is done on DC01h.

So, what do we do if we detect a read on address DC01h? Firstly we check address DC00h to find which row the KERNEL ROM intended to pull low. Remember, for now we are only interested in simulating pressing an 8. So, we need to be alert only when the KERNEL ROM pulls done bit 3.

So, we need to check if address DC00 has value F7 (e.g. bit 3 masked), we need to return F7 as the result of a DC01h address read. For all other values of DC00 we will return a FFh for a DC01h read.

Lets get coding. The place to implement this functionality is in 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) {
      if (mainMem[0xdc00] == 0xf7)
        return 0xf7;
      else
        return 0xff;
    }    return mainMem[address];
  }


With this implementation, a key down will be simulated from the time we start our emulator. I cannot tell for sure if this will effect any initialisation process.

To avoid any potential issues during bootup, we should perhaps postpone the simulated keypress until we know the boot process is completed.

So, what what I have in mind is to only start the simulated keypress after 6000000 CPU cycles (e.g. 6 seconds).

In the runBatch method we have a handle on the CPU cycle count. So, we need a way for the runBatch method to inform the Memory class that it can start to simulate a keypress.

For this we start off by implementing a private variable simulateKeypress in the memory class:

  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;

And we surface this variable to the runBatch method with a public method:

  this.setSimulateKeypress = function () {
    simulateKeypress = true;
  }


In the readMem method we should now consider this private variable before simulating a kepress:

  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) {
      if ((mainMem[0xdc00] == 0xf7) & simulateKeypress)
        return 0xf7;
      else
        return 0xff;
    }    
    return mainMem[address];
  }


Finally, within our runBatch method, we should trigger the simulation after 6 seconds:

        while (mycpu.getCycleCount() < targetCycleCount) { 
          mycpu.step();
          if (mycpu.getCycleCount() > 6000000)
            mymem.setSimulateKeypress();        ...
        }

We are now ready to give our changes a test run.

When running our code changes, an 8 doesn't appear as we expect. What went wrong?

Lets have a look at the first couple of lines of the scan keyboard routine:

; scan keyboard

EA87   A9 00      LDA #$00
EA89   8D 8D 02   STA $028D
EA8C   A0 40      LDY #$40
EA8E   84 CB      STY $CB
EA90   8D 00 DC   STA $DC00
EA93   AE 01 DC   LDX $DC01
EA96   E0 FF      CPX #$FF
EA98   F0 61      BEQ $EAFB
EA9A   A8         TAY
EA9B   A9 81      LDA #$81
EA9D   85 F5      STA $F5

Something strange that pops out here is the writing of the value 0 to memory location DC00. In effect this will pull all rows low.

Why would one want to do that? After doing some searching on the Internet, it appears that this is just a quick check to see if a key was press at all.

Well. this is a scenario we haven't included in our code, so lets do it:

  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) {
      if (((mainMem[0xdc00] == 0xf7) | (mainMem[0xdc00] == 0x0)) & simulateKeypress)
        return 0xf7;
      else
        return 0xff;
    }    
    return mainMem[address];
  }


This time it worked!

Lets be a bit naughty and simulating one of the repeater keys like the space bar. From the table we see that the spacebar has row code 7f and column code ef. We make the following adjustments and rerun:

  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) {
      if (((mainMem[0xdc00] == 0x7f) | (mainMem[0xdc00] == 0x0)) & simulateKeypress)
        return 0xef;
      else
        return 0xff;
    }
    return mainMem[address];
  }


As expected, our cursor continues to move while spaces are typed, eventually scrolling the welcome message off the screen.

In Summary

In this Blog we managed to simulate a simple key press.

In the next blog we will integrate our emulator with you keyboard, so you will be able to enter some Basic Programs in it an tun them.

Till next Time!

No comments:

Post a Comment