Jump to content
IGNORED

Decoding PS/2 scan codes


bluejay

Recommended Posts

After watching Ben Eater's video on PS/2 keyboards, I now have a general idea on how to work with one. I copied my interface entirely off of Ben's and added it into my computer schematics. For now it only uses the 8 data bits, but I will make use of all the 11 bits in the future.

 

Anyways, PS/2 scan codes seem to have no logic behind them and if it did it would probably be too complicated to come up with a formula to translate the scan codes to ASCII. As of now, I have the computer read the value in the address in which the keyboard resides. It CMPs that value with every PS/2 scan code, then BEQs to the subroutine corresponding to the key pressed. These keyboard input subroutines are divided into multiple segments to avoid out of range errors. This method, as you may think, is highly inefficient in every way. Is there an alternative method to decode PS/2 scan codes? Thoughts and comments on this very basic BIOS I've written in general is appreciated as well. Thanks!

6502bios.bin 6502bios.txt

Link to comment
Share on other sites

Use the scancode as an offset for a lookup table of subroutine addresses. This will allow equal execution time for any input value. Because you will need to left shift the scancodes to calculate the table offset, you will need two tables. One for scancodes 0-127 and the other for scancodes 128-255. I haven't looked at scancodes for a while and forget how high they actually go. You may also want separate table sets for scancodes 1 2 and 3, or have tables that convert undesired scancodes to the desired one. I did that in my PS/2 -> NES converter.

 

I can provide an example code when I get home from work.

  • Like 1
Link to comment
Share on other sites

 

Here are two approaches to the problem. One uses indirect addressing and the other uses the RTS Trick. No guarantees they are %100 correct but this should set you in the right direction.

 

If you only care to support one scancode type, it would be wise to initialize the attached keyboard to use that set of scancodes and ensure the keyboard actually supports it. From my experience scancode 3 was the most common in the dozen or so keyboards I tested (spanning several decades of manufacture dates) but not all keyboards default to scancode 3 on power up. Also, not all keyboards support all scancode sets. Just a thought.

 

Further reading:

https://wiki.nesdev.com/w/index.php/RTS_Trick

http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2 Keyboard Protocol.htm

 

Example Code:

Spoiler

;6502 lookup table routines written in asm6


;-----Method 1-----
;
;- Use indirect addressing and jmp to execute a single subroutine
;- This method is faster but requires 2 bytes of zero page memory
;- Each table can hold a maximum of 128 addresses
;- The routine that is executed must either jmp to another routine or "rts" the "jsr ExecuteSC" call
;- This code assumes all scancodes are only 8 bit values

	.enum $0000				;assign zero page variables
		JumpAddress	.dsb 2	;two byte variable for indirect address
		scancode	.dsb 1	;one byte variable for your scancode value
	.ende

ExecuteSC:
	lda scancode			;load current scancode
	bmi ExecuteSCHigh		;branch to load high scancode routines if scancode >= 0x80
	asl						;multiply scancode by 2 to calculate SCTable offset
	tax						;transfer offset to x
	lda SCTable+1, y		;load high byte of scancode subroutine address from SCTable
	sta JumpAddress+1		;and store it in the high byte of the indirect addressing variable
	lda SCTable, x			;load low byte of scancode subroutine address from SCTable
	sta JumpAddress			;and store it in the low byte of the indirect addressing variable
	jmp (JumpAddress)		;execute scancode subroutine
	
ExecuteSCHigh:
	sec						;I don't think this is necessary but we'll keep it for now
	sbc #$80				;subtract 0x80 from current scancode
	asl						;multiply difference by 2 to calculate SCTableHigh offset
	tax						;transfer offset to x
	lda SCTableHigh+1, x	;load high byte of scancode subroutine address from SCTableHigh
	sta JumpAddress+1		;and store it in the high byte of the indirect addressing variable
	lda SCTableHigh, x		;load low byte of scancode subroutine address from SCTableHigh
	sta JumpAddress			;and store it in the low byte of the indirect addressing variable
	jmp (JumpAddress)		;execute scancode subroutine


