Lab 5: Keyboard Surfin'


Lab written by Philip Levis and Pat Hanrahan

Goals

In your next assignment, you will write a PS/2 keyboard driver for your Pi. The primary goal of this lab is to set up the keyboard so that you can start on the assignment.

During this lab you will:

  • Wire up a PS/2 keyboard to the clock and data gpios on your Mango Pi.
  • Watch the signals from the keyboard using a logic analyzer.
  • Print out the scancodes sent by the keyboard.
  • Write code to read the 11 bits of a PS/2 scancode.

Prelab preparation

To prepare for lab, do the following:

  • Bring sure to bring your full parts kit, including your breadboard.
  • We will be using the Saleae Logic 2 application to visualize the signals captured by the logic analyzer. Saleae is a company known for its high-quality logic analyzers and software. Here is the page with download links for Logic 2. Download and install the version for your platform. If using WSL, download the Windows version.
  • Review this document detailing the PS/2 protocol. Print a copy of this PS/2 key code chart and have it on hand.
  • Bring in interesting code for show-and-tell!

Lab exercises

0. Pull lab starter code

Change to your local mycode repo and pull in the lab starter code:

$ cd ~/cs107e_home/mycode
$ git checkout dev
$ git pull code-mirror lab5-starter

1. Install PS/2 plug

In lab, we will distribute a PS/2 keyboard and plug board to each of you. (Click photos to enlarge).

  • PS/2 keyboard (including USB-to-PS/2 adapter perma-attached with hot glue) keyboard
  • PS/2 plug board plugboard

The keyboard and plugboard are lent to you, you are to return them at the end of the quarter. Please take care of them!

Most modern keyboards use the Universal Serial Bus (USB). The USB protocol is quite complicated: a typical USB keyboard driver is 2,000 lines of code – ouch! In this course, we instead use a PS/2 keyboard because PS/2 is a simple serial protocol that is easy to decode. The PS/2 keyboard appeared on the original IBM PC. Computers have long since stopped including a PS/2 port as standard equipment so we will wire a direct connection from a PS/2 plug board to the GPIO pins on the Pi.

Sourcing genuine PS/2 keyboards has become an archaeological expedition for us. We found a USB keyboard that can also operate in PS/2 mode. The keyboard has a wired USB connector and a USB-to-PS/2 adapter. We used hot glue to permanently attach that adapter, so the keyboard acts as a wired PS/2 keyboard.

There are two common PS/2 devices: a keyboard and a mouse. A PS/2 plug is a 6-pin mini-DIN connector. By convention, a mouse connector is green and a keyboard connector is purple, but the connectors are otherwise identical. Inspect the inside of the mini-din PS/2 connector on the keyboard. It contains a plastic tab (this forces the plug to be inserted with the correct polarity) and 6 male pins. Two pins are NC (not-connected), and the others carry VCC, GND, DATA and CLK.

PS/2 6-pin mini-din pinout

Grab the PS/2 plug board and fip it over to see the four-pin header that on the underside of the circuit board. These header pins connect to the male pins in the PS/2 plug through small traces. Find those traces on the circuit board. The CLK, DATA, and GND traces are on the top side of the board and the 5V trace on the underside.

Place the plug board into your breadboard so that each of the four pins is on a different row. Be careful not to mis-orient the pins; if the four pins are all in the same row, they are all connected together, which is not what you want!

From your kit, pick out seven female-to-male jumpers: one red, two black, two white, and two yellow. You'll be following these color conventions: red for 5V, black for GND, white for CLK, and yellow for DATA.

Use one red and one black jumper to supply power to your keyboard. Connect the female end of the red jumper to a 5V pin on your Pi and the male end to the 5V row in your breadboard. Similarly, use a black jumper to connect a GND pin on your Pi to the GND row.

2. Use a logic analyzer to visualize keyboard signals

We have a bin of logic analyzers available in lab. Use an analyzer and USB cable to do these exercises in lab, be sure to return both to us before leaving lab.

