picoZ80 Guía Técnica

picoZ80 Guía Técnica

Esta guía documenta la arquitectura de hardware del picoZ80, la interfaz de bus PIO del RP2350, el modelo de memoria, la referencia de configuración JSON, el framework de dispositivos virtuales y los procedimientos de depuración. Está destinada a desarrolladores que deseen comprender los aspectos internos, escribir nuevos controladores, portar el firmware a una nueva máquina host o depurar problemas a nivel de firmware.
Para la configuración de usuario final y el uso de la interfaz web, consulte el picoZ80 User Manual. Para la descripción general del proyecto e instrucciones de compilación, consulte la página del proyecto picoZ80.

Arquitectura de Hardware

El picoZ80 integra cinco subsistemas en una sola PCB compacta diseñada para caber dentro del formato de un paquete DIP-40. Toda la lógica opera a 3.3V; la interfaz del bus Z80 se encarga de la conversión de niveles y la corriente de excitación para el bus de 5V del host.

Diagrama de Bloques del Sistema

┌─────────────────────────────────────────────────────────────────────────┐
│                         picoZ80 PCB                                     │
│                                                                         │
│  ┌────────────────────────────┐      ┌──────────────────────────────┐  │
│  │         RP2350B            │      │           ESP32-S3           │  │
│  │  (Cortex-M33, dual core)   │      │                              │  │
│  │                            │      │  ┌──────┐  ┌──────────────┐ │  │
│  │  Core 0: USB, file I/O,    │◄────►│  │  SD  │  │  Web Server  │ │  │
│  │          ESP32 relay       │ FSPI │  │ Card │  │  (Bootstrap) │ │  │
│  │  Core 1: Z80 bus hot loop  │ UART │  └──────┘  └──────────────┘ │  │
│  │                            │      │                              │  │
│  │  PIO 0,1,2: bus interface  │      │  WiFi ─── 802.11 b/g/n AP   │  │
│  │                            │      │           or Client mode     │  │
│  │  16MB SPI Flash            │      └──────────────────────────────┘  │
│  │  8MB PSRAM (SPI)           │                                         │
│  └────────────────────────────┘                                         │
│                │                                                         │
│       ┌────────┴────────┐                                               │
│       │ Z80 Bus Interface│                                               │
│       └────────┬────────┘                                               │
│                │ 5V bus (A0–A15, D0–D7, MREQ, IORQ, RD, WR...)         │
└────────────────┼────────────────────────────────────────────────────────┘
                 │
         ┌───────┴───────┐
         │  Host Z80     │
         │  DIP-40 socket│
         │  (legacy      │
         │   computer)   │
         └───────────────┘

Componentes Principales

Componente Dispositivo Función
MCU PrincipalRP2350B (QFN-80)Dual Cortex-M33, 150MHz (hasta 300MHz OC), 512KB SRAM, 12 state machines PIO, 48 pines GPIO
FlashW25Q128 (16MB SPI)Bootloader, slots de firmware duales, particiones de configuración
PSRAM8MB SPI PSRAM64 × 64KB bancos RAM/ROM para el espacio de direcciones Z80
CoprocesadorESP32-S3-PICO-1WiFi, tarjeta SD, servidor web, OTA
Hub USBCH334FHub USB, puente para actualización de firmware
Fuente de alimentaciónTLV62590BVConvertidor buck síncrono 5V → 3.3V

Asignación GPIO del RP2350B

El paquete QFN-80 del RP2350B proporciona 48 pines GPIO. El picoZ80 utiliza prácticamente todos los pines. La asignación está fijada en el diseño de la placa y se refleja en los programas PIO:
Rango GPIO Señales Dirección
GPIO 0–15 A0–A15 (Bus de Direcciones Z80) Salida (controlado por PIO)
GPIO 16–23 D0–D7 (Bus de Datos Z80) Bidireccional (PIO tri-state)
GPIO 24 MREQ Salida
GPIO 25 IORQ Salida
GPIO 26 RD Salida
GPIO 27 WR Salida
GPIO 28 M1 Salida
GPIO 29 RFSH Salida
GPIO 30 BUSREQ Entrada
GPIO 31 BUSACK Salida
GPIO 32 HALT Salida
GPIO 33 INT Entrada
GPIO 34 NMI Entrada
GPIO 35 WAIT Salida
GPIO 36 CLK Entrada (reloj del host)
GPIO 37 RESET Entrada
GPIO 38–41 ESP32 FSPI (CS, CLK, MOSI, MISO) SPI
GPIO 42–43 ESP32 UART (TX, RX) UART
GPIO 44–45 PSRAM SPI SPI
GPIO 46–47 USB (D+, D–) USB

Arquitectura de Firmware

El firmware del RP2350 está construido con el Raspberry Pi Pico SDK 2.x orientado a la plataforma RP2350-arm-s. El firmware se divide en dos ejecutables independientes: el Bootloader y la Aplicación.

Disposición de la Memoria Flash

Partición Rango de Direcciones Tamaño Contenido
Bootloader 0x10000000 – 0x1001FFFF 128KB Puente USB, actualización de firmware, selector de partición
App Slot 1 0x10020000 – 0x1051FFFF 5MB Firmware Z80 — aplicación activa (slot 1)
App Slot 2 0x10520000 – 0x10A1FFFF 5MB Firmware Z80 — aplicación activa (slot 2)
App Config 1 0x10A20000 – 0x10C9FFFF 2.5MB Imágenes ROM + JSON de configuración minificado (slot 1)
App Config 2 0x10CA0000 – 0x10F1FFFF 2.5MB Imágenes ROM + JSON de configuración minificado (slot 2)
General Config 0x10F20000 – 0x10FFEFFF 892KB Configuración principal, espacio temporal
Partition Table 0x10FFF000 – 0x11000000 4KB Número de slot activo, checksums, metadatos

Responsabilidades Dual-Core

Los dos núcleos Cortex-M33 tienen responsabilidades completamente separadas y se comunican mediante una cola de mensajes inter-core (queue_t). Esta separación asegura que el trabajo no en tiempo real del Core 0 nunca introduzca jitter en las transacciones del bus Z80 del Core 1.
Núcleo Responsabilidades
Core 0 Puente serie USB CDC; coordinación de actualización de firmware; E/S de archivos (retransmitida al ESP32 por UART); despacho de comandos ESP32 (cambios de imagen de disco, recargas de configuración, consultas de versión); gestión de particiones; despacho de mensajes inter-core.
Core 1 Bucle principal de emulación del bus Z80 — se ejecuta exclusivamente. Atiende los FIFOs PIO, resuelve cada transacción de bus contra el mapa de memoria y despacha a: hardware físico del host (PHYSICAL), PSRAM (RAM/ROM) o handler de dispositivo virtual (FUNC). El bucle interno se coloca en SRAM.

Interfaz de Bus PIO

La interfaz del bus Z80 está implementada completamente en ensamblador PIO del RP2350 (z80.pio). El RP2350 proporciona tres bloques PIO (PIO 0, PIO 1, PIO 2) cada uno con cuatro state machines — doce state machines en total, de las cuales el firmware Z80 utiliza las doce.
Los programas PIO se ejecutan independientemente de los núcleos Cortex-M33. La interfaz del bus continúa respondiendo de forma determinista incluso cuando el Core 1 está ocupado con accesos a PSRAM o llamadas a funciones de dispositivos virtuales. Las state machines se comunican mediante flags IRQ PIO en lugar de polling, eliminando la latencia entre máquinas.

Tabla de Programas PIO

PIO State Machine Programa Función
0 SM 0 z80_addr Emite la dirección de 16 bits (A0–A15) en el bus y señala el inicio del ciclo a SM 2.
0 SM 1 z80_data Controla o muestrea D0–D7 con control tri-state; se libera durante BUSRQ.
0 SM 2 z80_cycle Secuenciador de ciclo de bus de nivel superior — orquesta ciclos de fetch, lectura, escritura, E/S y refresco de DRAM.
0 SM 3 z80_fetch Ciclo de fetch de opcode (M1 + MREQ + RD).
1 SM 0 z80_mem_read Ciclo de lectura de memoria (MREQ + RD).
1 SM 1 z80_mem_write Ciclo de escritura de memoria (MREQ + WR).
1 SM 2 z80_io_read Ciclo de lectura de E/S (IORQ + RD).
1 SM 3 z80_io_write Ciclo de escritura de E/S (IORQ + WR).
2 SM 0 z80_busrq Gestiona BUSREQ/BUSACK; libera /IORQ, /MREQ, /RFSH, /M1, /HALT, /WR, /RD.
2 SM 1 z80_nmi Detecta la activación de NMI y señala al Core 1.
2 SM 2 z80_clk_sync Sincroniza las state machines PIO con la señal CLK del Z80 del host.
2 SM 3 z80_int_ack Gestiona ciclos de reconocimiento de interrupción (M1 + IORQ).

Convenciones de Señales IRQ PIO

La comunicación entre state machines utiliza flags IRQ PIO. El Core 1 monitoriza estos flags en el bucle principal para actuar en cada evento del bus:
IRQ Evento
IRQ 0 Dirección válida / inicio de ciclo — ha comenzado un nuevo ciclo de bus y A0–A15 están estables.
IRQ 1 Fase de datos — la dirección del bus de datos ha sido resuelta; D0–D7 deben ser controlados o muestreados.
IRQ 2 T1 detectado — el flanco de subida de T1 en el ciclo actual. Se utiliza para sincronizar operaciones internas con el reloj del host.
IRQ 3 Evento RESET — la línea RESET del host ha sido activada. El Core 1 debe reinicializar el estado de emulación.
IRQ 4 NMI detectado — la línea NMI del host ha sido activada.
IRQ 6 BUSRQ activo — el host ha activado BUSREQ; PIO está liberando el bus.

Generación de Wait States

El programa PIO z80_wait en PIO 2 SM 0 inserta wait states de ciclo T configurables en el bus del host mediante la activación de /WAIT. El número de wait states adicionales se controla por bloque de memoria o E/S mediante el parámetro tcycwait en config.json.
Los wait states son necesarios cuando el RP2350 necesita tiempo adicional para completar un acceso a PSRAM o una llamada a función de dispositivo virtual antes de presentar los datos al bus del host. El parámetro tcycsync habilita la sincronización T1 (z80_sync en PIO 2 SM 1), que bloquea la ventana de acceso a PSRAM al flanco de subida T1 de cada ciclo de bus, previniendo la deriva de temporización en aplicaciones que dependen del reloj del host para temporización precisa (cassette, bit-banging serie).

Arquitectura PIO — Cómo se Recrean los Ciclos de Bus

El subsistema de E/S Programable (PIO) del RP2350 es la tecnología clave que permite al picoZ80 recrear la temporización del bus Z80 con precisión de ciclo. Comprender cómo las state machines PIO trabajan juntas es esencial para cualquiera que modifique la interfaz del bus o depure problemas de temporización.

Fundamentos PIO del RP2350
Cada bloque PIO contiene cuatro state machines (SM) independientes que ejecutan pequeños programas desde una memoria compartida de 32 instrucciones. Las state machines se ejecutan independientemente de los núcleos Cortex-M33 a la frecuencia del reloj del sistema (hasta 300 MHz). Recursos PIO clave utilizados por el picoZ80:
  • TX FIFO — una cola de 4 entradas desde la CPU hacia la state machine. El código C en el Core 1 envía datos (direcciones, palabras de control, instrucciones inyectadas) al FIFO; el programa PIO los extrae usando out o pull.
  • RX FIFO — una cola de 4 entradas desde la state machine hacia la CPU. El PIO envía muestras del bus de datos al FIFO usando in; el Core 1 las lee después de que cada ciclo de bus se complete.
  • Flags IRQ — 8 flags (IRQ 0–7) compartidos entre todas las state machines dentro de un bloque PIO. Los flags también pueden verse entre bloques PIO (IRQ 0–3 en un bloque se mapean a IRQ 4–7 en bloques adyacentes). Las state machines usan irq set / irq wait / irq clear para sincronizarse entre sí y con el código C.
  • Registros scratch X e Y — dos registros de 32 bits por SM utilizados para contadores de bucle y valores temporales.
  • out exec — una instrucción especial que extrae un valor del TX FIFO y lo ejecuta como instrucción PIO. Este es el mecanismo por el cual el código C controla dinámicamente las secuencias de ciclo de bus (ver más abajo).
  • set pins / out pins — controlan pines GPIO directamente. set usa un valor inmediato de 5 bits; out desplaza datos del registro de desplazamiento de salida (OSR) hacia los pines.
  • in pins — muestrea los pines GPIO en el registro de desplazamiento de entrada (ISR), y luego auto-envía al RX FIFO.
  • wait gpio — detiene la SM hasta que un pin GPIO específico alcanza un nivel determinado. Se usa extensivamente para sincronizarse con la señal de reloj CLK del Z80 del host.
  • Side-set — permite que uno o dos pines GPIO se controlen como efecto lateral de cualquier instrucción, sin usar un ciclo de instrucción. El picoZ80 usa side-set de 2 bits para controlar /RD y /WR simultáneamente con otras operaciones.
  • JMP PIN — salto condicional basado en el nivel de un pin GPIO designado. Se usa para verificar /WAIT, BUSREQ, /NMI y /RESET.

El Mecanismo out exec — Inyección Dinámica de Instrucciones
La característica más distintiva del diseño PIO del picoZ80 es el uso de out exec, 16 en la state machine orquestadora z80_cycle. Esta instrucción extrae un valor de 16 bits del TX FIFO y lo ejecuta inmediatamente como una instrucción PIO — el valor no es datos, es la siguiente instrucción que la state machine ejecutará.
Este mecanismo permite al código C en el Core 1 controlar la secuencia de ciclo de bus en tiempo real. En lugar de cargar un programa PIO fijo para cada tipo de ciclo, el Core 1 envía una secuencia de instrucciones PIO pre-codificadas al TX FIFO, y la SM de ciclo las ejecuta una por una:
// z80_cycle SM (PIO 0 SM 2) — the orchestrator
//
// .program z80_cycle
// .side_set 2 opt
// public start_cycle:
//     wait 0 irq 6             ; Pause if BUSACK is active (bus relinquished).
//     irq set 0                ; Signal "ready for new cycle".
//     wait 0 irq 0             ; Wait until C code clears IRQ 0 (address loaded).
//     wait 1 gpio Z80_PIN_CLK  ; Sync to T1 rising edge of host clock.
// cycle_exec:
//     out exec, 16             ; ← Pull next instruction from TX FIFO and execute it.
//     jmp cycle_exec           ; Loop: keep executing injected instructions.
//
// The C code pushes a sequence of encoded PIO instructions into the FIFO.
// Each instruction controls one step of the bus cycle (assert /MREQ, wait for
// clock edge, read data bus, etc.). The sequence ends with a JMP back to
// start_cycle, which restarts the orchestrator for the next bus transaction.
El código C pre-calcula estas secuencias de instrucciones al inicio para cada tipo de ciclo (fetch, lectura de memoria, escritura de memoria, lectura de E/S, escritura de E/S, refresco, reconocimiento de interrupción). Durante la ejecución, el Core 1 selecciona la secuencia pre-construida apropiada y la envía al FIFO. Este enfoque tiene dos ventajas críticas:
  • Eficiencia de espacio de programa — cada bloque PIO tiene solo 32 slots de instrucción. Al inyectar instrucciones dinámicamente, la SM de ciclo necesita solo 7 instrucciones de memoria de programa para orquestar todos los tipos de ciclo. Los programas de tipo de ciclo reales (fetch, lectura, escritura, etc.) existen como arreglos C de instrucciones codificadas, no como programas PIO residentes.
  • Flexibilidad — el código C puede modificar la secuencia de instrucciones inyectada en tiempo de ejecución para manejar casos especiales (por ejemplo, insertar wait states adicionales, omitir la fase de refresco o generar un ciclo no estándar para depuración).

Coordinación de State Machines
Las 12 state machines trabajan como un pipeline coordinado. El siguiente diagrama muestra el flujo de un ciclo típico de lectura de memoria:
                  Core 1 (C code)               PIO State Machines
                  ─────────────                 ──────────────────
    1. Resolve address                          z80_cycle: IRQ 0 set
       from memory map                           (waiting for work)
                  │
    2. Push addr → TX FIFO ──────────────────→ z80_addr: receives addr
       Clear IRQ 0                               outputs A0–A15 on pins
                  │
    3. Push cycle instructions ──────────────→ z80_cycle: out exec, 16
       (e.g. mem_read sequence)                   executes: set /MREQ low
       into cycle SM TX FIFO                      executes: set /RD low
                  │                               executes: wait CLK edges
    4. Wait for RX FIFO ←────────────────────  z80_data: samples D0–D7
       (data byte from bus)                       pushes to RX FIFO
                  │
    5. Read data from                          z80_cycle: JMP start_cycle
       RX FIFO                                    (ready for next cycle)
                  │
    6. Dispatch to PSRAM
       or driver handler
El handshake basado en IRQ asegura que el bus de direcciones esté estable antes de activar las señales de control, y que los datos se muestreen en el punto correcto del ciclo de bus. Las state machines nunca hacen polling — usan wait 0 irq N para dormir hasta que ocurra el evento relevante, consumiendo cero ciclos de CPU mientras esperan.

Ciclo Fetch del Z80 (Ciclo M1) — Paso a Paso
El fetch de opcode es el ciclo de bus Z80 más complejo — combina una lectura de memoria con un ciclo de refresco. El programa z80_fetch se ejecuta durante 4 ciclos T del reloj del host:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    T3    T4

A0–A15:    ══╤═══ PC address ══════╤═══ Refresh addr ══╗
             │                     │                    ║
/M1:       ──┘                     └────────────────────╜── (low during T1–T2, high T3–T4)
/MREQ:     ────┘              ┌────┘              ┌──── (low T1↓–T3↑, then T3↓–T4↓ for refresh)
/RD:       ────┘              ┌─────────────────────── (low T1↓–T3↑)
/RFSH:     ────────────────────┘                   ┌── (low T3↑–T4↓)
D0–D7:     ═══════════════╤═══╗                        (sampled at T3↑)
                          │   ║
                       opcode read
El programa PIO implementa esto de la siguiente manera:
  1. Flanco de subida T1 — la SM z80_addr coloca el valor de PC en A0–A15. z80_fetch activa /M1 a nivel bajo mediante set pins.
  2. Flanco de bajada T1/MREQ y /RD se activan a nivel bajo (mediante set pins y side-set). La dirección ahora es válida y el sistema de memoria puede comenzar a responder.
  3. T2 — la SM entra en un bucle de wait-state: espera el flanco de subida y luego el de bajada del CLK, y verifica el pin /WAIT mediante jmp pin. Si /WAIT está bajo, la SM repite el bucle (añadiendo ciclos Tw). Si está alto, procede a T3.
  4. Flanco de subida T3in pins, 8 muestrea D0–D7 (el byte de opcode) y lo envía al RX FIFO. Se activa IRQ 1 para señalar a z80_data/z80_addr que ahora debe emitirse la dirección de refresco. /M1, /MREQ y /RD se desactivan; /RFSH se activa a nivel bajo.
  5. Flanco de bajada T3/MREQ se activa nuevamente (para el strobe de fila de refresco).
  6. T4 — el refresco continúa. Al final de T4, /MREQ y /RFSH se desactivan. La SM de ciclo retorna a start_cycle lista para la siguiente transacción de bus.
El Core 1 lee el opcode del RX FIFO y lo usa para decodificar la instrucción, determinar cuántos ciclos adicionales de memoria o E/S se necesitan, y enviar las secuencias de instrucciones apropiadas.

Ciclos de Lectura y Escritura de Memoria
Los ciclos de lectura y escritura de memoria son más simples que el fetch — abarcan 3 ciclos T sin fase de refresco.
Memory Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/RD:       ────┘              ┌──── (low T1↓–T3↓)
D0–D7:     ═══════════════╤═══╗     (sampled at T3↓)


Memory Write:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
D0–D7:     ══════╤═══ data ═════╗   (driven from T2 onwards)
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/WR:       ──────────┘        ┌──── (low T2↓–T3↓)
Para las lecturas, la SM z80_mem_read activa /MREQ y /RD en el flanco de bajada de T1, espera durante T2 (verificando /WAIT para wait states), y luego muestrea el bus de datos en el flanco de bajada de T3 usando in pins, 8. Para las escrituras, z80_mem_write activa /MREQ en el flanco de bajada de T1, y luego activa /WR en el flanco de bajada de T2 después de que el bus de datos sea controlado por z80_data. Ambos desactivan todas las señales de control al final de T3.

Ciclos de Lectura y Escritura de E/S
Los ciclos de E/S del Z80 usan /IORQ en lugar de /MREQ y siempre incluyen un wait state automático (Tw) entre T2 y T3. Esta es una característica arquitectónica del Z80 — el ciclo adicional da tiempo a los dispositivos de E/S más lentos para responder:
I/O Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    Tw    T3

A0–A15:    ══╤═══ port address ══════════╗
/IORQ:     ──────┘                    ┌──── (low T2↑–T3↓)
/RD:       ──────┘                    ┌──── (low T2↑–T3↓)
D0–D7:     ═══════════════════════╤═══╗     (sampled at T3↓)
La SM z80_io_read activa /IORQ y /RD en el flanco de subida de T2 (no en T1 como en los ciclos de memoria — esto es la especificación del Z80). El wait state automático Tw se implementa mediante el mismo patrón de bucle jmp pin / wait utilizado para los ciclos de memoria. Las escrituras de E/S siguen el mismo patrón con /WR reemplazando a /RD.

Manejo de BUSREQ / BUSACK
La SM z80_busrq (PIO 2 SM 0) monitoriza el pin de entrada /BUSREQ del host. Cuando /BUSREQ se activa (nivel bajo), la SM:
  1. Activa IRQ 6 para señalar a la SM de ciclo que hay una solicitud de bus pendiente.
  2. Espera a que se complete el ciclo de bus actual (wait 1 irq 0).
  3. Extrae una palabra de control de 32 bits del TX FIFO que especifica las direcciones y valores de los pines para el estado de liberación del bus — esto pone en tri-state los buses de direcciones y datos y activa /BUSACK a nivel bajo.
  4. Permanece en bucle con jmp pin hasta que /BUSREQ se desactiva (nivel alto).
  5. Extrae una segunda palabra de 32 bits para restaurar las direcciones normales de los pines y desactivar /BUSACK.
  6. Limpia IRQ 6, permitiendo que la SM de ciclo reanude su operación.
La SM de ciclo verifica IRQ 6 al inicio de cada ciclo mediante wait 0 irq 6 — si el flag está activado, la SM se detiene hasta que la solicitud de bus se complete. Esto asegura que la liberación del bus ocurra limpiamente entre ciclos, nunca a mitad de ciclo.

Sincronización de Reloj
Todas las SM de tipo de ciclo se sincronizan con el reloj Z80 del host usando wait 1 gpio Z80_PIN_CLK (esperar flanco de subida) y wait 0 gpio Z80_PIN_CLK (esperar flanco de bajada). Esto significa:
  • Los programas PIO son independientes de la frecuencia del reloj — funcionan a cualquier velocidad de reloj del host desde DC hasta la tasa máxima que el RP2350 puede seguir (limitada por el reloj del sistema PIO y la tasa de muestreo GPIO).
  • El reloj PIO de 300 MHz del RP2350 proporciona aproximadamente 85 ciclos PIO por T-state del Z80 a 3.5 MHz, dando más que suficiente tiempo para ejecutar instrucciones PIO, enviar/recibir FIFOs y verificar flags IRQ entre flancos de reloj.
  • La SM z80_sync (PIO 2 SM 1) proporciona una IRQ de sincronización T1 que el código C usa para alinear los accesos a PSRAM con el reloj del host, previniendo la deriva de temporización en software del host sensible al reloj.
  • La SM z80_clk_sync (PIO 2 SM 2) regenera el reloj del host en un GPIO separado, proporcionando una salida de reloj limpia para monitorización externa o disparo de analizador lógico.

Ciclo de Reconocimiento de Interrupción
La SM z80_int_ack (PIO 2 SM 3) implementa la secuencia de reconocimiento de interrupción del Z80. Cuando el código C detecta una condición de interrupción, carga el programa int_ack. Este ciclo es similar a un fetch pero con diferencias clave:
  • /M1 se activa en T1 (como un fetch), pero /IORQ se activa en lugar de /MREQ en el wait state (Tw1).
  • Se insertan dos wait states automáticos (Tw1, Tw2) para dar tiempo al dispositivo que interrumpe a colocar un vector en el bus de datos.
  • El byte de vector se lee de D0–D7 y se envía al RX FIFO.
  • Sigue un ciclo de refresco, idéntico a la fase de refresco del fetch.

Modelo de Memoria

Los accesos a memoria se resuelven a través de tres niveles de latencia creciente. El diseño de tres niveles asegura que el caso común (RAM/ROM respaldada por PSRAM) sea rápido mientras permite máxima flexibilidad para dispositivos virtuales y paso directo al hardware físico del host.

Nivel 1 — Tabla de Despacho SRAM del RP2350

Un arreglo de 128 entradas de valores membankPtr de 32 bits, residente en la SRAM de 512KB del chip del RP2350, proporciona una búsqueda O(1) del tipo de bloque para cada transacción de bus. Una entrada cubre cada bloque de 512 bytes del espacio de direcciones de 64KB del Z80 (128 × 512 = 65,536 bytes). Cada entrada codifica:
  • El tipo de bloque (PHYSICAL, RAM, ROM, FUNC, etc.).
  • Para bloques respaldados por PSRAM: el número de banco PSRAM y el offset.
  • Para bloques FUNC: un índice en la tabla de punteros a funciones de dispositivos virtuales.
Este es el camino más rápido — el Core 1 lee la entrada de la tabla de despacho para la dirección actual en un solo acceso SRAM (cero wait states a 300MHz) antes de decidir qué hacer a continuación.

Nivel 2 — PSRAM Externa

La PSRAM de 8MB está organizada como:
  • 64 bancos × 64KB — datos de imagen RAM o ROM para el espacio de direcciones Z80.
  • 64KB memPtr — arreglo de punteros de redirección por byte para bloques tipo PTR.
  • 64KB memioPtr — arreglo de punteros a funciones para dispositivos FUNC mapeados en memoria.
  • 64KB ioPtr — arreglo de punteros a funciones para dispositivos FUNC de puertos E/S.
El acceso a PSRAM se realiza mediante el periférico SPI dedicado del RP2350 con DMA. La latencia de acceso es determinista y gestionada por el generador de wait states para evitar violaciones de bus.

Nivel 3 — Flash SPI de 16MB

Las imágenes ROM se cargan desde Flash (o la tarjeta SD, a través del ESP32) a PSRAM durante el arranque. En tiempo de ejecución, la Flash no se accede para transacciones de bus — todos los datos ROM se sirven desde PSRAM. La Flash se utiliza para:
  • Bootloader y firmware de aplicación.
  • config.json minificado (almacenado en caché desde la tarjeta SD en cada arranque).
  • Imágenes ROM en particiones App Config (utilizadas cuando no hay tarjeta SD presente).

Tipos de Bloques de Memoria

Tipo Descripción
PHYSICAL Paso directo — el RP2350 libera el bus y la memoria física del host responde. Se utiliza para la ROM y RAM nativas del host.
PHYSICAL_VRAM Como PHYSICAL pero con wait states adicionales para la temporización de la RAM de video del host. Adecuado para regiones VRAM del MZ-700/MZ-80A.
PHYSICAL_HW Paso directo para registros de hardware del host (dispositivos mapeados por E/S en el espacio de memoria).
RAM Lectura/escritura — respaldada por un banco PSRAM. El RP2350 atiende lecturas y escrituras desde/hacia PSRAM.
ROM Solo lectura — respaldada por un banco PSRAM. Los ciclos de escritura se ignoran silenciosamente (el host ve temporización de bus normal pero no se almacenan datos).
VRAM RAM de video respaldada por PSRAM. Los ciclos de escritura se reflejan simultáneamente tanto en PSRAM como en la VRAM física del host.
FUNC Dispositivo virtual — cada acceso dispara una llamada a función C a través de la tabla de punteros a funciones memioPtr o ioPtr, permitiendo emulación arbitraria de E/S.
PTR Redirección por byte — cada byte del bloque de 512 bytes puede apuntar independientemente a cualquier otro tipo de bloque o ubicación PSRAM.

Referencia de Configuración

Todo el comportamiento del picoZ80 se controla mediante config.json en la tarjeta SD. El RP2350 lee y minifica este archivo durante el arranque, almacenando el resultado en Flash. Los arranques posteriores usan la copia en Flash si no hay tarjeta SD presente.
La estructura JSON de nivel superior es:
{
  "esp32": {
    "core":  { ... },
    "wifi":  { ... }
  },
  "rp2350": {
    "core":  { ... },
    "z80":   [ { "memory": [...], "io": [...], "drivers": [...] } ]
  }
}

esp32.core

Clave Tipo Descripción
device cadena Personalidad de CPU — "Z80" para picoZ80, "6502" para pico6502, "6512" para pico6512.
mode entero Modo WiFi de arranque por defecto: 0 = cliente (estación), 1 = Punto de Acceso.

esp32.wifi

Clave Tipo Descripción
override 0/1 Interruptor maestro: 1 = aplicar todas las configuraciones siguientes; 0 = usar configuraciones NVS persistidas.
wifimode cadena "ap" = modo Punto de Acceso; "client" = modo Estación/cliente.
ssid cadena Nombre de red WiFi para crear (AP) o unirse (cliente).
password cadena Contraseña WiFi.
ip cadena Dirección IP fija (por ejemplo, "192.168.1.192").
netmask cadena Máscara de subred (por ejemplo, "255.255.255.0").
gateway cadena Gateway predeterminado (por ejemplo, "192.168.1.1").
dhcp 0/1 Modo cliente: 1 = DHCP; 0 = usar configuración de IP fija.
webfs cadena Directorio raíz del sistema de archivos web en la tarjeta SD (por defecto "webfs").
persist 0/1 1 = escribir configuraciones resueltas en NVS para persistencia entre reinicios.

rp2350.core

Clave Tipo Descripción
cpufreq entero Reloj del sistema RP2350 en Hz (por ejemplo, 300000000). La frecuencia estable máxima depende de la frecuencia PSRAM y el voltaje del núcleo.
psramfreq entero Reloj SPI de PSRAM en Hz (por ejemplo, 133000000).
voltage flotante Voltaje del núcleo RP2350 en voltios (por ejemplo, 1.10). Velocidades de reloj más altas requieren mayor voltaje.
addrDrive integer (0–3) Fuerza de excitación GPIO del bus de direcciones: 0=2mA, 1=4mA, 2=8mA, 3=12mA. Por defecto 0.
addrSlew integer (0–1) Tasa de slew GPIO del bus de direcciones: 0=lento (por defecto), 1=rápido.
dataDrive integer (0–3) Fuerza de excitación GPIO del bus de datos. Misma codificación que addrDrive. Por defecto 0.
dataSlew integer (0–1) Tasa de slew GPIO del bus de datos. Misma codificación que addrSlew. Por defecto 0.
ctrlDrive integer (0–3) Fuerza de excitación GPIO de señales de control. Misma codificación que addrDrive. Por defecto 0.
ctrlSlew integer (0–1) Tasa de slew GPIO de señales de control. Misma codificación que addrSlew. Por defecto 0.
addrSchmitt / dataSchmitt / ctrlSchmitt 0/1 Habilita el disparador Schmitt de entrada para el grupo de bus de direcciones / datos / control. 1 = habilitado (por defecto), 0 = deshabilitado. Se aplica a los pines configurados como entradas.
addrPull / dataPull / ctrlPull integer (0–2) Resistencia de pull para el grupo de bus de direcciones / datos / control: 0 = ninguna, 1 = pull-down, 2 = pull-up.
refresh integer Generación de refresco de DRAM del Z80: 0 = desactivado (sin ciclos de refresco), 1 = un ciclo de refresco en cada búsqueda de opcode, N = un ciclo de refresco por cada N búsquedas. Reduce la sobrecarga del bus en hosts cuya DRAM no requiere refresco por búsqueda.
pinOverrides arreglo Sobrescrituras GPIO por pin. Cada elemento: { "pin": N, "drive": D, "slew": S, "schmitt": 0/1, "pull": P }. Sobrescribe los valores predeterminados a nivel de bus para pines GPIO individuales cuando el hardware específico requiere diferentes características eléctricas.

z80[].memory — Entradas del Mapa de Memoria

El arreglo memory define el mapa de memoria del Z80. Las entradas deben estar ordenadas por dirección. Las regiones deben estar alineadas y dimensionadas como múltiplos de 512 bytes. Los espacios entre entradas se tratan como paso directo PHYSICAL.
Clave Tipo Descripción
enable 0/1 Si esta entrada está activa. Las entradas deshabilitadas se ignoran durante el arranque.
addr hex string Dirección de inicio en el espacio de direcciones Z80 (por ejemplo, "0x0000"). Debe estar alineada a 512 bytes.
size hex string Tamaño de la región en bytes (por ejemplo, "0x2000" para 8KB). Debe ser múltiplo de 512.
type cadena Tipo de bloque — ver Tipos de Bloques de Memoria.
bank entero Número de banco PSRAM (0–63) para tipos RAM/ROM/VRAM/FUNC.
tcycwait entero Wait states adicionales de ciclo T a insertar en cada acceso a esta región.
tcycsync 0/1 Habilitar sincronización T1 para esta región. Requerido para regiones sensibles a la temporización.
task cadena Identificador de tarea opcional para bloques tipo FUNC (cadena de enlace de controlador).
file cadena Ruta en la tarjeta SD a una imagen ROM para precargar en el banco PSRAM durante el arranque (por ejemplo, "/ROM/mz700.rom").
fileofs entero Offset en bytes dentro del archivo de imagen ROM desde donde comenzar la lectura.

z80[].io — Entradas del Mapa de Puertos E/S

El arreglo io mapea rangos de puertos de E/S del Z80 a tipos de bloque. Solo los tipos PHYSICAL y FUNC son significativos para entradas de E/S.
Clave Tipo Descripción
enable 0/1 Si esta entrada de E/S está activa.
addr hex string Dirección de puerto E/S de inicio (por ejemplo, "0xE0").
size hex string Número de puertos consecutivos (por ejemplo, "0x04" para puertos E0–E3).
type cadena PHYSICAL = pasar al host; FUNC = llamar función handler C.
task cadena Cadena de enlace de controlador para entradas tipo FUNC.

z80[].drivers — Instancias de Controladores

El arreglo drivers instancia controladores de dispositivos virtuales y los enlaza a regiones de memoria o E/S. Cada controlador tiene un tipo (el módulo de controlador C), un nombre (identificador de instancia) y uno o más objetos de interfaz que definen las imágenes ROM, mapas de direcciones, mapas de E/S y parámetros para esa instancia de controlador.
"drivers": [
  {
    "enable": 1,
    "name":   "MZ700",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "main",
        "type":   "PHYSICAL",
        "rom": [
          {
            "enable":   1,
            "file":     "/MZ700/mz700.rom",
            "loadaddr": [
              {
                "enable":   1,
                "position": 0,
                "addr":     "0x0000",
                "bank":     0,
                "size":     "0x1000",
                "tcycwait": 0,
                "tcycsync": 0
              }
            ]
          }
        ],
        "addrmap": [
          { "enable":1, "srcaddr":"0x0000", "size":"0x1000",
            "dstaddr":"0x0000" }
        ],
        "iomap": [
          { "enable":1, "srcaddr":"0xE0", "size":"0x08",
            "dstaddr":"0xE0", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/config/mz700.cfg" }
        ]
      }
    ]
  },
  {
    "enable": 1,
    "name":   "MZ-1E05",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "fdc0",
        "type":   "PHYSICAL",
        "rom":    [],
        "addrmap": [],
        "iomap": [
          { "enable":1, "srcaddr":"0xD8", "size":"0x04",
            "dstaddr":"0xD8", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/DSK/MZ700/disk0.dsk" }
        ]
      }
    ]
  }
]
Cada entrada de controlador tiene un "name" de nivel superior (el módulo de controlador C a instanciar), "type" (PHYSICAL o VIRTUAL), y un arreglo "if" (interfaz) que contiene uno o más objetos de interfaz. Cada objeto de interfaz describe un sub-controlador o tarjeta periférica y contiene estos campos:
Clave Tipo Descripción
enable 0/1 Si esta interfaz está activa (0 = se omite durante la inicialización).
name cadena Identificador de instancia — debe coincidir con un nombre en el interfaceFuncMap[] de la persona.
type cadena PHYSICAL o VIRTUAL.
rom arreglo Imágenes ROM a cargar en PSRAM (ver más abajo).
addrmap arreglo Entradas de remapeo de direcciones de memoria (srcaddr/dstaddr/size).
iomap arreglo Entradas de remapeo de puertos E/S (srcaddr/dstaddr/size/16bit).
param arreglo Parámetros específicos del controlador — típicamente { "enable":1, "file":"/path/to/image" } para imágenes de disco, { "name":"key", "value":"val" } para parámetros con nombre, o { "ip":"a.b.c.d:port", "enable":1 } para la dirección del servidor de archivos de red Celestite.

Puertos base de interfaz reubicables. Para que las tarjetas independientes de máquina puedan usarse en placas personalizadas / de experimentador (ver OpenZ80), varios controladores de interfaz leen su puerto E/S base desde el dstaddr de su entrada iomap (con srcaddr establecido en el puerto auténtico de la tarjeta), usando por defecto su puerto original cuando no hay una entrada iomap presente. Las tarjetas reubicables y sus bases por defecto son MZ-1R12 (0xF8/3), MZ-1R18 (0xEA/2), MZ-1R23 (0xB8/2), MZ-1R37 (0xAC/2), PIO-3034 (0x00/4), MZ-8BIO3 / MZ-1E24 (0xB0/4), MZ-1E05 (0xD8/7) y Celestite (0x60/16). Las claves JSON están en minúsculas (srcaddr / dstaddr) y sus valores se interpretan como números. El campo Base I/O Port en la página de Configuración de la GUI escribe automáticamente la entrada iomap correcta.

rom[].loadaddr[] — Direcciones de Carga ROM

Cada entrada de archivo ROM contiene un arreglo loadaddr que especifica dónde se cargan los datos ROM en PSRAM. Múltiples entradas loadaddr permiten que un solo archivo ROM se divida en rangos de direcciones no contiguos o bancos PSRAM.
Clave Tipo Descripción
enable 0/1 Si esta dirección de carga está activa.
position entero Offset en bytes dentro del archivo ROM desde donde comenzar la lectura.
addr hex string Dirección Z80 destino en el mapa de memoria (por ejemplo, "0x0000").
bank entero Número de banco PSRAM donde cargar.
size hex string Número de bytes a cargar (por ejemplo, "0x1000" para 4KB).
tcycwait entero Wait states extra de ciclo T para esta región.
tcycsync entero Valor de sincronización de ciclo T para esta región.

Compatibilidad Persona–Interfaz

No todos los controladores de interfaz están disponibles para cada persona. La tabla siguiente muestra la compatibilidad actual de interfaces para la serie Sharp MZ / X1. La persona Amstrad PCW-9512 es un controlador autónomo con un FDC uPD765 integrado (sin controladores de interfaz separados). La persona Tatung Einstein TC-01 es un controlador autónomo con un FDC WD1770 integrado y una sub-interfaz EinsteinFDC. La columna Open muestra las tarjetas independientes de máquina aceptadas por la persona de experimentador OpenZ80.
La tabla muestra qué interfaces pueden usarse con cada persona de máquina de nivel superior a través del arreglo "if". El valor "name" de la interfaz en el JSON debe coincidir con una de las entradas soportadas para la persona elegida.
Interfaz MZ-700 MZ-1500 MZ-80K MZ-800 MZ-80A MZ-2000 MZ-2200 MZ-80B MZ-2500 Open
RFS
TZFS
MZ-1E05
MZ80FIO
MZ80AFI
MZ-8BFI / E0054PA
MZ-1E14
MZ-1E19
MZ-1R12
MZ-1R18
MZ-1R23
MZ-1R37
PIO-3034
Celestite
MZ-8BIO3
MZ-1E24
MZ-1E30

Controladores Integrados

El firmware soporta un sistema de compilación dirigido con cinco modelos objetivo: BaseZ80 (todos los controladores), SharpZ80 (solo Sharp), AmstradZ80 (solo Amstrad), TatungZ80 (solo Tatung) y OpenZ80 (la persona de experimentador independiente de máquina). Las definiciones en tiempo de compilación INCLUDE_SHARP_DRIVERS, INCLUDE_AMSTRAD_DRIVERS, INCLUDE_TATUNG_DRIVERS e INCLUDE_OPEN_DRIVERS controlan qué módulos de controladores se compilan. Las tablas siguientes listan todos los controladores disponibles.
Serie Sharp MZ (INCLUDE_SHARP_DRIVERS)
Cuando el firmware se compila con INCLUDE_SHARP_DRIVERS, los siguientes módulos de controlador se compilan y pueden instanciarse a través del arreglo drivers:
Controlador Cadena de Tipo Descripción
MZ700.c MZ700 Sharp MZ-700 — conmutación de bancos, video, E/S de teclado
MZ80A.c MZ80A Sharp MZ-80A — ROM de monitor (SA-1510), VRAM, emulación Intel 8253 PIT, 8255 PPI, intercambio de memoria MEMSW/MEMSWR (incluyendo conmutación de bancos CP/M), soporte de modo mixto físico+virtual, y soporte para sub-interfaces RFS, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18
MZ2000.c MZ2000 Sharp MZ-2000 — conmutación de modo de memoria BST/NST, superposición de VRAM de caracteres + gráficos, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC. Soporta tanto modo físico (reemplazo directo del Z80 con detección automática de modo boot/normal) como modo virtual (emulación completa basada en PSRAM con reflejo de ROM IPL)
MZ2200.c MZ2200 Sharp MZ-2200 — conmutación de modo de memoria BST/NST, superposición VRAM, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC, CRT a color
MZ80B.c MZ80B Sharp MZ-80B — ROM IPL de 2K, BST/NST, pantalla monocromática, páginas GRPH duales, 8253 PIT, 8255 PPI, Z80 PIO
MZ2500.c MZ2500 Sharp MZ-2500 (SuperMZ) — MMU de 8 páginas (64 bloques), modos de compatibilidad MZ-2000/MZ-80B, YM2203, G-CRTC, MB8876 FDC, paleta, controlador de interrupciones. El modo virtual soporta software controlado por interrupciones mediante handlers personalizados fetchByte/RETI (ciclos de bus M1 físicos para temporización del gate array), formato de disco D88 nativo con auto-detección sparse/contiguous, supresión de interrupción de un solo disparo durante la inicialización
MZ1500.c MZ1500 Sharp MZ-1500 — superconjunto del MZ-700 con unidad Quick Disk incorporada, PCG, sonido estéreo PSG (SN76489AN), interfaz de impresora Z80 PIO, 8253 PIT, selección de modo MZ-700/MZ-1500 por DIP switch. Sub-interfaces: RFS, MZ-1E05, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite
MZ80K.c MZ80K Sharp MZ-80K — ROM de monitor SP-1002 (0x0000–0x0FFF), VRAM de 2KB (0xD000–0xD7FF), teclado 8255 PPI / 8253 PIT / LS367 (0xE000–0xE7FF), ROM de arranque MZ-80FD nativa (0xF000–0xF3FF), intercambio de memoria MEMSW/MEMSWR para CP/M. Modos físico + virtual (el CP/M en modo físico usa un remapeo de acceso Z80 por controlador; el modo virtual es necesario para el CP/M estándar). Dos rutas de disquete: MZ80FIO nativa (T3444M) — interfaz MZ-80FD original, arranca/lee todos los discos MZ-80K; y MZ80AFI (FDC del MZ-80A) — usada para CP/M, arranca el CP/M del MZ-80K y lee los discos CP/M del MZ-80K dentro de CP/M (C:/D:). Solo una interfaz de disquete está activa a la vez; MZ80FIO tiene prioridad si ambas están presentes. Sub-interfaces: RFS, MZ80FIO, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R37, PIO-3034
MZ800.c MZ800 Sharp MZ-800 — controlador de modo dual (compatibilidad MZ-700 + MZ-800 nativo). Espía el registro de Modo de Pantalla del GDG (puerto 0xCE) para conmutar de modo sobre la marcha: el modo nativo añade gráficos 320×200 / 640×200 (planos de VRAM 0x8000–0xBFFF), paleta de 4/16 colores, E/S del GDG mapeada por puertos (0xCC–0xCF, paleta 0xF0), interrupciones vectorizadas IM2 mediante la cadena daisy-chain del Z80-PIO; el modo MZ-700 usa 8255/8253 mapeados en memoria (0xE000–0xE7FF) y VRAM de texto (0xD000). PSG SN76489 (0xF2), FDC WD1773 (0xD8–0xDF), QuickDisk (0xF4–0xF7), puertos de conmutación de bancos de memoria 0xE0–0xE6. El modo virtual reproduce un RETI físico en el bus real para servir el latch en-servicio del PIO.
MZ80AFI.c MZ80AFI Interfaz de disquete Sharp MZ-80A — emula el controlador de disco flexible MZ-80A AFI
MZ80FIO.c MZ80FIO Interfaz de disquete Sharp MZ-80FD/MZ-80FIO (MZ-80K) — ROM de arranque FDIF en 0xF000–0xF3FF, puertos T3444M 0xF8–0xFB, hasta 4 unidades, cambio de disco en tiempo de ejecución
T3444M.c (usado por MZ80FIO) FDC Toshiba T3444M/T3444A — disquete nativo del MZ-80K. DSK extendido CPC, 35 pistas, 2 cabezas, 16 sectores/pista, sectores FM de 128 bytes; analizador de DSK Track-Info robusto; 4 imágenes de unidad simultáneas
WD1773.c WD1773 FDC WD1773 — imágenes DSK/RAW/D88 de 80 pistas, 2 cabezas, 8 sectores
QDDrive.c QDDRIVE Unidad QuickDisk Sharp — emulación completa de Z80 SIO/2 con datos de pista espiral, control de motor y E/S asíncrona de tarjeta SD
RFS.c RFS ROM Filing System — carga de MZF, CP/M, BASIC desde tarjeta SD
TZFS.c TZFS TranZPUter Filing System — monitor multibanco funcional + sistema de archivos CP/M, seleccionable en la persona MZ-700. Modos de memoria tranZPUter mediante el puerto 0x60, procesador de servicio virtual K64F mediante OUT (0x68), E/S de sectores CP/M a través del ESP32. ROM roms/tzfs.bin desde TZFS/asm/tzfs.asm
MZ-1E05.c MZ1E05 Unidad de interfaz de disco flexible Sharp MZ-1E05 (basada en WD1773)
MZ8BFI.c MZ8BFI / E0054PA Interfaz de disco flexible MZ-2000 — FDC MB8866 sin ROM de controlador (código en IPL). Soporte de formato D88.
MZ-1E14.c MZ1E14 Controlador QuickDisk MZ-1E14 con ROM BIOS (MZ-700/MZ-800)
MZ-1E19.c MZ1E19 Controlador QuickDisk MZ-1E19 sin ROM BIOS
MZ-1R12.c MZ1R12 Placa RAM de 32KB con batería de respaldo (persistida en tarjeta SD)
MZ-1R18.c MZ1R18 Placa de expansión RAM de 64KB
MZ-1R23.c MZ1R23 MZ-1R23 ROM Kanji de 128KB (patrones JIS 16×16) y MZ-1R24 ROM Diccionario de 256KB. Archivos ROM cargados desde tarjeta SD. Puertos E/S B8h–B9h con lectura auto-incrementada
MZ-1R37.c MZ1R37 MZ-1R37 EMM (Expanded Memory Manager) de 640KB — espacio de direcciones de 20 bits con enganche de dirección por puerto E/S
PIO-3034.c PIO3034 IO DATA PIO-3034 EMM de 320KB — contador de direcciones de 19 bits con puerto de datos auto-incrementado
Celestite.c Celestite Placa compuesta Celestite — controlador Ethernet Wiznet W5100 (emulación de registros), controlador de interrupciones, UFM, CMOS RAM MZ-1R12 de 32KB integrada (expandible a 64KB), EMM MZ-1R37 de 640KB opcional. Puertos E/S 60h–6Fh. El parámetro ip en el arreglo JSON param configura la dirección del servidor de archivos netfs.py (por ejemplo, "192.168.1.210:6800"). Fase 2: networking TCP/IP real a través de puente ESP32 — comandos de socket W5100 reenviados al ESP32 para operaciones de socket BSD reales. IPC inter-core para NET_CFG, NET_SOCK, NET_SEND, NET_RECV, NET_PING
MZ8BIO3.c MZ-8BIO3 Tarjeta serie RS-232C (conector BI), Z80 SIO emulado en los puertos 0xB0–0xB3 (base configurable). Canales A/B puenteados a los puertos serie USB CDC 2 y 3. Sin ROM.
MZ1E24.c MZ-1E24 Tarjeta serie RS-232C (conector Sharp ST); igual que MZ-8BIO3 con cableado de conector diferente.
Z80SIO.c (usado por MZ-8BIO3 / MZ-1E24) Zilog Z80 SIO/2 preciso a nivel de registro: WR0–WR7, RR0–RR2, interrupciones vectorizadas Z80 modo-2, cadena daisy-chain en-servicio de 4 niveles, status-affects-vector. Anillos SPSC sin bloqueo (lock-free) puentean el núcleo Z80 (core 1) y el servicio USB CDC (core 0).
SASI.c + MZ1E30.c MZ-1E30 Controlador de disco duro SASI MZ-1E30 — emula el controlador de disco duro SASI (Shugart Associates System Interface) Sharp MZ-1E30 para MZ-2500/MZ-80B. Hasta 4 destinos de disco (~21.4 MB cada uno, bloques de 256 bytes), ROM IPL de 32KB (accedida por E/S a través de puertos 0xA8–0xA9), E/S de sector bajo demanda desde imágenes de disco en tarjeta SD. Comandos SASI: TEST_UNIT_READY, REQUEST_SENSE, READ(6), WRITE(6), SEEK(6), INQUIRY. Puertos E/S 0xA4–0xA5 (datos/control SASI)
PIT8253.c PIT8253 Emulación Intel 8253 PIT independiente — los seis modos de contador, BCD/binario, latch, lectura/carga LSB/MSB
PPI8255.c PPI8255 Emulación Intel 8255 PPI independiente — E/S Modo 0, set/reset de bits, callbacks de salida, inyección de entrada
Serie Amstrad PCW (INCLUDE_AMSTRAD_DRIVERS)
Cuando el firmware se compila con INCLUDE_AMSTRAD_DRIVERS, los siguientes módulos de controlador se compilan:
Controlador Cadena de Tipo Descripción
PCW9512.c PCW9512 Amstrad PCW-9512 — Z80A @ 4MHz, 512KB RAM con conmutación de páginas de 16KB en 4 bancos (puertos F0–F3), gate array (ASIC) para video/reloj del sistema/enrutamiento FDC/control de motor (puerto F8), controlador de impresora de margarita 8041 (puertos FC–FD), emulación de secuencia de arranque. Modos virtual y físico soportados
uPD765.c Emulación FDC NEC uPD765 — soporte de formato CPC DSK (puertos 00–01). Comandos del gate array: finalizar bootstrap, reiniciar, enrutamiento de INT FDC (NMI/INT/ignorar), terminal count, motor on/off. Imagen de disco físico mediante código Z80 inyectado. Módulo FDC reutilizable (análogo a WD1773.c para Sharp)
Serie Tatung Einstein (INCLUDE_TATUNG_DRIVERS)
Cuando el firmware se compila con INCLUDE_TATUNG_DRIVERS, los siguientes módulos de controlador se compilan:
Controlador Cadena de Tipo Descripción
EinsteinTC01.c EinsteinTC01 Tatung Einstein TC-01 — Z80A @ 4MHz, 64KB RAM + 8KB ROM conmutable (X-TAL MOS), conmutación ROM/RAM por puerto 0x24, VDP TMS9129 con aplicación de temporización entre accesos (~2us de intervalo), PSG AY-3-8910 (puertos 0x02-0x03), Z80 CTC (puertos 0x28-0x2B), Z80 PIO (puertos 0x30-0x33), interfaz de teclado (puerto 0x20). Modos virtual y físico soportados
EinsteinFDC.c Sub-interfaz FDC Einstein — 2 unidades, soporte de formato DSK/D88. Imagen de disco físico: leer disquete físico a DSK, escribir DSK a disquete físico
WD1770.c Emulación FDC WD1770 — soporte de formato Extended CPC DSK, D88 y DSK estándar. 40 pistas, 1 cabeza, 10 sectores, 512 bytes (discos de 200KB). Módulo FDC reutilizable para máquinas basadas en WD1770 (separado de WD1773.c usado por Sharp)

