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 Principal | RP2350B (QFN-80) | Dual Cortex-M33, 150MHz (hasta 300MHz OC), 512KB SRAM, 12 state machines PIO, 48 pines GPIO |
| Flash | W25Q128 (16MB SPI) | Bootloader, slots de firmware duales, particiones de configuración |
| PSRAM | 8MB SPI PSRAM | 64 × 64KB bancos RAM/ROM para el espacio de direcciones Z80 |
| Coprocesador | ESP32-S3-PICO-1 | WiFi, tarjeta SD, servidor web, OTA |
| Hub USB | CH334F | Hub USB, puente para actualización de firmware |
| Fuente de alimentación | TLV62590BV | Convertidor 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:
- 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.
- 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.
- 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.
- Flanco de subida T3 —
in 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.
- Flanco de bajada T3 —
/MREQ se activa nuevamente (para el strobe de fila de refresco).
- 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:
- Activa IRQ 6 para señalar a la SM de ciclo que hay una solicitud de bus pendiente.
- Espera a que se complete el ciclo de bus actual (
wait 1 irq 0).
- 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.
- Permanece en bucle con
jmp pin hasta que /BUSREQ se desactiva (nivel alto).
- Extrae una segunda palabra de 32 bits para restaurar las direcciones normales de los pines y desactivar
/BUSACK.
- 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. Las personas Amstrad PCW-9512 y PCW-8256/8512 son controladores autónomos con un FDC uPD765 integrado y sus propias interfaces
FDC /
PRINTER /
MEMORY /
PAGEPROBE — véase
Soporte Amstrad PCW. 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 |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
— |
— |
— |
— |
| TZFS |
Sí |
— |
— |
— |
— |
— |
— |
— |
— |
— |
| MZ-1E05 |
Sí |
Sí |
— |
Sí |
— |
— |
— |
— |
— |
Sí |
| MZ80FIO |
— |
— |
Sí |
Sí |
— |
— |
— |
— |
— |
— |
| MZ80AFI |
— |
— |
Sí |
Sí |
Sí |
— |
— |
— |
— |
— |
| MZ-8BFI / E0054PA |
— |
— |
— |
Sí |
— |
Sí |
Sí |
Sí |
Sí |
— |
| MZ-1E14 |
Sí |
Sí |
Sí |
Sí |
— |
— |
— |
Sí |
Sí |
— |
| MZ-1E19 |
Sí |
Sí |
Sí |
Sí |
— |
Sí |
Sí |
Sí |
Sí |
— |
| MZ-1R12 |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
| MZ-1R18 |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
Sí |
| MZ-1R23 |
— |
Sí |
— |
Sí |
— |
— |
— |
Sí |
Sí |
Sí |
| MZ-1R37 |
— |
Sí |
Sí |
Sí |
— |
— |
— |
Sí |
Sí |
Sí |
| PIO-3034 |
— |
Sí |
Sí |
Sí |
— |
— |
— |
Sí |
Sí |
Sí |
| Celestite |
— |
Sí |
— |
Sí |
— |
— |
— |
Sí |
Sí |
Sí |
| MZ-8BIO3 |
Sí |
Sí |
— |
Sí |
— |
— |
— |
Sí |
— |
Sí |
| MZ-1E24 |
Sí |
Sí |
— |
Sí |
— |
— |
— |
Sí |
— |
Sí |
| MZ-1E30 |
— |
— |
— |
— |
— |
— |
— |
Sí |
Sí |
— |
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. |
X1.c |
X1 |
Sharp X1 / X1turbo — persona de paso directo físico: la máquina real aporta la RAM, la ROM IPL, la VRAM, el CRTC, la sub-CPU y el PSG, y todos los bloques de memoria pasan directamente al host. El picoZ80 añade el disquete y el cassette virtuales descritos a continuación; solo se interceptan sus puertos |
X1FDC.c |
(usado por X1) |
Controlador de disquete MB8877A virtual (familia WD1793, un adaptador ligero sobre el motor WD1773 compartido) en los puertos 0xFF8–0xFFF con decodificación completa de 16 bits. Las imágenes Raw, D88 y Extended CPC DSK se detectan por su contenido, no por la extensión. Se soportan medios 2D / 2DD y 2HD de 1MB con selección de medio por el host (las lecturas de 0FFC–0FFF seleccionan FM / MFM / 2HD), arrancando el disk BASIC CZ-8FB02 directamente desde una imagen 2HD. En modo de pletina Virtual el DMA de disquete se declara automáticamente como dispositivo integrado; en modo de pletina Real el DMA y el MB8877 propios de la máquina siguen emparejados |
X1CMT.c |
(usado por X1) |
Cassette virtual del X1 — reproduce imágenes de forma de onda .tap (un flujo de bits muestreado a tasa constante, no un formato codificado) desde la SD a través de un búfer en anillo, con el transporte gobernado a través de los puertos de la sub-CPU. La grabación captura a 48kHz y nombra la cinta guardada a partir de la cabecera de la cinta |
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), stub del controlador de impresora de margarita (puertos FC–FD), emulación de secuencia de arranque, sombra de la DRAM en PSRAM con reparación de video y memoria de expansión. Modos de controlador PHYSICAL y VIRTUAL — véase Soporte Amstrad PCW |
PCW8256.c |
PCW8256 |
Amstrad PCW-8256/8512 — comparte el núcleo del controlador PCW9512 (condicionado por variante) y añade el modelo de estado de la impresora matricial, cuyos bits de estado en el puerto FDh difieren de los de la margarita. Unidades CF2 de 3”, geometría tomada de la imagen de disco; 256KB (8256) o 512KB (8512) de RAM de máquina, hasta 1.5MB en total con la memoria de expansión |
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. Arranque sin disco desde una imagen .dsk, cambio de pletina Real ↔ Virtual en tiempo de ejecución, 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 —
0x0000–
0x003F se asignan al bloque de vectores y
0x0040–
0x01FF 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_OPEN →
INCLUDE_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.
Unidad CMT Virtual (Cassette)
La CMT Virtual es una
pletina de cassette virtual a nivel de forma de onda para la serie Sharp MZ. Emula la
forma de onda del cassette en lugar de interceptar el cargador, por lo que desde el punto de vista del Sharp es indistinguible de una pletina real: LOAD del monitor, BASIC, cargadores personalizados y turbo, y SAVE, todos funcionan sin cambios. La carga desde tarjeta SD sigue siendo el camino
rápido; la CMT Virtual es el camino
auténtico — tome un
.mzf y cárguelo "como un cassette real", o grabe un programa en ejecución de vuelta a un nuevo
.mzf.
El controlador es un único motor —
src/drivers/Sharp/CMT.c y
src/include/drivers/Sharp/CMT.h (
CMT_Init /
CMT_Reset /
CMT_PollCB /
CMT_TaskProcessor) — registrado en el
interfaceFuncMap[] de cada máquina como
{"CMT", false, CMT_Init, CMT_Reset, CMT_PollCB, CMT_TaskProcessor} y ofrecido como interfaz seleccionable dondequiera que la máquina tenga un puerto de cassette. Toda la temporización se ejecuta contra el
reloj de pared (
time_us_64() — el mismo dominio de temporización de disparo único que utiliza la CMT del RFS), nunca contra los T-states emulados del Z80. El diseño completo está documentado en
docs/VIRTUAL_CMT_DESIGN.md en el repositorio picoZ80.
Dos Familias de Hardware, Un Motor
El hardware de cassette del MZ se divide en dos familias, ambas gobernadas por el mismo motor de códec. La familia se infiere de la persona activa.
| Familia |
Máquinas |
Bus / Puertos |
Baudios |
Semi-onda (0 / 1) |
SIMPLE |
MZ-80K / MZ-80A / MZ-700 / MZ-800 |
8255 motor on/off, cinta lineal; 8255 mapeado en memoria en E000–E003 (el MZ-800 en su lugar usa los puertos E/S D0–D3) |
1200 baudios |
0 ≈ 504 µs, 1 ≈ 958 µs (el MZ-800 lo ajusta a ≈ 379 / 964 µs) |
CONTROLLED |
MZ-80B / MZ-2000 / MZ-2200 / MZ-2500 |
Transporte controlado por computadora (PLAY / STOP / FF / REW / EJECT + búsqueda de programa APSS); engancha ioPtr[] en E0–E3 |
2000 baudios |
≈ 333 / 667 µs (variante ≈ 1800 baudios) |
Códec compartido. Ambas familias comparten el mismo entramado de byte, bloque y archivo. Un
byte es un bit de inicio largo seguido de 8 bits de datos, MSB primero. Un
bloque es una cabecera (leader), una marca de cinta (tape-mark), los bytes de datos y una suma de comprobación de 16 bits (un conteo de los bits en 1). Un
archivo es un registro de cabecera (info) seguido de un registro de datos, cada uno escrito dos veces, de modo que una única lectura física dispone de una segunda copia a la que recurrir.
La Cinta Virtual — una Cola Ordenada
La cinta virtual es una cola de hasta 16 archivos MZF configurada por la GUI y ordenada, editable en vivo desde la GUI web (añadir / eliminar / reordenar). Al final de cada programa la cola avanza automáticamente — comportamiento de cinta consecutiva — de modo que el siguiente LOAD encuentra el siguiente programa. En la familia controlada, FF / REW saltan programas enteros y APSS busca el límite del programa siguiente o anterior y se detiene automáticamente. La cola se suministra al controlador a través del arreglo param de la interfaz como una lista ordenada de entradas { "enable", "file" }. Un nuevo panel de la GUI web — esp32/webserver/js/cmt.js — gestiona la cola.
Real ↔ Virtual — Paso Directo, Superposición, Supresión
La conmutación en vivo Real ↔ Virtual ya no es exclusiva de la CMT — ahora cubre las interfaces
CMT, Disquete (FDD) y Quick Disk (QD). En la GUI web la conmutación de pletina es un elemento de menú:
CMT en el menú CMT;
Floppy y
QD como elementos de menú disponibles en todas las páginas, cada uno mostrado solo cuando la interfaz correspondiente está habilitada. Al seleccionarlo se conmuta un flag en vivo del controlador a través de la cola de comandos inversos ESP32 → RP2350, sin reinicio. Para una CMT los dos modos difieren en cómo el controlador trata la pletina físicamente conectada:
- VIRTUAL — el controlador superpone los bits del cassette virtual en el lado de lectura y suprime las escrituras de motor y transporte a la pletina física. El Sharp carga desde la cinta virtual y la pletina real permanece inactiva.
- REAL — paso directo puro. La CMT espía en modo solo lectura y el motor acciona la pletina real; el cassette físico se comporta exactamente como si el picoZ80 no estuviera presente.
Disquete y QD siguen el mismo contrato VIRTUAL/REAL:
Virtual = el firmware emula la unidad a partir de imágenes respaldadas en la SD (una cinta virtual en el caso de la CMT);
Real = paso directo puro a la unidad físicamente conectada, con el firmware espiando en modo solo lectura. Una interfaz configurada como física ahora
arranca en modo Real, con el menú y la conmutación disponibles para poder pasarla a Virtual en vivo (sin reinicio). Disquete y QD también ganaron cada uno un botón
EJECT (menú Floppy / QD de la GUI web): una ruta de montaje vacía desmonta la unidad, de modo que el FDC notifica que no hay disco. Los modos de pletina se notifican mediante
INF — la CMT ya notificaba su modo, al que ahora se unen los nuevos campos
fddMode y
qdMode.
Implementación: una tarea
DRIVE_MODE_CHANGE con comandos inversos
FDDM /
QDDM sobre la cola de comandos inversos ESP32 → RP2350 — el mismo mecanismo que la conmutación de la CMT — instala o desinstala los hooks de bus de cada controlador en cada cambio de modo. En modo Real el paso directo del SIO del QD real está
ritmado: un suelo de recuperación de 10 µs entre accesos en los puertos F4–F7, porque el Z80 SIO necesita varios ciclos de reloj de recuperación entre accesos.
Debido a que la conmutación es en vivo, puede cargar desde una cinta virtual y luego hacer SAVE a una pletina física, o cargar desde una pletina física y grabar en una cinta virtual, sin reiniciar.
Cuando el motor está funcionando en modo grabación, el controlador muestrea la línea de datos de escritura y la decodifica de vuelta a un archivo. Cada intervalo de semi-onda se clasifica como corto o largo, los pares corto/largo se convierten en bits, los bits se convierten en bytes, y se valida la suma de comprobación de 16 bits. A partir de los registros recuperados se sintetiza un MZF y se guarda en la tarjeta SD como:
CMT/taperecord_<name>_<YYYYMMDD_HHMMSS>.mzf
donde
<name> es el nombre de archivo de la cabecera del Sharp transcodificado del código de visualización del Sharp a ASCII y luego saneado para FAT (los caracteres ilegales se convierten en
_). Cuando el reloj del ESP32 no ha sido ajustado, la marca de tiempo recurre a un número de secuencia (
taperecord_<name>_0001.mzf) para evitar la colisión con la fecha "1980" de FAT. El nombre real del Sharp se almacena literalmente dentro de la cabecera del MZF, de modo que el programa conserva su nombre original cuando se recarga.
Motor de Reproducción e Internos del Controlador
La reproducción utiliza un
cursor de flancos en streaming que calcula la siguiente transición de datos de lectura sobre la marcha contra el reloj de pared. Es O(1) amortizado y no mantiene un buffer de flancos gigante pre-expandido, por lo que una imagen de cinta completa cuesta casi nada de RAM independientemente de su longitud. Las dos familias enganchan el bus de forma distinta:
- Familia simple — el controlador re-tipifica el bloque del 8255 del cassette (512 bytes, E000–E1FF = bloque 112) a
MEMBANK_TYPE_RAM e instala un handler de bloque completo. Los registros no relacionados con el cassette de ese bloque — el teclado (E000 / E001), el 8253 (E004–E007), el joystick (E008) y sus espejos — pasan directamente a través de Z80CPU_readPhysicalMem / Z80CPU_writePhysicalMem, y solo los bits del cassette del Puerto C se superponen en las lecturas y se espían en las escrituras. El camino crítico ajustado PHYSICAL_HW permanece intacto.
- Familia controlada — el controlador engancha
ioPtr[] en los puertos E0–E3 y acciona allí el transporte controlado por computadora.
Las lecturas y escrituras de SD se entregan al Core 0 a través de las colas inter-core
requestQueue /
responseQueue, de modo que el bucle crítico del Core 1 nunca se bloquea por el almacenamiento. Como se señaló anteriormente, la cola de cinta llega a través del arreglo
param de la interfaz y la familia se infiere de la persona.
Arranque de Disquete del MZ-800 desde TZFS
TZFS ahora puede arrancar disquetes de MZ-80A, MZ-700 y MZ-800 — incluyendo CP/M del MZ-800 — directamente desde dentro del monitor, y regresar a TZFS con el interruptor de RESET del hardware después de que un SO arrancado desde disquete haya tomado el control. Esto abarca cambios tanto en la ROM de TZFS como en el firmware del picoZ80. La versión de TZFS es ahora v1.8.3.
Cargador de Arranque TZFS — GETBOOTDSK y Domesticado del 8253
GETBOOTDSK ahora acepta cualquier id de máquina Sharp (01 / 02 / 03) junto con la firma
"IPLPRO". Pre-pagina la DRAM del bloque 7 en
0000–0FFF para sistemas operativos que cargan por debajo de
1000H (por ejemplo CP/M del MZ-800), y entrega al programa cargado
BC = 0200H — exactamente como lo hace el IPL nativo 9Z-504M — de modo que un cargador de segunda etapa pueda gobernar su propia lectura de directorio.
Antes de ejecutar
JP (HL), TZFS
domestica el reloj 8253: reprograma el contador 2 a aproximadamente 1 Hz y enmascara el bit 2 del Puerto C del 8255, reflejando el
SORES del IPL. Esto evita que un contador de libre ejecución inunde el Z80 con interrupciones
RST 38H y aborte un cargador que rehabilita las interrupciones tempranamente — el fallo que producía "P-CP/M80 → No system file".
Handler de Fetch A10-Toggle del MZ-1E05
El controlador MZ-1E05 del picoZ80 añade MZ1E05_IO_A10Toggle, un handler de fetch de aceleración por hardware del FDC que aplica un OR entre DRQ y A10 en las direcciones 0xF3FE / 0xF7FE. Esto permite que la rutina DSKREAD de TZFS transmita un sector de arranque en el MZ-800 exactamente como ya lo hace en el MZ-80A.
Modos de Memoria TZMM_DSKLOAD / TZMM_DSKRUN
Dos modos de memoria de TZFS del picoZ80,
TZMM_DSKLOAD y
TZMM_DSKRUN, cargan y luego ejecutan un SO arrancado desde disquete en la DRAM nativa del bloque 0 exactamente como lo hace el propio IPL de la máquina, de modo que el SO arrancado ve el mapa de memoria que espera en lugar de la disposición de bancos de TZFS.
Un modo complementario,
TZMM_QDLOAD (un mapa TZFS4 con la RAM baja en el bloque 0), sirve a la ruta de arranque del Quick Disk para que un programa cargado desde el QD quede donde luego lo ejecuta la transferencia de control (exec hand-off).
Seguimiento de Fuente PCG cgWindow (MZ-800)
El controlador MZ800 del picoZ80 ahora rastrea el cgWindow de modo que un SO en modo MZ-800 que carga una fuente PCG (mediante IN 0xE0) alcanza la CG-ROM / CG-RAM real mientras se ejecuta bajo TZFS. Esto corrige el texto ilegible bajo el BASIC de disco, mientras que juegos nativos como Flappy — que no tocan la ventana PCG — no se ven afectados.
Restauración de Reset a TZFS
Cuando se pulsa el interruptor de RESET del hardware después de un SO arrancado desde disquete, el firmware recarga el monitor prístino del bloque 0 y la UROM de TZFS — que el SO sobrescribió mientras se ejecutaba en TZMM_DSKRUN, ya que la ROM y la RAM del bloque 0 comparten la misma PSRAM — y luego arranca en frío TZFS. El interruptor de reset, por lo tanto, regresa a TZFS en lugar de caer al monitor 1Z-013A desnudo.
Correcciones de Errores Relacionadas
- Desajuste de banco PSRAM de Flappy — bajo TZFS,
0x0000–0x0FFF ahora se mapea desde el banco 7 de modo que un programa nativo que hace LDIR de código a la RAM baja y luego cambia el modo de video vea la misma memoria en ambos caminos (una corrección general, no específica de Flappy).
- Recuperación del GDG en reset por hardware —
initVideoText se vuelve a ejecutar una vez que /RESET se libera, de modo que el GDG se re-escanea y se completa la sincronización HBLK del monitor.
- Retardo de estabilización de reset físico — un retardo de 10 ms permite que el GDG / 8255 a bordo terminen su reset por hardware antes de que el firmware reinicialice el video.
Soporte Amstrad PCW
El modelo
AmstradZ80 incorpora los procesadores de texto Amstrad PCW — el PCW-8256, el PCW-8512 y el PCW-9512 — a los hosts soportados. El PCW es arquitectónicamente distinto de las máquinas Sharp: su gate array (ASIC) media en
todos los accesos a la DRAM, ejecutando una rotación fija a 1MHz de ranuras de captura de video, refresco y CPU, y marcando el ritmo del Z80 con una onda
/WAIT de marcha libre. Eso lo convierte en el host más sensible a la temporización de cuantos acciona el picoZ80. Por ello el controlador combina una precisión de temporización del bus a nivel de nanosegundos — medida frente a un Z80 real con un analizador lógico, véase
Fidelidad de la Temporización del Bus — con una arquitectura de sombra en PSRAM que saca por completo la DRAM de la máquina de la ruta crítica de la CPU.
Variantes de Máquina
Un único núcleo de controlador — src/drivers/Amstrad/PCW9512.c — sirve a toda la familia, condicionado por variante en la inicialización. La persona PCW9512 modela la máquina de margarita con unidades de 3.5" de 720K; la persona PCW8256 cubre tanto el PCW-8256 como el PCW-8512 (impresora matricial, unidades CF2 de 3"). Las dos máquinas difieren únicamente en la impresora: PCW8256.c aporta el modelo de estado de la impresora matricial, cuyos bits de estado en el puerto FDh no comparten los significados de la margarita en el mismo puerto. La geometría de la unidad procede de la imagen de disco montada, por lo que no hace falta ningún cambio de controlador entre imágenes CF2 de 180K y de 720K.
Modos de Controlador PHYSICAL y VIRTUAL
El
type de nivel superior del controlador selecciona uno de dos modos de funcionamiento. Ambos siguen exactamente la misma ruta de preparación de la máquina física — la diferencia está en qué protege a la CPU de la DRAM de la máquina:
- PHYSICAL — cada acceso es un ciclo de bus real, exactamente como lo emitiría un Z80 genuino. Es el modo auténtico, y la referencia de precisión: el bus está ajustado en temporización al silicio real. La temporización interna marginal del gate array todavía puede provocar algún píxel suelto ocasional, porque no hay sombra que corrija un ciclo de DRAM mal capturado.
- VIRTUAL — la misma máquina física, más la sombra de la DRAM descrita a continuación. La CPU se ejecuta desde la sombra: estable, notablemente más rápida (LocoScript lo hace visiblemente), inmune a las peculiaridades de la DRAM de la máquina y capaz de albergar memoria de expansión — mientras la pantalla y el hardware se comportan de forma idéntica.
El nombre de interfaz heredado
MIRROR de configuraciones anteriores sigue reconociéndose y simplemente habilita la sombra — el modo VIRTUAL del controlador
es el modo espejo.
La Sombra de la DRAM
En modo VIRTUAL el controlador mantiene una sombra byte a byte de la DRAM de la máquina en la PSRAM del RP2350. El
hook de escritura se instala desde el encendido, de modo que cada escritura de la CPU llega tanto a la sombra como al bus y la sombra nunca puede desincronizarse. Los
hooks de lectura y de fetch se arman cinco segundos después del bootstrap, una vez que la carga del sistema operativo está en marcha. A partir de ese momento:
- Los accesos servidos no usan el bus. Un fetch o una lectura de un byte en sombra no realiza ningún ciclo de bus — la ejecución corre desde la PSRAM. Cada acceso servido se ritma con ~250 ciclos del RP2350 (~758ns, unos 3 estados T a 4MHz) para que los bucles de retardo calibrados por software mantengan una temporización auténtica.
- Un mapa de bits de validez por byte garantiza la corrección: un byte que el hook de escritura nunca ha visto se lee siempre desde el bus real.
- Una guarda de teclado de 16 bytes en la parte alta del bloque 3 se lee siempre desde el bus — el controlador de teclado 8041 del PCW escribe la matriz de teclas en la DRAM a espaldas de la CPU, así que esa ventana nunca debe servirse desde la sombra.
- Un keepalive M1 emite un ciclo real de fetch de opcode, ajustado en temporización, por cada 256 fetches servidos (~10kHz). El gate array deriva su planificación de refresco de la DRAM y la generación de la interrupción de 300Hz del tráfico de bus observado, de modo que esto mantiene viva la máquina mientras elimina ~99.6% de los strobes del bus.
- Un filtro de escritura solo-video envía los ciclos de escritura al bus real únicamente donde el ASIC lee de verdad — la roller RAM y las líneas de barrido a las que apuntan sus entradas — rastreado en un mapa de granularidad de 512 bytes sobre los 128K inferiores. El mapa falla en modo abierto: ante cualquier duda la escritura pasa al bus.
El efecto neto es que la DRAM de la máquina solo la lee el ASIC (para el video), la CPU nunca consume un byte corrompido y el bus transporta una pequeña fracción de su tráfico normal — lo que de por sí es la mayor reducción de exposición a los ciclos marginales del gate array.
Reparación de Video
Como la sombra contiene el valor verdadero de cada byte de video, la corrupción residual de la DRAM ya no necesita evitarse a la perfección — se repara. Un barrido en segundo plano de verificar y luego escribir recorre las regiones de video, relee cada byte desde la DRAM real y reescribe desde la sombra únicamente los daños genuinos. El barrido está dirigido por los fetches en breves mini-ráfagas, cubriendo la pantalla completa en aproximadamente 0.6 segundos, de modo que cualquier corrupción es un transitorio de menos de un segundo. Dado que el barrido solo escribe cuando hay discrepancia, su recuento de bytes reparados es un verdadero medidor de corrupción, notificado en la línea periódica de telemetría PCW HEALTH.
Disquete — uPD765 Virtual, Guarda del FDC y Cambio de Pletina
La interfaz
FDC selecciona un NEC uPD765 virtual que sirve imágenes
.dsk en formato CPC desde la tarjeta SD. Con un FDC virtual la máquina
arranca sin disco: el manejador se instala antes de que se ejecute el bootstrap y notifica "no listo" en el registro de estado principal mientras se carga la imagen, de modo que la ROM de arranque simplemente espera — exactamente como lo haría con una unidad que está acelerando. Un PCW con la unidad de disquete averiada o ausente arranca y funciona íntegramente desde imágenes.
Con un FDC
físico, una guarda del FDC protege el bucle de transferencia: el uPD765 real sufre un overrun si un byte no se atiende en ~26µs, así que cualquier acceso a un puerto del FDC aparca el servicio de lectura/fetch de la sombra durante la transferencia, rearmándose tras un segundo de silencio del FDC. Los accesos al FDC virtual usan en su lugar un tiempo de espera separado de 100ms para la reparación, de modo que la reparación nunca perturba una transferencia de sector pero se reanuda casi inmediatamente después.
La pletina de disquete puede
cambiarse entre Virtual y Real en tiempo de ejecución desde el menú Floppy de la GUI web. Al cambiar a la unidad real se acondiciona primero el controlador físico — un SPECIFY y un RECALIBRATE con purga de interrupciones obsoletas, ya que un controlador que nunca ha sido tocado no tiene ni idea de dónde están sus cabezas — y después se reproduce la elección del BIOS sobre el enrutamiento de la interrupción del FDC a NMI. Al volver atrás se reinicia el controlador virtual conservando sus imágenes cargadas. Esto permite leer discos reales, o rescatarlos a imágenes, en mitad de la sesión en una máquina que arrancó sin disco.
Memoria de Expansión
La interfaz MEMORY (llamada EXPANSION en configuraciones más antiguas — se aceptan ambos nombres) añade memoria por bancos servida desde la PSRAM por encima de la RAM propia de la máquina. La sombra proporciona 96 bancos de 16K — 1.5MB de memoria direccionable en total — de modo que un PCW-8256 de 256K gana hasta 1.25MB y un PCW-8512/9512 de 512K gana 1MB. CP/M dimensiona la memoria extra en una unidad RAM M: mucho mayor. Las páginas de expansión existen solo en la PSRAM y nunca se ponen en el bus: la DRAM real de la máquina simplemente se repite por encima de su tamaño instalado, así que la expansión se comporta como una RAM perfecta y rápida. La PSRAM está organizada como sigue (páginas de 64KB):
| Páginas de 64KB |
Contenido |
| 0–7 |
Conjunto de trabajo del núcleo del firmware |
| 8–31 |
Sombra de la DRAM + memoria de expansión (1.5MB) |
| 32–34 |
Mapa de bits de validez de la sombra |
| 35–47 |
Búfer de imagen de la unidad de disco 0 |
| 48–60 |
Búfer de imagen de la unidad de disco 1 |
| 61 |
Área temporal para la creación de imágenes de disco |
| 62–63 |
Libre |
El Modelo de Paginación — Medido, No Supuesto
La sombra debe seguir con exactitud la decodificación de bancos del gate array, así que el modelo de paginación se
midió en hardware real con el diagnóstico
PAGEPROBE del controlador (un barrido de un solo disparo que siembra firmas a través de todos los valores del registro de banco y relee qué página física mapea cada uno — la máquina se sacrifica a la medición y necesita un ciclo de alimentación después). Se sondearon los gate arrays tanto del PCW-8256 como del PCW-9512 y decodifican de forma idéntica:
- Paginación PCW extendida (bit 7 del valor de banco a 1): página física =
(value & 0x7F) mod nBanks — el aliasing por módulo es real y está verificado.
- Paginación al estilo CPC (bit 7 a 0): página de lectura
(value >> 4) & 7 y página de escritura value & 7 separadas.
- Bits de bloqueo del puerto F4 fuerzan las lecturas de un bloque bloqueado a su banco de escritura. El orden de los bits no es secuencial y se determinó empíricamente: bloque 0 = 0x40, bloque 1 = 0x10, bloque 2 = 0x20, bloque 3 = 0x80.
Trabajo de Temporización y Telemetría
La compilación Amstrad lleva su propia copia del motor de bus PIO —
src/z80_amstrad.pio, idéntico byte a byte a
z80.pio en el momento de su creación — de modo que el trabajo de temporización específico del PCW nunca puede perturbar el motor probado con el que funcionan las máquinas Sharp, Tatung y Base. Sobre él, el controlador usa flujos de instrucciones ajustados en temporización que sitúan cada flanco de las líneas de control donde lo pone un Z80 real: la comparación con analizador lógico mostró que los flujos originales activaban las líneas de control 10–25ns antes que el silicio real, y que el gate array decodificaba mal, de forma visible, esos flancos tempranos. Medidas adicionales:
- Ciclos de E/S y escritura sincronizados con la rejilla — cada petición de E/S y de escritura se fija a la fase en la que el gate array la integra en su rotación de DRAM a 1MHz, en lugar de forzar al ASIC a insertar una ranura de servicio fuera de la rejilla.
- Reconocimiento de interrupción IM1 — la compilación Amstrad emite el ciclo de bus real de reconocimiento M1+IORQ en cada interrupción aceptada, como hace el silicio genuino. (Existe un flag de compilación genérico
Z80_IM1_INTA para otras máquinas, desactivado por defecto.)
- Perfilador de rejilla WAIT sin analizador lógico — en cada arranque el controlador muestrea los pines
/WAIT y CLK con una resolución de ~13ns e imprime una línea PCW GRID: (periodo de WAIT, anchura de pulso, periodo de CLK, fase) frente a la tarjeta de referencia, de modo que la temporización del gate array de una máquina puede comprobarse sin conectar un analizador lógico.
- Telemetría de salud — una línea periódica
PCW HEALTH: notifica recuentos de huecos de fetch, divergencia de la sombra, enrutamiento de escrituras (bus frente a sombra), bytes reparados y estadísticas del barrido.
Las personas PCW suministradas ejecutan el RP2350 a 330MHz con el núcleo a 1.20V.
Dispositivos Periféricos Virtuales en la Compilación Amstrad
Restricción: el framework de Dispositivos Periféricos Virtuales no está disponible en la práctica en la compilación Amstrad — sus límites de instancias se recortan a un único dispositivo para liberar la SRAM interna que necesita la sombra de la DRAM. Las demás compilaciones no se ven afectadas.
Fidelidad de la Temporización del Bus — picoZ80 frente a un Z80 Real
¿Cuánto se acerca el bus del picoZ80 al silicio genuino? En el Amstrad PCW — el host cuyo gate array media en cada acceso a memoria y es por tanto el juez más severo — la pregunta se respondió midiendo: se capturó la temporización de los ciclos de bus del picoZ80 y se comparó con la de un Zilog Z80 genuino en el mismo PCW-8256, en la misma placa y monitor.
Método
- Capturado con un analizador lógico Kingst LA5016 a 500MHz (resolución de 2ns) sobre el bus en funcionamiento: CLK, /WAIT, /MREQ, /RD, /WR, /IORQ, /M1, /RFSH, /INT, A15, A0, D7, D0.
- Cada cifra es la mediana de varios miles de ciclos en una ventana en régimen estacionario.
- Las capturas de referencia del Z80 real y las del picoZ80 se tomaron en condiciones equivalentes (CP/M en el prompt
A> / copia con PIP). El picoZ80 se midió a dos relojes de núcleo, 340MHz y 350MHz, para mostrar que el resultado es independiente del reloj.
- Las posiciones de los flancos se expresan respecto al flanco de CLK más cercano: los strobes de memoria se refieren al flanco descendente de CLK (mitad del estado T); /M1 y /IORQ se refieren al flanco ascendente de CLK (inicio de T1), tal como los especifica la hoja de datos del Z80.
Comparación de Temporización
| Parámetro |
Z80 real |
picoZ80 @340MHz |
picoZ80 @350MHz |
Δ frente al real |
| Periodo de CLK |
250 ns |
250 ns |
250 ns |
0 ns |
| Pulso alto de /WAIT |
184 ns |
182 ns |
182 ns |
−2 ns |
| Caída de /MREQ (tras CLK↓) |
+42 ns |
+48 ns |
+46 ns |
+4…6 ns |
| Caída de /RD (tras CLK↓) |
+52 ns |
+48 ns |
+48 ns |
−4 ns |
| Caída de /WR (tras CLK↓) |
+36 ns |
+46 ns |
+46 ns |
+10 ns |
| Caída de /M1 (tras CLK↑) |
+54 ns |
+52 ns |
+50 ns |
−2…4 ns |
| Caída de /IORQ (tras CLK↑) |
+40 ns |
+28 ns |
+28 ns |
−12 ns |
| Anchura a nivel bajo de /MREQ (ciclo de mem.) |
500 ns |
498 ns |
496 ns |
−2…4 ns |
| Dirección válida antes de /MREQ |
115 ns |
~185 ns |
~180 ns |
+65 ns (antes) |
(Columna Δ: a qué distancia del Z80 real queda el picoZ80; el signo indica más tarde (+) o más temprano (−).)
Qué Muestran las Cifras
- Los tres strobes de la DRAM coinciden con el silicio real dentro de ~10ns, la mayoría dentro de 5ns: /MREQ, /RD y /M1 están todos a pocos nanosegundos del Z80 genuino, y /M1 — el identificador de fetch de opcode al que el ASIC del PCW es más sensible — cae en el objetivo a +50…52ns frente a los +54ns del chip real.
- El resultado es independiente del reloj: 340MHz y 350MHz producen las mismas posiciones de flanco dentro de la resolución de medida, confirmando que la temporización la fija la lógica de ciclo, no el reloj de núcleo del RP2350.
- La anchura del pulso de /MREQ coincide en ~500ns (un ciclo de memoria completo), así que el gate array ve el strobe activado durante la misma duración que con un Z80 real.
- El establecimiento de dirección es más largo que el de un Z80 real, no más corto (~180ns frente a 115ns). El picoZ80 impone la dirección pronto y la mantiene estable durante todo el ciclo, de modo que el ASIC siempre captura una dirección asentada — una desviación segura, no un riesgo.
- /IORQ es la mayor diferencia, 12ns antes. Los ciclos de E/S se dejan deliberadamente en marcha libre en lugar de alinearlos a la ranura, porque el gate array se refasa alrededor de la E/S por diseño; esto no afecta a la integridad de la DRAM.
Resumen
En todas las señales que gobiernan la DRAM del PCW, el picoZ80 reproduce un Zilog Z80 real dentro de un margen de un dígito hasta ~10 nanosegundos, con independencia del reloj de núcleo. Las únicas desviaciones significativas son el strobe de E/S (12ns antes, en ciclos que el ASIC refasa de todos modos) y un establecimiento de dirección más largo y más seguro. Para el acceso a memoria — la ruta que importa para la corrección — el bus del picoZ80 es, a la resolución de un analizador de 500MHz, el mismo que el silicio real. Este trabajo se realizó sobre el Amstrad PCW porque su gate array es el observador más exigente de la temporización del bus entre los hosts soportados; los flujos de ciclo ajustados en temporización están condicionados a Amstrad, mientras que todas las demás máquinas ejecutan el motor original, probado desde hace mucho tiempo.
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:
- Crear un nuevo archivo
.c / .h en el directorio src/drivers/.
- Implementar funciones handler de lectura y escritura que coincidan con las firmas anteriores.
- Registrar los punteros a funciones handler en las tablas
memioPtr o ioPtr durante la inicialización del controlador.
- Añadir el controlador al objetivo de compilación CMakeLists.txt.
- Añadir una entrada de cadena de tipo para que el parser de configuración JSON pueda instanciar el controlador por nombre.
- 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.
Dispositivos Periféricos Virtuales
Un dispositivo periférico virtual es un chip de apoyo completo — un Z80 DMA, un Z80 CTC, un 8255, etc. — añadido a una máquina simplemente declarándolo en config.json. El firmware modela el circuito integrado real, registro por registro, y el chip aparece en el espacio de direcciones del Z80 exactamente igual que lo haría una pieza física soldada a una placa. Los dispositivos se cablean entre sí mediante una netlist, de modo que el pin de salida de un chip puede excitar el pin de entrada de otro, igual que hace una pista en un circuito impreso.
Este es un mecanismo distinto del
Marco de Dispositivos Virtuales descrito anteriormente. Un bloque
FUNC es un
handler — código C que el firmware llama en cada acceso al bus, escrito por un desarrollador y compilado dentro del firmware. Un dispositivo periférico virtual es una
declaración — una instancia de un chip que el firmware ya modela, creada a partir de datos de configuración y sin código alguno. También es distinto de una
tarjeta de interfaz, que es un compuesto específico de máquina (imágenes ROM, medios, mapeo de direcciones) declarado bajo
drivers[].if[]. Los tres son pares, no capas: una tarjeta de interfaz puede muy bien contener el mismo chip que declara un dispositivo.
El propósito es permitir que cualquiera construya su propio sistema Z80 sobre un picoZ80 sin escribir firmware. Una personalidad de máquina aporta la CPU, la memoria y el vídeo; los dispositivos aportan los periféricos; y la netlist aporta el cableado entre ellos.
El Catálogo de Dispositivos
Hay trece dispositivos disponibles. La columna name es la cadena exacta que se usa en config.json. La columna Core de servicio importa para la temporización: los dispositivos servidos en el Core 0 son avanzados por un temporizador de marcha libre independiente del Z80, mientras que los dispositivos del Core 1 son accionados por la actividad del bus.
name |
Chip modelado |
Pines |
Tamaño de decodificación |
Core de servicio |
Z80DMA |
Controlador de Acceso Directo a Memoria Zilog Z8410 |
10 |
1 |
Core 1 |
Z80CTC |
Circuito Contador / Temporizador Zilog Z8430 |
10 |
4 |
Core 0 |
Z80PIO |
Controlador de Entrada / Salida Paralela Zilog Z8420 |
23 |
4 |
Core 1 |
PIT8253 |
Temporizador de Intervalo Programable Intel 8253 / 8254 |
6 |
4 |
Core 0 |
PPI8255 |
Interfaz Periférica Programable Intel 8255 |
24 |
4 |
Core 1 |
WD1773 |
Controlador de Disquete Western Digital WD1773 |
5 |
8 |
Core 1 |
MC6850 |
Puerto serie asíncrono ACIA Motorola 68B50 |
4 |
2 |
Core 1 |
I8251 |
Puerto serie USART Intel 8251 / 8251A |
7 |
2 |
Core 1 |
Z80DART |
Zilog Z80 DART — canal serie asíncrono dual (comparte el modelo de chip del Z80 SIO) |
13 |
4 |
Core 1 |
MSM5832 |
Reloj de tiempo real / calendario OKI MSM5832 |
1 |
16 |
Core 1 |
Centronics |
Puerto de impresora paralelo Centronics — la interfaz, no un chip |
4 |
2 |
Core 1 |
Latch |
no es un chip real — un latch / puerto de propósito general |
16 |
1 |
Core 1 |
SignalPort |
no es un chip real — un puerto de prueba y puente |
16 |
1 |
Core 1 |
Además de los dispositivos declarados, siempre existe un pseudo-dispositivo permanente llamado cpu. Expone los propios pines de control del procesador, de modo que un dispositivo pueda cablearse a la máquina misma.
Declarar un Dispositivo
Los dispositivos se declaran en un array device dentro de un objeto de partición z80[], junto a los arrays ya existentes memory, io y drivers. Cada partición lleva sus propios dispositivos y su propia netlist, así que ambas pueden equiparse de forma muy distinta. Fíjese bien en que name selecciona qué chip, mientras que type selecciona qué espacio de direcciones — esto suele pillar desprevenida a la gente.
| Clave |
Tipo |
Requerido |
Por defecto |
Descripción |
name |
cadena |
sí |
— |
Tipo de chip del catálogo anterior. No distingue mayúsculas de minúsculas. Un nombre no reconocido se omite con un mensaje de registro. |
addr |
número |
sí |
— |
Dirección base de la decodificación. |
id |
cadena |
no |
dev<i>N</i> |
Etiqueta de la instancia, usada para referirse a este chip al cablearlo. Máximo 11 caracteres; los nombres más largos se truncan. |
type |
cadena |
no |
"IO" |
Espacio de direcciones — "MEMORY" coloca el chip en el espacio de memoria; cualquier otro valor lo coloca en el espacio de E/S. |
size |
número |
no |
1 |
Número de direcciones consecutivas decodificadas. |
enable |
número |
no |
habilitado |
0 deshabilita el dispositivo. Use el número 0, no false. |
busack |
número |
no |
1 |
Activar el reconocimiento de bus del anfitrión mientras este dispositivo posee el bus. Solo Z80 DMA. |
maxburst |
número |
no |
4096 |
Máximo de bytes movidos antes de permitir que el procesador recupere el bus. Solo Z80 DMA; 0 elimina la salvaguarda. |
Algunos dispositivos admiten claves adicionales propias. Las claves machine, drives y file del controlador de disquete y la clave clockhz del 8253 se describen junto a sus pinouts más abajo; los dispositivos serie, de reloj, de impresora y de latch admiten las siguientes:
| Clave |
Se aplica a |
Por defecto |
Descripción |
usb |
MC6850, I8251, Z80DART, Centronics |
2 (serie), 4 (impresora) |
El canal USB CDC (0–4) que transporta el flujo de datos del dispositivo. El DART reclama dos canales consecutivos: usb es el canal A y usb+1 es el canal B. |
hours |
MSM5832 |
24 horas |
El número 12 selecciona el modo de 12 horas; cualquier otro valor, u omitir la clave, selecciona el modo de 24 horas. |
strobe |
Centronics |
auto |
"control" captura el byte a imprimir en el flanco ascendente del bit 0 del registro de control; por defecto se captura en la propia escritura del registro de datos. |
int |
Centronics |
0 |
Un valor distinto de cero activa la línea de interrupción a medida que se acepta cada byte; la lectura de estado la borra. |
readback |
Latch |
muestreo |
"latch" hace que una lectura devuelva el último byte escrito; por defecto se muestrean los ocho pines IN. |
Adónde van los flujos de bytes. Cada dispositivo serie es un flujo de bytes en uno de los cinco canales USB CDC del puerto USB del picoZ80 — conecte la placa a un PC y los datos del chip aparecen allí como un puerto serie ordinario. Los registros de velocidad en baudios y de formato de palabra de los chips serie se registran pero no se simulan: los datos se mueven a la velocidad del USB independientemente de lo que programe el invitado. La impresora es solo de salida y activa BUSY mientras su cola de 511 bytes está llena, de modo que el controlador del invitado reintenta y nunca se pierde ningún carácter. Los comandos ser, rtc y prn del shell de depuración inyectan caracteres en estos dispositivos, y leen de ellos, directamente — no hace falta conectar ningún terminal.
Los dispositivos se aplican después de las regiones memory e io de la personalidad, por lo que la decodificación de un dispositivo es la que manda — prevalece sobre cualquier región genérica que cubra la misma dirección. La decodificación debe caer íntegramente dentro del espacio de 64K o el dispositivo se omite.
"device": [ {
"name": "Z80DMA",
"id": "dma0",
"enable": 1,
"type": "IO",
"addr": 0x0F,
"size": 1,
"busack": 1,
"maxburst": 4096
},
{
"name": "Z80CTC",
"id": "ctc0",
"enable": 1,
"type": "IO",
"addr": 0x10,
"size": 4
} ]
Cablear Dispositivos Entre Sí — el Interlink
El array interlink es la netlist. Cada entrada describe un cable: un único pin de destino, y una o varias fuentes que lo excitan. Un pin se referencia como <id>.<PIN>, por ejemplo dma0.RDY o cpu.INT. Ninguna de las dos partes distingue mayúsculas de minúsculas.
| Clave |
Tipo |
Requerido |
Descripción |
dst |
cadena |
sí |
El único pin de entrada que este cable excita. Cada entrada puede ser excitada por exactamente un cable. |
src |
array |
sí |
De uno a ocho términos fuente. Un término es una referencia a un pin, o la cadena literal "0" o "1". Un ! inicial invierte ese término. |
op |
cadena |
no |
Cómo se combinan varias fuentes — BUF, NOT, AND, OR, NAND, NOR, XOR, XNOR. Por defecto BUF para una fuente y OR para dos o más. |
enable |
número |
no |
0 deshabilita este cable. |
name |
cadena |
no |
Una etiqueta para su propia comodidad. El firmware la ignora. |
Importante: una red transporta el nivel eléctrico del pin, nunca un estado abstracto de "activado". 1 es alto y 0 es bajo, exactamente como lo leería un voltímetro. Los pines activos a nivel bajo como /INT y /BUSREQ reposan en alto y se activan pasando a bajo. Esto es lo que hace que la netlist se comporte como un esquema: una NOR de dos salidas de temporizador activas a nivel alto hace en el picoZ80 exactamente lo que hace sobre el papel.
Las Tres Maneras de Excitar un Pin
Directamente, uno a uno — omita op e indique una única fuente. Esto es un cable sencillo desde la salida de un chip hasta la entrada de otro.
{ "name": "la peticion de datos del disquete excita el DMA",
"dst": "dma0.RDY",
"src": [ "fdc0.DRQ" ] }
Amarrado en firme a un nivel fijo — indique el literal "0" o "1" como única fuente. Es el equivalente a atar un pin a masa o a la línea de alimentación, y es lo que debe hacer con cada entrada que el diseño no utilice. Una entrada sin cablear no es un error, pero se notificará en el arranque y leerá su nivel de reposo, de modo que amarrarla explícitamente documenta su intención.
{ "dst": "dma0.CE", "src": [ "0" ] } /* atado a bajo - seleccionado de forma permanente */
{ "dst": "dma0.WAIT", "src": [ "1" ] } /* atado a alto - nunca se activa */
A través de una condición lógica — indique de dos a ocho fuentes y un op. Los términos se combinan de izquierda a derecha, y cualquier término puede invertirse individualmente con un ! inicial. Es el equivalente a soldar una pequeña puerta lógica entre los chips.
/* El DMA funciona solo mientras ninguna salida de temporizador este alta. */
{ "name": "el temporizador habilita el DMA",
"dst": "dma0.RDY",
"op": "NOR",
"src": [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] }
/* El bit 1 del puerto A, invertido, excita el bit 0 del puerto B. */
{ "dst": "pio0.B0", "src": [ "!pio0.A1" ] }
Las líneas de interrupción son un caso especial que conviene entender. En el hardware real cada pin /INT es una salida de drenador abierto: cualquier chip puede tirar de la línea compartida hacia abajo, y esta solo sube cuando todos la liberan. La netlist reproduce esto con un AND de todas las salidas de interrupción. Debe escribir "op":"AND" de forma explícita — el valor por defecto para varias fuentes es OR, lo que invertiría el significado y dispararía una interrupción solo cuando todos los chips la activasen a la vez.
{ "name": "cadena de interrupciones",
"dst": "cpu.INT",
"op": "AND",
"src": [ "dma0.INT", "ctc0.INT", "pio0.INT" ] }
Prioridad de Interrupción — la Cadena Daisy
La prioridad entre chips que interrumpen la decide cómo los cablee usted, no un número en la configuración — exactamente igual que en un sistema Z80 real. Cada chip tiene una entrada IEI (interrupt enable in) y una salida IEO (interrupt enable out). Cablear el IEO de un chip al IEI del siguiente sitúa al segundo por debajo del primero en prioridad. El primer chip de la cadena tiene su IEI amarrado a 1.
Un chip pone su IEO en alto solo cuando su propio IEI está alto y además ni está interrumpiendo ni está siendo atendido. Esa única regla produce todo el comportamiento: un chip que está siendo atendido mantiene bloqueado todo lo que está por debajo de él hasta que termina, y un chip cuyo IEI ha pasado a bajo sabe que un dispositivo de mayor prioridad tiene la atención del procesador y permanece callado.
/* ctc0 tiene la prioridad mas alta, luego dma0, y luego pio0. */
{ "dst": "ctc0.IEI", "src": [ "1" ] },
{ "dst": "dma0.IEI", "src": [ "ctc0.IEO" ] },
{ "dst": "pio0.IEI", "src": [ "dma0.IEO" ] }
El modo de interrupción 2 está totalmente soportado. Al reconocerse la interrupción, el dispositivo de mayor prioridad que la esté solicitando y no esté bloqueado suministra su vector programado, se marca a sí mismo en servicio y limpia su indicador de pendiente; un RETI limpia de nuevo el estado en servicio y libera a los chips situados por debajo.
Las tarjetas físicamente instaladas siempre tienen prioridad sobre los dispositivos declarados. Un periférico real en el bus del anfitrión tiene sus propios pines IEI e IEO, que el picoZ80 no puede ver ni arbitrar — no hay pines de procesador libres para ellos — por lo que el firmware ofrece primero el reconocimiento a cualquier handler de tarjeta existente y solo lo reclama cuando ninguna tarjeta responde. Esto es lo que permite añadir dispositivos a una máquina que ya lleva instalada, por ejemplo, una tarjeta MZ-8BIO3 o una tarjeta serie MZ-1E24 sin perturbarla.
Patillaje de los Dispositivos
Las tablas siguientes enumeran todos los pines que cada dispositivo expone a la netlist. La dirección se da desde el punto de vista del chip. Reposo es el nivel que lee una entrada cuando nada la excita. Los pines marcados como declarado existen y pueden cablearse, pero el firmware todavía no actúa sobre ellos — se listan para que una configuración escrita hoy siga siendo correcta cuando entren en funcionamiento.
cpu — el pseudo-dispositivo del procesador
| Pin |
Dirección |
Reposo |
Función |
INT |
ent |
1 |
Petición de interrupción enmascarable. El único destino funcional en la CPU. Ponerlo a nivel bajo genera una interrupción. |
BUSACK |
sal |
1 |
Reconocimiento de bus. Declarado — todavía no se excita, así que se lee permanentemente alto. |
BUSRQ, NMI, RESET, WAIT |
ent |
1 |
Declarados — cablearlos todavía no tiene efecto. |
M1, CLK, HALT |
sal |
— |
Declarados — todavía no se excitan. |
Z80DMA — Acceso Directo a Memoria (Z8410)
| Pin |
Dirección |
Reposo |
Función |
RDY |
ent |
1 |
Petición de transferencia desde el periférico. La única entrada funcional. Su sentido activo lo fija el registro WR5 del chip, no la configuración. Una transición al estado activo inicia una transferencia. |
BUSREQ |
sal |
1 |
Petición de bus, activa a nivel bajo, drenador abierto. Se pone a bajo mientras el DMA retiene el bus. |
INT |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. |
IEI |
ent |
1 |
Entrada de prioridad de interrupción. Cablear desde el IEO del chip anterior, o amarrar a 1 si este es el primero de la cadena. |
IEO |
sal |
1 |
Salida de prioridad de interrupción. Cablear al IEI del siguiente chip. |
BAI, CE, WAIT, M1 |
ent |
1 |
Declarados — el modelo no los consulta. |
BAO |
sal |
1 |
Declarado — todavía no se excita. |
Z80CTC — Contador / Temporizador (Z8430)
| Pin |
Dirección |
Reposo |
Función |
CLKTRG0 … CLKTRG3 |
ent |
0 |
Entrada de reloj o disparo para los canales 0 a 3. El flanco significativo lo elige la palabra de control del canal. |
ZCTO0 … ZCTO2 |
sal |
0 |
Cuenta a cero / fin de tiempo para los canales 0 a 2, emitido como un pulso breve. El canal 3 no tiene pin de salida, exactamente igual que en el dispositivo real. |
INT |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. |
IEI |
ent |
1 |
Entrada de prioridad de interrupción — véase la cadena daisy. |
IEO |
sal |
1 |
Salida de prioridad de interrupción. |
Z80PIO — Entrada / Salida Paralela (Z8420)
| Pin |
Dirección |
Reposo |
Función |
A0 … A7 |
bidireccional |
1 |
Bits de datos del puerto A. La dirección sigue el modo programado por el Z80 en tiempo de ejecución. |
B0 … B7 |
bidireccional |
1 |
Bits de datos del puerto B. |
ASTB, BSTB |
ent |
1 |
Entradas de estrobo de puerto, activas a nivel bajo. El flanco de subida es el significativo. |
ARDY, BRDY |
sal |
0 |
Salidas de listo del protocolo de puerto, activas a nivel alto. |
INT |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. |
IEI |
ent |
1 |
Entrada de prioridad de interrupción — véase la cadena daisy. |
IEO |
sal |
1 |
Salida de prioridad de interrupción. |
Las cuatro direcciones decodificadas son, en orden: datos del puerto A, control del puerto A, datos del puerto B y control del puerto B. El reinicio selecciona el modo 1 (entrada) en ambos puertos.
PIT8253 — Temporizador de Intervalo (8253 / 8254)
| Pin |
Dirección |
Reposo |
Función |
GATE0 … GATE2 |
ent |
1 |
Entradas de habilitación de los contadores. Los modos 0, 2, 3 y 4 responden al nivel; los modos 1 y 5 se disparan con el flanco de subida. |
OUT0 … OUT2 |
sal |
1 |
Salidas de los contadores. |
Las cuatro direcciones decodificadas son los contadores 0, 1 y 2 seguidos de la palabra de control. El 8253 no tiene pin de interrupción. A este dispositivo se le aplica una clave de configuración adicional: "clockhz" fija la frecuencia de entrada del contador en hercios, con valor por defecto 1000000. Las máquinas Sharp MZ accionan su temporizador a 895000.
PPI8255 — Interfaz Paralela (8255)
| Pin |
Dirección |
Reposo |
Función |
PA0 … PA7 |
bidireccional |
1 |
Bits de datos del puerto A. |
PB0 … PB7 |
bidireccional |
1 |
Bits de datos del puerto B. |
PC0 … PC7 |
bidireccional |
1 |
Bits de datos del puerto C. Las mitades alta y baja tienen direcciones independientes. |
Las cuatro direcciones decodificadas son los puertos A, B y C seguidos de la palabra de control. Las líneas de protocolo de los modos 1 y 2 no se exponen a la netlist.
Limitación conocida
Excitar cualquier pin de entrada individual de un puerto del 8255 borra actualmente los demás bits de entrada de ese mismo puerto. Hasta que esto se corrija, use los pines de entrada del 8255 de uno en uno, o utilice en su lugar un Z80 PIO — el PIO trata cada bit de forma independiente y no se ve afectado.
WD1773 — Controlador de Disquete
| Pin |
Dirección |
Reposo |
Función |
DDEN |
ent |
1 |
Selección de doble densidad, activa a nivel bajo. Un nivel bajo selecciona MFM. |
WPRT |
ent |
1 |
Protección contra escritura, activa a nivel bajo. |
INTRQ |
sal |
0 |
Petición de interrupción. Activa a nivel alto en este dispositivo, a diferencia de los chips de Zilog. |
DRQ |
sal |
0 |
Petición de datos. Pensada para excitar el pin RDY de un DMA. |
MOTOR |
sal |
0 |
Motor encendido. |
Las ocho direcciones decodificadas siguen la disposición del Sharp MZ-1E05: estado y comando, pista, sector, datos, selección de unidad y motor, selección de cara, densidad y una segunda lectura de estado. Se aplican tres claves de configuración adicionales — "machine" selecciona la geometría de la imagen ("MZ-80B", "MZ-2500", o MZ-700 si se omite), "drives" fija el número de unidades de 1 a 4, y "file" nombra la imagen o imágenes de disco a montar. Solo puede declararse un WD1773.
MC6850 — Puerto Serie (ACIA 68B50)
| Pin |
Dirección |
Reposo |
Función |
DCD |
ent |
0 |
Detección de portadora, activa a nivel bajo. En reposo está activada para que un diseño sin cablear siga recibiendo. |
CTS |
ent |
0 |
Listo para enviar (clear to send), activa a nivel bajo. En reposo está activada para que un diseño sin cablear siga transmitiendo. |
RTS |
sal |
1 |
Petición de envío (request to send), activa a nivel bajo. Solo se activa cuando el registro de control lo selecciona. |
IRQ |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. |
Las dos direcciones decodificadas son control (escritura) / estado (lectura) y, a continuación, datos. Escribir 11 en el campo de división del contador del registro de control es el reset maestro del chip, exactamente como en el componente real; por lo demás, la relación de división y el formato de palabra se registran pero no se simulan. Un overrun de recepción se señala en el registro de estado y se borra leyendo el registro de datos.
Nota de cableado
La salida de interrupción del 68B50 se llama IRQ, como en su hoja de datos — no INT. El auto-cableado al añadir de la interfaz web acepta cualquiera de los dos nombres y lo une a cpu.INT, en AND cableado con las demás fuentes de interrupción (interfaz web 3.58 en adelante; las versiones anteriores solo reconocían INT y dejaban sin cablear la interrupción del 68B50).
I8251 — Puerto Serie (USART 8251)
| Pin |
Dirección |
Reposo |
Función |
DSR |
ent |
0 |
Equipo de datos listo (data set ready), activa a nivel bajo. En reposo está activada. |
CTS |
ent |
0 |
Listo para enviar (clear to send), activa a nivel bajo. En reposo está activada. |
DTR |
sal |
1 |
Terminal de datos listo (data terminal ready), activa a nivel bajo. |
RTS |
sal |
1 |
Petición de envío (request to send), activa a nivel bajo. |
TXRDY |
sal |
0 |
Transmisor listo, activa a nivel alto. |
RXRDY |
sal |
0 |
Receptor listo, activa a nivel alto. |
INT |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. RxRDY y TxRDY se combinan en OR sobre este pin. |
Las dos direcciones decodificadas son datos y, a continuación, control (escritura) / estado (lectura) — nótese el orden opuesto al del 68B50. Se respeta la secuencia de escritura característica del 8251: tras el reset la primera escritura de control es la instrucción MODE, un modo síncrono va seguido de uno o dos caracteres SYNC, y solo entonces entra en vigor el registro COMMAND; un comando con el bit de reset interno devuelve el chip a la espera de un modo. Los caracteres de sincronismo se almacenan pero no se emula ningún entramado síncrono, y los bits de error de paridad y de trama nunca se activan. Una escritura de datos con el transmisor deshabilitado se descarta, como en el componente real.
Z80DART — Puerto Serie Dual (Z80 DART)
| Pin |
Dirección |
Reposo |
Función |
DCDA, DCDB |
ent |
0 |
Detección de portadora de los canales A y B, activa a nivel bajo. En reposo está activada. |
CTSA, CTSB |
ent |
0 |
Listo para enviar de los canales A y B, activa a nivel bajo. En reposo está activada. |
RIA, RIB |
ent |
1 |
Indicador de llamada. Declarado — el modelo no los consulta. |
RTSA, RTSB |
sal |
1 |
Petición de envío, activa a nivel bajo, siguiendo a WR5. |
DTRA, DTRB |
sal |
1 |
Terminal de datos listo, activa a nivel bajo, siguiendo a WR5. |
INT |
sal |
1 |
Petición de interrupción, activa a nivel bajo, drenador abierto. |
IEI |
ent |
1 |
Entrada de prioridad de interrupción — véase la cadena daisy. |
IEO |
sal |
1 |
Salida de prioridad de interrupción. |
Las cuatro direcciones decodificadas son, en orden: datos A, control A, datos B, control B. El DART es el único dispositivo serie que se incorpora a la cadena daisy de interrupciones, con interrupciones totalmente vectorizadas — el vector de WR2 se suministra en el reconocimiento con status-affects-vector aplicado, exactamente como hace el SIO. No es casualidad: el DART comparte el modelo de chip del Z80 SIO en el firmware — un mismo circuito integrado, dos caras de dispositivo — y se ofrece aquí como "el SIO sin los modos síncronos", que nunca se dan sobre un flujo de bytes USB. Un overrun de recepción fija el bit de error en lugar de descartarse en silencio. El DART ocupa dos canales USB: su canal usb para A y el siguiente para B.
MSM5832 — Reloj de Tiempo Real
| Pin |
Dirección |
Reposo |
Función |
HOLD |
ent |
0 |
Congela los registros legibles, activa a nivel alto. Al liberarla se relee el reloj inmediatamente. |
Los cuatro bits bajos de dirección son la selección de registro del chip, razón por la que el dispositivo ocupa un bloque de dieciséis direcciones: los registros 0 a 12 son los trece dígitos BCD desde los segundos hasta el año. El reloj obtiene la hora del sistema del ESP32 a través del enlace entre procesadores — el reloj propio del ESP32 lo fija el navegador en cada carga de página o el SNTP de la pasarela, y la hora local que notifica se reintenta cada diez segundos hasta que ha sido fijada de verdad, de modo que el chip lee la fecha y la hora correctas poco después de que lo haga la red. El comando rtc del shell de depuración muestra si el reloj se ha sincronizado. Un reloj que el programa invitado ya ha puesto en hora por sí mismo se deja intacto. Escribir fija el reloj como un desplazamiento respecto a la hora del sistema: una rutina de puesta en hora escribe los dígitos y termina con el registro de unidades de segundos, que es la escritura que recalcula el desplazamiento. El modo de doce horas (la clave hours) activa el flag PM en el registro de decenas de horas exactamente como lo hace el componente real.
Centronics — Puerto de Impresora
| Pin |
Dirección |
Reposo |
Función |
BUSY |
sal |
0 |
Impresora ocupada, activa a nivel alto. Se activa mientras la cola de impresión está llena. |
ACK |
sal |
1 |
Reconocimiento. Declarado — todavía no se excita; el reconocimiento aparece en su lugar en el registro de estado. |
STROBE |
ent |
1 |
Strobe externo. Declarado — no se actúa sobre él; el strobe se realiza mediante el registro de control o la escritura de datos. |
INT |
sal |
1 |
Interrupción en el reconocimiento, activa a nivel bajo, drenador abierto. Se habilita con la clave de configuración int. |
Las dos direcciones decodificadas son datos (una lectura devuelve el latch, una escritura captura un byte) y, a continuación, estado (lectura) / control (escritura). Es un modelo de la interfaz, no de ningún chip concreto: el extremo remoto es un canal serie USB, así que el registro de estado siempre notifica una impresora presente, seleccionada y en buen estado. El bit de estado de reconocimiento es de un solo disparo y se borra con la lectura de estado, y el bit de control INIT vacía la cola.
Latch — latch / puerto de propósito general
| Pin |
Dirección |
Reposo |
Función |
IN0 … IN7 |
ent |
0 |
Una lectura del Z80 en la dirección ensambla estos ocho niveles en los bits 0 a 7. |
OUT0 … OUT7 |
sal |
0 |
Una escritura del Z80 excita estos ocho niveles a partir de los bits 0 a 7. |
La lógica de pegamento que todo diseño casero tiene para la conmutación de bancos y las líneas de control — invisible en el hardware real porque no es más que un latch de la serie 74 y un búfer, pero aquí está cableada directamente en la netlist y es direccionable desde código Z80. Una escritura excita los ocho cables OUT; una lectura muestrea los ocho cables IN, o devuelve el último byte escrito cuando se establece "readback": "latch". El latch decodifica una única dirección, y su valor sobrevive a un reset del Z80 — solo se borra cuando se aplica la configuración.
SignalPort — puerto de prueba y puente
| Pin |
Dirección |
Reposo |
Función |
IN0 … IN7 |
ent |
0 |
Una lectura del puerto por parte del Z80 devuelve estos ocho niveles como los bits 0 a 7. |
OUT0 … OUT7 |
sal |
0 |
Una escritura del Z80 excita estos ocho niveles. |
Esto no es el modelo de ningún chip real. Es el instrumento de laboratorio de la netlist: pone ocho cables en una única dirección de E/S para que un programa que corra en el Z80 pueda inyectar señales en el diseño y muestrear lo que vuelve. Es la forma más directa de probar una idea de cableado antes de comprometerse con ella.
Ejemplo Resuelto — Añadir un Z80 DMA
Lo siguiente es una configuración completa y funcional. Coloca un Z80 DMA en el puerto de E/S 0x0F, un Z80 CTC en 0x10, y cablea ambos de modo que el temporizador habilite la transferencia. Toda entrada del DMA no utilizada se amarra a un nivel fijo, y las dos salidas de interrupción se combinan mediante AND cableada sobre el pin de interrupción del procesador.
"device": [ { "name": "Z80DMA", "id": "dma0", "enable": 1,
"type": "IO", "addr": 0x0F, "size": 1,
"busack": 1, "maxburst": 4096 },
{ "name": "Z80CTC", "id": "ctc0", "enable": 1,
"type": "IO", "addr": 0x10, "size": 4 } ],
"interlink": [
/* Entradas del DMA no usadas, amarradas para documentar su estado. */
{ "name": "reconocimiento de bus del dma", "dst": "dma0.BAI", "src": [ "cpu.BUSACK" ] },
{ "name": "habilitacion de chip del dma", "dst": "dma0.CE", "src": [ "0" ] },
{ "dst": "dma0.WAIT", "src": [ "1" ] },
{ "dst": "dma0.IEI", "src": [ "1" ] },
{ "dst": "dma0.M1", "src": [ "1" ] },
/* Entradas de disparo del CTC no usadas. */
{ "dst": "ctc0.CLKTRG2", "src": [ "0" ] },
{ "dst": "ctc0.CLKTRG3", "src": [ "0" ] },
{ "dst": "ctc0.IEI", "src": [ "1" ] },
/* Dos salidas del temporizador habilitan la peticion de transferencia
a traves de una puerta NOR. */
{ "name": "el temporizador habilita el dma",
"dst": "dma0.RDY", "op": "NOR",
"src": [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] },
/* Ambas salidas de interrupcion son de drenador abierto y forman una
AND cableada sobre la CPU. */
{ "name": "cadena de interrupciones",
"dst": "cpu.INT", "op": "AND",
"src": [ "dma0.INT", "ctc0.INT" ] } ]
Todo lo demás sobre el DMA — si mueve bytes o busca uno, en qué dirección los mueve, la longitud del bloque, las direcciones, y si trabaja en modo byte, ráfaga o continuo — lo programa el Z80 a través de los propios registros del chip en tiempo de ejecución, exactamente igual que ocurriría con hardware real. La configuración decide únicamente dónde se sitúa el chip y a qué está cableado.
Una transferencia de memoria a memoria merece una nota, porque es lo primero que casi todo el mundo intenta. Tal transferencia no tiene ningún periférico que la solicite, así que no hay línea RDY que excitar. El Z80 DMA resuelve esto en el hardware real con el comando Force Ready (B3), que suministra internamente la condición de listo, y el modelo del picoZ80 hace lo mismo. Recuerde que ENABLE DMA (87) debe ser el último byte escrito, porque casi cualquier otro byte de control deshabilita el chip. Force Ready se cancela al liberar el bus y al final del bloque, de modo que una transferencia que deba reiniciarse a sí misma tiene que tomar su señal de listo de una fuente cableada, como la salida de un temporizador, en lugar del comando Force Ready.
Límites y Restricciones Actuales
El marco está acotado por tablas de tamaño fijo. Sobrepasar cualquiera de ellas se notifica en el registro de arranque, pero por lo demás la configuración parecerá cargarse.
| Límite |
Valor |
Notas |
| Dispositivos |
12 |
Un único presupuesto compartido por todos los tipos — doce chips en total, en cualquier combinación. Las entradas adicionales se ignoran. |
| Señales en total |
192 |
Entre todos los dispositivos, más los nueve pines de la CPU. |
| Cables del interlink |
96 |
Las entradas adicionales se ignoran. |
| Fuentes por cable |
8 |
Los términos adicionales se ignoran. |
| Iteraciones de estabilización |
8 |
Suficientes para biestables acoplados en cruz; una oscilación genuina se notifica. |
| Identificador de dispositivo |
11 caracteres |
Los identificadores más largos se truncan. |
Dos excepciones al presupuesto compartido: solo puede declararse un WD1773, y los cuatro dispositivos de flujo — MC6850, I8251, Z80DART y Centronics — toman sus ranuras de línea de interrupción de un único fondo común; un chip que exceda el fondo se inicializa igualmente pero funciona sin línea de interrupción. Los dispositivos con muchos pines agotan antes el presupuesto de señales: un 8255 son 24 pines, un PIO 23, un Latch o SignalPort 16.
Los nueve pines de la CPU se registran los últimos, así que un diseño que agote la tabla de señales pierde cpu.INT el primero y todos los cables de interrupción fallan silenciosamente al resolverse. Si las interrupciones dejan de funcionar en un diseño grande, cuente sus pines.
Limitaciones Importantes
- Los dispositivos contadores solo avanzan bajo el modelo BaseZ80. El tick de marcha libre del Core 0 que da reloj al Z80 CTC y al 8253 solo lo arranca el firmware BaseZ80. Bajo las compilaciones Sharp, Amstrad, Tatung y OpenZ80 estos dos dispositivos se declaran y son direccionables, pero sus contadores no corren.
- Un dispositivo no se reinicia con un reinicio del procesador. Los dispositivos se inicializan una sola vez, cuando se aplica la configuración.
- Un DMA virtual no puede ser disparado por un periférico real. El picoZ80 alcanza únicamente el zócalo del Z80, y todos los pines de propósito general del RP2350 están ya asignados, así que no hay manera de introducir en la netlist la línea de petición de datos de una tarjeta física. Un DMA virtual toma su petición de una constante, de Force Ready, o de otro dispositivo virtual — siendo la salida
DRQ del controlador de disquete el ejemplo obvio. Esto es una propiedad del hardware, no un defecto.
- Las claves
mirror, refresh y trace se aceptan pero actualmente no tienen efecto.
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 hasta cinco 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, y son también los canales por defecto que reclaman los dispositivos serie declarados (
MC6850,
I8251,
Z80DART — véase
Dispositivos Periféricos Virtuales).
CDC 4 es el canal dedicado a un dispositivo de impresora
Centronics declarado. Estos puertos no tienen un UART físico detrás: son puentes de búfer en anillo hacia el chip emulado, de modo que los datos escritos en el host aparecen en el dispositivo que ve el invitado, y viceversa.
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 |
next |
next [n] |
Ejecutar paso a paso n instrucciones, saltando por encima de las llamadas a subrutina (alias n) |
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 |
qdprobe |
qdprobe [seconds] / qdprobe eng [pace_us] |
Accionar manualmente el SIO del Quick Disk REAL (puertos F4/F6/F7) y mapear sus señales — motor en marcha, búsquedas (hunts) y eventos de sincronismo (A = ChA RR0: b3=HDST, b4=hunt, b0=Rx; B = ChB RR0: b3=HOME) — durante hasta seconds (por defecto 20, máximo 60), manteniendo detenida la CPU Z80 hasta que go la reanude. eng [pace_us] reproduce la secuencia exacta de init/hunt/lectura del motor QD de TZFS a un ritmo entre accesos configurable, para validar la temporización del hardware real |
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) |
ser |
ser [id] [tx <text> \| rx] |
Estado de un dispositivo serie (MC6850 / I8251 / Z80DART). tx inyecta caracteres en el dispositivo, rx lee lo que envió el invitado — sin necesidad de terminal. En el DART la inyección es solo por el canal A |
rtc |
rtc [id] |
Volcar los registros del reloj de tiempo real MSM5832 decodificados en una fecha y hora legibles |
prn |
prn [id] [dump] |
Estado del dispositivo de impresora Centronics; dump vacía la cola de impresión en el shell en lugar de en el USB |
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 (60MHz, 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) — la ruta del puente USB-a-UART: el RP2350 reenvía un canal USB CDC a la consola del ESP32 para que el ESP32 pueda flashearse y monitorizarse a través del puerto USB del RP2350, y el ESP32 a su vez puede programar el RP2350.
El enlace FSPI se endureció en septiembre de 2026 después de que una clase de fallos diferidos por punteros descontrolados se rastreara hasta una respuesta tardía que llegaba después de que un comando hubiera expirado y desplazaba una trama todas las respuestas posteriores. Ahora cada respuesta lleva el opcode y el número de secuencia del comando que la originó y se valida al recibirla; una trama que no coincide se descarta, se contabiliza y el comando se reintenta. Toda la ruta caliente del SPI (CRC, aborto del DMA, transferencia, envío/recepción y el sondeo periódico NOP) se ejecuta desde la RAM en lugar de desde la flash XIP, después de que unos fallos de instrucción indefinida se rastrearan hasta recargas XIP corrompidas bajo contención de la PSRAM; con los búferes DMA en SRAM el enlace es estable a 60 MHz con un reloj de sistema de 320 MHz.
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 |
Sí |
No |
USB Serial/JTAG |
Sí |
| WiFi + NCM |
sdkconfig.mode_wifi_and_ncm |
Sí |
Sí |
TinyUSB CDC-ACM |
Sí |
| Solo NCM |
sdkconfig.mode_ncm_only |
No |
Sí |
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.
Corrección de lectura de directorios grandes. Se ha corregido un
"SD Read error" intermitente de TZFS en directorios grandes. El código antiguo realizaba reexploraciones de directorio O(N²) que excedían la ventana de handshake del FSPI cuando la caché de la SD estaba fría; se han sustituido por un cursor de directorio persistente
MZFDIR en el lado del ESP32 más un reintento de la caché de directorio en el lado del RP2350, de modo que los directorios grandes se enumeran de forma fiable.
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 el protocolo binario de tramas IPC sobre el enlace FSPI. Cada comando es un opcode de un solo byte en una cabecera binaria fija (número de secuencia, longitud del payload, campo de nombre de archivo) seguida de un payload opcional y un trailer CRC32; el ESP32 responde con una trama de respuesta que repite el opcode y el número de secuencia, un código de estado (OK, ERR, BUSY, CRCFAIL, NOTFOUND o PARTIAL) y cualquier dato de respuesta. Los comandos que el ESP32 necesita enviar
al RP2350 — cambios de persona, conmutaciones del modo de pletina, cambios de imagen de disquete/QD, recargas de configuración — viajan de vuelta en la respuesta al sondeo periódico
NOP del RP2350 como comandos inversos.
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 lista completa de opcodes está en la
Guía del Desarrollador — Opcodes de Comandos; incluye las transferencias de archivos y sectores,
INF (informe de versión/partición/estado de pletina), los listados de directorio
DIR/
MZFDIR, las cinco operaciones de red usadas por la placa Celestite y
TIME, que devuelve la hora del sistema del ESP32 (segundos de época local más un flag de sincronización) para el reloj de tiempo real MSM5832.
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) |
IPCF_CMD_TIME |
0x15 |
Obtención de la hora del sistema — segundos de época local de 64 bits más un flag de sincronización; usado por devicesTimeSync() para poner en hora las instancias del RTC MSM5832 |
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/ |
Documentos de diseño (repositorio projects/tzpuPico/docs/) |
PICOZ80_SIMULATOR_DESIGN.md, VIRTUAL_CMT_DESIGN.md, VIRTUAL_CMT_X1_DESIGN.md, VIRTUAL_DEVICE_GUI_DESIGN.md, VIRTUAL_Z80CTC_Z80PIO_DESIGN.md, VIRTUAL_Z80DMA_DESIGN.md, VIRTUAL_Z80SIO_DESIGN.md, X1_DISK_IMAGING.md — las notas de diseño que hay detrás del simulador, las pletinas de cassette virtuales, la GUI de dispositivos/interlink, los modelos de periféricos Z80 y la creación de imágenes de disco del X1 |
Estudio de temporización (repositorio projects/tzpuPico/picoZ80_vs_realZ80_timing.md) |
Datos de origen de la sección Fidelidad de la Temporización del Bus |
| 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.