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.
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
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.
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”.
- IC1 : 74LS163 4 bit binary counter
- IC2 : 74LS251 8 bit (row) counter
- IC3 : SN7445 8 bit (column) counter
- IC4 : 74LS30 8 input NAND gate
- 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.
|Function||Ground||Break||Clock||Row A||Row B||Row C||Col A||Col B||Col C||Col D||W||LED0||CA2||+5v||LED1||LED2|
Pin mappings for the BBC are from the top left to right, with the keyboard face up with the pins facing away.
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.
First thing’s first, supply the keyboard with ~5v from the VCC pin and generate a 1MHz clock on pin 9.
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.
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.
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.
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.
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
Upper case matrix
Shift case matrix
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 :
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.
There are quite a few keys on this keyboard that are not in use anymore:
- F0 - I’ve mapped to F10
- Shift Lock
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.
- 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