An logic analyzer allows you to examine the signals sent by the keyboard. Here is an inexpensive 8-channel logic analyzer made by Hiletgo (click photo to enlarge):

Hiletgo logic analyzer

One end of the logic analyzer has a 10-pin header. The pins correspond to the different signals or channels to be monitored by the analyzer. The analyzer supports reading up to 8 simultaneous channels. The other end of the logic analyzer has a mini-USB port that you will connect to the USB on your laptop to supply power and read data.

Use one white and one yellow jumper to connect the keyboard clock and data to the logic analyzer. Use the label on the analyzer to learn the pin layout and identify which pins correspond to channels 0 and 1. Connect the female end of the white jumper to channel 0 on your analyzer and the male end to the CLK row on your breadboard. Similarly, use the yellow jumper to connect channel 1 to the DATA row.

In the photo below, the four jumpers from left to right are power (red), ground (black), CLK (white), and DATA (yellow). The power and ground connect to your Pi. The CLK and DATA are connected to channels 0 and 1 of the logic analyzer.

wire order

You must also ground the logic analyzer. Voltage is relative: when looking at a signal, the reading is the difference from a reference voltage, which in this case should be the ground provided by the Pi. If you don't connect the logic analyzer to the Pi's ground, then it will be measuring voltage against whatever happens to be on the pins, which can act as tiny antennae. Identify the ground pin on the analyzer and use a black jumper to connect it to the keyboard ground.

The last two jumpers (one white and one yellow) will be be used to connect the keyboard clock and data to the gpio pins on the Pi. Connect the male end of the second white jumper to the CLK row and the second yellow jumper to the DATA row. Leave the female jumper ends unconnected. We will connect these later, but it's easier to get them into the breadboard now.

This is what it looks like when the plug board is wired up to the logic analyzer. (Click photos to enlarge)

wired up wired up

Plug your keyboard into the PS/2 port on the plug board.

Open the Logic 2 application you installed on your laptop as part of prelab preparation. When the logic analyzer is unconnected, the start-up screen is similar to this:

Logic2 Startup

Connect the USB cable from the mini-USB port on the logic analyzer to an open port on your laptop or USB hub. When the logic analyzer is connected, the Logic 2 screen will change to this:

Logic2 Connected

Press the flat cube icon in the upper left to access the device settings. Find the sample rate control in the settings pane; it is labeled something like 24 M/s. Adjust the sample rate down to 1 M/s (1 million samples per second is plenty, attempting to sample at a higher rates can sometimes produce errors). Close the settings pane.

Logic2 sample rate

The blue circle with a white "Play" triangle is the play/stop button. Press play to start reading the signal. Type a few keys on the PS/2 keyboard, then press stop to end the recording. The Logic2 window will show the signals recorded on channels 0 and 1. You can zoom in and out and pan left and right to see the details of the captured signal. You should see the characteristic pattern of the PS/2 protocol.

The Logic 2 application provides protocol analyzers that can be applied to the captured data. Along the right side, find the hexagon labeled 1F . Click this button to display the Analyzers pane. Click the + in upper right to access the list of analyzers and choose the PS/2 protocol analyzer. Configure CLK on channel 0 and DATA on channel 1. The captured data is now decoded according to the PS/2 protocol and interprets the sampled signal as scancodes.

Logic 2 PS/2 Analyzer

Hover over the visualization of the PS/2 clock channel to see the signal timing data. How far apart is each falling clock edge? At what frequency is the PS/2 clock running? Is the keyboard operating with the range dictated by the spec?

Test your understanding: The PS/2 clock frequency must be in the range 10 to 16.7 kHz. To be within spec, what should time period between falling clock edges be? When you measured using the logic analyzer, what time period did you observe? Is it within spec?

3. Run keyboard test

You're now ready to try reading the keyboard signals on the Pi. Earlier you added additional white and yellow jumpers to your breadboard rows, now you will connect the female ends to your Pi.

