MACHINE_TYPE in config.h is now a compile-time default only. Both machine paths (CoCo 2: SAM + VDG, CoCo 3: GIME) are compiled into every firmware image and the active one is selected at boot from a runtime variable g_machine_type. The user switches machines from the supervisor menu; the new selection is saved in NVS and the device reboots into it via esp_restart().
This document describes the end-to-end implementation. See the original design brief coco2and3.md in the project root for the staged rollout.
setup()(TTGO-VGA32-COCO.ino) callshal_init().- Before
machine_init(), the sketch reads the target machine type from NVS:Falls back to the compile-timeg_machine_type = supervisor_load_machine_type();
MACHINE_TYPEwhen NVS has no entry. machine_init(&coco)dispatches ong_machine_typeto eithermachine_init_coco2()ormachine_init_coco3().machine_load_roms()loads both ROM sets on every boot (missing ROMs log a warning but are not fatal).hal_video_init()brings up a single 640×200 FabGL framebuffer (DISPLAY_WIDTH × DISPLAY_HEIGHT); CoCo 2's 256×192 VDG output is runtime-centered inside it viaCOCO2_X_OFF/COCO2_Y_OFF.
extern uint8_t g_machine_type; // 3 = CoCo 2, 4 = CoCo 3Public machine functions are runtime dispatchers that branch on g_machine_type:
| Dispatcher | CoCo 2 impl | CoCo 3 impl |
|---|---|---|
machine_init |
machine_init_coco2 |
machine_init_coco3 |
machine_load_roms |
machine_load_roms_coco2 |
machine_load_roms_coco3 |
machine_reset |
machine_reset_coco2 |
machine_reset_coco3 |
machine_run_scanline |
machine_run_scanline_coco2 |
machine_run_scanline_coco3 |
machine_run_frame |
machine_run_frame_coco2 |
machine_run_frame_coco3 |
machine_read |
machine_read_coco2 |
machine_read_coco3 |
machine_write |
machine_write_coco2 |
machine_write_coco3 |
machine_free |
machine_free_coco2 |
machine_free_coco3 |
machine_load_roms is the one exception: it calls both loaders regardless of active machine and returns the active machine's success flag. This pre-validates both ROM sets on boot.
Per-machine machine_init_cocoN() pins the CPU callbacks directly:
m->cpu.read = machine_read_cocoN;
m->cpu.write = machine_write_cocoN;So the 6809 memory-access hot path does not go through the dispatcher — zero extra branches per CPU cycle.
Both machine paths' state coexists in a single flat Machine struct (machine.h). Field groups:
- Shared:
cpu,pia0,pia1,fdc, timing/frame counters. - CoCo 3 (used when
g_machine_type == 4):gime,ram_physical(512 KB PSRAM),rom_coco3(32 KB),rom_disk(8 KB). - CoCo 2 (used when
g_machine_type == 3):vdg,sam,ram(aliased to first 64 KB ofram_physical),rom_basic(8 KB),rom_extbas(8 KB),rom_cart(16 KB).
Both init paths allocate the same superset: 512 KB RAM + all five ROM buffers. This keeps PSRAM/heap footprint invariant across machine types. The inactive machine's buffers are populated but unused on a given boot.
uint8_t supervisor_load_machine_type(void); // read NVS "sv"/"machine_type"
void supervisor_set_machine_type(uint8_t); // save + esp_restart() — no return- Namespace
"sv", key"machine_type"(uint8). supervisor_set_machine_type()callssupervisor_save_state()first to preserve mounted disks across the restart.
- Main menu's "Machine:" row shows the active machine (dynamic via
g_machine_type, not the compile-time name). - ENTER on "Machine:" opens the
SV_MACHINE_SELECTstate (new case insupervisor.cppon_key / render dispatchers). - Submenu shows CoCo 2 / CoCo 3 with a
(current)marker on the active one. - ENTER on the active machine is a no-op return; ENTER on the other opens
SV_CONFIRM_DIALOG("Switch to CoCo X and restart?"). - Accept →
supervisor_set_machine_type()→ persists to NVS →esp_restart(). - Cancel → returns to the submenu. ESC from the submenu restores the main-menu cursor/count.
arduino-cli compile --fqbn esp32:esp32:esp32s3:USBMode=default,CDCOnBoot=default,FlashSize=16M,PSRAM=opi TTGO-VGA32-COCO/- Both
MACHINE_TYPE=3andMACHINE_TYPE=4builds compile cleanly. - Flash usage ~588 KB / 1.28 MB (~44%). PSRAM usage invariant at ~552 KB for the combined RAM + ROM buffers.
- NVS erase → boots whichever
MACHINE_TYPEis the compile-time default. - Mount a disk, switch machines, verify the disk auto-mounts after the reboot (
supervisor_save_state()persists mount paths before restart).
| File | Change |
|---|---|
config.h |
MACHINE_TYPE is now a default; added MACHINE_NAME_COCO2 / MACHINE_NAME_COCO3. |
src/core/machine.h |
Unconditionally includes both chip headers; flat superset struct; extern uint8_t g_machine_type. |
src/core/machine.cpp |
Removed #if MACHINE_TYPE guards around the two implementations; all per-machine symbols renamed with _coco2 / _coco3 suffix; added 8 runtime dispatchers; invariant superset allocation; dual ROM load. |
src/hal/hal_video.cpp |
Removed all MACHINE_TYPE guards; Mode-0 sprite unified to display superset; CoCo 2 output runtime-centered via COCO2_X_OFF / COCO2_Y_OFF. |
src/supervisor/supervisor.h |
Added supervisor_load_machine_type / supervisor_set_machine_type. |
src/supervisor/supervisor.cpp |
Wired SV_MACHINE_SELECT into on_key / render dispatchers; implemented NVS load/set. |
src/supervisor/sv_menu.h, sv_menu.cpp |
Dynamic main-menu label; new machine-select submenu handlers with confirm-dialog → restart. |
TTGO-VGA32-COCO.ino |
Seeds g_machine_type from NVS before machine_init(). |