File size: 7,903 Bytes
d8afd6f 8d577f5 d8afd6f | 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 | // PS/2 keyboard (and mouse) device models attached to a pair of GPIO pads (open-drain bus with pull-ups)
const SET2 = {
// key code (KeyboardEvent.code) -> [scancode bytes (make)], extended flag handled via 0xE0 prefix
Escape: [0x76], F1: [0x05], F2: [0x06], F3: [0x04], F4: [0x0C], F5: [0x03], F6: [0x0B], F7: [0x83], F8: [0x0A], F9: [0x01], F10: [0x09], F11: [0x78], F12: [0x07],
Backquote: [0x0E], Digit1: [0x16], Digit2: [0x1E], Digit3: [0x26], Digit4: [0x25], Digit5: [0x2E], Digit6: [0x36], Digit7: [0x3D], Digit8: [0x3E], Digit9: [0x46], Digit0: [0x45],
Minus: [0x4E], Equal: [0x55], Backspace: [0x66], Tab: [0x0D],
KeyQ: [0x15], KeyW: [0x1D], KeyE: [0x24], KeyR: [0x2D], KeyT: [0x2C], KeyY: [0x35], KeyU: [0x3C], KeyI: [0x43], KeyO: [0x44], KeyP: [0x4D], BracketLeft: [0x54], BracketRight: [0x5B], Backslash: [0x5D],
CapsLock: [0x58], KeyA: [0x1C], KeyS: [0x1B], KeyD: [0x23], KeyF: [0x2B], KeyG: [0x34], KeyH: [0x33], KeyJ: [0x3B], KeyK: [0x42], KeyL: [0x4B], Semicolon: [0x4C], Quote: [0x52], Enter: [0x5A],
ShiftLeft: [0x12], KeyZ: [0x1A], KeyX: [0x22], KeyC: [0x21], KeyV: [0x2A], KeyB: [0x32], KeyN: [0x31], KeyM: [0x3A], Comma: [0x41], Period: [0x49], Slash: [0x4A], ShiftRight: [0x59],
ControlLeft: [0x14], AltLeft: [0x11], Space: [0x29], ControlRight: [0xE0, 0x14], AltRight: [0xE0, 0x11], MetaLeft: [0xE0, 0x1F], MetaRight: [0xE0, 0x27], ContextMenu: [0xE0, 0x2F],
Insert: [0xE0, 0x70], Delete: [0xE0, 0x71], Home: [0xE0, 0x6C], End: [0xE0, 0x69], PageUp: [0xE0, 0x7D], PageDown: [0xE0, 0x7A],
ArrowUp: [0xE0, 0x75], ArrowDown: [0xE0, 0x72], ArrowLeft: [0xE0, 0x6B], ArrowRight: [0xE0, 0x74],
NumLock: [0x77], ScrollLock: [0x7E], NumpadDivide: [0xE0, 0x4A], NumpadMultiply: [0x7C], NumpadSubtract: [0x7B], NumpadAdd: [0x79], NumpadEnter: [0xE0, 0x5A], NumpadDecimal: [0x71],
Numpad0: [0x70], Numpad1: [0x69], Numpad2: [0x72], Numpad3: [0x7A], Numpad4: [0x6B], Numpad5: [0x73], Numpad6: [0x74], Numpad7: [0x6C], Numpad8: [0x75], Numpad9: [0x7D],
IntlBackslash: [0x61], PrintScreen: [0xE0, 0x12, 0xE0, 0x7C],
};
export { SET2 };
export class Ps2Device {
constructor(soc, clkPad, datPad, kind = 'keyboard') {
this.soc = soc; this.gpio = soc.gpio; this.clk = clkPad; this.dat = datPad; this.kind = kind;
this.txq = []; this.state = 'idle'; this.half = 40000; this.pending = null;
this.gpio.setPullup(clkPad, true); this.gpio.setPullup(datPad, true);
this.lastClk = 1; this.clkFallNs = 0; this.enabled = false; this.scanning = false; this.pendingCmd = 0; this.sendTimer = null;
this.gpio.listeners.push((n, lvl) => { if (n === this.clk) this.onClk(lvl); else if (n === this.dat) this.onDat(lvl); });
this.mouseBuf = [];
}
reset() { this.txq.length = 0; this.state = 'idle'; this.scanning = false; this.pendingCmd = 0; this.release(); }
release() { this.gpio.setExt(this.clk, false); this.gpio.setExt(this.dat, false); }
now() { return this.soc.nowNs; }
// ---- host events ----
onClk(lvl) {
const t = this.now();
if (this.driving) { return; }
if (lvl === 0) this.clkFallNs = t;
else {
// host released CLK: if DATA is low and CLK was held >= 60us -> host wants to send
if (this.gpio.level(this.dat) === 0 && this.state === 'idle') { // host released CLK while holding DATA low: request-to-send
this.startReceive();
} else if (this.state === 'idle') this.scheduleTx(200000);
}
this.lastClk = lvl;
}
onDat(lvl) {}
// ---- device -> host ----
send(bytes, delayNs = 1000000) {
for (const b of bytes) this.txq.push(b);
this.scheduleTx(delayNs);
}
scheduleTx(delayNs) {
if (this.sendTimer) return;
this.sendTimer = this.soc.scheduleIn(delayNs, () => { this.sendTimer = null; this.tryTx(); });
}
tryTx() {
if (this.state !== 'idle' || !this.txq.length) return;
if (this.gpio.level(this.clk) === 0 || this.gpio.level(this.dat) === 0) { this.scheduleTx(300000); return; } // bus busy / inhibited
const b = this.txq[0];
let par = 1; for (let i = 0; i < 8; i++) par ^= (b >> i) & 1;
const bits = [0]; for (let i = 0; i < 8; i++) bits.push((b >> i) & 1); bits.push(par); bits.push(1);
this.state = 'tx'; this.driving = true;
let i = 0;
const H = this.half;
const step = () => {
if (i >= bits.length) { this.gpio.setExt(this.dat, false); this.gpio.setExt(this.clk, false); this.driving = false; this.state = 'idle'; this.txq.shift(); this.lastClk = 1; if (this.txq.length) this.scheduleTx(500000); return; }
// host inhibit check (CLK held low by host while we're not driving)
this.gpio.setExt(this.dat, bits[i] === 0);
this.soc.scheduleIn(8000, () => {
if (this.gpio.level(this.clk) === 0 && !this.gpio.extLow[this.clk >> 5]) { /* unexpected */ }
this.gpio.setExt(this.clk, true);
this.soc.scheduleIn(H, () => { this.gpio.setExt(this.clk, false); this.soc.scheduleIn(H - 8000, () => { i++; step(); }); });
});
};
step();
}
// ---- host -> device ----
startReceive() {
this.state = 'rx'; this.driving = true;
const H = this.half; let bit = 0; const got = [];
const next = () => {
if (bit === 10) { // ACK
this.gpio.setExt(this.dat, true);
this.soc.scheduleIn(8000, () => { this.gpio.setExt(this.clk, true); this.soc.scheduleIn(H, () => { this.gpio.setExt(this.clk, false); this.soc.scheduleIn(H, () => { this.gpio.setExt(this.dat, false); this.finishReceive(got); }); }); });
return;
}
this.gpio.setExt(this.clk, true); // CLK low (device)
this.soc.scheduleIn(H, () => {
this.gpio.setExt(this.clk, false); // CLK high; sample data
this.soc.scheduleIn(H / 2, () => { got.push(this.gpio.level(this.dat)); this.soc.scheduleIn(H / 2, () => { bit++; next(); }); });
});
};
this.soc.scheduleIn(30000, next);
}
finishReceive(bits) {
this.state = 'idle'; this.driving = false;
let b = 0; for (let i = 0; i < 8; i++) b |= (bits[i] << i);
this.lastClk = 1; this.clkFallNs = 0;
this.command(b);
if (this.txq.length) this.scheduleTx(300000);
}
command(b) {
if (this.pendingCmd) {
const c = this.pendingCmd; this.pendingCmd = 0;
if (c === 0xF0 && b === 0) this.txq.push(0xFA, 0x02); else this.txq.push(0xFA);
return;
}
switch (b) {
case 0xFF: this.txq.push(0xFA, 0xAA); if (this.kind === 'mouse') this.txq.push(0x00); this.scanning = false; break;
case 0xF2: this.txq.push(0xFA); if (this.kind === 'keyboard') this.txq.push(0xAB, 0x83); else this.txq.push(0x00); break;
case 0xEE: this.txq.push(0xEE); break;
case 0xED: case 0xF3: case 0xF0: case 0xE8: this.pendingCmd = b; this.txq.push(0xFA); break;
case 0xF4: this.scanning = true; this.txq.push(0xFA); break;
case 0xF5: this.scanning = false; this.txq.push(0xFA); break;
case 0xE9: this.txq.push(0xFA, 0x00, 0x02, 0x64); break;
default: this.txq.push(0xFA);
}
}
// ---- user input ----
key(code, down) {
const sc = SET2[code]; if (!sc) return false;
if (!this.scanning && this.kind === 'keyboard') { /* still queue; real keyboards buffer */ }
let out;
if (code === 'PrintScreen') out = down ? sc : [0xE0, 0xF0, 0x7C, 0xE0, 0xF0, 0x12];
else if (down) out = sc.slice();
else { out = []; if (sc[0] === 0xE0) { out.push(0xE0, 0xF0, sc[1]); } else out.push(0xF0, sc[0]); }
if (code === 'Pause') out = down ? [0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xF0, 0x77] : [];
this.send(out, 200000);
return true;
}
mouse(dx, dy, buttons) {
if (this.kind !== 'mouse' || !this.scanning) return;
dx = Math.max(-255, Math.min(255, dx | 0)); dy = Math.max(-255, Math.min(255, dy | 0));
const b0 = 0x08 | (buttons & 7) | (dx < 0 ? 0x10 : 0) | (dy < 0 ? 0x20 : 0);
this.send([b0, dx & 255, dy & 255], 100000);
}
}
|