feat(esp32): emulate INPUT_PULLUP on RTC pins + drive the digital read
Completes the internal-pull emulation for the common case (a button on an RTC-capable GPIO like 4/15/25/... with INPUT_PULLUP): - Backend reads the RTC_IO pad RUE/RDE bits via the new get_internals(QEMU_INTERNAL_RTCIO) and emits gpio_pull for RTC pins, so pull-up/down on those pads is finally visible (it lives in RTC_IO, not IO_MUX). IO_MUX path still covers non-RTC pins. - The digitalRead path is driven by seeding the GPIO input level, not by SPICE. The part-level INPUT_PULLUP seed (BasicParts) is sent at attach, before the multi-second QEMU boot finishes, so it is lost and the pin reads LOW. makePinPullHandler now drives the pin to the pull's idle level via sendPinEvent when the guest programs the pull (post-boot), so it sticks. A real button press/release still overrides it.
This commit is contained in:
parent
df9e06c99a
commit
f9fee8ad7c
|
|
@ -540,14 +540,34 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
|||
100 ms from the LEDC poll thread (off the QEMU iothread, so the
|
||||
_emit here can't stall the guest the way _on_gpio_matrix would).
|
||||
"""
|
||||
# RTC-capable GPIOs route their pull through the RTC_IO peripheral
|
||||
# (RTC pad RUE/RDE bits), not IO_MUX — so a button on GPIO4/15/etc.
|
||||
# with INPUT_PULLUP is invisible in muxgpios. Read both and let RTC win
|
||||
# for its pads. Map: GPIO -> (rtcio word index, RUE bit, RDE bit), from
|
||||
# the esp32 rtc_gpio_desc table (TOUCH_PADn / PDAC / XTAL_32K pads).
|
||||
rtc_map = {
|
||||
0: (0x98 >> 2, 27, 28), 2: (0x9c >> 2, 27, 28), 4: (0x94 >> 2, 27, 28),
|
||||
12: (0xa8 >> 2, 27, 28), 13: (0xa4 >> 2, 27, 28), 14: (0xac >> 2, 27, 28),
|
||||
15: (0xa0 >> 2, 27, 28), 25: (0x84 >> 2, 27, 28), 26: (0x88 >> 2, 27, 28),
|
||||
27: (0xb0 >> 2, 27, 28), 32: (0x8c >> 2, 22, 23), 33: (0x8c >> 2, 27, 28),
|
||||
}
|
||||
try:
|
||||
pulls: dict[int, int] = {}
|
||||
iomux_ptr = lib.qemu_picsimlab_get_internals(3) # QEMU_INTERNAL_IOMUX_GPIOS
|
||||
if not iomux_ptr:
|
||||
return
|
||||
mux = (ctypes.c_uint32 * 40).from_address(iomux_ptr)
|
||||
for gpio_pin in range(40):
|
||||
reg = int(mux[gpio_pin])
|
||||
pull = 1 if (reg >> 8) & 1 else (2 if (reg >> 7) & 1 else 0)
|
||||
if iomux_ptr:
|
||||
mux = (ctypes.c_uint32 * 40).from_address(iomux_ptr)
|
||||
for gpio_pin in range(40):
|
||||
reg = int(mux[gpio_pin])
|
||||
pulls[gpio_pin] = 1 if (reg >> 8) & 1 else (2 if (reg >> 7) & 1 else 0)
|
||||
rtcio_ptr = lib.qemu_picsimlab_get_internals(10) # QEMU_INTERNAL_RTCIO
|
||||
if rtcio_ptr:
|
||||
rtc = (ctypes.c_uint32 * 256).from_address(rtcio_ptr)
|
||||
for gpio_pin, (idx, ub, db) in rtc_map.items():
|
||||
reg = int(rtc[idx])
|
||||
rtc_pull = 1 if (reg >> ub) & 1 else (2 if (reg >> db) & 1 else 0)
|
||||
if rtc_pull:
|
||||
pulls[gpio_pin] = rtc_pull # RTC pad pull is authoritative
|
||||
for gpio_pin, pull in pulls.items():
|
||||
if _pull_state.get(gpio_pin, 0) != pull:
|
||||
_pull_state[gpio_pin] = pull
|
||||
_emit({'type': 'gpio_pull', 'pin': gpio_pin, 'pull': pull})
|
||||
|
|
|
|||
|
|
@ -575,10 +575,16 @@ function makeGpioRoutingClearHandler(boardId: string) {
|
|||
function makePinPullHandler(boardId: string) {
|
||||
return (gpio: number, pull: 0 | 1 | 2) => {
|
||||
pinManagerMap.get(boardId)?.setPinPull(gpio, pull);
|
||||
// The pull adds/removes a netlist resistor (structural change), so a full
|
||||
// re-solve is needed. It also matters at idle: nothing else triggers a
|
||||
// solve while the firmware just polls digitalRead, so without this an
|
||||
// INPUT_PULLUP input stays stuck at the floating 0 V the last solve found.
|
||||
// Drive the digital input to the pull's idle level so the firmware's
|
||||
// digitalRead reflects INPUT_PULLUP / INPUT_PULLDOWN. QEMU does not model
|
||||
// the MCU's internal pull on the GPIO input register, and the part-level
|
||||
// HIGH seed (BasicParts) is sent at component-attach — before the
|
||||
// several-second QEMU boot finishes — so it's lost and the pin reads LOW.
|
||||
// This fires when the guest actually programs the pull (inside pinMode,
|
||||
// post-boot), so it sticks. A real button press/release overrides it after.
|
||||
if (pull !== 0) getBoardBridge(boardId)?.sendPinEvent?.(gpio, pull === 1);
|
||||
// The pull also feeds the SPICE netlist (a weak resistor); re-solve so the
|
||||
// electrical view matches.
|
||||
requestElectricalResolve();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue