pico6502 Entwicklerhandbuch

English

Hinweis: Das pico6502-Projekt befindet sich in einer fruehen Entwicklungsphase. Das Treiberframework und das Speichermodell sind vollstaendig implementiert und teilen die gleiche Architektur wie der picoZ80. Allerdings existiert derzeit nur ein Platzhalter-BBC-Model-B-Persona — dieser Leitfaden erklaert, wie Sie auf dieser Grundlage eigene Treiber hinzufuegen koennen. Wo der pico6502 strukturell identisch mit dem picoZ80 ist, werden Querverweise zum picoZ80 Entwicklerhandbuch angegeben.

Uebersicht

Die pico6502-Firmware ist strukturell identisch mit der picoZ80-Firmware. Sie verwendet die gleiche Dual-Core-RP2350B-Architektur, das gleiche PSRAM-Speichermodell, das gleiche JSON-Konfigurationssystem und das gleiche Treiberframework — die wesentlichen Unterschiede sind:
  • M6502CPU.c / M6502CPU.h ersetzen Z80CPU.c / Z80CPU.h als Haupt-Dispatch- und Treiberframework-Dateien.
  • Der 6502 hat keinen separaten I/O-Adressraum — es gibt kein ioPtr[]-Array und keinen iomap-JSON-Abschnitt. Alle Peripheriegeraete werden als FUNC-Typ-Bloecke in der 64KB-Speicherbelegung virtualisiert.
  • Das PIO-Bus-Interface (M6502.pio) implementiert Zweiphasen-PHI1/PHI2-Taktung anstelle des Z80-Einzeltaktmodells.
  • Das Build-Zielverzeichnis ist BaseM6502 statt BaseZ80.
  • Der JSON-CPU-Abschnittsschluessel ist "6502" statt "z80".
Alles andere — das Inter-Core-Queue-Muster, Treiber-Lifecycle-Callbacks (reset_ptr, poll_ptr, task_ptr), die virtualFuncMap[]-Registrierungstabelle, Speicherblocktyp-Konstanten, die t_6502PSRAM-Struktur, das ESP32-Koprozessor-Protokoll und das Build-System — wird mit dem picoZ80-Codebase geteilt.

Quellbaum

Der gesamte Quellcode befindet sich unter projects/tzpuPico/ im Repository-Stammverzeichnis. Das folgende Layout hebt pico6502-spezifische Dateien hervor:
tzpuPico/
├── CMakeLists.txt                          Top-Level-Build-Datei
├── src/
│   ├── CMakeLists.txt                      Quellcode-Build-Datei — neue Treiberdateien hier hinzufuegen
│   ├── M6502CPU.c                          *** SCHLUESSELDATEI: 6502-Dispatch, Treiberframework, virtualFuncMap
│   ├── M6502CPU.h                          (Legacy — eingebunden ueber include/)
│   ├── M6502.c                             PIO-Initialisierung und Buszyklus-Einstiegspunkte
│   ├── M6502.pio                           PIO-Programme: clock, addr, data, cycle, fetch, read, write, irq, nmi, so
│   ├── Z80CPU.c / Z80CPU.h                 Z80-Gegenstueck (nicht in pico6502-Builds verwendet)
│   ├── FSPI.c / FSPI.h                     Flash-SPI-Interface (gemeinsam)
│   ├── ESP.c / ESP.h                       ESP32-Kommunikationsschicht (gemeinsam)
│   ├── psram.c / psram.h                   PSRAM-Allokation und Verwaltung (gemeinsam)
│   ├── cJSON.c / cJSON.h                   JSON-Parser fuer config.json (gemeinsam)
│   ├── include/
│   │   ├── M6502CPU.h                      *** SCHLUESSELDATEI: alle 6502-Typdefinitionen und Makros
│   │   ├── M6502.h                         PIO-Hilfsdefinitionen
│   │   └── drivers/
│   │       └── BBC/
│   │           └── ModelB.h               BBC Model B Treiber-Header (fruehes Platzhalter-Stadium)
│   ├── drivers/
│   │   └── BBC/
│   │       └── ModelB.c                   *** BEISPIEL/PLATZHALTER-TREIBER (BBC Model B Persona)
│   └── model/
│       ├── BaseM6502/
│       │   ├── CMakeLists.txt             Modellspezifische Build-Ziele
│       │   ├── main.c                     Einstiegspunkt (Core 0 + Core 1 Start)
│       │   ├── main_memmap_partition_1.ld Linker-Skript fuer Slot 1
│       │   └── main_memmap_partition_2.ld Linker-Skript fuer Slot 2
│       └── Bootloader/
│           ├── CMakeLists.txt
│           └── main.c                     Gemeinsamer Bootloader (gleich wie picoZ80)
└── esp32/
    ├── CMakeLists.txt
    └── main/
        └── ...                            ESP32-Firmware (geteilt mit picoZ80)

Wesentliche Unterschiede zum picoZ80

Kein separater I/O-Adressraum

Der wichtigste architektonische Unterschied ist, dass der 6502 keine IN/OUT-Instruktionen und kein IORQ-Signal hat. Jedes Peripheriegeraet muss irgendwo in der 64KB-Speicherbelegung erscheinen. Beim picoZ80 koennen virtuelle Geraete ueber memioPtr[] (speicherabgebildet) oder ioPtr[] (I/O-abgebildet) registriert werden. Beim pico6502 gibt es nur memioPtr[] — aber da alle Peripheriezugriffe ueber den Speicherbus erfolgen, deckt dies alle Faelle ab.
Die t_6502PSRAM-Struktur hat daher kein ioPtr[]-Array. Die JSON-Konfiguration hat kein "io"-Array. Die Dispatch-Schleife in M6502CPU.c prueft nur das _membankPtr[]-Array und ruft fuer MEMBANK_TYPE_FUNC-Bloecke den entsprechenden memioPtr[]-Handler auf.

PIO-Bus-Interface-Unterschiede

Der 6502 verwendet ein Zweiphasen-nicht-ueberlappendes Taktschema. PHI0 ist ein externer Takteingang; der RP2350 erzeugt daraus PHI1 und PHI2 ueber eine dedizierte PIO 2-Zustandsmaschine (m6502_clock_6502). Dies unterscheidet sich vom Z80, der ein einzelnes CLK-Signal verwendet.
PIO-Block Zustandsmaschinen Zweck
PIO 0 m6502_addr, m6502_data Adressbus (A0-A15), Datenbus (D0-D7)
PIO 1 m6502_cycle, m6502_fetch, m6502_read, m6502_write, m6502_irq, m6502_nmi, m6502_so Buszyklus-Sequenzierung und Steuersignalueberwachung
PIO 2 m6502_clock_6502 PHI1/PHI2-Zweiphasen-Takterzeugung aus externem PHI0
Die IRQ-Konvention unterscheidet sich leicht vom Z80-PIO-Interface:
IRQ Flag Signal Bedeutung
IRQ 0 Adresse/Zyklusstart Neuer 6502-Buszyklus — Adresse gueltig bei steigender PHI1-Flanke
IRQ 1 Datenphase Datenbus aktiv — Lesen oder Schreiben je nach RNW
IRQ 4 NMI Nicht-maskierbarer Interrupt aktiviert
IRQ 5 IRQ Maskierbarer Interrupt aktiviert
IRQ 6 SO Set Overflow-Pin aktiviert
IRQ 7 Schleifen-Austritt Core 1-Ausfuehrungsschleifen-Austrittsanfrage

PIO-Programmierung — Wie C-Code Buszyklen steuert