TZFS — Modelo de Monitor + CP/M (persona MZ-700)
TZFS.c implementa un monitor de bajo nivel multibanco y sistema de archivos funcional — un conjunto de mejoras sobre el MONITOR 1Z-013A original (acceso a SD, banqueo de ROM, un ensamblador / desensamblador y herramientas) — modelado sobre el TZFS del tranZPUter SW y su procesador de E/S virtual K64F. Se ofrece como una interfaz seleccionable únicamente en la persona MZ-700 (registrado en MZ700.c junto a RFS, y en la práctica mutuamente excluyente con él), no como una persona de nivel superior. CP/M se ejecuta por debajo del monitor.
Emulación de modos de memoria (puerto 0x60)
Al escribir un modo de memoria tranZPUter en el puerto de E/S 0x60 se reapuntan los punteros de banco. Los modos son TZMM_ORIG, TZMM_BOOT, TZMM_TZFS, TZMM_TZFS2, TZMM_TZFS3, TZMM_TZFS4, TZMM_CPM, TZMM_CPM2 y TZMM_COMPAT. La disposición CP/M CPM2 usa paginación del bloque 0 con granularidad de byte — 0x00000x003F se asignan al bloque de vectores y 0x00400x01FF al inicio del TPA.
Procesador de servicio virtual K64F (puerto 0x68)
El Z80 solicita servicios ejecutando OUT (0x68), que encola un mensaje MSG_TZFS_SVCREQ al Core 0; el Core 0 ejecuta TZFS_processServiceRequest. Los servicios del sistema de archivos son READDIR / NEXTDIR (bloques de directorio de 16 entradas en caché), READFILE / NEXTREADFILE, LOADFILE (consciente de la cabecera MZF y de los objetos de banco), CHANGEDIR y CLOSE. Los servicios de CP/M son LOADBDOS (recarga de CCP + BDOS en arranque en caliente), ADDSDDRIVE, READSDDRIVE y WRITESDDRIVE, más servicios de frecuencia de CPU. Como picoZ80 no tiene acceso directo a la SD, los sectores de 512 bytes de CP/M se leen y escriben a través del ESP32 (ESP_readSector / ESP_writeSector) contra archivos de imagen completos; las rutas de imagen por unidad provienen de las entradas param[].file del JSON de la interfaz, con la plantilla de reserva CPM/SDC16M/RAW/CPMDSK<nn>.RAW. La ROM TZFS es roms/tzfs.bin en la tarjeta SD, ensamblada desde TZFS/asm/tzfs.asm (su BIOS de CP/M desde TZFS/asm/cbios.asm / cpm22.asm). Se distribuye en config_MZ-700_MZ-700.json con "enable": 0 y se activa en el JSON o desde la página de Configuración de la GUI web.

OpenZ80 — Persona de Experimentador (INCLUDE_OPEN_DRIVERS)
El objetivo OpenZ80 (TARGET_MODEL_OPENINCLUDE_OPEN_DRIVERS) compila una persona Z80 "vanilla" deliberadamente básica para experimentadores que instalan el picoZ80 en una placa de su propio diseño o en una máquina sin controlador dedicado. En modo PHYSICAL, todo el espacio de 64K de memoria y E/S pasa directamente a la placa real (las tarjetas de interfaz superponen sus puertos E/S); en modo VIRTUAL presenta una RAM plana de 64K en la que las imágenes ROM a nivel de controlador se cargan secuencialmente desde 0x0000. No incorpora hardware de máquina y reutiliza los módulos de tarjetas de interfaz Sharp de forma literal (no mantienen estado específico de la persona), exponiendo solo las tarjetas independientes de máquina a continuación — cada una de las cuales soporta reubicación del puerto E/S base.
Controlador Cadena de Tipo Descripción
Open.c Open Persona Z80 vanilla / de experimentador — sin hardware de máquina. Paso directo físico o RAM virtual plana de 64K con carga de ROM a nivel de controlador desde 0x0000
MZ-1R12.c MZ1R12 Tarjeta RAM-file MZ-1R12 de 32K (base por defecto 0xF8, reubicable)
MZ-1R18.c MZ1R18 Placa RAM MZ-1R18 de 64K (base por defecto 0xEA, reubicable)
MZ-1R23.c MZ1R23 Placa ROM Kanji MZ-1R23 / diccionario MZ-1R24 (base por defecto 0xB8, reubicable)
MZ-1R37.c MZ1R37 MZ-1R37 EMM de 640K (base por defecto 0xAC, reubicable)
PIO-3034.c PIO3034 PIO-3034 EMM paralelo / E/S paralela (base por defecto 0x00, reubicable)
MZ8BIO3.c / MZ1E24.c MZ8BIO3 / MZ1E24 Tarjetas serie RS-232C duales (Z80 SIO, base por defecto 0xB0, reubicable; canales A/B en USB CDC 2/3)
MZ-1E05.c MZ1E05 Interfaz de disquete WD1773 (base por defecto 0xD8, reubicable; requiere una ROM de arranque FDC)
Celestite.c Celestite Placa LAN ESP32 Celestite (base por defecto 0x60, reubicable)
Compile con build_tzpuPico.sh open. Para adaptar el picoZ80 a una nueva máquina, tome uno de los controladores de persona bajo src/drivers/{Sharp,Amstrad,Tatung,Other}/ como base y combínelo con el código fuente de la ROM de monitor / IPL / BIOS CP/M / arranque de disquete correspondiente en los directorios asm/ de los proyectos RFS y TZFS (por ejemplo, RFS/asm/sa1510.asm, RFS/asm/cbios.asm, TZFS/asm/mz2000_ipl.asm, RFS/asm/mz80afi.asm). Consulte la Guía del Desarrollador para el procedimiento paso a paso.

Framework de Dispositivos Virtuales

El tipo de bloque FUNC permite la emulación arbitraria de E/S llamando funciones handler C en cada acceso al bus. Cualquier bloque de 512 bytes de memoria o rango de puertos E/S puede estar respaldado por una función.

Firmas de Funciones Handler

Los handlers FUNC de memoria se almacenan en la tabla memioPtr en PSRAM. Los handlers FUNC de E/S se almacenan en la tabla ioPtr. Las firmas de las funciones son:
/* Memory read handler */
uint8_t mem_read_handler(uint16_t addr, void *ctx);

/* Memory write handler */
void mem_write_handler(uint16_t addr, uint8_t data, void *ctx);

/* I/O read handler */
uint8_t io_read_handler(uint8_t port, void *ctx);

/* I/O write handler */
void io_write_handler(uint8_t port, uint8_t data, void *ctx);
Las funciones handler se llaman directamente desde el bucle principal del Core 1. Deben completarse antes de que expiren los wait states del ciclo de bus actual — mantenga los handlers breves y evite cualquier operación bloqueante (E/S de archivos, UART, etc.). Si un handler necesita disparar una operación más larga (por ejemplo, cargar un sector de disco), debe enviar un mensaje al Core 0 a través de la cola inter-core y retornar inmediatamente con un byte de estado, difiriendo la E/S real al Core 0.

Escribir un Nuevo Controlador

Para añadir soporte para un nuevo periférico o máquina host:
  1. Crear un nuevo archivo .c / .h en el directorio src/drivers/.
  2. Implementar funciones handler de lectura y escritura que coincidan con las firmas anteriores.
  3. Registrar los punteros a funciones handler en las tablas memioPtr o ioPtr durante la inicialización del controlador.
  4. Añadir el controlador al objetivo de compilación CMakeLists.txt.
  5. Añadir una entrada de cadena de tipo para que el parser de configuración JSON pueda instanciar el controlador por nombre.
  6. Documentar las claves param del controlador en el archivo de cabecera del controlador.
La función de inicialización del controlador se llama una vez durante el arranque, después de que se haya parseado config.json. El controlador recibe un puntero a su bloque de configuración de interfaz y debe configurar cualquier estado interno y registrar sus handlers en ese momento.

ICE (Shell de Depuración)

El picoZ80 incluye un shell de depuración ICE (In-Circuit Emulator) integrado en el Canal 1 USB CDC (el segundo puerto serie enumerado cuando la placa está conectada por USB). El shell se ejecuta en el Core 0 y se comunica con el bucle de emulación del Core 1 mediante flags compartidos en la estructura de contexto Z80CPU. El shell de depuración solo está disponible en la variante de firmware DBGSH, que se compila con la definición INCLUDE_DBGSH.

Canales Serie USB CDC

El picoZ80 enumera varios puertos serie USB CDC cuando se conecta a un host. CDC 0 y CDC 1 sirven a los UARTs físicos / puente ESP32 y al shell de depuración ICE (CDC 1, variantes DBGSH). CDC 2 y CDC 3 son los dos canales (A y B) de una tarjeta serie RS-232C virtual — el controlador MZ-8BIO3 o MZ-1E24 — cuando uno de ellos está configurado en config.json. Estos dos puertos no tienen un UART físico detrás: son puentes de búfer en anillo hacia el Z80 SIO emulado, de modo que los datos escritos en CDC 2/3 en el host aparecen en el canal A/B del Z80 SIO visto por el invitado, y viceversa. Si no hay ninguna tarjeta serie configurada, CDC 2 y CDC 3 no se enumeran.

Arquitectura

  • Entrada/Salida: Canal 1 USB CDC a 115200 baudios. El prompt del shell es dbg> . Se soporta historial de comandos (16 entradas) y eco de caracteres.
  • Breakpoints: Hasta 8 breakpoints simultáneos almacenados en cpu->dbgBpAddr[]. El Core 1 verifica el arreglo de breakpoints antes de cada fetch de opcode; al encontrar una coincidencia, establece cpu->hold = true y señala al Core 0 mediante dbgBpHit.
  • Paso a paso: El comando step establece cpu->dbgStepCount. El Core 1 decrementa este contador después de cada instrucción, deteniendo automáticamente cuando llega a cero. Se muestra el estado de registros antes/después y la instrucción desensamblada para cada paso.
  • Traza de ejecución: Un buffer circular de 512 entradas (cpu->dbgTrace[]) registra PC, opcode y registro de flags para cada instrucción ejecutada cuando la traza está habilitada. Cada entrada de 32 bits empaqueta [31:16]=PC, [15:8]=opcode, [7:0]=registro F.
  • Acceso a memoria: El acceso a memoria física (dm p, wm p) genera ciclos de bus Z80 reales a través de las state machines PIO. El acceso virtual (dm v, wm v) lee/escribe PSRAM directamente. El modo automático (wm sin calificador) sigue el mapa de memoria. El acceso RP2350 (dm r) lee el espacio de direcciones del microcontrolador host con validación de rango.
  • Hold/Release: El comando hold establece cpu->hold = true. El Core 1 confirma mediante cpu->holdAck, asegurando que la CPU esté inactiva antes de que el shell acceda al estado compartido.
  • Break: El comando break detiene el Core 1 y luego reporta el PC actual, la instrucción a punto de ejecutarse (desensamblada) y el conjunto completo de registros — la forma más rápida de ver dónde se encuentra un programa en ejecución, antes de avanzar paso a paso o inspeccionar. go/cont reanuda.
  • Teclas rápidas y abreviaturas: Para reducir la escritura durante la depuración, los comandos más comunes tienen teclas rápidas de una sola letra — c=cont, s=step, g=go, b=break, r=regs, h=hold — que se resuelven antes de la coincidencia por prefijo, por lo que nunca se tratan como ambiguas. Cualquier otro comando puede introducirse como su prefijo único más corto (p. ej. ste=step, sta=status, dis=disassemble); un prefijo ambiguo lista los candidatos, y un nombre escrito por completo siempre tiene prioridad.

Referencia de Comandos

Comando Sintaxis Descripción
help help Listar todos los comandos
regs regs Volcar todos los registros Z80, flags y contador de ciclos
dm dm <p|f|v|r> <addr> [len] Volcar memoria (física / fetch / virtual / RP2350)
search search [p|v] <start> <end> <hex..>|"text" Buscar en memoria un patrón de bytes o cadena de texto ASCII. p = bus físico, v = PSRAM virtual, omitir para mapeado. Las coincidencias se muestran con 8 bytes de contexto. Auto-detiene la CPU para acceso físico/mapeado. Patrón de hasta 32 bytes
cmp cmp [f] <phys> <virt> <len> Comparar memoria del bus físico con PSRAM virtual
dis dis [p|v] [addr] [count] Desensamblar código Z80
asm asm [addr] Ensamblador Z80 interactivo
memmap memmap [block] Mostrar tabla de punteros de banco de memoria
memptr memptr [addr] Mostrar tabla PSRAM memPtr
iomap iomap [port] Mostrar tabla de handlers de puertos E/S
status status Estado del sistema (frecuencia CPU, PSRAM, tiempo de actividad)
ver ver Versión de firmware e información de partición
drivers drivers Listar controladores e interfaces activos
hold hold Pausar emulación de CPU
release release Reanudar emulación de CPU
break break Detener el Z80 en ejecución y reportar dónde se detuvo — PC, la instrucción (bytes + desensamblado) a punto de ejecutarse, y un volcado completo de registros. A diferencia de hold (que pausa silenciosamente), break muestra el estado para que puedas ver exactamente dónde está la ejecución (p. ej. cuando está atascado en un bucle). Reemitir break vuelve a mostrar el estado. Reanudar con go/cont o avanzar paso a paso con step. Expira (~3 s) si el Z80 se mantiene en reset o su reloj está bloqueado
go go Continuar (liberar hold, breakpoints activos)
cont cont Alias de go. Continuar ejecución (liberar hold, breakpoints activos)
step step [n] Ejecutar paso a paso n instrucciones
bp bp <addr> Establecer breakpoint (máximo 8)
bc bc <n|*> Limpiar breakpoint n o todos
bl bl Listar breakpoints
wm wm [p|v] <addr> <byte>... Escribir en memoria (física/virtual/auto)
fill fill [p|v] <addr> <len> [w|d] <val> Llenar memoria con un valor constante
copy copy <pv|fp|vp> <src> <len> <dst> Copiar memoria entre física y virtual
memtest memtest <addr> <len> [pattern] Probar memoria física (escritura+lectura, escritura+fetch, intercalado)
in in <port> Leer puerto E/S Z80
out out <port> <byte> Escribir puerto E/S Z80
trace trace <on|off|dump [n]|clear|rt|byte ...> Control de traza de ejecución; rt habilita salida de traza en tiempo real; byte habilita traza a nivel de byte
verify verify <on|off> Alternar verificación completa de fetch de opcode
fwait fwait <0-4> Forzar wait states extra en M1 (fetch de opcode); 0 = desactivado (por defecto)
iowait iowait <0-8> Forzar wait states extra en ciclo E/S; 0 = desactivado (por defecto)
corrupt corrupt [clear] Mostrar o limpiar corrupciones de fetch detectadas
fdctrace fdctrace <on|off|dump> Habilitar/deshabilitar traza de E/S FDC; dump muestra las últimas 64 operaciones
qdtrace qdtrace <on|off|dump> Habilitar/deshabilitar traza de E/S Quick Disk; dump muestra las últimas 64 operaciones
piodbg piodbg [clear] Mostrar diagnósticos de hardware PIO del RP2350 (FDEBUG, FSTAT, FIFO, PCs, GPIO); clear reinicia flags persistentes
load load <p|v> <file> <addr> [len] [ofs] Cargar un archivo desde la tarjeta SD del ESP32 en memoria Z80. p = bus físico, v = banco 0 de PSRAM virtual. file relativo a /sdcard/. Si se omite len, se carga el archivo completo (hasta 64KB); si se especifica, máximo 1MB. ofs opcional para offset del archivo. Auto-detiene la CPU para escrituras físicas. Usa banco 63 de PSRAM como buffer temporal
save save <p|pf|v> <file> <addr> <len> Guardar memoria Z80 en un archivo en la tarjeta SD del ESP32. p = bus físico, pf = fetch físico (M1), v = banco 0 de PSRAM virtual. file relativo a /sdcard/. Máximo 64KB. Auto-detiene la CPU para lecturas físicas. Refresco periódico de DRAM durante lecturas físicas
dir dir [path] Listar archivos en la tarjeta SD del ESP32. La ruta opcional es relativa a /sdcard/. Muestra nombres de archivo y tamaños
echo echo [on|off] Alternar eco del terminal
reset reset Forzar reset del Z80
set set <reg|flags|memmap|memptr|iomap> <idx> <val> Modificar registro Z80, flags, mapa de memoria, memPtr o entrada de mapa E/S en tiempo de ejecución
hist hist [n] Mostrar historial de comandos (preservado entre sesiones mediante ESP32 NVS)
savehst savehst Forzar guardado del historial de comandos en ESP32 NVS
ipl ipl Realizar un reset IPL (modo BST) alternando el bit 3 del Puerto C del 8255 PPI. Reinicia al modo boot sin un reset completo del Z80
mmutrace mmutrace Volcar información de traza específica de la máquina (estado MMU, instantáneas de registros E/S). La salida varía según la persona a través del handler de traza registrado
intcount intcount Mostrar conteo de reconocimiento de interrupciones y estado actual de interrupción
psync psync [start end] Sincronizar memoria física con PSRAM. Rango de direcciones opcional; por defecto el espacio completo de direcciones
dskimage dskimage read <filename> [cylinders] [heads] / dskimage write <filename> Crear imagen de un disquete físico a un archivo DSK en la tarjeta SD (read), o escribir un archivo DSK desde la tarjeta SD a un disquete físico (write). dskimage <filename> por defecto es lectura (compatible con versiones anteriores). Auto-detecta geometría si se omite
busdiag busdiag Mostrar diagnósticos de bus (estado PIO, niveles de señal, contención de bus)
fdcimage fdcimage Mostrar estado y progreso de creación de imagen FDC
fdcdiag fdcdiag Mostrar información de diagnóstico FDC (estado del controlador, volcado de registros)
gadiag gadiag Mostrar información de diagnóstico del gate array (estado de comandos, enrutamiento de interrupciones)
Teclas rápidas y abreviaturas. Los comandos no necesitan escribirse completos. Los seis comandos más usados tienen teclas rápidas de una sola letra — c (cont), s (step), g (go), b (break), r (regs) y h (hold) — y cualquier otro comando puede introducirse como su prefijo único más corto (por ejemplo ste para step, sta para status, dis para disassemble). Un nombre de comando escrito por completo siempre tiene prioridad, y un prefijo ambiguo imprime la lista de comandos coincidentes.

Coprocesador ESP32

El módulo ESP32-S3-PICO-1 actúa como coprocesador manejando todas las funciones de red y almacenamiento. Se comunica con el RP2350 a través de dos interfaces:
  • FSPI (50MHz, SPI de 4 hilos) — Protocolo IPC Binario v1.1 — transferencia masiva de datos de alta velocidad (imágenes ROM, lecturas/escrituras de sectores de disco, descarga de archivo de configuración). El protocolo usa un encabezado de trama binario fijo de 64 bytes con verificación de integridad CRC32 (reemplazando el anterior checksum XOR). Los canales DMA se pre-asignan durante la inicialización y nunca se liberan, eliminando la sobrecarga de claim/unclaim por transferencia y las condiciones de carrera. Las transferencias de sectores en ráfaga permiten hasta 16 × 512 bytes de sectores (8KB) en una sola transacción SPI, mejorando significativamente los tiempos de carga de imágenes de disquete y QuickDisk. El canal DMA RX se eleva a PRIORIDAD ALTA para prevenir desbordamiento del FIFO causado por la contención del bus QMI PSRAM del Core 1.
  • UART (460.8kbaud) — protocolo de comando/respuesta para mensajes de control, consultas de estado e intercambios cortos de datos.

Modos de Red

El firmware ESP32 soporta tres modos de red, seleccionados en tiempo de compilación mediante archivos sdkconfig pre-construidos:
Modo Archivo de Config WiFi USB NCM Consola FCC/RED Requerido
Solo WiFi sdkconfig.mode_wifi_only No USB Serial/JTAG
WiFi + NCM sdkconfig.mode_wifi_and_ncm TinyUSB CDC-ACM
Solo NCM sdkconfig.mode_ncm_only No TinyUSB CDC-ACM No
USB NCM (Network Control Model) presenta un adaptador Ethernet CDC-NCM en el puerto USB OTG del ESP32-S3 (GPIO 19/20). Un dispositivo USB compuesto expone tanto un puerto serie CDC-ACM (para registro de depuración) como la interfaz de red NCM. El servidor DHCP integrado asigna al host una dirección IP de la subred 192.168.7.0/24, con el picoZ80 accesible en 192.168.7.1. El tiempo de arrendamiento es de 120 minutos.
En modo WiFi+NCM, el servidor HTTP se enlaza a INADDR_ANY:80 y sirve ambas interfaces simultáneamente. WiFi se conecta asíncronamente por lo que la interfaz USB NCM está disponible inmediatamente al encender.
Cuando solo NCM está habilitado, la radio WiFi se deshabilita completamente, la página del WiFi Manager se elimina de la interfaz web, y el título del panel de estado del Dashboard cambia de "WiFi Configuration" a "Network Configuration" (mostrando el estado USB NCM en lugar de SSID/detalles WiFi). La red de adaptación de antena del ESP32-S3 no necesita ser poblada en la PCB.

Interfaz de Tarjeta SD

El ESP32 gestiona la tarjeta SD a través de su interfaz SPI. La tarjeta SD se monta como FAT32 y todo el acceso a archivos desde el RP2350 es mediado por el ESP32 — el RP2350 envía comandos de E/S de archivos por el enlace FSPI/UART y el ESP32 realiza las operaciones reales de lectura/escritura FAT32.
La tarjeta SD también es directamente accesible para el servidor web del ESP32, que sirve archivos desde el directorio webfs/ y permite al Administrador de Archivos navegar y modificar el contenido de la tarjeta mediante HTTP.

Servidor Web

El ESP32 ejecuta un servidor HTTP en el puerto 80 (sin TLS — solo para uso en red local). Todos los recursos web (HTML, CSS, JavaScript) se sirven desde el directorio webfs/ en la tarjeta SD, permitiendo que la interfaz web se actualice sin reflashear el firmware del ESP32. El servidor web maneja:
  • Servir recursos web estáticos desde el directorio webfs/ de la tarjeta SD.
  • Endpoints de API REST para datos JSON (estado del sistema, lectura/escritura de configuración, operaciones de archivos).
  • Endpoints de carga de firmware OTA tanto para el RP2350 como para el ESP32.
  • Conexión WebSocket para actualizaciones de estado del Dashboard en tiempo real.

Protocolo de Comandos RP2350 ↔ ESP32

El RP2350 (Core 0) se comunica con el ESP32 usando un protocolo simple de comando/respuesta sobre el enlace UART. Los comandos son opcodes de un solo byte con bytes de payload opcionales. El ESP32 confirma cada comando con un byte de estado seguido de cualquier dato de respuesta.
Categorías comunes de comandos:
  • E/S de Archivos — abrir, leer, escribir, cerrar, listado de directorio, stat de archivo.
  • Configuración — solicitar contenido de config.json, escribir configuración actualizada, solicitud de recarga.
  • Disco — montar/desmontar imagen de disco, leer/escribir sector (retransmitido desde emulación WD1773 y QDDrive). Los nombres de archivo de la imagen de disquete y QuickDisk actualmente montada son rastreados por el ESP32 y mostrados en el menú de Acciones de la interfaz web.
  • Sistema — consulta de versión, solicitud de reinicio, lectura/escritura NVS.
La interfaz FSPI se usa para transferencias masivas donde el payload es demasiado grande para el UART (subidas de imágenes ROM, datos de sectores de disco), mientras que el UART maneja todos los comandos de control.

Comandos IPC de Red

La implementación de red Celestite Fase 2 añade cinco comandos IPC inter-core que el RP2350 usa para solicitar operaciones de red al ESP32. Estos comandos se reenvían por el enlace FSPI/UART y el ESP32 realiza las operaciones de socket BSD correspondientes. Los connects no bloqueantes usan select() con timeout, y flags pendientes por socket rastrean las operaciones en curso.
Comando Opcode Descripción
IPCF_CMD_NET_CFG 0x10 Obtener configuración de red del ESP32 — devuelve dirección IP, gateway, máscara de subred y dirección MAC
IPCF_CMD_NET_SOCK 0x11 Operación de ciclo de vida de socket — abrir, conectar, escuchar, cerrar o desconectar un socket
IPCF_CMD_NET_SEND 0x12 Enviar datos a un socket abierto
IPCF_CMD_NET_RECV 0x13 Recibir datos de un socket abierto
IPCF_CMD_NET_PING 0x14 Solicitud de eco ICMP (ping)
Los comandos de socket W5100 soportados a través de esta capa IPC son: OPEN, CONNECT, LISTEN, SEND, RECV, CLOSE y DISCON. El ESP32 los traduce a llamadas estándar de la API de socket BSD (socket(), connect(), listen(), send(), recv(), close(), shutdown()), permitiendo que la placa Celestite se comunique con servicios de red como el servidor de archivos netfs.py.

Watchdog y Diagnósticos de Arranque

El firmware del RP2350 utiliza un temporizador watchdog de hardware para detectar y recuperarse de bloqueos durante el arranque y paradas del bucle principal. El watchdog se habilita tempranamente en la secuencia de arranque con un timeout de 30 segundos y se actualiza (watchdog_update()) en cada hito importante. Si cualquier etapa de arranque o iteración del bucle principal tarda más que el timeout, el watchdog reinicia el RP2350 automáticamente.

Seguimiento del Progreso de Arranque

El progreso de arranque se rastrea usando los registros scratch del watchdog del RP2350, que sobreviven a resets del watchdog (pero no a resets de encendido). Esto permite al firmware determinar, después de un reset del watchdog, exactamente a qué etapa de arranque se llegó antes del bloqueo.
Registro Scratch Nombre Contenido
scratch[0–3] Historial de arranque Últimos cuatro intentos de reset — cada entrada codifica (attempt_count << 24) | (stage << 16) | (resetCause & 0xFFFF). Las entradas se desplazan en cada reset del watchdog: [0]←[1]←[2]←[3]←actual.
scratch[4] Diagnósticos SPI Contadores de diagnóstico empaquetados para el enlace FSPI: breadcrumbs, tipo de mensaje, contadores de timeout T1/T3, conteo de tramas erróneas y conteo OK.
scratch[5] Marcador mágico Establecido a 0xB00710BE para indicar que los registros scratch contienen datos válidos de progreso de arranque.
scratch[6] Etapa actual El código de etapa de arranque más reciente (ver tabla siguiente).
scratch[7] Causa de reset El código de causa de reset del controlador de reset de hardware.
Los códigos de etapa de arranque progresan desde 0x01 (inicio) hasta 0x10 (bucle principal iniciado). Las sub-etapas dentro del bucle principal (0x11–0x17) y el procesamiento de comandos inter-core (0x20–0x27) proporcionan seguimiento de grano fino:
Código Etapa Descripción
0x01 BOOTP_START Punto de entrada alcanzado
0x02 BOOTP_CLK_SET Reloj del sistema configurado
0x03 BOOTP_PSRAM_INIT Inicialización de PSRAM comenzada
0x04 BOOTP_PSRAM_OK PSRAM inicializada exitosamente
0x05 BOOTP_STDIO_INIT USB stdio inicializado
0x06 BOOTP_PIO_INIT State machines PIO cargadas
0x07 BOOTP_Z80_INIT Contexto de CPU Z80 inicializado
0x08 BOOTP_USB_INIT Puente USB inicializado
0x0A BOOTP_ESP_HS_SYNC Sincronización de handshake SPI ESP32
0x0B BOOTP_CORE1_LAUNCH Core 1 lanzado
0x0D BOOTP_FSPI_INIT IPC binario FSPI inicializado
0x0E BOOTP_ESP_INIT Comunicación ESP32 lista
0x10 BOOTP_MAIN_LOOP Bucle principal iniciado
0x11–0x17 Sub-etapas del bucle principal Sondeo USB, inter-core, SPI NOP/CMD, tareas
0x20–0x27 Comandos inter-core Carga de disquete, carga QD, carga RAMFILE, E/S de archivos

Log Persistente en PSRAM (plogf)

Los últimos 4KB de la PSRAM de 8MB (dirección 0x117FF000) están reservados para un log de depuración persistente que sobrevive a resets del watchdog. La macro plogf() escribe mensajes estilo printf en este buffer durante el arranque, antes de que USB esté disponible para la salida normal de debugf(). En el siguiente arranque exitoso, la función dump_plog() envía cualquier mensaje capturado a la consola de depuración y luego limpia el buffer. El log usa una estructura simple: un marcador mágico de 4 bytes (0x504C4F47 = "PLOG"), un contador de longitud de 4 bytes, y un buffer de texto circular de 3840 bytes.

Diagnósticos de Fallos

El firmware instala handlers de fallo Cortex-M33 para fallos graves, fallos de gestión de memoria, fallos de bus y fallos de uso. Cuando ocurre un fallo, el handler guarda una instantánea de diagnóstico completa en los últimos 256 bytes de PSRAM (dirección 0x117FFF00) con un marcador mágico (0xFA017000), el tipo de fallo, todos los registros relevantes (PC, LR, SP, R0–R3, R12, PSR), el Registro de Estado de Fallo Configurable (CFSR), el Registro de Estado de Fallo Grave (HFSR), el Registro de Dirección de Fallo de Bus (BFAR), el Registro de Dirección de Fallo de Gestión de Memoria (MMFAR) y el ID del núcleo. El handler luego entra en un bucle infinito, permitiendo que el watchdog dispare un reset. En el siguiente arranque, el firmware verifica si hay un diagnóstico de fallo válido y envía la información capturada mediante debugf(), permitiendo análisis post-mortem sin requerir una sesión de depurador en vivo.

Limpieza de Configuración Flash

El mecanismo de actualización OTA del RP2350 soporta dos operaciones adicionales más allá de la carga de firmware:
  • Limpiar App Config (FW_CFGCLEAR_ID = 0xB1D7E5FA) — borra la partición App Config (imágenes ROM y JSON minificado) asociada con el slot de firmware destino. Esto fuerza al firmware a releer config.json desde la tarjeta SD en el siguiente arranque, lo cual es necesario cuando el esquema de configuración ha cambiado entre versiones de firmware.
  • Limpiar Flash Header (FW_HDRCLEAR_ID = 0xC2E8F6AB) — reinicia el encabezado de partición Flash a valores de fábrica. La configuración del bootloader (partición 0) se preserva, pero todos los metadatos de particiones de aplicación se reconstruyen desde cero. Úselo cuando la tabla de particiones se haya corrompido o al regresar a una versión anterior de firmware que espera un diseño de partición diferente.
Ambas operaciones se activan desde las casillas de verificación de la página web OTA del RP2350 y son ejecutadas por el bootloader durante el proceso de actualización de firmware.

Depuración SWD — RP2350

El RP2350 soporta depuración completa a nivel de código fuente sobre ARM Serial Wire Debug (SWD). Conecte una sonda compatible con CMSIS-DAP (Raspberry Pi Debug Probe, Black Magic Probe o similar) a los Pines 1 (SWCLK), 2 (SWDIO) y 5 (GND) del conector de depuración.

