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 get you set up with the keyboard to be ready to 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:

  1. Be up to date on recent lectures: Keyboard
  2. Review this document detailing the PS/2 protocol. Print a copy of this PS/2 key code chart and have it on hand.
  3. Install Logic 2 application.
    • 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.
  4. Bring in interesting code for show-and-tell!
  5. Organize your supplies to bring to lab
    • Bring your laptop (with full charge) and full parts kit.

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. Connect PS/2 plug, power up keyboard

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 plug board are lent to you, take them home to work on the upcoming assignments. Write your name and id number of your keyboard on the sign-out sheet. Please take care of keyboard and plug board, you must return both at the end of the quarter.

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; we wire our own 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 use hot glue to 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 has 6 male pins and a plastic tab (SHLD) to guide inserting the plug with the correct polarity. Two of the pins are NC (not-connected); the others carry VCC, GND, DATA and CLK.

PS/2 6-pin mini-din pinout

Grab the PS/2 plug board and look at its four-pin header. Each pin on the header corresponds to one of the four connected pins of the PS/2 plug. The circuit board has traces to connect each pin. On the red plug boards, the CLK, DATA, and GND traces are on the top side of the board and the 5V trace on the underside. On the green plug boards, all four traces are on the bottom side of the board.

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

Use the red and black jumpers to supply power to the plug board. First have your Pi switched off, and use a red jumper to connect a 5V pin on your Pi to the 5V pin on your plug board and a black jumper to connect a GND pin on your Pi to the GND pin on your plug board. Double-check these connections to confirm they are correct (wiring a short circuit through the keyboard can cause fatal damage; ask us how we know…).

Plug your keyboard into the plug board, being sure to rotate the plug to the correct position to align the plastic tab. If you try to force a misaligned plug, you can bend the pins or break off the tab. Please do not do this!

Power on your Pi and the keyboard should flash the small green LEDs in the upper corner when it powers up. These LEDs are not very bright and the flash is brief. You may get a better view if you use your hand to block the ambient light.

If your keyboard does not power on, grab a staff member to help diagnose.

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.

Read the analyzer label to learn its pin layout and identify which pins correspond to the two lowest-numbered channels. Some of our logic analyzers label the channels using a 0-based index (Ch0, Ch1, … Ch7) and others use 1-based index (Ch1, Ch2,… Ch8). In either case, you want to use the two lowest-numbered channels for the clock and data connections. We will refer to the two lowest-numbered channels as Ch0 and Ch1 but they may be labelled Ch1 and Ch2 on the analyzer you are using.

Use the white jumper to connect Ch0 on the analyzer to the CLK pin on your plug board and the purple jumper to connect Ch1 to the DATA pin.

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 tie the logic analyzer's ground 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 an open ground pin on your Pi.

Below is a photo of the circuit (click photo to enlarge). The plug board power (red) and ground (black) are connected to the Pi. The plug board CLK (white) and DATA (purple) are connected to channels 0 and 1 of the logic analyzer. The logic analyzer ground (black) is connected to Pi ground.

wired up

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 right 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 triangle at the top 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. Zoom in and out and pan left and right to view the signal details. You should see the characteristic pattern of the PS/2 protocol.

The Logic 2 application has signal analyzers for common protocols. Along the top of the windonw, click the hexagon labeled 1F to display the "Add Analyzer" pane. Type "PS" to filter the list of analyzers and select "PS/2 Keyboard/Mouse". 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 signal Analyzed

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?

You're ready to answer the first check-in question1.

3. Run keyboard test

Now rewire the circuit to connnect the clock and data lines of plug board to the Pi, instead of the logic analyzer.

Turn off power to the Pi and disconnect the logic analyzer and return to us. Re-use the white and purple jumpers to connect the plug board clock and data to the Mango Pi keyboard clock and data gpios.

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 purple jumper (DATA) to KEYBOARD_DATA (PB7). Find the corresponding header pins on the Mango Pi pinout.

$ pinout.py keyboard

All four jumpers (red, black, white, purple) connect from the plug board to the Mango Pi as shown in the photo below (click photo to enlarge):

Plugboard connected to 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. 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 2-D 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.

You're ready for the second check-in question 2

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 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). The 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 and lastly, read the parity and stop bits.

In which order do the 8 data bits arrive? 3

  • 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.

Error-checking in ps2_read In the starter version you write during lab, the only error-checking is to detect and discard an invalid start bit. For the assignment, your driver will implement additional error-checking for parity, stop bit, and timeout. For lab, it's okay to just read the bits and assume they are correct.

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. Show us your working code! 4

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.

Using a logic analyzer to "snoop"

In this lab, we connected the keyboard clock and data lines to either the logic analzyer or to the Pi, but it's also possible to connect to both simultaneously! You would run clock and data jumpers from the plug board to a breadboard and then fan out two connections from there, one to the logic analyzer and another to your Pi. Your Pi receives the data while the same signal is simultaneously captured by the logic analyzer. This can be very useful during debugging as you can compare what your Pi thinks it's receiving with the ground truth of the logic analyzer capture. Using the logic analzyer to observe exactly what signals are sent while simultaneously seeing how your Pi interprets them is like having gdb for the pins! The logic analyzers are available in lab if you need to borrow one in the future.

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!

Check in with TA

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.5

  1. 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 your keyboard operating within spec? 

  2. What sequence of codes is sent when typing capital A? If you hold down both the shift key and 'a' key, what is the sequence of repeating codes is sent? 

  3. In a PS/2 scancode, does the least significant or most significant data bit arrive first? 

  4. Show off that your implementation of keyboard_read_scancode correctly receives scancodes from your keyboard. 

  5. Please return logic analyzer and usb cable to the lab cabinet (not to take home). The PS/2 keyboard and plug board are on loan to you to take home. Please take care of this equipment; you will return both at the end of quarter.
    Were you able to complete all of the lab exercises? Do you need followup assistance? How can we help?