Like I mentioned in the status update, I was attempting to port WozMon to my 6502 computer, until I remembered something; I hadn't written a proper keyboard decoding routine! Well, this was mainly because I had no idea how to program something like this, and was trying to test every bit of the matrix manually to decode the key presses. That was obviously much too inefficient, and it didn't even work because there were so many lines of code, resulting in out of range branches. Not good.
To get an idea on how to write keyboard decoding routines, I took a peek into the C64 programming scene. Apparently the C64's keyboard is a pain in the ass to read without using the built in kernal routines, and I didn't understand much of the code I read. However, there was one thing I realized that had never occurred to me; I had to use tables. From here, I figured that if I could somehow have the computer calculate the X and Y coordinates of the pressed key on the matrix, I could use those values to find out which key was pressed with the table.
The first step was to draw the keyboard into an 8x8 matrix. This was trivial, as I'd originally designed the keyboard layout from an 8x8 matrix to begin with. Here's what I drew compared to the actual keyboard layout.
The number labels were later used for verifying that you could correctly calculate the memory address in which the character would be stored in using the x and y coordinate values.
Now I needed to write some code. My old non-working keyboard code had set each of the 8 "rows" high to calculate which row the pressed key was in, then cycled through each bit in that row to figure out precisely which key it was that was pressed. This time, not being an idiot, I used a counter (the X register) and some ROL instructions to cycle through each row. If the keyboard returned a value that wasn't zero, it would mean that a key in that row was pressed, so it stored the value that was returned by the keyboard and the row number (0-7) to a location in memory. Once it was done processing every row, It jumps to a subroutine that figures out what column the pressed key is in. It uses LSR instructions to send each bit to the carry, then uses BCS to test the bit. If carry is set, it jumps to a subroutine that saves the column number (0-7) to a location in memory. Once both the row and column numbers (X and Y coordinates) are calculated, the computer multiplies the row number by 8 then adds the column value to it. The computer saves that value to the X register, then loads a value from the key table, indexed by the X register. And just like that, the computer has figured out which key was pressed.
I haven't made the key table yet, so this code isn't quite functional yet. However, that's only because I don't know what kind of values the MC6847 wants for each character in its internal character ROM, and there seems to be no documentation on this anywhere (the datasheet simply says "6-bit ASCII", well, what the hell kind of 6-bit ASCII?) Once I get the video chip circuitry working (goddammit AliExpress, I want my inveters now!) in hardware I can figure it out manually, then I'll make the key table. I shall work tirelessly so I can feature the working keyboard and hopefully WozMon in the next blog entry. Before I end this entry however, here's the assembly code for the keyboard routines. (BEWARE of massive numbers of comments. I had to do this because I'd for sure forget what this code was doing within three days of not looking at it. Maybe this is overdoing it, but better safe than sorry.)
kbpreinput: LDA #%00000001 ; each bit will be set once to detect row LDX #$08 ; counter kbinput: DEX ; decrement X STA KBOUT ; send the row test bit to the keyboard matrix PHA ; the RTB needs to be reused so gotta be pushed to stack LDA KBIN ; load the response value from keyboard BNE kbrowtrue ; if value is not zero then key in that row was pressed PLA ; if not then load RTB to accumulator ROL ; rotate left to set next bit high CPX #$00 ; check if counter is zero BNE kbinput ; if it isn't, repeat loop LDX #$08 ; if it is, reset counter for next subroutine LDA KBSTORE ; load the row value (stored in following subroutine) JMP kbindex ; jump to keyboard index subroutine kbrowtrue: STA KBSTORE ; store row value STX KBROW ; store row number PLA ; restore RTB ROL ; rotate left to set next bit high DEX ; decrement counter CPX #$00 ; check if counter is zero BNE kbinput ; if it isn't, repeat loop LDX #$08 ; if it is, reset counter for next subroutine LDA KBSTORE ; load the row value kbindex: DEX ; decrement counter LSR ; test bit 7 (column 8) BCS kbcoltrue ; if it is 1, then key in that column was pressed CPX #$00 ; check if counter is zero BNE kbindex ; if it isn't, repeat loop kbcoltrue: STX KBCOL ; store column value kbpredecode: LDA KBROW ; load row number to calculate key location on table LDX #$08 ; set counter (multiply by 8 = add 8 times) kbdecode: DEX ; decrement counter ADC KBROW ; add row number (times 8) CPX #$00 ; check if counter is zero BNE kbdecode ; if it isn't, repeat subroutine ADC KBCOL ; if it is, then add the column value TAX ; transfer final value to X LDA keytable,X ; load the character corresponding to the calculated value from key table keytable: (insert table here)
I'll see ya in the next entry.