Der pico6502 verwendet den gleichen out exec-Mechanismus wie der picoZ80 fuer dynamische Instruktionsinjektion, aber mit 32-Bit-Instruktionen (out exec, 32) anstelle von 16-Bit. Der C-Code auf Core 1 schiebt vorab kodierte PIO-Instruktionssequenzen in den TX-FIFO der Zyklus-SM, um jede Bustransaktion zu steuern.
Der Koordinationsablauf fuer einen 6502-Speicher-Lesezyklus:
                  Core 1 (C code)               PIO State Machines
                  ─────────────                 ──────────────────
    1. Resolve address                          m6502_cycle: IRQ 0 set
       from memory map                           (waiting on PHI2 low)
                  │
    2. Push addr → TX FIFO ──────────────────→ m6502_addr: receives addr
       Clear IRQ 0                               outputs A0–A15 on pins
                  │
    3. Push cycle instructions ──────────────→ m6502_cycle: out exec, 32
       (read sequence) into                       executes: set R/W̄ high, SYNC low
       cycle SM TX FIFO                           executes: wait PHI2 high
                  │                               executes: check RDY
    4. Wait for RX FIFO ←────────────────────  m6502_data: samples D0–D7
       (data byte from bus)                       at PHI2 falling edge
                  │
    5. Read data from                          m6502_cycle: JMP start_cycle
       RX FIFO                                    (ready for next cycle)
                  │
    6. Dispatch to PSRAM
       or driver handler
Bei Schreibzyklen schiebt Core 1 das Datenbyte nach der Adresse in den TX-FIFO der m6502_data-SM. Die Daten-SM treibt D0-D7 waehrend der PHI2-High-Phase und setzt den Datenbus in Tri-State, wenn PHI2 faellt. Die Zyklus-SM setzt R/W̄ low, um einen Schreibvorgang anzuzeigen.
Fuer eine detaillierte Erklaerung der PIO-Grundlagen, des out exec-Mechanismus und der Zustandsmaschinen-Koordinierungsmuster siehe den picoZ80 Technischen Leitfaden — PIO-Architektur Deep Dive. Die 6502-spezifischen PIO-Unterschiede sind:
  • 32-Bit out exec — erlaubt vollstaendige PIO-Instruktionskodierung einschliesslich Side-Set- und Delay-Feldern in einem einzigen FIFO-Push.
  • Taktphasen-Synchronisation — Zyklus-SMs synchronisieren sich auf PHI2-Flanken (wait 0/1 gpio CLK2) anstelle eines einzelnen CLK-Signals.
  • Kein BUSREQ/BUSACK — der 6502 hat keinen Bus-Request-Mechanismus, daher gibt es kein Aequivalent zur Z80-z80_busrq-SM.
  • RDY statt WAIT — Wartezustaende werden durch Pruefen des RDY-Pins nach der steigenden PHI2-Flanke eingefuegt, anstelle des Z80-/WAIT-Pins bei fallender T2-Flanke.

t_6502PSRAM-Struktur

Aequivalent zu t_Z80PSRAM im picoZ80, aber ohne das ioPtr[]-Array:
// src/include/M6502CPU.h (simplified)
typedef struct {
    uint8_t      RAM[MAX_MEMORY_BANKS * MEMORY_PAGE_SIZE]; // 64 banks × 64KB = 4MB RAM/ROM area
    MemoryFunc   memPtr[MEMORY_PAGE_SIZE];                  // 64K per-byte read redirect (PTR type)
    MemoryFunc   memioPtr[MEMORY_PAGE_SIZE];                // 64K memory-mapped handler array (FUNC type)
    // NOTE: no ioPtr[] — the 6502 has no separate I/O space
} t_6502PSRAM;

Handler-Funktionssignatur

Die MemoryFunc-Typdefinition ist die gleiche wie beim picoZ80:
// Handler called for MEMBANK_TYPE_FUNC blocks on every access
typedef uint8_t (*MemoryFunc)(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data);

// Parameters:
//   cpu  — pointer to the M6502CPU struct (access PSRAM, state, queues via this)
//   read — true for a 6502 read cycle (RNW=1), false for a write cycle (RNW=0)
//   addr — 16-bit address that triggered this handler
//   data — byte written (only meaningful when read=false)
// Return:
//   byte to place on the data bus (only meaningful when read=true)
Da es keinen I/O-Adressraum gibt, gibt es keine separate IOFunc-Typdefinition — MemoryFunc verarbeitet alle Peripheriezugriffe.

Treiberframework

Das Treiberframework in M6502CPU.c ist strukturell identisch mit dem picoZ80-Treiberframework. Siehe den picoZ80 Entwicklerhandbuch — Treiberframework-Abschnitt fuer die vollstaendige Beschreibung von:
  • Der virtualFuncMap[]-Registrierungstabelle und wie das JSON-"name"-Feld mit einer Treiber-Init-Funktion abgeglichen wird.
  • Der VirtualFunc-Typdefinition und M6502CPU_getVirtualFunc()-Suche.
  • Dem Zweiphasen-Init-Ablauf (Validierungsdurchlauf dann Konfigurationsdurchlauf).
  • Treiber-Lifecycle-Callbacks: reset_ptr, poll_ptr, task_ptr.
Die einzigen Namensunterschiede sind:
picoZ80 pico6502 Hinweise
Z80CPU struct M6502CPU struct Gleiche Felder, gleiches Layout
Z80CPU_getVirtualFunc() M6502CPU_getVirtualFunc() Gleiche Suchlogik
Z80CPU_cpu() M6502CPU_cpu() Core 1-Hauptschleifen-Einstiegspunkt
Z80CPU_readMem() M6502CPU_readMem() Speicher-Lese-Dispatch
Z80CPU_writeMem() M6502CPU_writeMem() Speicher-Schreib-Dispatch
Z80CPU_readIO() Kein Aequivalent — 6502 hat keinen I/O-Adressraum
Z80CPU_writeIO() Kein Aequivalent — 6502 hat keinen I/O-Adressraum
cpu->_z80PSRAM cpu->_6502PSRAM PSRAM-Strukturzeiger

Core 1-Dispatch-Schleife

Die Core 1-Hauptschleife in M6502CPU_cpu() folgt dem gleichen Muster wie beim picoZ80. Bei jedem 6502-Buszyklus:
  1. Warte auf IRQ 0 (Adresse gueltig bei PHI1).
  2. Lese die 16-Bit-Adresse aus dem PIO 0 RX FIFO.
  3. Schlage _membankPtr[addr >> 9] nach, um Blocktyp, Bank und Basis zu erhalten.
  4. Bestimme RNW (Read/Not-Write) vom Steuer-PIO.
  5. Dispatch basierend auf Blocktyp: PHYSICAL (an Host weiterleiten), RAM (PSRAM lesen/schreiben), ROM (nur PSRAM lesen), FUNC (memioPtr[addr] aufrufen), PTR (ueber memPtr[addr] umleiten).
  6. Bei Lesen: Ergebnis ueber PIO 0 TX FIFO auf Datenbus legen. Auf IRQ 1 (Datenphase) warten. Bei Schreiben: Daten aus PIO 0 RX FIFO nach IRQ 1 lesen.
  7. poll_ptr fuer jeden registrierten Treiber aufrufen (ca. alle ~2048 Zyklen).
// Simplified M6502CPU_readMem / M6502CPU_writeMem dispatch
uint8_t M6502CPU_readMem(M6502CPU *cpu, uint16_t addr)
{
    uint32_t entry = cpu->_membankPtr[addr >> 9];
    uint8_t  type  = (entry >> 24) & 0xFF;
    uint8_t  bank  = (entry >> 16) & 0xFF;
    uint32_t base  = (entry & 0xFFFF) << 9;

    switch(type)
    {
        case MEMBANK_TYPE_RAM:
        case MEMBANK_TYPE_ROM:
            return cpu->_6502PSRAM->RAM[(bank * MEMORY_PAGE_SIZE) + addr];

        case MEMBANK_TYPE_FUNC:
            if(cpu->_6502PSRAM->memioPtr[addr] != NULL)
                return cpu->_6502PSRAM->memioPtr[addr](cpu, true, addr, 0);
            return 0x00;

        case MEMBANK_TYPE_PTR:
            if(cpu->_6502PSRAM->memPtr[addr] != NULL)
                return cpu->_6502PSRAM->memPtr[addr](cpu, true, addr, 0);
            return 0x00;

        case MEMBANK_TYPE_PHYSICAL:
        default:
            return 0xFF;  // Pass-through to real host hardware
    }
}

Einen neuen Treiber schreiben

Der Prozess zum Schreiben eines pico6502-Treibers ist identisch mit dem picoZ80-Prozess, beschrieben im picoZ80 Entwicklerhandbuch — Einen neuen Treiber schreiben. Die sechs Schritte sind die gleichen:
  1. Eine Header-Datei in src/include/drivers/<Familie>/MyDriver.h erstellen
  2. Eine Implementierungsdatei in src/drivers/<Familie>/MyDriver.c erstellen
  3. Die Quelldatei zu src/CMakeLists.txt hinzufuegen
  4. Den Treiber in M6502CPU.c unter einem #ifdef INCLUDE_<FAMILY>_DRIVERS-Guard einbinden
  5. Den Treiber in virtualFuncMap[] in M6502CPU.c registrieren
  6. Einen JSON-Treibereintrag in config.json hinzufuegen
Der wesentliche Unterschied ist, dass anstelle eines Z80CPU *cpu-Parameters alle Funktionen einen M6502CPU *cpu-Parameter empfangen und Sie auf PSRAM ueber cpu->_6502PSRAM statt cpu->_z80PSRAM zugreifen. Es gibt keine ioPtr[]-Slots zum Installieren — alle virtuellen Peripheriegeraete werden in memioPtr[] registriert.

Minimale Treibervorlage

Das Folgende zeigt einen minimalen 6502-Peripherietreiber — eine virtualisierte 6522 VIA (Versatile Interface Adapter) bei Adresse 0xC000-0xC00F:
// src/include/drivers/BBC/VIA6522.h
#ifndef VIA6522_H
#define VIA6522_H

#include "M6502CPU.h"

int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass);

#endif
// src/drivers/BBC/VIA6522.c
#include "../../include/M6502CPU.h"
#include "../../include/drivers/BBC/VIA6522.h"

// --- Internal state ---
typedef struct {
    uint8_t  regs[16];      // VIA internal registers (ORB, ORA, DDRB, DDRA, T1CL, T1CH, ...)
    bool     irqPending;
} t_VIA6522State;

static t_VIA6522State ViaState = { .irqPending = false };

// --- Memory handler (called for every access to 0xC000–0xC00F) ---
static uint8_t VIA6522_Handler(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
    uint8_t reg = addr & 0x0F;   // VIA has 16 internal registers

    if(read)
    {
        return ViaState.regs[reg];
    }
    else
    {
        ViaState.regs[reg] = data;
        // TODO: implement timer, shift register, port logic as needed
        return 0;
    }
}

// --- Reset handler (called when host system resets) ---
static uint8_t VIA6522_Reset(M6502CPU *cpu)
{
    memset(&ViaState.regs, 0, sizeof(ViaState.regs));
    ViaState.irqPending = false;
    return 0;
}

// --- Init function (called during JSON config parse) ---
int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass)
{
    if(pass == 0)
    {
        // Validation pass — just check the name matches
        return (strcasecmp(drvConfig->name, "via6522") == 0) ? 1 : 0;
    }

    // Configuration pass — install handlers
    // Install the handler for all 16 VIA registers at 0xC000–0xC00F
    for(uint16_t addr = 0xC000; addr <= 0xC00F; addr++)
    {
        cpu->_6502PSRAM->memioPtr[addr] = VIA6522_Handler;
    }

    // Register reset callback
    cpu->reset_ptr = VIA6522_Reset;

    debugf("VIA6522: Initialised at 0xC000–0xC00F\n");
    return 1;
}

JSON-Konfiguration

Um den VIA6522-Treiber zu aktivieren, fuegen Sie einen FUNC-Block zur Speicherbelegung und einen Treibereintrag hinzu:
{
  "rp2350": {
    "6502": {
      "memory": [
        { "enable":1, "addr":"0x0000", "size":"0xC000",
          "type":"RAM",  "bank":0, "task":"", "file":"" },
        { "enable":1, "addr":"0xC000", "size":"0x0010",
          "type":"FUNC", "bank":0, "task":"via6522", "file":"" },
        { "enable":1, "addr":"0xE000", "size":"0x2000",
          "type":"ROM",  "bank":0, "task":"", "file":"/ROM/bios.rom" }
      ],
      "drivers": [
        { "enable":1, "name":"via6522" }
      ]
    }
  }
}

In virtualFuncMap registrieren

Fuegen Sie einen Eintrag zur virtualFuncMap[]-Tabelle in M6502CPU.c hinzu. Der name-String wird gross-/kleinschreibungsunabhaengig gegen das JSON-Treiber-"name"-Feld abgeglichen:
// In M6502CPU.c — add to virtualFuncMap[]
static const t_VirtualFuncMap virtualFuncMap[] = {
    { "BBCModelB", BBCModelB_Init },   // existing entry
    { "via6522",   VIA6522_Init   },   // your new driver
    { NULL, NULL }                     // terminator
};

CMakeLists.txt-Aenderung

Fuegen Sie die neue Quelldatei hinzu und aktivieren Sie sie ueber eine Kompilierungsdefinition in src/CMakeLists.txt:
# In src/CMakeLists.txt
target_sources(tzpuPico PRIVATE
    ...
    drivers/BBC/ModelB.c        # existing
    drivers/BBC/VIA6522.c       # your new driver
)

target_compile_definitions(tzpuPico PRIVATE
    INCLUDE_BBC_DRIVERS=1
)

Erstellen und Testen

# Vom Projektstammverzeichnis
./build_tzpuPico.sh

# Ueber USB-Massenspeicher flashen
cp build/bin/model/BaseM6502/BaseM6502_0x10020000.uf2 /media/$USER/RPI-RP2/

# Oder OTA: hochladen ueber http://<device-ip>/ota-rp2350.htm

Speicher-Hook-Muster

Da der 6502 keinen I/O-Adressraum hat, verwenden alle Hook-Muster memioPtr[]. Die Muster sind die gleichen wie im picoZ80 Entwicklerhandbuch — Speicher-Hook-Muster beschrieben, wobei das I/O-Port-Handler-Muster durch einen speicherabgebildeten Handler an einer beliebigen Adresse ersetzt wird. Die wichtigsten Muster fuer 6502-Treiber sind:

FUNC-Block — Virtuelles Peripheriegeraet

Installieren Sie einen Handler fuer einen zusammenhaengenden Adressbereich. Dies ist das Standardmuster fuer alle 6502-Peripherie-Emulation:
// Install handler for address range 0xD000–0xD0FF (256-byte peripheral block)
for(uint16_t addr = 0xD000; addr <= 0xD0FF; addr++)
{
    cpu->_6502PSRAM->memioPtr[addr] = MyPeripheral_Handler;
}

RAM-Schreib-Abfangen

Fangen Sie Schreibzugriffe auf einen RAM-Bereich ab, waehrend Lesezugriffe weiterhin aus dem PSRAM bedient werden. Nuetzlich fuer das Spiegeln von Zero-Page-Zugriffen oder das Erkennen von Schreibzugriffen auf bestimmte Adressen:
// Handler intercepts writes; passes reads to PSRAM
static uint8_t ZeroPage_Intercept(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
    if(read)
    {
        // Read from PSRAM normally
        return cpu->_6502PSRAM->RAM[addr];
    }
    else
    {
        // Write to PSRAM and update shadow state
        cpu->_6502PSRAM->RAM[addr] = data;
        MyDriver_OnZeroPageWrite(addr, data);
        return 0;
    }
}

// In init: set the block type to FUNC and install the handler
cpu->_membankPtr[0x0000 >> 9] = MEMBANK_ENCODE(MEMBANK_TYPE_FUNC, 0, 0x0000);
cpu->_6502PSRAM->memioPtr[0x0000] = ZeroPage_Intercept;
// ... repeat for each 512-byte block in the range

Verteilte / einzelne Adressen

Fuer Hardware, bei der nur wenige Adressen innerhalb eines groesseren Bereichs relevant sind (z.B. ein 4-Bit-Adressdecoder), installieren Sie den Handler nur an diesen spezifischen Adressen und lassen den Rest 0xFF oder PSRAM-Daten zurueckgeben:
// Only addresses 0xFFFE and 0xFFFF (6502 reset vector) are intercepted
cpu->_6502PSRAM->memioPtr[0xFFFE] = ResetVector_Lo_Handler;
cpu->_6502PSRAM->memioPtr[0xFFFF] = ResetVector_Hi_Handler;

BBC Model B Persona (Platzhalter)

Der einzige derzeit in der pico6502-Firmware vorhandene Treiber ist die BBC Model B Persona (src/drivers/BBC/ModelB.c). Dies ist ein fruehes Platzhalter-Stadium, das das Treiberregistrierungsmuster demonstriert, aber noch nicht die vollstaendige BBC Micro-Hardware implementiert — der 6845 CRTC, die 6522 VIAs, der 6850 ACIA, der SAA5050-Teletext-Chip und die ULA sind nicht virtualisiert.
Der BBC Model B ist ein geeignetes erstes Ziel fuer den pico6502, da er einen Standard-6502 mit 2MHz verwendet, gut dokumentierte Hardware hat und ausschliesslich auf speicherabgebildete Peripheriegeraete angewiesen ist — was sich natuerlich mit dem FUNC-basierten Peripheriemodell des pico6502 deckt. Wenn die Persona vollstaendig ist, muss sie mindestens Folgendes virtualisieren:
Adresse Breite Chip Funktion
0xFC00–0xFCFF 256B 6845 CRTC Video-Timing-/Speicheradressgenerator
0xFE00–0xFE0F 16B 6522 VIA A System-VIA (Tastatur, Sound, Echtzeituhr)
0xFE40–0xFE4F 16B 6522 VIA B User-VIA (Parallelport, Benutzer-I/O)
0xFE60–0xFE6F 16B 6850 ACIA Serielles Interface
0xFC00–0xFCFF 256B SAA5050 / ULA Teletext / Video-ULA
0xFE30 1B ROM select Seitenweise ROM-Bankauswahl-Register

Core 0 / Core 1-Interaktion

Das Inter-Core-Queue-Muster ist identisch mit dem picoZ80. Siehe den picoZ80 Entwicklerhandbuch — Core 0 / Core 1-Interaktion fuer die vollstaendige Beschreibung und das Codebeispiel. Der einzige Namensunterschied ist, dass auf die Queue ueber einen M6502CPU *cpu-Zeiger statt Z80CPU *cpu zugegriffen wird.
Wichtige Regel: Handler- und Poll-Callbacks laufen auf Core 1. Jede Datei-I/O, UART-Befehle an den ESP32 oder andere blockierende Operationen muessen ueber cpu->requestQueue an Core 0 gesendet werden. Ergebnisse werden ueber cpu->responseQueue zurueckgegeben und in task_ptr verarbeitet.

Haeufige Fallstricke

  • Handler in ioPtr statt memioPtr installieren. Der pico6502 hat kein ioPtr[]-Array. Alle Peripherie-Handler gehoeren in memioPtr[]. Der Versuch, ioPtr[] zu verwenden, fuehrt zu einem Build-Fehler oder undefiniertem Verhalten.
  • I/O-Dispatch-Aufruf erwarten. Es gibt kein M6502CPU_readIO() oder M6502CPU_writeIO(). Wenn Sie einen Z80-Treiber portieren, der ioPtr[] verwendet, verschieben Sie alle diese Handler auf memioPtr[] an den entsprechenden Speicheradressen.
  • Blockieren in einem Handler. Jeder Aufruf von debugf, sleep_ms oder fopen aus einem Handler heraus blockiert Core 1 und beschaedigt das 6502-Bus-Timing. Verwenden Sie die Inter-Core-Queue fuer alle blockierenden Operationen.
  • Nicht uebereinstimmender virtualFuncMap-Name. Das JSON-"name"-Feld wird gross-/kleinschreibungsunabhaengig gegen die virtualFuncMap[]-Tabelle in M6502CPU.c abgeglichen. Ein Tippfehler in einem der beiden ueberspringt den Treiber stillschweigend. Fuegen Sie ein temporaeres debugf in M6502CPU_getVirtualFunc() zur Diagnose hinzu.
  • Off-by-one im Handler-Bereich. Verwenden Sie addr <= endAddr und nicht addr < endAddr + 1 — Letzteres riskiert einen Integer-Ueberlauf bei 0xFFFF. Verwenden Sie addr <= 0xFFFF mit einer uint32_t-Schleifenvariable, wenn der Bereich bis zum oberen Speicherende reicht.
  • Vergessen, den Blocktyp auf FUNC zu setzen. Die Installation eines Handlers in memioPtr[] hat keine Wirkung, wenn die entsprechenden _membankPtr[]-Eintraege noch MEMBANK_TYPE_RAM oder MEMBANK_TYPE_ROM anzeigen. Rufen Sie immer MEMBANK_ENCODE(MEMBANK_TYPE_FUNC, ...) fuer jeden 512-Byte-Block in Ihrem Handler-Bereich auf.
  • reset_ptr beim Treiber-Shutdown nicht geloescht. Wenn Ihr Treiber zur Laufzeit neu konfiguriert wird und der Reset-Callback nicht mehr gueltig ist, setzen Sie cpu->reset_ptr vor der Neuinitialisierung auf NULL, andernfalls ruft Core 1 beim naechsten Reset einen veralteten Funktionszeiger auf.

Referenz-Websites

Ressource Link
pico6502 Projektseite /pico6502/
pico6502 Benutzerhandbuch /pico6502-usermanual/
pico6502 Technischer Leitfaden /pico6502-technicalguide/
picoZ80 Entwicklerhandbuch /picoz80-developersguide/
picoZ80 Projektseite /picoz80/
RP2350 Datasheet datasheets.raspberrypi.com
Pico SDK Multicore API raspberrypi.github.io/pico-sdk-doxygen
MOS 6502 Datasheet archive.org
BBC Micro Hardware Guide stardot.org.uk
cJSON Library github.com/DaveGamble/cJSON

Regulatorischer Hinweis fuer Funkgeraete

Dieses Geraet enthaelt ein ESP32-S3-PICO-1-Funkmodul, das im 2,4 GHz ISM-Band sendet und es damit zu einem beabsichtigten Strahler gemaess den Funkfrequenzvorschriften weltweit macht (einschliesslich FCC Part 15 Subpart C in den Vereinigten Staaten und der Funkanlagenrichtlinie 2014/53/EU in der Europaeischen Union).
Obwohl das ESP32-S3-PICO-1-Modul selbst ueber bestehende regulatorische Zertifizierungen verfuegt (FCC, CE und andere), erstrecken sich diese Modulzertifizierungen nicht automatisch auf ein fertiges Produkt, das das Modul enthaelt. Die Vorzertifizierungs-Ausnahme erlaubt es einzelnen Hobbyisten, eine begrenzte Anzahl von Geraeten fuer den persoenlichen, experimentellen oder bildungsbezogenen Gebrauch zu bauen, ohne eine separate Geraetezulassung zu erhalten.
Wichtige Einschraenkungen
  • Zusammengebaute Geraete duerfen nicht verkauft, zum Verkauf angeboten, verschenkt oder anderweitig an Dritte verteilt werden, es sei denn, das fertige Produkt wurde unabhaengig getestet und hat eine eigene Geraetezulassung erhalten.
  • Der Bau dieses Projekts fuer den persoenlichen Gebrauch in begrenzten Stueckzahlen ist im Allgemeinen unter Hobbyisten- und Experimentiervorschriften gestattet (z.B. FCC § 15.23), vorausgesetzt das Geraet verursacht keine schaedlichen Stoerungen.
  • Regulatorische Anforderungen variieren je nach Land. Bauherren ausserhalb der Vereinigten Staaten sollten ihre nationale Funkfrequenzbehoerde konsultieren.
Verantwortung des Bauherrn
Es liegt in der alleinigen Verantwortung des Bauherrn sicherzustellen, dass jedes aus diesen Designs konstruierte Geraet allen anwendbaren Funkfrequenzvorschriften in seiner Gerichtsbarkeit entspricht. Der Autor stellt diese Designs fuer den persoenlichen, bildungsbezogenen und Hobbygebrauch zur Verfuegung und gibt keine Zusicherung, dass ein aus ihnen gebautes Geraet die regulatorischen Anforderungen fuer den kommerziellen Vertrieb erfuellt.