PIC18: Connecting to a PS/2 keyboard

ps2 pic With the advent of the USB protocol in the 90's many peripherals' manufacturers abandoned the previous protocols and connections to adopt it as a unifying standard; in the past printers mostly used parallel/Centronics ports, modems were serials, scanners could use SCSI interfaces, which often required a daughter board to be placed inside the cabinet of PC's, and mice and keyboards had to be connected to PS/2 ports. USB slowly emerged and unified all the connection needs in one single type of port and protocol stack; nowadays it is rare to encounter a new computer equipped with serial, parallel or PS2 ports.

Nevertheless PS/2 protocol is easier to understand and implement compared to USB, which has an entire protocol stack to be understood and implemented (material for a future article). So let's recover that old PS/2 keyboard we threw in the wastebin long ago and see how we can connect it to a PIC!

Wires and Pins

The PS/2 keyboard male connector is called mini-DIN and got six pins, two of which are not connected (n.c.), leaving us with four pins/wires to consider:

ps2 maleps2 pin functions

Using the pins of the mini-DIN male connector to connect to a PIC is a bit awkward, because they are very close to each other and they are closely surrounded by a round metal plate. It is simpler to cut the keyboard cable, separate the wires and solder jumpers onto them. Unfortunately wire colours are not standard; on some HP keyboards we can find Yellow (Clock), Red (Data), Brown (+5V), Grey (Gnd), while on some Logitech ones the colour scheme is Green (Clock), White (Data), Pink (+5V), Brown (Gnd).

So, in order to be sure to get the correct pin-to-wire combinations, once the cable is cut, we should definitely check the mini-DIN connector with a multimeter, touching the leads one by one and probing the associated wire.

Make and Break codes

As soon as a key is pressed two distinct signals originates from the microcontroller on the keyboard, clock and data.

ps2 q key make code

Here (and in the subsequent screenshots) clock line is blue and data is yellow. A PS/2 communication is serial and can be bi-directional, usually from the device (keyboard) to the host (in our case the PIC), but also the other way; we will see both type of communication in action.

Data and Clock lines are are high when no communication takes place (idle). Clock pulses are generated by the keyboard only when a transmission occurs, at a measured frequency of around 12.5 kHz, the accepted range being 10-16.7 kHz:

ps2 clock freq

The keyboard is free to send data to the host when both Data and Clock lines are kept high. The keyboard will take the Data line low (Start bit) and then start generating the clock pulses on the Clock line. Each bit is sent on the Data line in series with the following order:

Start bit => 0...7 data bits => Odd parity bit => Stop bit

Data sent from the device (keyboard) to the host (pc) is read on the falling edge of the clock signal; data is trasmitted Least Significant Bit (LSB) first, so bits have to be reverted on the host in order to obtain the correct key code.

The following picture explains better the Make code that 'q' key generates:

q key make code explantion

A: Start (0)

B - I: eight Data bits

J: Odd Parity

K: Stop (1)

L: line idle

So the sequence of bits that was sent from the keyboard was 10101000; we know it's LSB, so we invert it and get b'00010101' or 0x15 which corresponds exactly to the make code for 'q'.

Here's the complete reference for Scan Set 2 make/break codes, which is the set to consider for PC AT keyboards (all values are hexadecimal):

ps2 scan set 2

The Break code is generated when the key is released. Here on the right of the pic we see the two distinct codes that comprise the break code; generally the first is 0xF0 and the second is the Make code repeated:

milliseconds between make break codes

Connecting to the PIC18

As said before, Data and Clock are open-collector, so they both need a resistor to stay at +5V; to do that we are going to use the internal pull-up resistors of PORTB. The keyboard wires will be connected in this way:

ps2 pic18 wires

The program

Receiving key strokes

Whenever a key is pressed clock and data pulses are generated by the keyboard and intercepted by RB1 and RB2 pins; in particular clock falling pulses raise interrupts on RB2/INT2 which mark the beginning of the reception (A on the image above) for the PIC, by calling ReadMakeCode routine.

We know that the first bit sent by the keyboard is a Start bit, nothing we are interested in: so we simply discard it and wait for the clock pulse to finish, that means waiting for the clock line to go low and then high (B on the image). This is the purpose of WaitLowHighPS2Clock routine:

    btfss PS2ClockPin    ; Wait for the LOW clock pulse to finish
    goto $ - 2          ; No, still low. Go back to previous instruction
    btfsc PS2ClockPin    ; After low edge wait for the HIGH clock pulse to finish
    goto $ - 2          ; No, still high. Go back to previous instruction   

The end of the routine marks the beginning of the clock falling edge, when the next bit can be safely acquired.

Then we need a loop to get eight data bits, the juice we're looking for: every acquired bit is tested in AcquirePS2DataBit routine to see if it's '1' or '0', by checking the carry:

    bcf STATUS,C        ; Carry = 0
    btfsc PS2DataPin    ; Test incoming PS2 data bit
    bsf STATUS,C        ; Incoming bit is '1': Carry = 1
    rrcf PS2Data        ; Insert Carry into PS2Data, rotate right register

