Michael Cartwright
Published © LGPL

RC2014 Micro Keyboard reworked as PS/2

Squeezing the full functionality of a computer out of just a 40 keys is a lot to ask for. Challenge accepted..

IntermediateFull instructions provided6 hours750
RC2014 Micro Keyboard reworked as PS/2

Things used in this project

Hardware components

RC2014 Universal Micro Keyboard
×1

Software apps and online services

Arduino IDE
Arduino IDE
inkscape: draw freely
Fusion
Autodesk Fusion
PrusaSlicer

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Prusa i3 MK3S+ 3D printer
Hantek 6022BE USB Oscilloscope

Story

Read more

Custom parts and enclosures

3D Printer Gerber for Bottom Part

3D Printer Gerber for Middle Part

3D Printer Gerber for Top Part

Autodesk Fusion 360 - Bottom Part

Autodesk Fusion 360 - Middle Part

Autodesk Fusion 360 - Top Part

Schematics

Hopper Keyboard Layout Template

SVG: use InkScape to edit and make it your own.

Hopper Keyboard Layout PDF (4-up)

Something you can print at your local print shop in colour.

Code

RC2014 Firmware modified for the Hopper layout

Arduino
This is a PS/2 protocol version of the firmware for the RC2014 keyboard (bitbanged out the Tx and Rx pins so the wiring works).
// ZX81 USB Keyboard for Leonardo
// (c) Dave Curran
// 2013-04-27

// Modified with Function keys by Tony Smith
// 2014-02-15

// Modified for serial output by Spencer Owen
// 2014-05-02
//
// Modified for Hopper Keyboard layout and PS/2 interface by Michael Cartwright (https://www.hackster.io/michael-cartwright)
// 2022-07-28

#define NUM_ROWS 8
#define NUM_COLS 5

//#define SERIAL_DEBUGGING

#ifdef SERIAL_DEBUGGING
// when testing under the Arduino IDE using the serial port, we need to use other digital pins
#define CLOCK_PIN 12
#define DATA_PIN  13
#else
// when deployed, I use RX and TX since those are the pins wired to the connector on the PC2014
#define CLOCK_PIN 0 // RX
#define DATA_PIN  1 // TX
#endif

// PS/2 keymap for our 40 key subset
byte keyMapPS2[NUM_ROWS][NUM_COLS] = {
  { 0x2E, 0x25, 0x26, 0x1E, 0x16 },
  { 0x2C, 0x2D, 0x24, 0x1D, 0x15 },
  { 0x36, 0x3D, 0x3E, 0x46, 0x45 },
  { 0x34, 0x2B, 0x23, 0x1B, 0x1C },
  { 0x35, 0x3C, 0x43, 0x44, 0x4D },
  { 0x21, 0x22, 0x1A, 0x12, 0x14 },
  { 0x33, 0x3B, 0x42, 0x4B, 0x5A },
  { 0x2A, 0x32, 0x31, 0x3A, 0x29 }
};

int debounceCount[NUM_ROWS][NUM_COLS];

// define the row and column pins
byte colPins[NUM_COLS] = {
  6, 5, 4, 3, 2
};
byte rowPins[NUM_ROWS] = {
  7, 8, 16, 11, 15, 9, 10, 14
};

// where is the shift key
#define SHIFT_COL 3
#define SHIFT_ROW 5

#define CTRL_COL 4
#define CTRL_ROW 5

// How many times does a key need to register as pressed?
// Since we have a delay(1) in the main loop these delays
//   are approximately ms (less the loop code execution time)
#define DEBOUNCE_VALUE 50
#define REPEAT_DELAY 250

// length of single data bit in microseconds (100us = 0.1ms, 11 bits per packet = 1100us = 1.1ms)
#define BIT_LENGTH 100
#define HALF_BIT_LENGTH BIT_LENGTH/2

