This post is a summary of a Deskthority thread that I created in 2018. I’ve tried my best to piece the details together.
Introduction
At some point I purchased a very dirty NEC PC-8810 keyboard with blue Alps that was exceptionally dusty. My original plan was just to clean it up and sell it, but I soon became enthralled with the idea of converting the keyboard to USB.
Clean Up
As I mentioned, the board was super dusty under the caps, so my first job was to give it a good cleaning.
The two arrows in the photo above turned out to be SKCM heavy blues, which are around 200g force to press. I desoldered all of the switches and broke down the board into components.
Then I stripped all of the rust off the plate and painted it. Cleaned all of the switches and reinstalled them.
Analysis
I bought a pack of these connector plugs, but they unfortunately the pins were not very accessible. So I ended up making a really messy but functional cable connector with a bunch of wire head-shrink tubes.
I used a multimeter in continuity mode to do some basic tracing of the pins for the cable.
I found a blog post by Leaded Solder who had the actual computer itself and was trying to reverse engineer the keyboard. This was a super helpful find.
This explanation of how the keyboard works definitely pointed me in the right direction.
What this means in practice is that the PC88 itself does the key reading. The keyboard connector has 13 pins (14 if you count the ground sleeve), and in order to read a key, the computer triggers four of those pins. A multiplexer on the keyboard uses those four lines to activate the appropriate row on the keyboard matrix, and eight lines are triggered in response to tell the computer which keys on that row are pressed.
I confirmed with a quick and dirty Arduino sketch that this is most likely how the keyboard reading works.
My sketch pulls one of the signal pins low then reads each of the remaining 8 pins. If you press a key on the keyboard that’s in the row corresponding to the signal pin that was pulled low, the sketch will output the matching column number.
I ran my sketch for each key and compared against the memory map table from leadedsolder and everything matched up with just a couple of exceptions where a keypress didn’t generate any output.
I eventually was able to revise my Arduino sketch into a working prototype converter.
Converter
Pinout
I used a Teensy 2.0 which has this pinout:
The corresponding pins on the keyboard connector are:
Code
Here is the code for the converter. I’m using the arduino keyboard library so if you’d like to switch around the keymap, you can use this page to find all the keycodes. To compile the sketch in the arduino IDE, make sure you’ve set the USB type to keyboard by going to: Tools > Usb Type > Keyboard + Mouse + Joystick.
A big thank you again to the author of leadedsolder.com whose posts were key in getting this working.
/* Copyright (C) 2018 Snacksthecat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
ypconst int keyCount = 92;
const long keyMap[12][8] = {
{ KEYPAD_0, KEYPAD_1, KEYPAD_2, KEYPAD_3, KEYPAD_4, KEYPAD_5, KEYPAD_6, KEYPAD_7 },
{ KEYPAD_8, KEYPAD_9, KEYPAD_ASTERIX, KEYPAD_PLUS, KEYPAD_ENTER, KEY_COMMA, KEYPAD_PERIOD, KEYPAD_ENTER },
{ KEY_LEFT_BRACE, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G },
{ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O },
{ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W },
{ KEY_X, KEY_Y, KEY_Z, KEY_TILDE, KEY_SLASH, KEY_SLASH, KEY_EQUAL, KEY_MINUS },
{ KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7 },
{ KEY_8, KEY_9, KEY_QUOTE, KEY_SEMICOLON, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_BACKSLASH },
{ KEY_DELETE, KEY_UP, KEY_RIGHT, KEY_BACKSPACE, MODIFIERKEY_ALT, MODIFIERKEY_CTRL, MODIFIERKEY_SHIFT, MODIFIERKEY_CTRL },
{ KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_SPACE, KEY_ESC },
{ KEY_TAB, KEY_DOWN, KEY_LEFT, KEY_INSERT, KEY_ESC, KEYPAD_MINUS, KEYPAD_SLASH, KEY_CAPS_LOCK },
{ KEY_PAGE_UP, KEY_PAGE_DOWN, 0, 0, 0, 0, 0, 0 }
};
long keysHeld[keyCount];
long keysPressed[keyCount];
void setup() {
// put your setup code here, to run once:
// Signal pins
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
// Input pins
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
pinMode(11, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("hidave");
}
void addHeldKey(long key) {
for(int i = 0; i < keyCount; i++) {
if(keysHeld[i] == 0) {
keysHeld[i] = key;
Keyboard.release(key);
break;
}
}
}
void removeHeldKey(long key) {
for(int i = 0; i < keyCount; i++) {
if(keysHeld[i] == key) {
keysHeld[i] = 0;
break;
}
}
}
void readInputPins(int row) {
int col = 0;
// For each column pin
for(int i = 4; i < 12; i++) {
// If the key is pressed
if(digitalRead(i) == LOW) {
long key = keyMap[row][col];
// Place it in an empty slot
for(int j = 0; j < keyCount; j++) {
if(keysPressed[j] == 0) {
keysPressed[j] = key;
break;
}
}
}
col++;
}
}
void queryRow(int row) {
// Pin 0
if(row == 0 or
row == 1 or
row == 2 or
row == 3 or
row == 4 or
row == 5 or
row == 6 or
row == 7) {
pinMode(0, OUTPUT);
digitalWrite(0, LOW);
}
// Pin 1
if(row == 0 or
row == 1 or
row == 2 or
row == 3 or
row == 8 or
row == 9 or
row == 10 or
row == 11) {
pinMode(1, OUTPUT);
digitalWrite(1, LOW);
}
// Pin 2
if(row == 0 or
row == 1 or
row == 4 or
row == 5 or
row == 8 or
row == 9) {
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
}
// Pin 3
if(row == 0 or
row == 2 or
row == 4 or
row == 6 or
row == 8 or
row == 10) {
pinMode(3, OUTPUT);
digitalWrite(3, LOW);
}
readInputPins(row);
// Revert pin to input
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
}
void updateKeysPressed() {
for(int i = 0; i < 12; i++) {
queryRow(i);
}
}
void clearKeysPressed() {
for(int i = 0; i < keyCount; i++) {
keysPressed[i] = 0;
}
}
void updateKeysHeld() {
long key;
for(int i = 0; i < keyCount; i++) {
key = keysPressed[i];
if(key != 0) {
if(checkForHeldKey(key) == false) {
Serial.println(key);
addHeldKey(key);
Keyboard.press(key);
}
}
}
for(int i = 0; i < keyCount; i++) {
key = keysHeld[i];
if(key != 0) {
if(checkForReleasedKey(key) == true) {
removeHeldKey(key);
Keyboard.release(key);
}
}
}
}
bool checkForHeldKey(long key) {
bool result = false;
for(int i = 0; i < keyCount; i++) {
if(keysHeld[i] == key and key != 0) {
result = true;
break;
}
}
return result;
}
bool checkForReleasedKey(long key) {
bool result = true;
for(int i = 0; i < keyCount; i++) {
if(keysPressed[i] == key and key != 0) {
result = false;
break;
}
}
return result;
}
void loop() {
// Clear out the old keys
clearKeysPressed();
// Load up the currently pressed keys
updateKeysPressed();
updateKeysHeld();
}