esp32emu / js /gpio.js
HelloSun's picture
ESP32 emulator update
d8afd6f verified
Raw History Blame Contribute Delete
6.53 kB
// GPIO matrix, IO_MUX and pad model
export class GPIO {
constructor(soc) { this.soc = soc; this.iomux = new IoMux(soc); this.listeners = []; this.reset(); }
reset() {
this.r = new Int32Array(0x400);
this.out = new Uint32Array(2); this.en = new Uint32Array(2);
this.pin = new Int32Array(40);
this.inSel = new Int32Array(256); this.outSel = new Int32Array(40);
for (let i = 0; i < 40; i++) this.outSel[i] = 256;
this.status = new Uint32Array(2);
this.extLow = new Uint32Array(2); // external devices pulling pad low
this.extHigh = new Uint32Array(2); // external devices driving pad high
this.pullup = new Uint32Array(2);
this.strap = 0x12;
this.lastLevel = new Uint32Array(2);
this.sigOut = new Map(); // signal -> value (peripheral outputs)
this.pad = new Uint8Array(40);
this.matrixVersion = (this.matrixVersion || 0) + 1;
}
// pad electrical level (0/1)
level(n) {
const w = n >> 5, b = 1 << (n & 31);
const drv = this.driving(n);
if (drv === 0) return 0; if (drv === 1) { return (this.extLow[w] & b) ? 0 : 1; }
// not driven by chip
if (this.extLow[w] & b) return 0;
if (this.extHigh[w] & b) return 1;
if (this.pullup[w] & b) return 1;
return (this.floating ? this.floating[n] : 0) | 0;
}
// returns 0, 1 or -1 (high-z)
driving(n) {
const rio = this.soc.rtcio;
if (rio && rio.drives(n)) return rio.outLevel(n);
const sel = this.outSel[n];
const sig = sel & 0x1FF;
let en, val;
if (sig === 256 || sig === 0x100) { en = (this.en[n >> 5] >>> (n & 31)) & 1; val = (this.out[n >> 5] >>> (n & 31)) & 1; }
else {
const oenSel = (sel >> 10) & 1;
en = oenSel ? ((this.en[n >> 5] >>> (n & 31)) & 1) : 1;
if (((sel >> 11) & 1)) en ^= 1;
val = this.sigOut.get(sig) | 0; if ((sel >> 9) & 1) val ^= 1;
}
if (!en) return -1;
if ((this.pin[n] >> 2) & 1) return val ? -1 : 0; // open drain
return val;
}
inputLevel(n) { return this.level(n); }
reg32Read(off) {
switch (off) {
case 0x04: return this.out[0]; case 0x10: return this.out[1];
case 0x20: return this.en[0]; case 0x2c: return this.en[1];
case 0x38: return this.strap;
case 0x3c: return this.inReg(0); case 0x40: return this.inReg(1);
case 0x44: return this.status[0]; case 0x50: return this.status[1];
case 0x60: return this.intFor(1, 0); case 0x68: return this.intFor(0, 0);
case 0x74: return this.intFor(1, 1); case 0x7c: return this.intFor(0, 1);
}
if (off >= 0x88 && off < 0x88 + 40 * 4) return this.pin[(off - 0x88) >> 2];
if (off >= 0x130 && off < 0x530) return this.inSel[(off - 0x130) >> 2];
if (off >= 0x530 && off < 0x530 + 40 * 4) return this.outSel[(off - 0x530) >> 2];
return this.r[off >> 2];
}
inReg(w) { let v = 0; for (let i = 0; i < 32; i++) { const n = w * 32 + i; if (n < 40 && this.level(n)) v |= (1 << i); } return v | 0; }
intFor(cpu, w) {
// cpu 0 = PRO (int_ena bit 2, nmi bit3), cpu 1 = APP (bit 0, nmi bit1)
let v = 0;
const st = this.status[w];
for (let i = 0; i < 32; i++) {
const n = w * 32 + i; if (n >= 40) break;
if (!((st >>> i) & 1)) continue;
const ena = (this.pin[n] >> 13) & 31;
if (cpu === 0 ? (ena & 4) : (ena & 1)) v |= (1 << i);
}
return v | 0;
}
read(off) { return this.reg32Read(off); }
write(off, v) {
v |= 0;
const old = this.snapshot();
switch (off) {
case 0x04: this.out[0] = v; break; case 0x08: this.out[0] |= v; break; case 0x0c: this.out[0] &= ~v; break;
case 0x10: this.out[1] = v & 0xFF; break; case 0x14: this.out[1] |= v & 0xFF; break; case 0x18: this.out[1] &= ~v; break;
case 0x20: this.en[0] = v; break; case 0x24: this.en[0] |= v; break; case 0x28: this.en[0] &= ~v; break;
case 0x2c: this.en[1] = v & 0xFF; break; case 0x30: this.en[1] |= v & 0xFF; break; case 0x34: this.en[1] &= ~v; break;
case 0x48: this.status[0] |= v; break; case 0x4c: this.status[0] &= ~v; break;
case 0x54: this.status[1] |= v; break; case 0x58: this.status[1] &= ~v; break;
default:
if (off >= 0x88 && off < 0x88 + 40 * 4) this.pin[(off - 0x88) >> 2] = v;
else if (off >= 0x130 && off < 0x530) this.inSel[(off - 0x130) >> 2] = v;
else if (off >= 0x530 && off < 0x530 + 40 * 4) { this.outSel[(off - 0x530) >> 2] = v; this.matrixVersion++; }
else this.r[off >> 2] = v;
}
this.padsChanged(old);
this.updateInt();
}
snapshot() { const a = new Uint8Array(40); for (let i = 0; i < 40; i++) a[i] = this.level(i); return a; }
// called whenever pad levels might have changed (chip drive or external)
padsChanged(old) {
old = old || this.lastSnap;
const now = this.snapshot();
this.lastSnap = now;
if (!old) return;
for (let n = 0; n < 40; n++) {
if (old[n] === now[n]) continue;
const w = n >> 5, b = 1 << (n & 31);
const type = (this.pin[n] >> 7) & 7;
if ((type === 1 && now[n]) || (type === 2 && !now[n]) || type === 3) this.status[w] |= b;
for (const l of this.listeners) l(n, now[n]);
}
for (const t of this.levelWatch || []) t();
}
updateInt() {
// level interrupts
for (let n = 0; n < 40; n++) {
const type = (this.pin[n] >> 7) & 7; const w = n >> 5, b = 1 << (n & 31);
if (type === 4 && !this.level(n)) this.status[w] |= b;
else if (type === 5 && this.level(n)) this.status[w] |= b;
}
this.soc.setIrqCpu(0, 22, this.intFor(0, 0) | this.intFor(0, 1));
this.soc.setIrqCpu(1, 22, this.intFor(1, 0) | this.intFor(1, 1));
}
// external drive from devices
setExt(n, low) { const w = n >> 5, b = 1 << (n & 31); const old = this.snapshot(); if (low) this.extLow[w] |= b; else this.extLow[w] &= ~b; this.padsChanged(old); this.updateInt(); }
setPullup(n, on) { const w = n >> 5, b = 1 << (n & 31); if (on) this.pullup[w] |= b; else this.pullup[w] &= ~b; }
setSignalOut(sig, v) { this.sigOut.set(sig, v); }
// peripheral input: value for input signal (e.g. UART RX) = pad level via inSel
signalIn(sig) {
const s = this.inSel[sig]; const pad = s & 0x3F;
if (s & 0x40) return 1; // constant high
if (pad === 0x30) return 0; // constant low
if (pad === 0x38 || (s & 0x40)) return 1;
return this.level(pad);
}
}
class IoMux {
constructor(soc) { this.soc = soc; this.r = new Int32Array(0x400); }
read(off) { return this.r[off >> 2]; }
write(off, v) { this.r[off >> 2] = v; }
}