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

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):

  1. Write $00 to the data port
  2. Wait until bit 4 becomes 0
  3. Read bits 3-0 to get a nibble
  4. Write $20 to the data port
  5. Wait until bit 4 becomes 1
  6. 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:

Saturn keyboard packet
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 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):

Saturn keyboard scancodes
$x0 $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $xA $xB $xC $xD $xE $xF
$0x F9F5 F3F1F2F12 F10F8F6 F4Tab` ~
$1x Left AltLeft Shift Left CtrlQ1Right Alt Right CtrlNum EnterZS AW2Left GUI
$2x CXD E43Right GUI SpaceVF TR5Menu
$3x NBH GY6 MJ U78
$4x , <KI O09 . >/ ?L ; :P- _
$5x ' " [ {= + Caps LockRight ShiftEnter] } \ |
$6x Backspace Num 1Num 4 Num 7
$7x Num 0Num .Num 2Num 5 Num 6Num 8EscNum Lock F11Num +Num 3Num - Num *Num 9Scroll Lock
$8x Num /InsertPauseF7 PrnScrDeleteLeftHome EndUpDownPage Up Page DownRight

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:

Saturn keyboard to ASCII look-up table (ZIP, 2.0 KB)

Further remarks:

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.