gigatron/rom/Utils/BabelFish/PS2.ino
2025-01-28 19:17:01 +03:00

711 lines
25 KiB
C++

/*
* PS/2 keyboard handling for Babelfish talking to Gigatron
* Complete rewrite of PS2Keyboard for game controller mapping
* and keymap compression to reduce footprint
*
* References:
*
* https://www.avrfreaks.net/sites/default/files/PS2%20Keyboard.pdf
* The PS/2 Mouse/Keyboard Protocol
*
* http://www.quadibloc.com/comp/scan.htm
* Scan Codes Demytified
*
* https://retired.beyondlogic.org/keyboard/keybrd.htm
* Interfacing the AT keyboard
*
* https://web.archive.org/web/20180101224739/http://www.computer-engineering.org/ps2keyboard/scancodes2.html
* Keyboard Scan Codes: Set 2
*
* https://github.com/PaulStoffregen/PS2Keyboard
* PS/2 Keyboard Library for Arduino
*
* https://github.com/techpaul/PS2KeyAdvanced
* Arduino PS2 Keyboard FULL keyboard protocol support and full keys to integer coding
*
* https://github.com/techpaul/PS2KeyMap/
* Arduino PS2 keyboard International Keyboard mapping from PS2KeyAdvanced and return as UTF-8
*
* https://www.terena.org/activities/multiling/ml-mua/test/kbd-all.html
* This page provides information about layouts of different national
* keyboard their mapping to used Character Sets and UCS
*
* https://github.com/ilpianista/itlinux-win-keyboard
* An Italian keyboard layout for Windows, customized with shortcuts from Linux systems
*/
// TODO
// [Basic features]
// XXX Test keymap for DE
// XXX Test keymap for FR
// XXX Test keymap for GB
// XXX Test keymap for ES
// XXX Issue: Sometimes keyboard starts injecting break codes
// when pressing [buttonA] + arrow keys, making it impossible
// to set the time in Mandelbrot??
// XXX (Left)Alt for buttonB?
//
// [Advanced features, for consideration]
// XXX Caps-Lock and setting LEDs
// XXX Do something with NumLock?
// XXX Key combination to reset keyboard?
// XXX Allow Gigatron to control the keyboard lights
// XXX Let keyboard lights run in a pattern (bad idea :-)
#define PS2_ENTER '\n'
#define PS2_TAB 9
#define PS2_BACKSPACE 127
#define PS2_ESC 27
#define PS2_F1 (0xc0 + 1)
#define PS2_F2 (0xc0 + 2)
#define PS2_F3 (0xc0 + 3)
#define PS2_F4 (0xc0 + 4)
#define PS2_F5 (0xc0 + 5)
#define PS2_F6 (0xc0 + 6)
#define PS2_F7 (0xc0 + 7)
#define PS2_F8 (0xc0 + 8)
#define PS2_F9 (0xc0 + 9)
#define PS2_F10 (0xc0 + 10)
#define PS2_F11 (0xc0 + 11)
#define PS2_F12 (0xc0 + 12)
#define CTRLALTDEL (255 ^ buttonStart)
/*
* Keyboard layout mapping
*/
const char keymapNames[][3] = {
"US", "GB", "DE", "FR", "IT", "ES",
};
const byte nrKeymaps = arrayLen(keymapNames);
int getKeymapIndex(void)
{
byte index = EEPROM.read(offsetof(struct EEPROMlayout, keymapIndex));
return (index >= nrKeymaps) ? 0 : index; // Also handle invalid values
}
const char *getKeymapName()
{
return getKeymapName(getKeymapIndex());
}
const char *getKeymapName(byte index)
{
return (index > arrayLen(keymapNames)) ? 0 : keymapNames[index];
}
enum {
US = 1 << 0,
GB = 1 << 1,
DE = 1 << 2,
FR = 1 << 3,
IT = 1 << 4,
ES = 1 << 5,
NOMOD = 0 << nrKeymaps,
SHIFT = 1 << nrKeymaps,
ALTGR = 2 << nrKeymaps,
EVERY = 3 << nrKeymaps,
};
typedef struct {
byte flags; // Change from byte to word if we add more keymaps
byte code;
byte ascii; // XXX Remove this if we add flags. Value can be inferred by lookup()
} keyTuple_t;
// Keymaps courtesy of
// Arduino PS2Keyboard library (US,DE,FR)
// https://playground.arduino.cc/Main/PS2Keyboard
// and
// Teensy PS2Keyboard library (GB,IT,ES)
// http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html
//
// 229 * 3 bytes = 687 bytes for holding 6 keyboard layouts
// To save space this table excludes codes that aren't in US-ASCII
// because these aren't in the Gigatron font either (yet).
// So accented letters and such are all absent. This is needed
// to make it all fit in the ATtiny85 configuration.
const PROGMEM keyTuple_t keymaps[] = {
{ +US+GB+DE+FR+IT+ES +EVERY, 0x0d, 9 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x5a, 10 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x76, 27 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x29, 32 },
{ +US+GB+DE +IT+ES +SHIFT, 0x16, 33 }, // '!'
{ +FR +NOMOD, 0x4a, 33 },
{ +GB+DE +IT+ES +SHIFT, 0x1e, 34 }, // '"'
{ +FR +NOMOD, 0x26, 34 },
{ +US +SHIFT, 0x52, 34 },
{ +US +SHIFT, 0x26, 35 }, // '#'
{ +FR +ES +ALTGR, 0x26, 35 },
{ +IT +ALTGR, 0x52, 35 },
{ +GB+DE +NOMOD, 0x5d, 35 },
{ +DE+FR +ALTGR, 0x5d, 35 },
{ +US+GB+DE +IT+ES +SHIFT, 0x25, 36 }, // '$'
{ +FR +NOMOD, 0x5b, 36 },
{ +US+GB+DE +IT+ES +SHIFT, 0x2e, 37 }, // '%'
{ +FR +SHIFT, 0x52, 37 },
{ +FR +NOMOD, 0x16, 38 }, // '&'
{ +DE +IT+ES +SHIFT, 0x36, 38 },
{ +US+GB +SHIFT, 0x3d, 38 },
{ +FR +NOMOD, 0x25, 39 }, // '''
{ +IT+ES +NOMOD, 0x4e, 39 },
{ +US+GB +NOMOD, 0x52, 39 },
{ +DE +NOMOD, 0x55, 39 },
{ +DE +SHIFT, 0x5d, 39 },
{ +FR +NOMOD, 0x2e, 40 }, // '('
{ +DE +IT+ES +SHIFT, 0x3e, 40 },
{ +US+GB +SHIFT, 0x46, 40 },
{ +US+GB +SHIFT, 0x45, 41 }, // ')'
{ +DE +IT+ES +SHIFT, 0x46, 41 },
{ +FR +NOMOD, 0x4e, 41 },
{ +US+GB +SHIFT, 0x3e, 42 }, // '*'
{ +DE +IT+ES +SHIFT, 0x5b, 42 },
{ +FR +NOMOD, 0x5d, 42 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x7c, 42 },
{ +US+GB +FR +SHIFT, 0x55, 43 }, // '+'
{ +DE +IT+ES +NOMOD, 0x5b, 43 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x79, 43 },
{ +FR +NOMOD, 0x3a, 44 }, // ','
{ +US+GB+DE +IT+ES +NOMOD, 0x41, 44 },
{ +FR +NOMOD, 0x36, 45 }, // '-'
{ +DE +IT+ES +NOMOD, 0x4a, 45 },
{ +US+GB +NOMOD, 0x4e, 45 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x7b, 45 },
{ +FR +SHIFT, 0x41, 46 }, // '.'
{ +US+GB+DE +IT+ES +NOMOD, 0x49, 46 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x71, 46 },
{ +DE +IT+ES +SHIFT, 0x3d, 47 }, // '/'
{ +FR +SHIFT, 0x49, 47 },
{ +US+GB +NOMOD, 0x4a, 47 },
{ +US+GB+DE +IT+ES +NOMOD, 0x45, 48 }, // '0'
{ +FR +SHIFT, 0x45, 48 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x70, 48 },
{ +US+GB+DE +IT+ES +NOMOD, 0x16, 49 }, // '1'
{ +FR +SHIFT, 0x16, 49 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x69, 49 },
{ +US+GB+DE +IT+ES +NOMOD, 0x1e, 50 }, // '2'
{ +FR +SHIFT, 0x1e, 50 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x72, 50 },
{ +US+GB+DE +IT+ES +NOMOD, 0x26, 51 }, // '3'
{ +FR +SHIFT, 0x26, 51 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x7a, 51 },
{ +US+GB+DE +IT+ES +NOMOD, 0x25, 52 }, // '4'
{ +FR +SHIFT, 0x25, 52 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x6b, 52 },
{ +US+GB+DE +IT+ES +NOMOD, 0x2e, 53 }, // '5'
{ +FR +SHIFT, 0x2e, 53 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x73, 53 },
{ +US+GB+DE +IT+ES +NOMOD, 0x36, 54 }, // '6'
{ +FR +SHIFT, 0x36, 54 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x74, 54 },
{ +US+GB+DE +IT+ES +NOMOD, 0x3d, 55 }, // '7'
{ +FR +SHIFT, 0x3d, 55 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x6c, 55 },
{ +US+GB+DE +IT+ES +NOMOD, 0x3e, 56 }, // '8'
{ +FR +SHIFT, 0x3e, 56 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x75, 56 },
{ +US+GB+DE +IT+ES +NOMOD, 0x46, 57 }, // '9'
{ +FR +SHIFT, 0x46, 57 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x7d, 57 },
{ +FR +NOMOD, 0x49, 58 }, // ':'
{ +DE +IT+ES +SHIFT, 0x49, 58 },
{ +US+GB +SHIFT, 0x4c, 58 },
{ +FR +NOMOD, 0x41, 59 }, // ';'
{ +DE +IT+ES +SHIFT, 0x41, 59 },
{ +US+GB +NOMOD, 0x4c, 59 },
{ +US+GB +SHIFT, 0x41, 60 }, // '<'
{ +DE+FR+IT+ES +NOMOD, 0x61, 60 },
{ +DE +IT+ES +SHIFT, 0x45, 61 }, // '='
{ +US+GB +FR +NOMOD, 0x55, 61 },
{ +US+GB +SHIFT, 0x49, 62 }, // '>'
{ +DE+FR+IT+ES +SHIFT, 0x61, 62 },
{ +FR +SHIFT, 0x3a, 63 }, // '?'
{ +US+GB +SHIFT, 0x4a, 63 },
{ +DE +IT+ES +SHIFT, 0x4e, 63 },
{ +DE+FR +ALTGR, 0x15, 64 }, // '@'
{ +US +SHIFT, 0x1e, 64 },
{ +ES +ALTGR, 0x1e, 64 },
{ +FR +ALTGR, 0x45, 64 },
{ +IT +ALTGR, 0x4c, 64 },
{ +GB +SHIFT, 0x52, 64 },
{ +FR +SHIFT, 0x15, 65 }, // 'A'
{ +US+GB+DE +IT+ES +SHIFT, 0x1c, 65 },
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x32, 66 }, // 'B'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x21, 67 }, // 'C'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x23, 68 }, // 'D'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x24, 69 }, // 'E'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x2b, 70 }, // 'F'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x34, 71 }, // 'G'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x33, 72 }, // 'H'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x43, 73 }, // 'I'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x3b, 74 }, // 'J'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x42, 75 }, // 'K'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x4b, 76 }, // 'L'
{ +US+GB+DE +IT+ES +SHIFT, 0x3a, 77 }, // 'M'
{ +FR +SHIFT, 0x4c, 77 },
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x31, 78 }, // 'N'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x44, 79 }, // 'O'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x4d, 80 }, // 'P'
{ +US+GB+DE +IT+ES +SHIFT, 0x15, 81 }, // 'Q'
{ +FR +SHIFT, 0x1c, 81 },
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x2d, 82 }, // 'R'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x1b, 83 }, // 'S'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x2c, 84 }, // 'T'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x3c, 85 }, // 'U'
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x2a, 86 }, // 'V'
{ +FR +SHIFT, 0x1a, 87 }, // 'W'
{ +US+GB+DE +IT+ES +SHIFT, 0x1d, 87 },
{ +US+GB+DE+FR+IT+ES +SHIFT, 0x22, 88 }, // 'X'
{ +DE +SHIFT, 0x1a, 89 }, // 'Y'
{ +US+GB +FR+IT+ES +SHIFT, 0x35, 89 },
{ +US+GB +IT+ES +SHIFT, 0x1a, 90 }, // 'Z'
{ +FR +SHIFT, 0x1d, 90 },
{ +DE +SHIFT, 0x35, 90 },
{ +FR +ALTGR, 0x2e, 91 }, // '['
{ +DE +IT +ALTGR, 0x3e, 91 },
{ +US+GB +NOMOD, 0x54, 91 },
{ +IT+ES +ALTGR, 0x54, 91 },
{ +IT +NOMOD, 0x0e, 92 }, // '\'
{ +ES +ALTGR, 0x0e, 92 },
{ +FR +ALTGR, 0x3e, 92 },
{ +DE +ALTGR, 0x4e, 92 },
{ +US +NOMOD, 0x5d, 92 },
{ +GB +NOMOD, 0x61, 92 },
{ +DE +IT +ALTGR, 0x46, 93 }, // ']'
{ +FR +ALTGR, 0x4e, 93 },
{ +US+GB +NOMOD, 0x5b, 93 },
{ +IT+ES +ALTGR, 0x5b, 93 },
{ +DE +NOMOD, 0x0e, 94 }, // '^'
{ +US+GB +SHIFT, 0x36, 94 },
{ +FR +ALTGR, 0x46, 94 },
{ +FR +NOMOD, 0x54, 94 },
{ +ES +SHIFT, 0x54, 94 },
{ +IT +SHIFT, 0x55, 94 },
{ +FR +NOMOD, 0x3e, 95 }, // '_'
{ +DE +IT+ES +SHIFT, 0x4a, 95 },
{ +US+GB +SHIFT, 0x4e, 95 },
{ +US+GB +NOMOD, 0x0e, 96 }, // '`'
{ +FR +ALTGR, 0x3d, 96 },
{ +IT +ALTGR, 0x4e, 96 },
{ +ES +NOMOD, 0x54, 96 },
{ +DE +SHIFT, 0x55, 96 },
{ +FR +NOMOD, 0x15, 97 }, // 'a'
{ +US+GB+DE +IT+ES +NOMOD, 0x1c, 97 },
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x32, 98 }, // 'b'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x21, 99 }, // 'c'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x23, 100 }, // 'd'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x24, 101 }, // 'e'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x2b, 102 }, // 'f'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x34, 103 }, // 'g'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x33, 104 }, // 'h'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x43, 105 }, // 'i'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x3b, 106 }, // 'j'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x42, 107 }, // 'k'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x4b, 108 }, // 'l'
{ +US+GB+DE +IT+ES +NOMOD, 0x3a, 109 }, // 'm'
{ +FR +NOMOD, 0x4c, 109 },
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x31, 110 }, // 'n'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x44, 111 }, // 'o'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x4d, 112 }, // 'p'
{ +US+GB+DE +IT+ES +NOMOD, 0x15, 113 }, // 'q'
{ +FR +NOMOD, 0x1c, 113 },
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x2d, 114 }, // 'r'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x1b, 115 }, // 's'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x2c, 116 }, // 't'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x3c, 117 }, // 'u'
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x2a, 118 }, // 'v'
{ +FR +NOMOD, 0x1a, 119 }, // 'w'
{ +US+GB+DE +IT+ES +NOMOD, 0x1d, 119 },
{ +US+GB+DE+FR+IT+ES +NOMOD, 0x22, 120 }, // 'x'
{ +DE +NOMOD, 0x1a, 121 }, // 'y'
{ +US+GB +FR+IT+ES +NOMOD, 0x35, 121 },
{ +US+GB +IT+ES +NOMOD, 0x1a, 122 }, // 'z'
{ +FR +NOMOD, 0x1d, 122 },
{ +DE +NOMOD, 0x35, 122 },
{ +FR +ALTGR, 0x25, 123 }, // '{'
{ +DE +IT +ALTGR, 0x3d, 123 },
{ +ES +ALTGR, 0x52, 123 },
{ +US+GB +SHIFT, 0x54, 123 },
{ +IT +SHIFT, 0x0e, 124 }, // '|'
{ +ES +ALTGR, 0x16, 124 },
{ +FR +ALTGR, 0x36, 124 },
{ +US +SHIFT, 0x5d, 124 },
{ +GB +SHIFT, 0x61, 124 },
{ +DE+FR +ES +ALTGR, 0x61, 124 },
{ +DE +IT +ALTGR, 0x45, 125 }, // '}'
{ +FR +ALTGR, 0x56, 125 },
{ +US+GB +SHIFT, 0x5b, 125 },
{ +ES +ALTGR, 0x5d, 125 },
{ +US +SHIFT, 0x0e, 126 }, // '~'
{ +FR +ALTGR, 0x1e, 126 },
{ +ES +ALTGR, 0x25, 126 },
{ +IT +ALTGR, 0x55, 126 },
{ +DE +ALTGR, 0x5b, 126 },
{ +GB +SHIFT, 0x5d, 126 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x66, 127 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x05, 193 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x06, 194 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x04, 195 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x0c, 196 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x03, 197 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x0b, 198 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x83, 199 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x0a, 200 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x01, 201 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x09, 202 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x78, 203 },
{ +US+GB+DE+FR+IT+ES +EVERY, 0x07, 204 },
};
/*
* Find ASCII code corresponding to key code and active modifiers
* for the current keymap
*/
static inline byte lookup(byte mods, word code)
{
byte keymapFlag = 1 << getKeymapIndex();
for (int i=0; i<arrayLen(keymaps); i++) {
const keyTuple_t *t = &keymaps[i];
byte f = pgm_read_byte(&t->flags);
if (~f & keymapFlag)
continue; // Different keymap
if ((f & EVERY) != EVERY && (f & EVERY) != mods)
continue; // Different modifiers
byte c = pgm_read_byte(&t->code);
if (c != code)
continue; // Different scan code
return pgm_read_byte(&t->ascii);
}
return 0;
}
/*
* Output state
*/
enum {
// bit 0:7 Equivalent game controller buttons (positive)
// buttonRight .. buttonA
// bit 8:13 State of modifier keys
leftShiftFlag = 1<<8,
rightShiftFlag = 1<<9,
leftCtrlFlag = 1<<10,
rightCtrlFlag = 1<<11,
altFlag = 1<<12,
altGrFlag = 1<<13,
shiftFlags = leftShiftFlag | rightShiftFlag,
ctrlFlags = leftCtrlFlag | rightCtrlFlag,
altFlags = altFlag | altGrFlag,
// bit 14:15 Protocol state
breakFlag = 1<<14, // Release of physical key
extendedFlag = 1<<15, // Extended PS/2 keyboard codes
};
#define nextPs2()\
flags &= ~(breakFlag | extendedFlag)
static word flags = 0; // Modifier keys and button state
static byte ascii = 0; // Current ASCII key press
static word lastChange, hold; // Babelfish controls the hold time for keys
// XXX Put variables in a class for better scoping
/*
* PS/2 sequences
*/
static int bitBuffer;
static byte _n;
static word lastClock;
/*
* Input buffering
*/
static volatile byte ps2Buffer[15+1];
static volatile byte head = 0, tail = 0;
#if defined(ARDUINO_attiny)
// attachInterrupt() doesn't work on the ATtiny85
static byte keyboardClockBit;
ISR(PCINT0_vect)
{
if (~PINB & keyboardClockBit) // FALLING edge of PS/2 clock
ps2interrupt();
}
#endif
void keyboard_setup()
{
// Initialize the pins
pinMode(keyboardDataPin, INPUT_PULLUP);
allowPs2();
#if defined(ARDUINO_attiny)
keyboardClockBit = digitalPinToBitMask(keyboardClockPin);
GIMSK |= 1<<PCIE; // Pin change interrupt enable
PCMSK |= keyboardClockBit; // ... for keyboard clock
#else
attachInterrupt(
digitalPinToInterrupt(keyboardClockPin),
ps2interrupt, FALLING);
#endif
}
// Handle one bit from PS/2 keyboard for the next byte
// bit 0 : start bit (0)
// bit 1..8 : data bits 0..7 (lowest bit first)
// bit 9 : parity (odd)
// bit 10 : stop bit (1)
void ps2interrupt()
{
byte nextBit = digitalRead(keyboardDataPin);
word now = (word) millis(); // millis() stops when interrupts are disabled,
// but that is ok, because we will reset n
// after every vPulse (through allowPs2())
// At least 10 kHz required for PS/2, self-reset after time out
if (now - lastClock > 5 || _n >= 11) {
bitBuffer = 1; // Clear bits and setup for odd parity
_n = 0;
}
lastClock = now;
bitBuffer |= nextBit << _n;
bitBuffer ^= nextBit; // Track parity in bit 0
if (_n == 10) {
ps2Buffer[head] = (bitBuffer >> 1) & 255;
byte nextHead = (head + 1) % sizeof ps2Buffer;
if (nextHead != tail // Buffer not (almost) full
&& (bitBuffer & 1)) // Parity check, with bonus check of start/stop bits
head = nextHead;
}
_n++;
}
// Ready to receive
void allowPs2()
{
pinMode(keyboardClockPin, INPUT_PULLUP);
_n = 11; // Consider previous bits lost
}
// Pull clock line LOW to signal that we're not ready to receive
void forbidPs2()
{
digitalWrite(keyboardClockPin, LOW);
pinMode(keyboardClockPin, OUTPUT);
}
// Read next byte from incoming PS/2 buffer, or 0 when empty
static byte readPs2Buffer()
{
byte value = 0;
if (head != tail) {
value = ps2Buffer[tail];
tail = (tail + 1) % sizeof ps2Buffer;
}
return value;
}
byte keyboard_getState()
{
word now = (word) millis(); // Note: without interrupts millis() stops counting
for (;;) {
word value = readPs2Buffer();
if (value == 0) { // Buffer is empty
if (now - lastChange > hold) // Auto-release keys after `hold' milliseconds
ascii = 0;
if (now - lastChange > 1000) // Hold `buttons' a bit longer than the repeat delay. This
flags &= ~255; // is merely a safeguard against missing the break event
// and then ending up with a stuck virtual button press.
break;
}
switch (value) {
// The BREAK or EXTENDED bytes are part of a longer sequence
// Here we don't mind the order they appear in
case 0xe0: flags |= extendedFlag; continue;
case 0xf0: flags |= breakFlag; continue;
case 0xaa: // BAT ok
#define oddParity(x) ((1 ^ x ^ (x>>1) ^ (x>>2) ^ (x>>3) ^ (x>>4) ^ (x>>5) ^ (x>>6) ^ (x>>7)) & 1)
#define code(x) ((1 << 10) | (oddParity(x) << 9) | ((x) << 1))
keyboard_send(code(0xf4)); // Enable (fixes Walter's PERIBOARD-409)
continue;
}
if (flags & extendedFlag)
value |= 0xe0 << 8; // Simplify our switch statements
// Recognize modifier keys
word modifier = 0;
switch (value) {
case 0x11: modifier = altFlag; break;
case 0x12: modifier = leftShiftFlag; break;
case 0x14: modifier = leftCtrlFlag; break;
case 0x59: modifier = rightShiftFlag; break;
case 0xe011: modifier = altGrFlag; break;
case 0xe014: modifier = rightCtrlFlag; break;
}
// Case 1: Handle modifier keys
if (modifier) {
if (flags & breakFlag)
flags &= ~modifier; // Modifier key released
else
flags |= modifier; // Modifier key pressed
nextPs2(); // Reset PS/2 sequence
continue; // Back to reading from buffer
}
// Recognize character and control keys and map them to ASCII
// or game controller buttons
// Always return an updated state to the caller from this point on,
// even if there are still bytes waiting in the buffer.
byte button = 0;
byte newAscii = 0;
switch (value) {
// Extended codes in "Set 2"
case 0xe075: button = buttonUp; break; // [UpArrow]
case 0xe06b: button = buttonLeft; break; // [LeftArrow]
case 0xe072: button = buttonDown; break; // [DownArrow]
case 0xe074: button = buttonRight; break; // [RightArrow]
case 0x66: // [BackSpace]
case 0xe071: if ((flags & ctrlFlags) // [Delete]
&& (flags & altFlags))
newAscii = CTRLALTDEL; // [Ctrl-Alt-Del] becomes [Start]
else
button = buttonA; break; // ASCII DEL is 127 is ~buttonA
case 0xe069: button = buttonA; break; // [End]
case 0xe070: // [Insert]
case 0xe06c: button = buttonB; break; // [Home]
case 0xe07a: button = buttonSelect; break; // [PageDown]
case 0xe07d: button = buttonStart; break; // [PageUp]
case 0xe04a: newAscii = '/'; break; // [/] on numeric island
case 0xe05a: newAscii = '\n'; break; // [Enter] on numeric island
default:
// Apply keyboard mapping to ASCII
if ((flags & altGrFlag) ||
((flags & ctrlFlags) && (flags & altFlag))) // Ctrl+Alt is AltGr
newAscii = lookup(ALTGR, value);
else if (flags & shiftFlags)
newAscii = lookup(SHIFT, value);
else
newAscii = lookup(NOMOD, value);
// Handle control key combinations
if (flags & ctrlFlags)
switch (newAscii) {
case '?': newAscii = 127; break; // Traditional mapping of Ctrl-? to DEL
case ' ': button = 255; break; // Make it send a 0 byte
default:
byte f = fnKey(newAscii);
if (f) {
if (flags & altFlags)
// Ctrl-Alt-Fxx changes the keymap
EEPROM.write(offsetof(struct EEPROMlayout, keymapIndex), f-1);
else
// Ctrl+Fxx are BabelFish commands
newAscii ^= 64;
} else
// Make control codes (what the key is for...)
newAscii &= 31;
}
}
// Consolidate the `flags' and `ascii' states
if (button) { // Case 2: Simulated game controller buttons
lastChange = now;
if (flags & breakFlag)
flags &= ~button; // Button released
else
flags |= button; // Button pressed
ascii = 0;
} else { // Case 3: ASCII keys
if (newAscii != ascii)
lastChange = now;
if (~flags & breakFlag) // Ignore ASCII release events (use 'hold' instead)
ascii = newAscii; // ASCII pressed
hold = 35; // Hold for 3 frames (less than the repeat rate)
if (ascii == CTRLALTDEL) // At least 2 seconds for the [Start] signal
hold = max(500 + now - lastChange, 2500); // No accidental Easter Egg trigger (8b guy)
flags &= ~255;
}
nextPs2(); // Reset PS/2 sequence
break; // Always return, leave anything in buffer behind
}
// Return the newly determined state to caller
return ascii ? ascii : (255 & ~flags);
}
// Return 0 if not a function key, or its ordinal otherwise
byte fnKey(byte key)
{
return (PS2_F1 <= key && key <= PS2_F12) ? (key & 15) : 0;
}
// 'code' must include start, data, parity and stop bits
void keyboard_send(word code)
{
// 1. Bring the Clock line low for at least 100 microseconds
forbidPs2();
delayMicroseconds(100);
// 2. Bring the data line low
digitalWrite(keyboardDataPin, 0);
pinMode(keyboardDataPin, OUTPUT);
// 3. Release the Clock line
allowPs2();
// Pull-up resistor will bring clock line up first
do {
// 6. Wait for the device to bring Clock high
while (digitalRead(keyboardClockPin) == 0)
;
// 4. 7. Wait for the device to bring the Clock line low
// MvK: Falling edge transfers the data
while (digitalRead(keyboardClockPin) != 0)
;
// 5. Set/reset the Data line to send the next data bit
code >>= 1;
if (code & 1)
pinMode(keyboardDataPin, INPUT_PULLUP); // To send 1
else {
digitalWrite(keyboardDataPin, 0);
pinMode(keyboardDataPin, OUTPUT); // To send 0
}
// 8. Repeat steps 5-7 for the other seven data bits and the parity bit
// 9. Release the Data line
} while (code > 1);
pinMode(keyboardDataPin, INPUT_PULLUP);
// 10. Wait for the device to bring Data low
while (digitalRead(keyboardDataPin) != 0)
;
// 11. Wait for the device to bring Clock low
while (digitalRead(keyboardClockPin) != 0)
;
// 12. Wait for the device to release Data and Clock
while (digitalRead(keyboardClockPin) == 0 || digitalRead(keyboardDataPin) == 0)
;
}