Review the keyboard module interface keyboard.h to see which gpio pins to use for the keyboard clock and data lines. The white jumper (CLK) connects to KEYBOARD_CLOCK (PG12) and the yellow jumper (DATA) to KEYBOARD_DATA (PB7). Find the corresponding header pins on the Mango Pi pinout.

Fanning out two connections from the PS/2 plug (one to the logic analyzer, the other to your Pi) allows your Pi to receive the data while simultaneously capturing the same signal on the logic analyzer. This is useful during debugging as you can compare what your Pi thinks it's receiving with the ground truth of what the logic analyzer sees.

Here are the connections to the Pi:

Keyboard plugged into the Pi

The keyboard_test application uses the reference implementation of the keyboard driver. Let's try it now:

% cd lab5/keyboard_test
% make run

Type keys on the PS/2 keyboard and the program should print the scancodes received. You should also be able to see the signals in the Logic 2 application. If you aren't getting events, check your wiring.

Note that scancodes are not ASCII characters. Instead, these values relate to the physical placement of the key on the keyboard. Inside the keyboard, there's a 2D matrix of wires that generates the scancode for a given key. Your keyboard driver will implement the logic to lookup that scancode and produce the appropriate ASCII character.

Each key press and key release is reported as a distinct action. Press a key; the keyboard sends a scancode. Release the key; the keyboard sends another scancode; this code is same as the first one, except it is one byte longer: it has a leading 0xF0. Tap the z key now. The keyboard sends 0x1A on key press, followed by 0xF0 0x1A on key release.

If you press z and hold it down, the keyboard enters auto-repeat or typematic mode where it repeatedly generates key press actions until you release the key. Press and hold zand watch for the repeat events to start firing. About how long does it seem to take for auto-repeat to kick in? At about what rate does it seem to generate auto-repeat events?

Press and hold one key, then press and hold another without releasing the first. Which key repeats? What happens when you release that key? Try those same actions on your laptop's keyboard, does it behave the same way?

Type single keys to observe the scancodes for press, release, and auto-repeat. Then try typing modifier keys like Shift and Alt, singularly and in conjunction with other keys. Does shift being pressed changed what scancode is sent by a letter key? What about caps lock? Observe the sequence of scancodes to suss out what functionality is provided by the keyboard hardware and what features are to be implemented in the keyboard driver software.

4. Implement ps2_read

In this lab exercise, you will get a start on writing the keyboard driver that will be a part of your next assignment. We want you to do this task in lab because working at the intersection of hardware and software requires a specialized kind of debugging (logic analyzer, etc.) which can be tricky; it helps to have staff around!

Change to the directory lab5/my_keyboard. This is the same application as lab5/keyboard_test, except that rather than using the reference implementation, you will write your own code to read a scancode.

Browse the headers for ps2.h and keyboard.h to review the module documentation. The ps2 module manages the low-level communication with a PS/2 device. The keyboard module layers on the ps2 module to interpret scancodes into typed keys. During lab, you will implement an initial version of the function ps2_read.

Open ps2.c in your editor. The function ps2_new has already been written for you. This function configures a new PS/2 device for the specified clock and data gpio. In the library modules we have seen thus far, we have used global variables to store data that is shared across the module. A single set of global variables for the ps2 module does not work, as each device needs its own independent settings (i.e clock and data gpio). ps2_new creates a new struct to hold the per-device settings. Because that memory needs to be persistent after the function call exits, it allocates memory using your shiny new malloc . The rest of the function is setting the clock and data GPIOs as inputs and enabling the internal pull-up resistor so these pins default to high, as expected in the PS/2 protocol.

Once you understand the given code in ps2.c you are to implement the function ps2_read to read the bits that make up a scancode. The basic operation is to wait for the falling edge on the clock line and then read a bit from the data line. You will need to do this 11 times for a scancode, but rather than duplicate that code 11 times, we suggest you define a private helper function read_bit. The helper waits until observes the transition from high to low on the clock line and then reads a bit from the data line. Unifying repeated code into a shared helper aids readability and maintainability; this is a good habit to adopt.

A scancode transmission consists of 11 bits: a start bit (always low), 8 data bits, a parity bit, and a stop bit (always high). To synchronize with the keyboard, ps2_read should verify that first bit read is a valid start bit, e.g. is 0. If not, discard it and read again until a valid start bit is received. Next, read the 8 data bits.

In which order do the 8 data bits arrive?

  • Hint: if you're not sure, take a look at the signal you captured for the keyboard's data line with the logic analyzer or look back at the PS/2 protocol documentation linked in the prelab.

The last two bis are the parity and stop bits. For the assignment, your driver will validate these bits have the correct values, but for lab, just read the bits and assume they are correct.

If you're having trouble reading in the bits correctly, debug by comparing the bits your code is seeing with the signals seen by the logic analyzer: run your program, start the logic analyzer, and type one key. Being able to simultaneously see exactly what signals are sent and how your Pi interprets them is extremely useful: it's like gdb for the pins!

Disconnecting logic analyzer The wiring we are using in lab has four jumpers for CLK and DATA: one pair to send keyboard signals to the Pi and another to share with the logic analyzer. The essential connection is between the keyboard and the Pi; the additional connection to the logic analyzer is just for debugging. When leaving lab, remove the extra jumpers and return the logic analyzer to us. The logic analyzers are available in lab if you need to borrow one again in future.

The function keyboard_read_scancode in keyboard.c simply calls ps2_read to get the next scancode. This means that once you have a working ps2_read, your keyboard_read_scancode should automatically spring to life. Build and run the application and see that it receives each scancode sent by the keyboard.

If your implementation of ps2_read is working correctly, you should be able to compile your application and have it act identically to the keyboard_test version you tested in Step 3. If you run into any snags, please be sure to get help from us now so that you'll be able to hit the ground running on the assignment. We strongly recommend that you confirm with us in person to show that your implementation of keyboard_read_scancode correctly receives scancodes from your keyboard.

Caution on adding debug code in timing-sensitive passages Back in lab1, you estimated how many instructions the Pi was executing (~400 million/second). Earlier in this lab, you measured the time of one cycle of the PS/2 clock. Work out how many instructions the Pi can execute in that time. Now consider a call to printf. Make a ballpark estimate of how many instructions are executed to process and output each character and multiply that count by length of the format string for a rough total count. Imagine adding a debug print statement to your keyboard driver after reading one bit and before reading the next. What would be the consequence if that printf call takes longer to execute that you have available? To ensure you stay within budget, best to limit debug output to a quick jot of a few characters via uart_putchar. Keep this lesson in mind whenever working with code that has similar tight timing requirements.

5. Code reading

One great way to deepen your understanding of programming and computer systems is to read and review code written by others.

xkcd code quality

If we have some time at end of lab period, we hope to do some group code review. We welcome you to bring a passage of particular interest to you. It can be code you wrote yourself (for CS107e, of in past courses or personal project) or code from a public source (github, open source repository, blog, textbook, paper, etc.). Perhaps you've seen a clever technique that you would like to further understand (Fast inverse square root? ) Maybe you have a piece of code that you struggled to design and would like to brainstorm alternative approaches? Perhaps we will settle a score with code that deserves to be drawn and quartered for its crimes against good taste and all that is sacred? Bring it in for show-and-tell!

Finishing

Check in as you go

Please touch base with us during lab to ask your questions and confirm your understanding. We are here to help!

Submit exit form

Before leaving, submit the lab exit form to let us how the lab went for you and note any issues needing followup. The key goals for this lab are to leave with wired connection to the PS/2 keyboard and a working draft code to read the 11 bits of a single scancode.

Please return logic analyzer and usb cable before leaving lab (not to take home). The PS/2 keyboard and plug board are loaned to you to take home. Please take care of this equipment; you will return it at the end of quarter.