Check Out my (Atari Key-)Pad, Part 3: Making it Work September 11, 2013
Note: for some reason, something in the combination of the code listings in this post, the syntax highlighter I’m using (Crayon), and my theme causes rendering problems in some browsers. I apologize if parts are difficult to read. I’m looking into it.
To recap: In part 1, I figured out how the CX85 keypad works and how it’s wired; in part 2, I made a change to the Arduino’s HID library that will give me more control over the keypad emulation. Now, to write a sketch to drive it!
Hooking the keypad up to an Arduino Leonardo was trivially easy. I used a male DB9 on a ribbon cable, the serial port from an old computer. A simple breakout board (the most complex part of the setup) just widens the 2×5 header connector enough to fit across the gap of a breadboard, DIP style. I wired it to the Arduino somewhat out of order:
DB9 Pin | Name | Arduino Pin |
---|---|---|
1 | OUTA |
3 |
2 | OUTB |
6 |
3 | OUTC |
4 |
4 | OUTD |
7 |
5 | OUTE |
5 |
6 | DATA_AV |
2 |
7 | VCC |
+5V |
8 | GND |
GND |
9 | (No connection) |
This order was simply the tidiest layout on my little breadboard; I didn’t bother getting the OUT*
pins in sequence. The only critical pin is DATA_AV
, attached to Arduino pin 2; an interrupt handler listening to changes on that pin does all the work. It’s worth mentioning that pins 3-5 are all part of the same port on an Arduino Uno (specifically, PORTD
), which I used for my initial experiments. The Leonardo uses a different microcontroller with a weirder layout of ports, so I’m just reading the inputs with old digitalRead()
, but the consecutive pins are still useful for iterating.
The Arduino sketch itself is moderately simple. As I mentioned earlier, it uses an interrupt handler (handleKeypress()
)attached to pin 2 to actually handles the key presses on the keypad and send the corresponding USB keyboard scan codes. Note that the values sent as the first argument to sendKey()
are the codes that the keyboard uses to represent the keys, not the characters.
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 |
/* Atari CX85 Keypad to USB David Stokes, 2013 http://www.logicalzero.com/blog/?cat=59 */ // The keypad's output for each of its keys const byte KEYPAD_DEL = B00100; const byte KEYPAD_YES = B00101; const byte KEYPAD_5 = B00110; const byte KEYPAD_2 = B00111; const byte KEYPAD_ESC = B01001; const byte KEYPAD_NO = B01100; const byte KEYPAD_0 = B01101; const byte KEYPAD_8 = B01110; const byte KEYPAD_PLUSENTER = B01111; const byte KEYPAD_4 = B10100; const byte KEYPAD_1 = B10101; const byte KEYPAD_6 = B10110; const byte KEYPAD_3 = B10111; const byte KEYPAD_7 = B11100; const byte KEYPAD_DOT = B11101; const byte KEYPAD_9 = B11110; const byte KEYPAD_MINUS = B11111; static byte key; void handleKeypress() { boolean d = digitalRead(2); // Emergency escape. If pin 13 is LOW, output is inhibited. if (!digitalRead(13)) return; if (!d) { // Pressed! // Read the key pressed. key = 0; for (byte i=3; i<8; i++) key = (key << 1) | digitalRead(i); switch (key) { // Standard keys: map directly to modern keys case KEYPAD_MINUS: sendKey(0x56, 0); break; case KEYPAD_PLUSENTER: // The keypad "Plus" key is 0x57, "Enter" key is 0x58 // Enter is probably more useful. sendKey(0x58, 0); break; case KEYPAD_1: sendKey(0x59, 0); break; case KEYPAD_2: sendKey(0x5A, 0); break; case KEYPAD_3: sendKey(0x5B, 0); break; case KEYPAD_4: sendKey(0x5C, 0); break; case KEYPAD_5: sendKey(0x5D, 0); break; case KEYPAD_6: sendKey(0x5E, 0); break; case KEYPAD_7: sendKey(0x5F, 0); break; case KEYPAD_8: sendKey(0x60, 0); break; case KEYPAD_9: sendKey(0x61, 0); break; case KEYPAD_0: sendKey(0x62, 0); break; case KEYPAD_DOT: sendKey(0x63, 0); break; // Non-standard keys, arbitrarily mapped. case KEYPAD_DEL: // Send "Del" (from the cluster above the arrow keys) sendKey(0x4C, 0); break; case KEYPAD_YES: // Send multimedia keyboard "Volume Up" sendKey(0x80, 0); break; case KEYPAD_NO: // Send multimedia keyboard "Volume Down" sendKey(0x81, 0); break; case KEYPAD_ESC: // Sends "Play/Pause" key (semi-standard) // BUG: This doesn't actually work; this seem to be 'special' and may // involve multiple bytes. sendKey(0xE8, 0); break; } } else { // Key released. If the user has pressed more than one key, the keypad // only reports the ID of the last one, so releaseKeys() releases all // keys, not just the one identified by OUT*. releaseKeys(); } key = 0; } void setup(){ // The keypad's DATA_AV and OUT* should be attached to pins 2-7. for (byte i=2; i<8; i++) pinMode(i, INPUT); // Pin 13 is used as a safety mechanism. If LOW, keypad output is disabled, // just in case a bug causes the Arduino to freak out. This can be safely // removed later. pinMode(13, INPUT_PULLUP); // Keypad's DATA_AV is on pin 2, which corresponds to interrupt 1 on the // Arduino Leonardo. attachInterrupt(1, handleKeypress, CHANGE); } void loop() { // This space intentionally left blank; the interrupt handler does all // the real work. } // sendKey() was largely borrowed from: // https://github.com/una1veritas/Arduino/tree/master/HID_Keyboard_Advanced void sendKey(byte key, byte modifiers) { KeyReport report = { 0 }; // Create an empty KeyReport report.keys[0] = key; // set the KeyReport to key report.modifiers = modifiers; // set the KeyReport's modifiers report.reserved = 1; Keyboard.sendReport(&report); // send the KeyReport } // releaseKeys() was separated from sendKey() so that things like auto-repeat // still work. void releaseKeys() { KeyReport report = { 0 }; // Create an empty KeyReport report.reserved = 0; Keyboard.sendReport(&report); // send the empty key report } |
What’s next? On the hardware side:
- Use a smaller microcontroller board, one that can fit inside the keypad’s housing. The 9-pin cable is attached by a form of spade connector, so if I can find similar connectors, the entire operation can be done non-destructively.
- Use an entirely different microcontroller, one without the Arduino bootloader. The Arduino Leonardo was great for prototyping, but it is much larger and more powerful than I need — and, at $20, more expensive. I could use a microcontroller like AT90USB82 with LUFA. Since all I really need are eight GPIO pins (two for USB, six for reading the keypad), and the device doesn’t have to respond much faster than its human operator, I could use an even smaller/cheaper microcontroller with software USB emulation.
On the software side:
- Tidy up the sketch. The code isn’t the cleanest. Seeing it here, I can spot a couple of things I’d have done slightly differently, but these are moderately trivial. Better to post code I know works.
- Get the ‘Play/Pause’ key to work. Maybe this is just a Mac thing, but I’ve seen a couple of things that imply that some of the farthest ‘multimedia’ keys function somewhat differently than the normal ones.
- Rewrite for a completely different framework, like LUFA. This would make at least the first point moot.
If/when I continue this project, I’ll continue posting my progress on the blog!
Leave a Reply
You must be logged in to post a comment.