Monthly archive

Pages

PIC18: Connecting to a PS/2 keyboard

Submitted by admin on Sat, 12/27/2014 - 19:42

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:

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.

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:

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:

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

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:

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:

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:

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:

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:

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

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

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:

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: