Converting a BBC Micro keyboard to USB with an Arduino Micro (ATmega 32U4)


I’m a self-confessed keyboard nerd and got a hold of this keyboard for the keycaps off of a friend. Initially I’d thought they would be cherry compatible, but unfortunately, they are not. So rather than butchering a perfectly good keyboard, I wanted to make it USB compatible. Please bear with me, this is the first time I’ve done any projects interfacing with unknown hardware, or even with an Arduino.

BBC Micro keyboard

Resources

Here’s the GitHub repository for the current code, and all the circuit diagrams I could find. There are a few issues with the layouts of each of those, however:

  • A couple of the matrix values in the diagrams are swapped
  • The pins for the logic chips don’t line up to the rows and columns
  • To get the LEDs to enable, you should ground the LED pins, they are already supplied 5V

Hardware

The ATmega32U4 was chosen for a few reasons:

  • It can act as a USB device
  • It is compatible with the Arduino programming framework, and there’s plenty of resources and a good community behind it
  • Another project from Tynemouth Software doing the same thing is using it, so I know it could be done.

Circuit diagrams

These were collected online from various sources, the first of two contains more information, the second is more basic and easier to understand at a glance.

Note that the rows are organised differently in the two diagrams and there is some disagreement between the locations of “I” and “9”.

IC Diagrams

  • IC1 : 74LS163 4 bit binary counter
  • IC2 : 74LS251 8 bit (row) counter
  • IC3 : SN7445 8 bit (column) counter
  • IC4 : 74LS30 8 input NAND gate

Kit needed

  • Multimeter
  • Oscilloscope
  • Ardunio pro micro (5v 16MHz), I used the cheap ones from eBay for ~£3 delivered
  • Breadboard and breakout leads
  • BBC Micro keyboard

How it works

The keyboard has some integrated logic on board, but as a basic breakdown a few things happen (as if it was still in the main box):

  • The keyboard scans at 1MHz, this clock signal comes from the BBC Micro’s slow bus into IMHZ.
  • A key-press is detected and sets CA2 to high.
  • The BBC Micro receives the keypress signal and pauses the scanning of the keyboard matrix by setting KB EN to high.
  • The rows and columns are checked by the BBC Micro by binary counting the row and col pins
  • W (PA7) goes high to indicate the correct row and column combination is found
  • Searching the matrix is stopped, KB EN is set to low and the keyboard resumes scanning

There is one exception, which is the break key that is a directly wired switch, that avoids the logic designed to perform soft/hard resets of the BBC micro.

There is a more detailed explanation of the whole system and keyboard available here.

Pin mapping

BBC Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Function Ground Break Clock Row A Row B Row C Col A Col B Col C Col D W LED0 CA2 +5v LED1 LED2
Arduino Pin GND A3 9 6 7 8 5 4 3 2 A1 16 10 VCC 14 15

Pin mappings for the BBC are from the top left to right, with the keyboard face up with the pins facing away.

The Code

So there are a few things we need to do here; generate a clock, listen for a keypress, scan the keyboard matrix, find out when we have the right combination of rol and col, and finally convert the (row, col) value to the correct character.

Clock generation

First thing’s first, supply the keyboard with ~5v from the VCC pin and generate a 1MHz clock on pin 9.

pinMode(9, OUTPUT);
TCCR1A = ((1 << COM1A0));
TCCR1B = ((1 << WGM12) | (1 << CS10));
TIMSK1 = 0;
OCR1A = 7;

Put simply, this enables the fast PWM mode on pin 9 for a 1MHz signal, further details can be found in section 14.8.3 of the ATMega manual.

LED Control

The LEDs on the main motherboard are already powered and need to be connected to ground to get to work. Setting the LED pins to output mode, and using the digitalWrite function to turn them on (LOW) and off (HIGH). Currently, the LEDs are mapped in the code to display keypress events and when the correct key has been found from scanning the rows and columns.

‘High’ and ‘Low’

Early on, it became clear that the digital read mode on the Arduino would not be suitable as it uses a +5v signal for high, where the keyboard was returning +1.8-2.2v for high. So to compensate, an auto-adjusting sensor for each of the input pins using the analogue input.

Each input pin was set to an AnalogInput mode and checked every cycle. The low value was stored as it would drift. To ignore the drifting and nose on the pin, a threshold was added to ensure the readings for high were not false positives.

int wHigh(){
  WVal = analogRead(WPin);
  if (WVal > WLow){
    return true;
  }else{
    WLow = WVal + WLowOffset;
    return false;
  }
}

Enter the matrix

Once a keypress event is detected by a ‘high’ on CA2, KB EN is set to pause the scan high and start searching the matrix.This is done is by counting in binary across the logic chips e.g. 0 = 000, 1 = 001,… 4 = 100.

To encode this information, set the lines you want to count with to digitalWrite(high) and then check to see if W has gone high, indicating that you have found the correct row & col value.

int rowStates[numRowStates][3] = { {0,0,0}, {0,1,1},... {1,1,1} };
int colStates[numColStates][4] = { {0,0,0,0}, {0,0,0,1},... {1,0,0,1} };


for (int thisRowState = 0; thisRowState < numRowStates; thisRowState++){
  digitalWritePins(3, RowPins, rowStates[thisRowState]);
    for (int thisColState = 0; thisColState < numColStates; thisColState++){
      digitalWritePins(4, ColPins, colStates[thisColState]);
      if (wHigh()){
        sendKey(thisRowState, thisColState);
      }
    }
  }
}

Arduino as a keyboard

Arduino has a great (but limited) keyboard library, that I’ve used here to turn the device into a USB keyboard.

It allows the device to send ASCII characters in the same way a keyboard would. It can be sent as a quoted character, ASCII binary, base 10 or hexadecimal.

Keyboard.begin();
Keyboard.write('a'));

Keypress to character

The next step is to map out the keyboard matrix into numerical matrices with the ASCII characters for three states: lower case, upper case and shifted. Once we have the correct mappings for these, they are converted to the integer values in ASCII using this very helpful tool.

Lower case matrix
{ {129, 128, -50, -50, -50, -50, -50, -50, -50, -50},
  {179, 122, 32, 118, 98, 109, 44, 46, 47, -50},
  {-99, 115, 99, 103, 104, 110, 108, 59, 93, 178},
  {193, 97, 120, 102, 121, 106, 107, 34, 58, 176},
  {49, 50, 100, 114, 54, 117, 111, 112, 91, 218},
  {203, 119, 101, 116, 55, 105, 57, 48, 95, 217},
  {113, 51, 52, 53, 197, 56, 200, 45, 94, 216},
  {27, 194, 195, 196, 198, 199, 201, 202, 92, 215}};
Upper case matrix
{ {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
  {-1, 90, -1, 86, 66, 77, -1, -1, -1, -1},
  {-1, 83, 67, 71, 72, 78, 76, -1, -1, -1},
  {-1, 65, 88, 70, 89, 74, 75, -1, -1, -1},
  {-1, -1, 68, 82, -1, 85, 79, 80, -1, -1},
  {-1, 87, 69, 84, -1, 73, -1, -1, -1, -1},
  {81, -1, -1, -1, -1, -1, -1, -1, -1, -1},
  {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
Shift case matrix
{ {-2, -2, -2, -2, -2, -2, -2, -2, -2, -2},
  {-2, -2, -2, -2, -2, -2, 60, 62, 63, -2},
  {-2, -2, -2, -2, -2, -2, -2, 43, 125, -2},
  {-2, -2, -2, -2, -2, -2, -2, -2, 42, -2},
  {33, 64, -2, -2, 38, -2, -2, -2, 123, -2},
  {-2, -2, -2, -2, 39, -2, 41, -2, 35, -2},
  {-2, 92, 36, 37, -2, 40, -2, 61, 124, -2},
  {-2, -2, -2, -2, -2, -2, -2, -2, 126, -2}};

The negative numbers are not valid ASCII characters and are used to represent special cases:

  • -99 is the shift lock key and requires special handling
  • -2 = go to upper case matrix
  • -1 = go to lower case matrix
  • any other negative are unmapped keys, in this case, the startup option switch and Copy

Keyboard Layout assumptions

In order to get the key mapping to work, it is necessary to not send the data about the state of the shift key. This is done as I found that assumptions are made about the key mapping (at least on Linux), so “Shift + 3” is the “£” key on a UK layout, however, on the BBC KB this is the “#” character, so sending “Shift + 3” would print the “£” character.

So send “#” and problem solved, right? Well, no. Even though the correct ASCII code for “#” would be sent, as the shift key would also be held, the assumption is that it should be remapped this to the shifted version on a UK keyboard which is “~”.

So in order to send the correct characters, I had to withhold the shift status and store it locally on the Arduino to modify the keypress.

[0, 1, 2] != [0, 1, 2]

After scouring over the circuit diagrams, I had assumed that the layout of the rows and columns would be in a logical order. However, after a long and frustrating time, it appears that the layout of the rows does not follow my assumed logical mapping. After looking at it, this is clearly done to keep the trace layouts simple and efficient. (However, be wrong and just have some wires crossed.) The row order that works for me is :

Decimal 0 1 2 3 4 5 6 7
Expected 000 001 010 011 100 101 110 111
Actual 000 011 101 001 110 010 100 111

Shift & Ctrl

Currently, my ‘Shift’ and ‘Ctrl’ buttons are not working. Unfortunately, it looks like the whole row of keys is out of commission. I’ll get back to this where I have more time, the rest of the row should be a row of selector switches, but this isn’t included on this keyboard model.

Irregular keys

There are quite a few keys on this keyboard that are not in use anymore:

  • F0 - I’ve mapped to F10
  • Shift Lock
  • Copy
  • Break

The break key is an interesting one and is wired independently to the keyboard. It is intended to be used to warm or cold restart the machine. I’m currently thinking I should map this and the ‘Copy’ key to “Ctrl + c”.

Shift lock has been implemented, by storing the value on the Arduino and passing the appropriate shifted ASCII code. F0 has been mapped to F10.

Future work

  • Make the keyboard QMK compatible. However, I’m not sure if this is possible
  • Play the BBC Micro keyboard startup sound when plugged in/turned on
  • Improve on the ‘trickery’ for the shift keys, I’m not a fan of how it’s currently done.
  • Speed up the key scanning, it’s horribly slow
  • Improve on the key repeat mechanism when a key is held down, and add a delay before the first repeat and a shorter delay for the second+ repeats.
  • Handle multiple key presses at the same time, I believe this keyboard has a 2/3 key rollover.
  • Code in the 8 bit startup options switch

Related

PfSense DNS Resolver with PiHole DNS forwarder for network-wide ad-blocking

Configuring Unbound on PfSense as a DNS Resolver to register DHCP hostnames on localdomain and using PiHole as a DNS Forwarder to perform network-wide ad-blocking.

Setting up a Greylog server for central logging with Ansible

Learning how to use Ansible through setting up a Graylog server for centralised syslogging.

Proxmox Setup v2 - moving root onto an SSD and away from the ZFS array

Moving away from ZFS on root, and using it for the HDD array.

Proxmox host migration; new Home Server day

A workbook of migrating Proxmox to a new host for the first time.

Migrating Docker from Digital Ocean to home

Moving my docker containers to a new host at home.

Setting up a VLAN with PfSence

A quick introduction to setting up VLANs with PfSence for a guest network.

Simulating Spin-Echo Metabolite NMR / MR Spectra with PyGamma (VeSPA)

How to simulate MR metabolite spectra with PyGamma, including binning and plotting.