Download js/ps2.js from HelloSun/esp32emu: direct link, hf CLI and curl.
- Browser
- Download file 7.9 kB
-
https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/ps2.js
- Command line
-
hf download hf://spaces/HelloSun/esp32emu/js/ps2.js
-
curl -L -o ps2.js https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/ps2.js
7.9 kB
| // 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); | |
| } | |
| } | |