I’m writing these lines on an old Atari 800 XL. Or rather, it looks like one from the outside. But everything beneath the surface has been removed and replaced by a stamp-sized 20$ microcontroller that – plugged into a PC – pretends successfully to be a USB keyboard.

I don’t even remember what got me into mechanical keyboards. Maybe the news that Notch wrote Minecraft on a IBM Model M? It’s not the first time I heard that particular keyboard being mentioned and finally I gave in and bought one for myself. I wasn’t aware how much of a difference the choice of keyboard makes. In hindsight that’s just ignorant – now that I’ve typed on decent keyboards I’ll never be content with typing on a mushy OEM device again. The Model M, however? It feels great, but a little too heavy and loud for everyday use. I’m typing on much lighter Cherry MX Reds now. But it will always remain the keyboard that introduced me to the world of mechanical keyboards and it was the reason why – some rainy day last fall – I was browsing a local flee market for used keyboards.

I had no luck with that, but I found an Atari 800XL for 20€ that was in great external condition and I figured I could try to turn it into a keyboard instead.

At home I opened it and found the keyboard to employ some kind of circuit printed on thin transparent plastic sheets which was directly plugged into a connector on the main board.

It wasn’t too hard to figure out how this worked. The plastic sheet is folded so that the circuit traces would touch in various places were it not for an extra seperator sheet in the middle, seperating the top and bottom half. This seperating sheet has a small hole underneath each key.

If you press the key a spring presses a small part of the top sheet down on the bottom sheet. Now with each key press a unique pair of pins at the connector get to conduct electricity. To find out if a specific key is pressed apply current to one of it’s associated pins and probe the other if the current can flow freely.

Now you just have to figure out the pair of pins for each key on the keyboard. Luckily I found the Atari 800 XL’s keyboard matrix documented online.

Because I couldn’t find an identical connector to purchase online I figured I could just desolder it from the PCB. Well, turns out I couldn’t. To get it free you probably need to heat up all 20-something pins at the same time which I couldn’t figure out how to do with the simple soldering iron I had. So I bought a fretsaw and once I had the connector removed from the board without any desoldering (*cough*) I got rid of the PCB piece by piece!

I bought a Teensy 2.0 microcontroller, soldered a bunch of pinheaders to it…

…and a complementary bunch of pinheaders to the connector I hijacked from the PCB. Then connected both with a ribbon cable.

From now on it was basically a coding job. The Teensy is well documented with good tutorials to get started and there’s also a plugin for the popular Arduino IDE. All you have to do is to apply some voltage to a pin and check all other pins if there’s some measurable current. Then you know what keys are currently pressed and you can communicate the changed state via the handy Keyboard API.

It still took me a while to come up with a program that would allow me to map all buttons correctly because the keyboard layout is far from standard. And I still haven’t figured out what the button in the lower right corner is supposed to mean!

But after a few evenings I got it all working. Here’s the full code of the “firmware”.

//mapping of keyboard matrix to controller pins int pin_map[23] = { 21,//yellow [1] 0,//orange 20,//red 1, //brown 19,//black 2, //white 18,//gray 3, //purple [8] 17,//blue [9] 4, //green 16,//yellow 5, //ornage 15,//red 6, //brown 14,//black 7, //white 13,//gray [17] 8, //purple //X//blue 12,//green 9, //yellow 11,//orange 10,//red 22 //brown }; //mapping of keyboard matrix to keys const int size_a = 8; const int size_b = 9; int key_codes[size_a][size_b] = { { KEY_PAUSE, KEY_7, 0x00, KEY_8, KEY_9, KEY_0, 0x01, 0x02, KEY_BACKSPACE }, { 0x00, KEY_6, 0x00, KEY_5, KEY_4, KEY_3, KEY_2, KEY_1, KEY_ESC }, { 0x00, KEY_U, 0x00, KEY_I, KEY_O, KEY_P, KEY_MINUS, KEY_EQUAL, KEY_RETURN }, { 0x00, KEY_Y, 0x00, KEY_T, KEY_R, KEY_E, KEY_W, KEY_Q, KEY_TAB }, { 0x00, 0x00, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, 0x03, 0x04, 0x00 }, { 0x00, 0x00, KEY_H, KEY_G, KEY_F, KEY_D, KEY_S, KEY_A, KEY_CAPS_LOCK}, { 0x00, KEY_N, KEY_SPACE, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_SLASH, 0x00, 0x00 }, { 0x00, 0x00, 0x00, KEY_B, KEY_V, KEY_C, KEY_X, KEY_Z, 0x00 }, }; //current state of keys int key_states[size_a][size_b]; //mapping of special keycodes+mods to US layout keycodes+mods typedef struct { int modifier; int key_code; } key_map_entry; typedef struct { key_map_entry in; key_map_entry out; } key_mapping; const int size_special = 20; key_mapping special[size_special] = { //TOP ROW {{MODIFIERKEY_SHIFT, KEY_2}, {MODIFIERKEY_SHIFT, KEY_QUOTE}}, {{MODIFIERKEY_SHIFT, KEY_6}, {MODIFIERKEY_SHIFT, KEY_7}}, {{MODIFIERKEY_SHIFT, KEY_7}, {0x0, KEY_QUOTE}}, {{MODIFIERKEY_SHIFT, KEY_8}, {MODIFIERKEY_SHIFT, KEY_2}}, {{0x0, 0x01}, {MODIFIERKEY_SHIFT, KEY_COMMA}}, {{0x0, 0x02}, {MODIFIERKEY_SHIFT, KEY_PERIOD}}, {{MODIFIERKEY_SHIFT, 0x01}, {0x0, KEY_DELETE}}, //there is no CLEAR key on windows keyboards {{MODIFIERKEY_SHIFT, 0x02}, {0x0, KEY_INSERT}}, {{MODIFIERKEY_SHIFT, KEY_BACKSPACE}, {0x0, KEY_DELETE}}, //CURSOR BLOCK {{MODIFIERKEY_CTRL, KEY_MINUS}, {0x0, KEY_UP}}, {{MODIFIERKEY_SHIFT, KEY_EQUAL}, {MODIFIERKEY_SHIFT, KEY_BACKSLASH}}, {{MODIFIERKEY_CTRL, KEY_EQUAL}, {0x0, KEY_DOWN}}, {{0x0, 0x03}, {MODIFIERKEY_SHIFT, KEY_EQUAL}}, {{MODIFIERKEY_SHIFT, 0x03}, {0x0, KEY_BACKSLASH}}, {{MODIFIERKEY_CTRL, 0x03}, {0x0, KEY_LEFT}}, {{0x0, 0x04}, {MODIFIERKEY_SHIFT, KEY_8}}, {{MODIFIERKEY_SHIFT, 0x04}, {MODIFIERKEY_SHIFT, KEY_6}}, {{MODIFIERKEY_CTRL, 0x04}, {0x0, KEY_RIGHT}}, {{MODIFIERKEY_SHIFT, KEY_COMMA}, {0x0, KEY_LEFT_BRACE}}, {{MODIFIERKEY_SHIFT, KEY_PERIOD}, {0x0, KEY_RIGHT_BRACE}} }; void initPins() { //init basic grid //yellow->purple for(int a = 0; a < size_a; a++) { int pin = pin_map[a]; pinMode(pin, OUTPUT); digitalWrite(pin, HIGH); }//blue-gray for(int b = 0; b < size_b; b++) pinMode(pin_map[size_a+b], INPUT_PULLUP); //special cluster } void resetStates() { for(int a = 0; a < size_a; a++) for(int b = 0; b < size_b; b++) key_states[a][b] = LOW; } void readStates() { for(int a = 0; a < size_a; a++) { int pinA = pin_map[a]; digitalWrite(pinA, LOW); for(int b = 0; b < size_b; b++) { int state = digitalRead(pin_map[size_a+b]); int prev = key_states[a][b]; if(state != prev) notifyChange(a, b, state); key_states[a][b] = state; } digitalWrite(pinA, HIGH); } delay(10); } void setKey(int kc, int modifier) { for(int i = 0; i < size_special; i++) if(kc == special[i].in.key_code && modifier == special[i].in.modifier) { kc = special[i].out.key_code; modifier = special[i].out.modifier; break; } Keyboard.set_key1(kc); Keyboard.set_modifier(modifier); Keyboard.send_now(); } void notifyChange(int a, int b, int state) { int modifier = 0; if(key_states[7][0] == LOW) modifier |= MODIFIERKEY_SHIFT; if(key_states[4][0] == LOW) modifier |= MODIFIERKEY_CTRL; if(key_states[6][7] == LOW) modifier |= MODIFIERKEY_GUI; int keyCode = key_codes[a][b]; if(keyCode != 0x00 && state == LOW) setKey(keyCode, modifier); else setKey(0, 0); } void setup() { initPins(); resetStates(); } void loop() { readStates(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 //mapping of keyboard matrix to controller pins int pin_map [ 23 ] = { 21 , //yellow [1] 0 , //orange 20 , //red 1 , //brown 19 , //black 2 , //white 18 , //gray 3 , //purple [8] 17 , //blue [9] 4 , //green 16 , //yellow 5 , //ornage 15 , //red 6 , //brown 14 , //black 7 , //white 13 , //gray [17] 8 , //purple //X//blue 12 , //green 9 , //yellow 11 , //orange 10 , //red 22 //brown } ; //mapping of keyboard matrix to keys const int size_a = 8 ; const int size_b = 9 ; int key_codes [ size_a ] [ size_b ] = { { KEY_PAUSE , KEY_7 , 0x00 , KEY_8 , KEY_9 , KEY_0 , 0x01 , 0x02 , KEY _ BACKSPACE } , { 0x00 , KEY_6 , 0x00 , KEY_5 , KEY_4 , KEY_3 , KEY_2 , KEY_1 , KEY _ ESC } , { 0x00 , KEY_U , 0x00 , KEY_I , KEY_O , KEY_P , KEY_MINUS , KEY_EQUAL , KEY _ RETURN } , { 0x00 , KEY_Y , 0x00 , KEY_T , KEY_R , KEY_E , KEY_W , KEY_Q , KEY _ TAB } , { 0x00 , 0x00 , KEY_J , KEY_K , KEY_L , KEY_SEMICOLON , 0x03 , 0x04 , 0x00 } , { 0x00 , 0x00 , KEY_H , KEY_G , KEY_F , KEY_D , KEY_S , KEY_A , KEY_CAPS_LOCK } , { 0x00 , KEY_N , KEY_SPACE , KEY_M , KEY_COMMA , KEY_PERIOD , KEY_SLASH , 0x00 , 0x00 } , { 0x00 , 0x00 , 0x00 , KEY_B , KEY_V , KEY_C , KEY_X , KEY_Z , 0x00 } , } ; //current state of keys int key_states [ size_a ] [ size_b ] ; //mapping of special keycodes+mods to US layout keycodes+mods typedef struct { int modifier ; int key_code ; } key_map_entry ; typedef struct { key_map _ entry in ; key_map _ entry out ; } key_mapping ; const int size_special = 20 ; key _ mapping special [ size_special ] = { //TOP ROW { { MODIFIERKEY_SHIFT , KEY_2 } , { MODIFIERKEY_SHIFT , KEY_QUOTE } } , { { MODIFIERKEY_SHIFT , KEY_6 } , { MODIFIERKEY_SHIFT , KEY_7 } } , { { MODIFIERKEY_SHIFT , KEY_7 } , { 0x0 , KEY_QUOTE } } , { { MODIFIERKEY_SHIFT , KEY_8 } , { MODIFIERKEY_SHIFT , KEY_2 } } , { { 0x0 , 0x01 } , { MODIFIERKEY_SHIFT , KEY_COMMA } } , { { 0x0 , 0x02 } , { MODIFIERKEY_SHIFT , KEY_PERIOD } } , { { MODIFIERKEY_SHIFT , 0x01 } , { 0x0 , KEY_DELETE } } , //there is no CLEAR key on windows keyboards { { MODIFIERKEY_SHIFT , 0x02 } , { 0x0 , KEY_INSERT } } , { { MODIFIERKEY_SHIFT , KEY_BACKSPACE } , { 0x0 , KEY_DELETE } } , //CURSOR BLOCK { { MODIFIERKEY_CTRL , KEY_MINUS } , { 0x0 , KEY_UP } } , { { MODIFIERKEY_SHIFT , KEY_EQUAL } , { MODIFIERKEY_SHIFT , KEY_BACKSLASH } } , { { MODIFIERKEY_CTRL , KEY_EQUAL } , { 0x0 , KEY_DOWN } } , { { 0x0 , 0x03 } , { MODIFIERKEY_SHIFT , KEY_EQUAL } } , { { MODIFIERKEY_SHIFT , 0x03 } , { 0x0 , KEY_BACKSLASH } } , { { MODIFIERKEY_CTRL , 0x03 } , { 0x0 , KEY_LEFT } } , { { 0x0 , 0x04 } , { MODIFIERKEY_SHIFT , KEY_8 } } , { { MODIFIERKEY_SHIFT , 0x04 } , { MODIFIERKEY_SHIFT , KEY_6 } } , { { MODIFIERKEY_CTRL , 0x04 } , { 0x0 , KEY_RIGHT } } , { { MODIFIERKEY_SHIFT , KEY_COMMA } , { 0x0 , KEY_LEFT_BRACE } } , { { MODIFIERKEY_SHIFT , KEY_PERIOD } , { 0x0 , KEY_RIGHT_BRACE } } } ; void initPins ( ) { //init basic grid //yellow->purple for ( int a = 0 ; a < size_a ; a ++ ) { int pin = pin_map [ a ] ; pinMode ( pin , OUTPUT ) ; digitalWrite ( pin , HIGH ) ; } //blue-gray for ( int b = 0 ; b < size_b ; b ++ ) pinMode ( pin_map [ size_a + b ] , INPUT_PULLUP ) ; //special cluster } void resetStates ( ) { for ( int a = 0 ; a < size_a ; a ++ ) for ( int b = 0 ; b < size_b ; b ++ ) key_states [ a ] [ b ] = LOW ; } void readStates ( ) { for ( int a = 0 ; a < size_a ; a ++ ) { int pinA = pin_map [ a ] ; digitalWrite ( pinA , LOW ) ; for ( int b = 0 ; b < size_b ; b ++ ) { int state = digitalRead ( pin_map [ size_a + b ] ) ; int prev = key_states [ a ] [ b ] ; if ( state != prev ) notifyChange ( a , b , state ) ; key_states [ a ] [ b ] = state ; } digitalWrite ( pinA , HIGH ) ; } delay ( 10 ) ; } void setKey ( int kc , int modifier ) { for ( int i = 0 ; i < size_special ; i ++ ) if ( kc == special [ i ] . in . key_code && modifier == special [ i ] . in . modifier ) { kc = special [ i ] . out . key_code ; modifier = special [ i ] . out . modifier ; break ; } Keyboard . set_key1 ( kc ) ; Keyboard . set_modifier ( modifier ) ; Keyboard . send_now ( ) ; } void notifyChange ( int a , int b , int state ) { int modifier = 0 ; if ( key_states [ 7 ] [ 0 ] == LOW ) modifier |= MODIFIERKEY_SHIFT ; if ( key_states [ 4 ] [ 0 ] == LOW ) modifier |= MODIFIERKEY_CTRL ; if ( key_states [ 6 ] [ 7 ] == LOW ) modifier |= MODIFIERKEY_GUI ; int keyCode = key_codes [ a ] [ b ] ; if ( keyCode != 0x00 && state == LOW ) setKey ( keyCode , modifier ) ; else setKey ( 0 , 0 ) ; } void setup ( ) { initPins ( ) ; resetStates ( ) ; } void loop ( ) { readStates ( ) ; }

The only major hurdle was that right from the start there was a short circuit. The keyboard behaved like the Escape key was being pressed all the time. But by carfully seperating the foil with the circuits and cleaning it in that area that problem was quickly resolved.

The bad news is that now that I got it working I know that it isn’t exactly what I went to the flea market for: a nice mechanical keyboard fit for every day use. I’ll never get used to that layout. And while the caps are beautiful and sturdy typing feels a little weird on it. So it’s a typical Pixelpracht project: fun but totally useless.