Saturn keyboard
Sega released a keyboard for the Saturn. They also released an adapter that lets you connect a PS/2 keyboard to the Saturn. Now, the Saturn has the same connections as the Mega Drive (just a different plug shape), so using a simple Saturn-to-Mega Drive adapter, you can also make use of this keyboard in your homebrew!
- Basics of keyboard usage
- Setting up the keyboard
- Reading the keyboard
- Scancode list
- Converting scancodes into ASCII
- Checking for Ctrl, Alt, Shift
Basics of keyboard usage
Before anything first you need to understand how keyboards work.
Unlike with controllers, you don't get the state of every key. Instead, the keyboard tells you when a key got pressed or released. This means you need to keep track of the state of every key in RAM, and update it whenever the keyboard tells you so.
The keyboard returns "scancodes", which are a number unique to each key. They are not ASCII codes, it's up to you to convert scancodes to ASCII characters for text input. Scancodes identify every physical key, so when not doing text input you'll prefer to work with them instead.
Setting up the keyboard
The Saturn keyboard needs $60
written to both the control and data ports. Remember to keep the Z80 out of the way while
accessing the I/O ports. For example, to set up the player 2 port for use
with the keyboard:
FastPauseZ80
move.b #$60, (IoCtrl2)
move.b #$60, (IoData2)
ResumeZ80
If you're using peripheral ID to detect the
keyboard, it has an ID of 5 (%0101
). Beware that many Saturn
devices use this ID, so you'll need to examine the data it returns if
you support other Saturn peripherals.
Reading the keyboard
First write $20
to the data port (IoData1
or
IoData2
, depending where it's plugged). Read back and check
bits 3-0: if they aren't $01
, move on, it's not a keyboard.
Now we need to read the keyboard data proper. We need to do this (you may want to include a timeout in the steps where you wait in case the keyboard gets unplugged in the middle of waiting):
- Write
$00
to the data port - Wait until bit 4 becomes 0
- Read bits 3-0 to get a nibble
- Write
$20
to the data port - Wait until bit 4 becomes 1
- Read bits 3-0 to get a nibble
Keep doing it until you get 12 nibbles. Then write $60
to
the data port to be done with it.
; ReadKeyboard
; Routine for reading a keyboard packet
; Assumes it's in the second player port
;
; out d0.w = 0 on success
; = -1 on failure
;
; trashes d1, a0, a1
ReadKeyboard:
lea (IoData2), a0
lea (Buffer), a1
; Pause Z80 while we acccess the
; I/O ports to avoid glitches
FastPauseZ80
; Initial step, also check
; that it's indeed a keyboard
move.b #$20, (a0)
moveq #$0F, d0
and.b (a0), d0
cmp.b #$01, d0
bne @Error
; Now try reading every nibble
moveq #(12/2)-1, d0
@Loop:
; Read a nibble
move.b #$00, (a0)
moveq #$7F, d1
@Wait1:
btst #4, (a0)
beq.s @DataOk1
dbf d1, @Wait1
bra @Error
@DataOk1:
moveq #$0F, d1
and.b (a0), d1
move.b d1, (a1)+
; Read another nibble
move.b #$20, (a0)
moveq #$7F, d1
@Wait2:
btst #4, (a0)
bne.s @DataOk2
dbf d1, @Wait2
bra @Error
@DataOk2:
moveq #$0F, d1
and.b (a0), d1
move.b d1, (a1)+
; Onto next pair
dbf d0, @Loop
; Let keyboard and Z80 idle
move.b #$60, (a0)
ResumeZ80
; Return success!
moveq #0, d0
rts
@Error:
; Let keyboard and Z80 idle
move.b #$60, (a0)
ResumeZ80
; Return failure...
moveq #-1, d0
rts
You should have received the following nibbles:
Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
---|---|---|---|---|
1st nibble ($00 )
| 0
| 0
| 1
| 1
|
2nd nibble ($20 )
| 0
| 1
| 0
| 0
|
3rd nibble ($00 )
| Right
| Left
| Down
| Up
|
4th nibble ($20 )
| Start
| A
| C
| B
|
5th nibble ($00 )
| R
| X
| Y
| Z
|
6th nibble ($20 )
| L
| 0
| 0
| 0
|
7th nibble ($00 )
| 0
| CapsLock
| NumLock
| ScrLock
|
8th nibble ($20 )
| Make
| 1
| 1
| Break
|
9th nibble ($00 )
| D7
| D6
| D5
| D4
|
10th nibble ($20 )
| D3
| D2
| D1
| D0
|
11th nibble ($00 )
| 0
| 0
| 0
| 0
|
12th nibble ($20 )
| 0
| 0
| 0
| 1
|
The first two nibbles must be %0011 %0100
, this is how you
tell that this is a keyboard. If they're anything else, ignore the
packet.
The 3rd to 6th nibbles are there for compatibility (some keys act as if they were controller buttons so you can use the keyboard even with Saturn games not explicitly made for it). They aren't relevant for our use, so ignore them.
The 7th nibble has the status of the keyboard lights (the keyboard toggles them on its own). A value of 0 means the light is off, a value of 1 means the light is on. You should at least pay attention to Caps Lock to know when to swap uppercase and lowercase.
The 8th nibble tells us whether a key was pressed or not:
- If
Make
is 1, a key has been pressed - If
Break
is 1, a key has been released - Otherwise, nothing has changed
If either of those two is set, then the value in the 9th and 10th nibbles has the scancode of the key (see list of scancodes below).
The 11th and 12th nibbles can be ignored, they aren't needed but it seems the keyboard doesn't like it when you don't read them.
Scancode list
The scancodes are mostly based on AT set 2 scancodes, albeit multi-scancode keys have been assigned new codes instead. The full list is below (assuming an US layout):
$x0
| $x1
| $x2
| $x3
| $x4
| $x5
| $x6
| $x7
| $x8
| $x9
| $xA
| $xB
| $xC
| $xD
| $xE
| $xF
| |||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$0x
| F9 | F5 | F3 | F1 | F2 | F12 | F10 | F8 | F6 | F4 | Tab | ` ~ | ||||||||||||||||||||||||||||||||||||||
$1x
| Left Alt | Left Shift | Left Ctrl | Q | 1 | Right Alt | Right Ctrl | Num Enter | Z | S | A | W | 2 | Left GUI | ||||||||||||||||||||||||||||||||||||
$2x
| C | X | D | E | 4 | 3 | Right GUI | Space | V | F | T | R | 5 | Menu | ||||||||||||||||||||||||||||||||||||
$3x
| N | B | H | G | Y | 6 | M | J | U | 7 | 8 | |||||||||||||||||||||||||||||||||||||||
$4x
| , < | K | I | O | 0 | 9 | . > | / ? | L | ; : | P | - _ | ||||||||||||||||||||||||||||||||||||||
$5x
| ' " | [ { | = + | Caps Lock | Right Shift | Enter | ] } | \ | | ||||||||||||||||||||||||||||||||||||||||||
$6x
| Back
| Num 1 | Num 4
| Num 7 |
| $7x
Num 0 | Num . | Num 2 | Num 5
| Num 6 | Num 8 | Esc | Num Lock
| F11 | Num + | Num 3 | Num -
| Num * | Num 9 | Scroll Lock |
| $8x
Num / | Insert | Pause | F7
| PrnScr | Delete | Left | Home
| End | Up | Down | Page Up
| Page Down | Right |
| |
Converting scancodes into ASCII
In order to use the keyboard for writing text, you need to convert the scancodes into ASCII (or whatever else you prefer). The easiest way to do this is to use a look-up table that maps scancodes to their equivalent ASCII code. In practice you want two tables: one for when the Shift key isn't held and one for when it is.
I'll just save you the effort and provide a look-up table. The first 256 bytes are the unshifted keys, the next 256 bytes are the shifted keys. Keys that don't generate ASCII codes will have 0 in their entries (and you'll need to handle them by scancode instead).
File download:
Further remarks:
- Text editing keys (Backspace, Enter, arrows, etc.) need to be handled separately.
- To check if the character is shifted, check if the left Shift and/or right Shift keys are held down. If at least one of them is, the key is shifted.
- This table don't handle Caps Lock: you'll have to do this separately by checking if the resulting ASCII code is a letter and if so swap between uppercase and lowercase before returning it. Do not mess with Shift keys because that would shift the numbers and symbols too, which is wrong.
Checking for Ctrl, Alt, Shift
Normally, the Ctrl, Alt and Shift keys are used as modifiers for shortcuts and such, but the keyboard doesn't care about it. Instead, you should take care of it yourself, by checking if the respective keys are held down when you need to know.
Remember that there are two of these keys! So if you want to e.g. check if you're entering Ctrl+key, you need to check if either left or right Ctrl (or both!) are being held down. Same goes for Alt and Shift.