Configuración de OpenOCD

El picoZ80 requiere una pequeña modificación al script estándar de destino OpenOCD del RP2350 para habilitar depuración SMP con puertos GDB separados por núcleo:
sudo cp /usr/local/share/openocd/scripts/target/rp2350.cfg \
        /usr/local/share/openocd/scripts/target/rp2350_tzpu.cfg
Edite rp2350_tzpu.cfg — busque la línea target smp dentro del bloque if {[string compare $_USE_CORE SMP] == 0} y elimine el # inicial:
# Before:
    #target smp $_TARGETNAME_0 $_TARGETNAME_1

# After:
    target smp $_TARGETNAME_0 $_TARGETNAME_1
Este único cambio hace que OpenOCD registre el Core 0 en el puerto GDB 3333 y el Core 1 en el puerto GDB 3334, permitiendo sesiones GDB independientes por núcleo. Inicie OpenOCD antes de iniciar GDB:
openocd -f interface/cmsis-dap.cfg -f target/rp2350_tzpu.cfg -c "adapter speed 5000"

Configuración de GDB

Añada lo siguiente a ~/.gdbinit (con rutas absolutas que coincidan con la ubicación de su proyecto) para permitir la carga automática de archivos .gdbinit por directorio:
set history save on
set history filename ~/.gdb_history
set history size 65536
add-auto-load-safe-path /path/to/project/build/bin/model/BaseZ80/.gdbinit
add-auto-load-safe-path /path/to/project/build/bin/model/Bootloader/.gdbinit
Depuración del Bootloader
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3333 .gdbinit
gdb-multiarch Bootloader.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3334 .gdbinit
gdb-multiarch Bootloader.elf
Depuración del Firmware Principal
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3333 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3334 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Memory dump (from GDB prompt) — hex + ASCII:
(gdb) xac 0x20000000 64
El comando GDB xac <address> <count> está definido en los archivos .gdbinit.3333 / .gdbinit.3334. Vuelca memoria como salida combinada hexadecimal y ASCII y es útil para inspeccionar contenidos de bancos PSRAM y estado de dispositivos mapeados en memoria.

Depuración USB del ESP32

El coprocesador ESP32-S3 tiene una interfaz USB-JTAG incorporada — no se requiere sonda de depuración externa. Conecte un cable USB desde el PC host directamente al puerto USB del ESP32 en la placa picoZ80.
# Start OpenOCD for ESP32-S3
openocd -f board/esp32s3-builtin.cfg

# In a second terminal — launch Xtensa GDB
xtensa-esp32s3-elf-gdb esp32/build/main.elf
(gdb) target extended-remote :3333
Asegúrese de que el ELF fue compilado desde la misma revisión de código fuente que el firmware ejecutándose en el dispositivo, para que los símbolos y direcciones se alineen correctamente.

Sistema de Compilación

El firmware del picoZ80 usa CMake con el Raspberry Pi Pico SDK 2.x. El sistema de compilación produce el firmware Bootloader y la Aplicación. La aplicación se compila en cuatro variantes — dos particiones (Partición 1 en 0x10020000, Partición 2 en 0x10520000) cada una en configuraciones estándar y DBGSH. Las variantes DBGSH añaden INCLUDE_DBGSH a los flags de compilación, habilitando el shell de depuración completo en el Canal 1 USB CDC. El firmware ESP32 se compila separadamente usando ESP-IDF v5.4, gestionado mediante Docker.
La forma más rápida de obtener un entorno funcional es el script automatizado setup_picoZ80 (macOS/Linux y Windows), que instala el SDK y todas las dependencias y crea scripts de compilación listos para usar — consulte el README y la Guía del Desarrollador. Los objetivos, flags y comandos de CMake que se describen a continuación documentan la compilación subyacente que el script automatiza, para quienes prefieran configurarla manualmente.

Objetivos de Compilación CMake

Objetivo Salida Dirección Flash Notas
Bootloader Bootloader.elf, Bootloader.uf2 0x10000000  
BaseZ80_0x10020000 BaseZ80_0x10020000.elf, .bin 0x10020000 (Slot 1) Estándar (sin shell de depuración)
BaseZ80_0x10520000 BaseZ80_0x10520000.elf, .bin 0x10520000 (Slot 2) Estándar (sin shell de depuración)
BaseZ80_DBGSH_0x10020000 BaseZ80_DBGSH_0x10020000.elf, .bin 0x10020000 (Slot 1) DBGSH — incluye shell de depuración ICE
BaseZ80_DBGSH_0x10520000 BaseZ80_DBGSH_0x10520000.elf, .bin 0x10520000 (Slot 2) DBGSH — incluye shell de depuración ICE
Los modelos SharpZ80, AmstradZ80, TatungZ80 y OpenZ80 siguen el mismo patrón de slot / DBGSH, por ejemplo OpenZ80_0x10020000, OpenZ80_0x10020000_DBGSH, OpenZ80_0x10520000 y OpenZ80_0x10520000_DBGSH (cada uno emite .elf, .bin, .hex y .map; los slots de aplicación usan .bin simple, no UF2). Compile un solo modelo con el filtro build_tzpuPico.sh, por ejemplo build_tzpuPico.sh open.

Flags Clave de Compilación CMake

Flag Efecto
INCLUDE_SHARP_DRIVERS Compila todos los controladores de periféricos Sharp MZ (MZ700, MZ80K, MZ800, MZ80A, MZ80B, MZ2000, MZ2200, MZ2500, MZ1500, WD1773, T3444M, QDDrive, RFS, TZFS, MZ-1E05, MZ80AFI, MZ80FIO, MZ8BFI, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite, MZ-1E30).
INCLUDE_AMSTRAD_DRIVERS Compila los controladores de periféricos Amstrad PCW (PCW9512, uPD765).
INCLUDE_TATUNG_DRIVERS Compila los controladores de periféricos Tatung Einstein (EinsteinTC01, EinsteinFDC, WD1770).
INCLUDE_OPEN_DRIVERS Compila la persona de experimentador OpenZ80 (Open.c) y las tarjetas de interfaz independientes de máquina (MZ-1R12/1R18/1R23/1R37, PIO-3034, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E05, WD1773, Celestite).
TARGET_MODEL_TATUNG Establece Tatung Einstein como el modelo objetivo exclusivo (usado en la compilación TatungZ80).
TARGET_MODEL_OPEN Establece la persona de experimentador OpenZ80 como el modelo objetivo exclusivo (usado en la compilación OpenZ80; habilita INCLUDE_OPEN_DRIVERS).
INCLUDE_DBGSH Compila el shell de depuración ICE en el Canal 1 USB CDC. Presente solo en variantes de compilación DBGSH.
CMAKE_BUILD_TYPE=Debug Habilita símbolos de depuración y deshabilita optimización. Requerido para depuración GDB a nivel de código fuente.
CMAKE_BUILD_TYPE=Release Optimización completa (-O3). Usado para firmware de producción.

Comandos de Compilación

# First time: clone and build the SDK
./get_and_build_sdk.sh

# Standard release build (RP2350 only)
./build_tzpuPico.sh

# Debug build
./build_tzpuPico.sh DEBUG

# Full build: RP2350 + ESP32 (ESP32 built via Docker)
./build_tzpuPico.sh ALL

# ESP32 only, using the Docker idf54 alias
cd projects/tzpuPico/esp32
idf54 build
El script build_tzpuPico.sh incrementa automáticamente el número de versión en una compilación exitosa y copia los archivos de salida versionados a fw/uf2/ (bootloader UF2) y fw/bin/ (binario de aplicación para OTA). El UF2 del Bootloader se usa solo para el flasheo inicial mediante almacenamiento masivo USB. Los binarios de slot de aplicación usan formato binario simple (no UF2) porque residen en direcciones de Flash no estándar.

Selección de Modo de Red ESP32

Para cambiar el modo de red, copie el archivo de configuración pre-construido apropiado a sdkconfig antes de compilar:
cd esp32/
cp sdkconfig.mode_ncm_only sdkconfig    # NCM only (FCC/RED safe)
# or: cp sdkconfig.mode_wifi_only sdkconfig
# or: cp sdkconfig.mode_wifi_and_ncm sdkconfig
idf.py build
idf.py flash

Sitios de Referencia

Recurso Enlace
Página del proyecto picoZ80 /es/picoz80/
Manual de Usuario picoZ80 /es/picoz80-usermanual/
Página del proyecto pico6502 /es/pico6502/
Hoja de datos RP2350 datasheets.raspberrypi.com
Referencia PIO RP2350 datasheets.raspberrypi.com — Appendix B
Documentación Pico SDK raspberrypi.github.io/pico-sdk-doxygen
Referencia Técnica ESP32-S3 docs.espressif.com
Guía de Programación ESP-IDF docs.espressif.com/esp-idf
Manual de Usuario CPU Zilog Z80 zilog.com
Documentación OpenOCD openocd.org
Vista previa del proyecto en X (Twitter) engineerswork1

Aviso Regulatorio de Comunicaciones Inalámbricas

Este dispositivo incorpora un módulo inalámbrico ESP32-S3-PICO-1 capaz de transmitir en la banda ISM de 2.4 GHz, lo que lo convierte en un radiador intencional bajo las regulaciones de radiofrecuencia a nivel mundial (incluyendo FCC Part 15 Subpart C en los Estados Unidos y la Directiva de Equipos Radioeléctricos 2014/53/EU en la Unión Europea) cuando el firmware WiFi está instalado.
Configuración de Fábrica
Tal como se envía, la placa picoZ80 viene flasheada con el firmware solo NCM (sdkconfig.mode_ncm_only). En esta configuración, la radio WiFi está completamente deshabilitada y los componentes de la red de adaptación de antena del ESP32-S3 no están poblados en la PCB. Debido a que no ocurre transmisión RF, el dispositivo no es un radiador intencional y no requiere certificación FCC, CE/RED o equivalente. Puede venderse, distribuirse o regalarse sin autorización regulatoria.
Añadir WiFi
Los usuarios finales pueden poblar la red de adaptación de antena y flashear firmware con WiFi habilitado (sdkconfig.mode_wifi_only o sdkconfig.mode_wifi_and_ncm) para uso personal, experimental o educativo. Una vez que el WiFi está habilitado, el dispositivo se convierte en un radiador intencional y se aplican las siguientes reglas:
Aunque el módulo ESP32-S3-PICO-1 por sí mismo lleva certificaciones regulatorias preexistentes (FCC, CE y otras), esas certificaciones a nivel de módulo no se extienden automáticamente a un producto terminado que incorpore el módulo. La exención de módulo pre-certificado permite a individuos aficionados construir un número limitado de dispositivos para uso personal, experimental o educativo sin obtener una autorización de equipo separada.
Limitaciones Importantes
  • Los dispositivos con firmware WiFi no deben venderse, ofrecerse a la venta, regalarse ni distribuirse de otra manera a terceros a menos que el producto terminado haya sido probado independientemente y se le haya otorgado su propia autorización de equipo (por ejemplo, FCC ID, marcado CE con evaluación de un Organismo Notificado) en la jurisdicción correspondiente.
  • Construir este proyecto con WiFi habilitado para uso personal en cantidades limitadas generalmente está permitido bajo las disposiciones de uso aficionado y experimental (por ejemplo, FCC § 15.23), siempre que el dispositivo no cause interferencia perjudicial.
  • La venta comercial con WiFi habilitado requiere certificación completa FCC/RED (o equivalente) a nivel de producto.
  • Los requisitos regulatorios varían según el país. Los constructores fuera de los Estados Unidos deben consultar a su autoridad nacional de radiofrecuencia para las reglas aplicables.
Responsabilidad del Constructor
Es responsabilidad exclusiva del constructor asegurar que cualquier dispositivo construido a partir de estos diseños cumpla con todas las regulaciones de radiofrecuencia aplicables en su jurisdicción. El autor proporciona estos diseños para uso personal, educativo y de aficionados y no hace ninguna representación de que un dispositivo construido a partir de ellos satisfaga los requisitos regulatorios para distribución comercial.