This value is then cleverly right-shifted into PS2Data register, the cleverness here being that, by doing so, PS2Data ends up loaded with the already inverted value that represents the correct Make code. Let's see how that is achieved:

ps2 right shift

As we can see every data bit sent by the keyboard, starting from the leftmost one, is right shifted into PS2Data, effectively ending in the inversion of the original sent byte and resulting in the correct Make code for the key pressed.

Unfortunately Make codes don't match with ASCII codes, so the value has to be translated by means of a look-up table, as done by PS2DisplayTable routine (for an explanation of look-up tables see this previous article on 7-segment displays).

We skip Parity and Stop bits as we did for the Start one and, finally, the translated value is displayed onto LCD; after the Stop bit there is a delay of 150ms, in order to avoid getting the break codes (as seen in above there is a delay of 129 ms between the beginning of the make code and the end of the break code).

This is the end of how the PIC manages a single key reception.

Lighting up LEDs on the keyboard

As already said communications can occur also from the host (PIC) to the device (keyboard); in this way the PIC is sending one or more commands to the keyboard. There is a bunch of commands that the keyboard can accept and they vary from setting Typematic Rate/Delay to reading its ID and some more. Here we want the PIC to send a command to light up the Num Lock, Caps Lock and Scroll Lock LEDs.

The following image represent an example of this communication in action, when a single command is sent:

ps2 request-to-send 0xED 0x05 0xFA

The first thing the host has to do is telling the device it has something to send; this is done by:

  • pulling Clock low for at least 100 microseconds
  • applying a "Request-to-send" by pulling Data low, then release Clock

It's the task of the PS2RequestToSend routine:

; 1) Bring the Clock line low for at least 100 microseconds:
    bcf TRISB,2         ; Set Clock pin as Output
    bcf PS2ClockPin         ; Set Clock down
    movlw .100
    call usDelay            ; Wait
; 2) Bring the Data line low:
    bcf TRISB,1         ; Set Data pin as Output
    bcf PS2DataPin          ; Set Data down
    movlw .50
    call usDelay            ; Wait
; 3) Release the Clock line:
    bsf PS2ClockPin         ; Set Clock up
    bsf TRISB,2         ; Set Clock pin as Input again

The PIC has also to keep Data low as long as the keyboard responds and starts generating clock pulses. Even in a host-to-device communication it's always the device's task to generate the clock signal.

; 4) Wait for the keyboard to respond (wait for the device to bring the Clock line low)
    btfsc PS2ClockPin   ; Wait for the HIGH clock pulse to finish
    goto $ - 2      ; No, still high. Go back to previous instruction 

After the keyboard has brought the clock line low it should start to generate regular clock pulses.

Here we see:

  • the Request-to-Send sequence
  • the Clock pulses by the keyboard
  • the first command sent (0xED - set/reset LEDs) by the PIC
  • an Ack bit by the keyboard

ps2 request-to-send 0xED Ack

The first command above (0xED) needs two things:

  • to be sent LSB
  • an added odd parity bit

It is to be noted that Data sent from the host to the device is read on the rising edge, contrary to what we saw before, from device to host.

Routine PS2SendData takes into account all those requirements; the LSB transmission is obtained by copying the command to be sent into PS2Data and then by right-rotating this register and evaluating the 'expelled' Carry. The same Carry is then cleared before reinjecting it into PS2Data:

rrcf ps2data

The evaluation of the Carry is functional also to the calculation of the odd parity; infact when Carry = 1 the counter PS2Parity is incremented. This counter has been previously initialized to 1 in PS2RequestToSend routine; odd parity is then equal to the value of PS2Parity rightmost bit (bit 0), as seen in the PS2SendParity routine:

    movlw .1        
    andwf PS2Parity         ; Get bit 0 of PS2Parity
   bnz PS2Parity1           ; 
   bcf PS2DataPin           ; PS2Parity,0 = 0
   goto EndPS2SendParity
   bsf PS2DataPin           ; PS2Parity,0 = 1

After that it is necessary to orderly end the transmission; PS2StopReleaseAck is more than happy to accomplish that simple task.

The 0xED command needs another byte as a parameter, that specifies the state of the keyboard's Num Lock, Caps Lock, and Scroll Lock LEDs, where:

bit 0 - Scroll Lock

bit 1 - Num Lock

bit 2 - Caps Lock

In the example bit 0 and 2 are sent (0x05), in order to turn Scroll and Caps Lock on.

The last byte seen, 0xFA, is an ack byte sent by the device, a common response that normally follows host commands:

ps2 0xFA ack

The scheme of the entire three-byte communication:

ps2 request-to-send 0xED 0x05 0xFA

In the code, freely available at the bottom of the page, the pression of buttons BT1, BT2 and BT3 turns on Scroll, Caps and Num Lock respectively .

See here how it all works:






Add new comment

Filtered HTML

  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <p> <h1> <h2> <h3> <h4> <h5> <div> <pre> <object>
  • You may insert videos with [video:URL]
  • Web page addresses and e-mail addresses turn into links automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.