SCTable:
	.word SC0				;each .word is a two byte address label
	;...more routines here	;stored in little endian format and
	.word SC127			;is defined during compilation
	
SCTableHigh:
	.word SC128
	;...more routines here
	.word SC255

SC0:
	;...do some stuff
	rts
	
SC127:
	;...do some stuff
	rts

SC128
	;...do some stuff
	rts
	
SC255:
	;...do some stuff
	rts
	
	
	
	
;-----Method 2-----
;
;- Use the "RTS Trick" by pushing addresses onto the stack as subroutines to be executed
;- This method is slower but saves two bytes of zero page memory
;- All subroutines must end in "rts"
;- Note the minus 1 after the data table addresses
;- This code assumes all scancodes are only 8 bit values

	.enum $0000				;assign zero page variables
		scancode	.dsb 1	;one byte variable for your scancode value
	.ende

ExecuteSC:
	lda scancode			;load current scancode
	bmi ExecuteSCHigh		;branch to load high scancode routines if scancode >= 0x80
	asl						;multiply scancode by 2 to calculate SCTable offset
	tax						;transfer offset to x
	lda SCTable+1, x		;load high byte of scancode subroutine address from SCTable
	pha						;and push it onto the stack
	lda SCTable, x			;load low byte of scancode subroutine address from SCTable
	pha						;and push it onto the stack
	rts						;execute scancode subroutine
	
ExecuteSCHigh:
	sec						;I don't think this is necessary but we'll keep it for now
	sbc #$80				;subtract 0x80 from current scancode
	asl						;multiply difference by 2 to calculate SCTableHigh offset
	tax						;transfer offset to x
	lda SCTableHigh+1, x	;load high byte of scancode subroutine address from SCTableHigh
	pha						;and push it onto the stack
	lda SCTableHigh, x		;load low byte of scancode subroutine address from SCTableHigh
	pha						;and push it onto the stack
	rts						;execute scancode subroutine


SCTable:
	.word SC0-1			;again, note the minus 1
	;...more routines here
	.word SC127-1
	
SCTableHigh:
	.word SC128-1
	;...more routines here
	.word SC255-1

SC0:
	;...do some stuff
	rts
	
SC127:
	;...do some stuff
	rts
	
SC128
	;...do some stuff
	rts
	
SC255:
	;...do some stuff
	rts
	

 

 

example_6502_lookup_tables.asm

Edited by emerson
  • Like 1
Link to comment
Share on other sites

I know the following examples are in C, but I believe they will translate to ASM quite well:

 

https://github.com/go4retro/c-key/blob/master/src/poll64.c

 

As others note, use a lookup table for the main scan codes.  Many are a 1-1 mapping.

 

The first thing to do in the code is watch for a scan code.

Then, for a few compares.  E0,E1,F0 and such are special modifiers.  Handle them special, and use a state machine to keep things in order.

Then, as you get to the main char code, reference into a lookup table.  if the result coming back has a high bit clear or something, use as the exact key to send. 

If the high bit is set, assume the code is an indirect code location reference. strip off the high bit and jump to that code.  That way, you can handle special cases.

Most keys are of the form (I think scan set 2, but could be 3) <char> and $f0 <char>.  Extended chars are $e0 <char> and $e0 $f0 <char> (I might have the $e0 and $f0 backwards, check the code).  The only 2 that are truly bizarre are print screen and pause/break.  printscreen tries to handle a key down/key up sequence all on the downpress, so it can be handled like the others if desired, but I did it special.  Print/Screen is just bizarre and I handled it with the main state machine.

 

You can choose the ignore the $e0 if you want, as the $e0 keys end up factoring back into their reguar versions if you strip it. (1 on keypad is extended version of 1 on main keyboard).

 

Jim

 

  • Like 1
Link to comment
Share on other sites

5 hours ago, brain said:

Most keys are of the form (I think scan set 2, but could be 3) <char> and $f0 <char>

This is correct. I looked back at my notes and every keyboard that I could read defaulted to scancode 2. I had converted everything to scancode 3 in my project which is probably why I thought scancode 3 was most common as I dealt with it most.

 

My apologies.

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...