void setup()
{
#ifdef SERIAL_DEBUGGING  
  Serial.begin(9600);  // RC2014 runs at 115,200 baud
#endif
  pinMode(CLOCK_PIN, OUTPUT);
  digitalWrite(DATA_PIN, HIGH);
  pinMode(DATA_PIN, OUTPUT);
  digitalWrite(CLOCK_PIN, HIGH);

  // set all pins as inputs and activate pullups
  for (byte c = 0; c < NUM_COLS; c++)
  {
    pinMode(colPins[c], INPUT);
    digitalWrite(colPins[c], HIGH);

    // clear debounce counts
    for (byte r = 0; r < NUM_ROWS; r++)
    {
      debounceCount[r][c] = 0;
    }
  }

  // set all pins as inputs
  for (byte r = 0; r < NUM_ROWS; r++)
  {
    pinMode(rowPins[r], INPUT);
  }
}

/*             */
/*    loop     */
/*             */
void loop()
{
  bool addDelay = true;
  // for each row
  for (byte r = 0 ; r < NUM_ROWS ; r++)
  {
    // turn the row on
    pinMode(rowPins[r], OUTPUT);
    digitalWrite(rowPins[r], LOW);

    for (byte c = 0 ; c < NUM_COLS ; c++)
    {
      if (digitalRead(colPins[c]) == LOW)
      {
        // increase the debounce count
        debounceCount[r][c]++;

        // has the switch been pressed continually for long enough?
        int count = debounceCount[r][c];
        if (count == DEBOUNCE_VALUE)
        {
          // first press
          pressKey(r, c, false);
          addDelay = false;
        }
        else if (count > DEBOUNCE_VALUE)
        {
          // check for repeats
          count -= DEBOUNCE_VALUE;
          if (count % REPEAT_DELAY == 0)
          {
            // send repeat press
            if ( ((c == SHIFT_COL) && (r == SHIFT_ROW)) || ((c == CTRL_COL) && (r == CTRL_ROW)) )
            {
              // modifier keys do not repeat
            }
            else
            {
              pressKey(r, c, false);
              addDelay = false;
            }
          }
        }
      }
      else
      {
        int count = debounceCount[r][c];
        if (count >= DEBOUNCE_VALUE) // was pressed
        {
          // send release
          pressKey(r, c, true); 
          addDelay = false;
        }
        // no key pressed, so reset debounce count
        debounceCount[r][c] = 0;
      }
    } // for each COL

    // turn the row back off
    pinMode(rowPins[r], INPUT);
  } // for each ROW
  if (addDelay)
  {
    delay(1); // 1ms delay per loop means the DEBOUNCE_VALUE & REPEAT_DELAY are in ms
  }
}

void sendBit(bool bit)
{
  digitalWrite(CLOCK_PIN, LOW);
  digitalWrite(DATA_PIN, bit ? HIGH : LOW);
  delayMicroseconds(HALF_BIT_LENGTH);
  digitalWrite(CLOCK_PIN, HIGH);
  delayMicroseconds(HALF_BIT_LENGTH);
}
void sendCode(byte code)
{
  //  PS/2 protocol: 1 start bit, 8 data bits (LSB first), 1 parity bit (odd), 1 stop bit
  sendBit(0); // start bit
  byte mask = 1;
  byte parity = 0;
  for (int i=0; i < 8; i++)
  {
    bool bit = (code & mask) != 0;
    sendBit(bit);
    mask = mask << 1;
    if (bit)
    {
      parity++;
    }
  }
  sendBit(parity % 2 == 0); // odd parity (0 when odd number of data bits, 1 when even number of data bits)
  sendBit(1); // stop bit
}

void pressKey(byte r, byte c, bool release)
{
  byte key = keyMapPS2[r][c];

#ifdef SERIAL_DEBUGGING  
  // for debugging the key map
  if (release)
  {
    Serial.print("0xF0 ");
  }
  Serial.print("0x");
  Serial.print(key, HEX);
  Serial.println();
#endif
  if (release)
  {
    sendCode(0xF0);
    // delay between release code and key code
    delayMicroseconds(BIT_LENGTH*11); 
  }
  sendCode(key);
}

Credits

Michael Cartwright

Michael Cartwright

21 projects • 14 followers

Comments