├── .github ├── FUNDING.yml └── workflows │ ├── firmware-linux.yml │ ├── firmware-macos.yml │ ├── configurator-linux.yml │ ├── configurator-macos.yml │ ├── firmware-windows.yml │ ├── configurator-windows.yml │ └── build-common.yml ├── img ├── logo.png ├── pico9918_v0_3_sm.jpg ├── pico9918_v0_3_sm2.jpg ├── pico9918_v0_4_sm.png ├── pico9918_v1_0_sm.png ├── pico9918_v1_2_sm.jpg ├── pico9918_v1_2_top_sm.jpg └── pico9918_v1_2_bottom_sm.jpg ├── src ├── res │ ├── font.png │ └── splash.png ├── flash.h ├── clocks.pio ├── pio-utils │ ├── pio_utils.h │ ├── CMakeLists.txt │ └── pio_utils.c ├── temperature.h ├── splash.h ├── gpu │ ├── CMakeLists.txt │ ├── gpu.h │ └── gpu.c ├── vga │ ├── CMakeLists.txt │ ├── vga-modes.h │ ├── vga.h │ ├── vga.pio │ └── vga-modes.c ├── diag.h ├── display.h ├── gpio.c ├── palconv.pio ├── temperature.c ├── config.h ├── splash.c ├── boards │ └── pico9918.h ├── gpio.h ├── tms9918.pio ├── CMakeLists.txt └── config.c ├── configtool ├── img │ ├── tool.png │ └── pico_sprites.png ├── src │ ├── pletter │ │ ├── font.bin │ │ ├── logoTop.bin │ │ ├── font.bin.plet5 │ │ ├── logoBottom.bin │ │ ├── logoSprites.bin │ │ ├── logoTop.bin.plet5 │ │ ├── logoBottom.bin.plet5 │ │ ├── logoSprites.bin.plet5 │ │ ├── logoSprites.bin.plet5.bas │ │ ├── logoBottom.bin.plet5.bas │ │ ├── logoTop.bin.plet5.bas │ │ └── font.bin.plet5.bas │ ├── lib │ │ ├── cvbasic_9900_epilogue.asm │ │ ├── cvbasic_6502_epilogue.asm │ │ └── cvbasic_epilogue.asm │ ├── banksel.bas │ ├── pico9918conf.bas │ ├── menu-diag.bas │ ├── input.bas │ ├── conf-scanline-sprites.bas │ ├── vdp-utils.bas │ ├── config.bas │ ├── menu-info.bas │ ├── ui.bas │ ├── menu-firmware.bas │ └── core.bas ├── tools │ ├── cvbasic │ │ └── pletter.exe │ ├── bin2cvb.py │ ├── cvpletter.py │ └── uf2cvb.py ├── .vscode │ └── tasks.json ├── README.md └── CMakeLists.txt ├── test ├── host │ ├── res │ │ ├── BREAKOUT.TIAC │ │ └── BREAKOUT.TIAP │ ├── CMakeLists.txt │ ├── clocks.pio │ └── font.c ├── CMakeLists.txt └── qc │ ├── CMakeLists.txt │ └── qc.c ├── pcb ├── v0.3 │ ├── pico9918_v0_3_pcb.png │ ├── pico9918_v0_3_vga.jpg │ ├── pico9918_v0_3_bom.xlsx │ ├── pico9918_v0_3_gerber.zip │ ├── pico9918_v0_3_picknplace.csv │ ├── pico9918_v0_3_schematic.png │ ├── pico9918_v0_3_vga_gerber.zip │ └── README.md ├── v0.4 │ ├── pico9918_v0_4_bom.csv │ └── pico9918_v0_4_gerber.zip ├── v1.0 │ ├── pico9918_v1_0_bom.csv │ └── pico9918_v1_0_gerber.zip ├── v1.1 │ ├── pico9918_v1_1_bom.xls │ ├── pico9918_v1_1_gerber.zip │ └── pico9918_v1_1_schematic.pdf └── README.md ├── nocut ├── ti99 │ ├── img │ │ ├── nocut-ti99-enclosure-1.jpg │ │ ├── nocut-ti99-enclosure-2.jpg │ │ ├── nocut-ti99-enclosure-3.jpg │ │ ├── nocut-ti99-installed-bare.jpg │ │ ├── nocut-ti99-installed-pcb.jpg │ │ ├── nocut-ti99-installed-enclosure.jpg │ │ ├── pico9918-nocut-ti99-build-plate.png │ │ └── nocut-ti99-installed-enclosure-2.jpeg │ ├── stl │ │ ├── pico9918-nocut-ti99-bottom.stl │ │ ├── pico9918-nocut-ti99-beige-top.stl │ │ ├── pico9918-nocut-ti99-black-top.stl │ │ ├── pico9918-nocut-ti99-pcb-spacer.stl │ │ └── pico9918-nocut-ti99-pcb-spacer-jig.stl │ └── pcb │ │ ├── bom-pico9918-nocut-ti994a-v1-3.csv │ │ ├── cpl-pico9918-nocut-ti994a-v1-3.csv │ │ └── gerber-pico9918-nocut-ti994a-v1-3.zip └── README.md ├── .gitmodules ├── .vscode ├── extensions.json ├── cmake_configurations.json ├── cmake-kits.json ├── c_cpp_properties.json ├── settings.json ├── launch.json └── tasks.json ├── .gitignore ├── picosdk-2.0.0-visrealm-fastboot.patch ├── LICENSE_FIRMWARE.md ├── visrealm_tools.cmake ├── tools ├── README.md └── bin2carray.py ├── CMakeLists.txt └── pico_sdk_import.cmake /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: visrealm -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/logo.png -------------------------------------------------------------------------------- /src/res/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/src/res/font.png -------------------------------------------------------------------------------- /src/res/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/src/res/splash.png -------------------------------------------------------------------------------- /configtool/img/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/img/tool.png -------------------------------------------------------------------------------- /img/pico9918_v0_3_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v0_3_sm.jpg -------------------------------------------------------------------------------- /img/pico9918_v0_3_sm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v0_3_sm2.jpg -------------------------------------------------------------------------------- /img/pico9918_v0_4_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v0_4_sm.png -------------------------------------------------------------------------------- /img/pico9918_v1_0_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v1_0_sm.png -------------------------------------------------------------------------------- /img/pico9918_v1_2_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v1_2_sm.jpg -------------------------------------------------------------------------------- /test/host/res/BREAKOUT.TIAC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/test/host/res/BREAKOUT.TIAC -------------------------------------------------------------------------------- /test/host/res/BREAKOUT.TIAP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/test/host/res/BREAKOUT.TIAP -------------------------------------------------------------------------------- /img/pico9918_v1_2_top_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v1_2_top_sm.jpg -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_pcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_pcb.png -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_vga.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_vga.jpg -------------------------------------------------------------------------------- /pcb/v0.4/pico9918_v0_4_bom.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.4/pico9918_v0_4_bom.csv -------------------------------------------------------------------------------- /pcb/v1.0/pico9918_v1_0_bom.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v1.0/pico9918_v1_0_bom.csv -------------------------------------------------------------------------------- /pcb/v1.1/pico9918_v1_1_bom.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v1.1/pico9918_v1_1_bom.xls -------------------------------------------------------------------------------- /configtool/img/pico_sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/img/pico_sprites.png -------------------------------------------------------------------------------- /configtool/src/pletter/font.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/font.bin -------------------------------------------------------------------------------- /img/pico9918_v1_2_bottom_sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/img/pico9918_v1_2_bottom_sm.jpg -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_bom.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_bom.xlsx -------------------------------------------------------------------------------- /configtool/src/pletter/logoTop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoTop.bin -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_gerber.zip -------------------------------------------------------------------------------- /pcb/v0.4/pico9918_v0_4_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.4/pico9918_v0_4_gerber.zip -------------------------------------------------------------------------------- /pcb/v1.0/pico9918_v1_0_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v1.0/pico9918_v1_0_gerber.zip -------------------------------------------------------------------------------- /pcb/v1.1/pico9918_v1_1_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v1.1/pico9918_v1_1_gerber.zip -------------------------------------------------------------------------------- /configtool/src/pletter/font.bin.plet5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/font.bin.plet5 -------------------------------------------------------------------------------- /configtool/src/pletter/logoBottom.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoBottom.bin -------------------------------------------------------------------------------- /configtool/tools/cvbasic/pletter.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/tools/cvbasic/pletter.exe -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_picknplace.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_picknplace.csv -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_schematic.png -------------------------------------------------------------------------------- /pcb/v0.3/pico9918_v0_3_vga_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v0.3/pico9918_v0_3_vga_gerber.zip -------------------------------------------------------------------------------- /pcb/v1.1/pico9918_v1_1_schematic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/pcb/v1.1/pico9918_v1_1_schematic.pdf -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | add_subdirectory(host) 4 | add_subdirectory(qc) 5 | 6 | -------------------------------------------------------------------------------- /configtool/src/pletter/logoSprites.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoSprites.bin -------------------------------------------------------------------------------- /configtool/src/pletter/logoTop.bin.plet5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoTop.bin.plet5 -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-enclosure-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-enclosure-1.jpg -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-enclosure-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-enclosure-2.jpg -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-enclosure-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-enclosure-3.jpg -------------------------------------------------------------------------------- /configtool/src/pletter/logoBottom.bin.plet5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoBottom.bin.plet5 -------------------------------------------------------------------------------- /configtool/src/pletter/logoSprites.bin.plet5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/configtool/src/pletter/logoSprites.bin.plet5 -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-installed-bare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-installed-bare.jpg -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-installed-pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-installed-pcb.jpg -------------------------------------------------------------------------------- /nocut/ti99/stl/pico9918-nocut-ti99-bottom.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/stl/pico9918-nocut-ti99-bottom.stl -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/vrEmuTms9918"] 2 | path = submodules/vrEmuTms9918 3 | url = https://github.com/visrealm/vrEmuTms9918.git 4 | -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-installed-enclosure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-installed-enclosure.jpg -------------------------------------------------------------------------------- /nocut/ti99/img/pico9918-nocut-ti99-build-plate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/pico9918-nocut-ti99-build-plate.png -------------------------------------------------------------------------------- /nocut/ti99/pcb/bom-pico9918-nocut-ti994a-v1-3.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/pcb/bom-pico9918-nocut-ti994a-v1-3.csv -------------------------------------------------------------------------------- /nocut/ti99/pcb/cpl-pico9918-nocut-ti994a-v1-3.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/pcb/cpl-pico9918-nocut-ti994a-v1-3.csv -------------------------------------------------------------------------------- /nocut/ti99/stl/pico9918-nocut-ti99-beige-top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/stl/pico9918-nocut-ti99-beige-top.stl -------------------------------------------------------------------------------- /nocut/ti99/stl/pico9918-nocut-ti99-black-top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/stl/pico9918-nocut-ti99-black-top.stl -------------------------------------------------------------------------------- /nocut/ti99/stl/pico9918-nocut-ti99-pcb-spacer.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/stl/pico9918-nocut-ti99-pcb-spacer.stl -------------------------------------------------------------------------------- /nocut/ti99/img/nocut-ti99-installed-enclosure-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/img/nocut-ti99-installed-enclosure-2.jpeg -------------------------------------------------------------------------------- /nocut/ti99/pcb/gerber-pico9918-nocut-ti994a-v1-3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/pcb/gerber-pico9918-nocut-ti994a-v1-3.zip -------------------------------------------------------------------------------- /nocut/ti99/stl/pico9918-nocut-ti99-pcb-spacer-jig.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visrealm/pico9918/HEAD/nocut/ti99/stl/pico9918-nocut-ti99-pcb-spacer-jig.stl -------------------------------------------------------------------------------- /src/flash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | void flashSector(); -------------------------------------------------------------------------------- /configtool/src/lib/cvbasic_9900_epilogue.asm: -------------------------------------------------------------------------------- 1 | SLAST 2 | 3 | ;;; CV BASIC Epilogue 4 | 5 | ; data in low RAM 6 | dorg >2000 7 | 8 | ; must be even aligned 9 | ; mirror for sprite table 10 | sprites bss 128 11 | 12 | ; Vars can start at >2080 13 | dorg >2080 14 | 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "marus25.cortex-debug", 4 | "ms-vscode.cpptools", 5 | "ms-vscode.cpptools-extension-pack", 6 | "ms-vscode.vscode-serial-monitor", 7 | "raspberry-pi.raspberry-pi-pico", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Claude.md 6 | Testing 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | _deps 13 | build/** 14 | testapps/** 15 | configtool/build/** 16 | configtool/CLAUDE.md -------------------------------------------------------------------------------- /src/clocks.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | .program clock 13 | .wrap_target 14 | set pins, 1 15 | set pins, 0 16 | .wrap 17 | -------------------------------------------------------------------------------- /test/qc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROGRAM pico9918qc) 2 | 3 | add_executable(${PROGRAM}) 4 | 5 | target_sources(${PROGRAM} PRIVATE qc.c) 6 | 7 | pico_add_extra_outputs(${PROGRAM}) 8 | pico_enable_stdio_usb(${PROGRAM} 1) 9 | 10 | target_link_libraries(${PROGRAM} PUBLIC 11 | pico_stdlib 12 | pico_time) 13 | 14 | -------------------------------------------------------------------------------- /src/pio-utils/pio_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico-56 - pio utilities 3 | * 4 | * Copyright (c) 2023 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico-56 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "hardware/pio.h" 15 | 16 | void pio_set_y(PIO pio, uint sm, uint32_t y); -------------------------------------------------------------------------------- /configtool/src/lib/cvbasic_6502_epilogue.asm: -------------------------------------------------------------------------------- 1 | rom_end: 2 | times $bfe8-$ db $ff 3 | 4 | dw START 5 | dw 0 ; IRQ2 handler. 6 | 7 | dw 0 8 | dw 0 9 | 10 | ; Initial VDP registers 11 | db $02 12 | db $82 13 | db $06 14 | db $ff 15 | db $00 16 | db $36 17 | db $07 18 | db $01 19 | 20 | dw 0 21 | dw 0 22 | dw BIOS_NMI_RESET_ADDR ; Handler for reset. 23 | dw int_handler ; IRQ1 handler. 24 | -------------------------------------------------------------------------------- /.vscode/cmake_configurations.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug", 5 | "buildType": "Debug" 6 | }, 7 | { 8 | "name": "Release", 9 | "buildType": "Release" 10 | }, 11 | { 12 | "name": "RelWithDebInfo", 13 | "buildType": "RelWithDebInfo" 14 | }, 15 | { 16 | "name": "MinSizeRel", 17 | "buildType": "MinSizeRel" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.github/workflows/firmware-linux.yml: -------------------------------------------------------------------------------- 1 | name: Firmware Linux 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: false 16 | artifact-name-suffix: linux 17 | run-windows: false 18 | run-linux: true 19 | run-macos: false -------------------------------------------------------------------------------- /.github/workflows/firmware-macos.yml: -------------------------------------------------------------------------------- 1 | name: Firmware macOS 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: false 16 | artifact-name-suffix: macos 17 | run-windows: false 18 | run-linux: false 19 | run-macos: true -------------------------------------------------------------------------------- /.github/workflows/configurator-linux.yml: -------------------------------------------------------------------------------- 1 | name: Configurator Linux 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: true 16 | artifact-name-suffix: linux 17 | run-windows: false 18 | run-linux: true 19 | run-macos: false -------------------------------------------------------------------------------- /.github/workflows/configurator-macos.yml: -------------------------------------------------------------------------------- 1 | name: Configurator macOS 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: true 16 | artifact-name-suffix: macos 17 | run-windows: false 18 | run-linux: false 19 | run-macos: true -------------------------------------------------------------------------------- /.github/workflows/firmware-windows.yml: -------------------------------------------------------------------------------- 1 | name: Firmware Windows 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: false 16 | artifact-name-suffix: windows 17 | run-windows: true 18 | run-linux: false 19 | run-macos: false -------------------------------------------------------------------------------- /.github/workflows/configurator-windows.yml: -------------------------------------------------------------------------------- 1 | name: Configurator Windows 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build-common.yml 13 | with: 14 | build-firmware: true 15 | build-configurator: true 16 | artifact-name-suffix: windows 17 | run-windows: true 18 | run-linux: false 19 | run-macos: false -------------------------------------------------------------------------------- /src/pio-utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(LIBRARY pico9918-pio-utils) 4 | 5 | project (${LIBRARY} C) 6 | 7 | set(CMAKE_C_STANDARD 11) 8 | 9 | add_library(${LIBRARY} STATIC pio_utils.c) 10 | 11 | target_include_directories (${LIBRARY} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | target_link_libraries(${LIBRARY} PUBLIC hardware_pio pico_stdlib) 14 | 15 | target_compile_options(${PROJECT_NAME} PUBLIC -include pico/platform/compiler.h) -------------------------------------------------------------------------------- /src/temperature.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | 15 | /* initialise temperature sensor */ 16 | void initTemperature(); 17 | 18 | /* read temperature (celsius) */ 19 | float coreTemperatureC(); 20 | 21 | /* read temperature (fahrenheit) */ 22 | float coreTemperatureF(); -------------------------------------------------------------------------------- /test/host/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROGRAM pico9918test) 2 | 3 | add_executable(${PROGRAM}) 4 | 5 | # generate image array source files from png images 6 | visrealm_generate_bindata_source(${PROGRAM} breakout res/BREAKOUT.* ) 7 | 8 | pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/clocks.pio) 9 | 10 | target_sources(${PROGRAM} PRIVATE test.c font.c) 11 | 12 | pico_add_extra_outputs(${PROGRAM}) 13 | 14 | target_link_libraries(${PROGRAM} PUBLIC 15 | pico_stdlib 16 | hardware_pio) 17 | 18 | -------------------------------------------------------------------------------- /configtool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build Configurator", 6 | "type": "shell", 7 | "command": ".\\build.bat", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "echo": true, 14 | "reveal": "always", 15 | "focus": false, 16 | "panel": "shared" 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/splash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #ifndef PICO9918_NO_SPLASH 18 | #define PICO9918_NO_SPLASH 0 19 | #endif 20 | 21 | void resetSplash(); 22 | 23 | void allowSplashHide(); 24 | 25 | void outputSplash(uint16_t y, uint32_t frameCount, uint32_t vBorder, uint32_t vPixels, uint16_t* pixels); -------------------------------------------------------------------------------- /src/gpu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(LIBRARY pico9918-gpu) 4 | 5 | project (${LIBRARY} C) 6 | 7 | set(CMAKE_C_STANDARD 11) 8 | 9 | if(PICO_PLATFORM STREQUAL "rp2040") 10 | set(PICO9918_ASM_SUFFIX "_m0") 11 | else() 12 | set(PICO9918_ASM_SUFFIX "_m33") 13 | endif() 14 | 15 | add_library(${LIBRARY} STATIC gpu.c thumb9900${PICO9918_ASM_SUFFIX}.S) 16 | 17 | target_include_directories (${LIBRARY} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 18 | 19 | target_link_libraries(${LIBRARY} PRIVATE 20 | pico_stdlib 21 | hardware_flash 22 | vrEmuTms9918) -------------------------------------------------------------------------------- /src/vga/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(LIBRARY pico9918-vga) 4 | 5 | project (${LIBRARY} C) 6 | 7 | set(CMAKE_C_STANDARD 11) 8 | 9 | add_library(${LIBRARY} STATIC vga.c vga-modes.c) 10 | 11 | # generate header file from pio 12 | pico_generate_pio_header(${LIBRARY} ${CMAKE_CURRENT_LIST_DIR}/vga.pio) 13 | 14 | target_include_directories (${LIBRARY} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 15 | 16 | target_link_libraries(${LIBRARY} PRIVATE 17 | pico9918-pio-utils 18 | pico_stdlib 19 | pico_multicore 20 | hardware_pio 21 | hardware_dma) -------------------------------------------------------------------------------- /picosdk-2.0.0-visrealm-fastboot.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/rp2040/boot_stage2/boot2_w25q080.S b/src/rp2040/boot_stage2/boot2_w25q080.S 2 | index c35fb81..7597273 100644 3 | --- a/src/rp2040/boot_stage2/boot2_w25q080.S 4 | +++ b/src/rp2040/boot_stage2/boot2_w25q080.S 5 | @@ -100,6 +100,12 @@ pico_default_asm_setup 6 | regular_func _stage2_boot 7 | push {lr} 8 | 9 | + // ROSC to full speed (testing) 10 | + ldr r3, =0x40060AA1 11 | + uxth r2,r3 12 | + bics r3,r2 13 | + str r2, [r3, #16] 14 | + 15 | // Set pad configuration: 16 | // - SCLK 8mA drive, no slew limiting 17 | // - SDx disable input Schmitt to reduce delay 18 | -------------------------------------------------------------------------------- /.vscode/cmake-kits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Pico", 4 | "compilers": { 5 | "C": "${userHome}/.pico-sdk/toolchain/13_3_Rel1/bin/arm-none-eabi-gcc.exe", 6 | "CXX": "${userHome}/.pico-sdk/toolchain/13_3_Rel1/bin/arm-none-eabi-gcc.exe" 7 | }, 8 | "toolchainFile": "${env:USERPROFILE}/.pico-sdk/sdk/2.0.0/cmake/preload/toolchains/pico_arm_cortex_m33_gcc.cmake", 9 | "environmentVariables": { 10 | "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" 11 | }, 12 | "cmakeSettings": { 13 | "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" 14 | } 15 | } 16 | ] -------------------------------------------------------------------------------- /src/gpu/gpu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * This code is licensed under the MIT license 5 | * 6 | * https://github.com/visrealm/pico9918 7 | * 8 | * Purpose: TMS9900 GPU glue code 9 | * 10 | * Credits: JasonACT (AtariAge) 11 | * 12 | */ 13 | 14 | #pragma once 15 | 16 | #include "impl/vrEmuTms9918Priv.h" 17 | 18 | /* initialize the TMS9900 GPU */ 19 | void gpuInit(); 20 | 21 | /* TMS9900 GPU main loop */ 22 | void gpuLoop(); 23 | 24 | /* trigger the GPU to run */ 25 | inline void gpuTrigger() 26 | { 27 | tms9918->restart = 1; 28 | } 29 | 30 | /* GPU runtime in microseconds */ 31 | uint32_t gpuTime(uint32_t totalTime); 32 | 33 | /* reset the internal gpu time to 0 */ 34 | void resetGpuTime(); 35 | 36 | -------------------------------------------------------------------------------- /configtool/src/pletter/logoSprites.bin.plet5.bas: -------------------------------------------------------------------------------- 1 | DATA BYTE $00, $3f, $7f, $ff, $00, $00, $ff, $93 2 | DATA BYTE $00, $e0, $00, $00, $80, $00, $e0, $f8 3 | DATA BYTE $fc, $3c, $3c, $fc, $1e, $f8, $f0, $00 4 | DATA BYTE $35, $00, $f0, $9a, $00, $00, $f2, $00 5 | DATA BYTE $0f, $40, $60, $f0, $3b, $f0, $7f, $3f 6 | DATA BYTE $68, $0f, $0f, $f8, $bd, $0d, $3f, $07 7 | DATA BYTE $b0, $1f, $5f, $1e, $0e, $96, $00, $1e 8 | DATA BYTE $62, $c0, $1f, $3f, $40, $8f, $90, $8f 9 | DATA BYTE $31, $40, $3f, $07, $ff, $b0, $6f, $08 10 | DATA BYTE $c4, $24, $03, $e4, $04, $e4, $24, $c4 11 | DATA BYTE $08, $61, $6f, $3c, $44, $84, $68, $0c 12 | DATA BYTE $00, $3c, $dd, $39, $7f, $3f, $4f, $e7 13 | DATA BYTE $43, $cc, $3f, $c8, $03, $fb, $3f, $ff 14 | DATA BYTE $ff, $ff, $ff 15 | -------------------------------------------------------------------------------- /src/diag.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | void initDiagnostics(); 17 | 18 | void diagSetTemperature(float tempC); 19 | 20 | void diagSetClockHz(float clockHz); 21 | 22 | void diagnosticsConfigUpdated(); 23 | 24 | void updateDiagnostics(uint32_t frameCount); 25 | 26 | void updateRenderTime(uint32_t renderTime, uint32_t frameTime); 27 | 28 | int renderText(uint16_t scanline, const char *text, uint16_t x, uint16_t y, uint16_t fg, uint16_t bg, uint16_t* pixels); 29 | 30 | void renderDiagnostics(uint16_t y, uint16_t* pixels); 31 | 32 | -------------------------------------------------------------------------------- /src/display.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #if PICO9918_SCART_RGBS 15 | #if PICO9918_SCART_PAL 16 | #define DISPLAY_MODE RGBS_PAL_720_576i_50HZ 17 | #define VIRTUAL_PIXELS_Y (576 / 2) 18 | #else 19 | #define DISPLAY_MODE RGBS_NTSC_720_480i_60HZ 20 | #define VIRTUAL_PIXELS_Y (480 / 2) 21 | #endif 22 | #define DISPLAY_YSCALE 1 23 | #define VIRTUAL_PIXELS_X 720 24 | #else // VGA 25 | #define DISPLAY_MODE VGA_640_480_60HZ 26 | #define DISPLAY_YSCALE 2 27 | #define VIRTUAL_PIXELS_X 640 28 | #define VIRTUAL_PIXELS_Y (480 / DISPLAY_YSCALE) 29 | #endif 30 | -------------------------------------------------------------------------------- /src/gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #include "gpio.h" 13 | 14 | #include "pico/binary_info.h" 15 | 16 | 17 | bi_decl(bi_1pin_with_name(GPIO_GROMCL, "GROM Clock")); 18 | bi_decl(bi_1pin_with_name(GPIO_CPUCL, "CPU Clock")); 19 | bi_decl(bi_pin_mask_with_names(GPIO_CD_MASK, "CPU Data (CD7 - CD0)")); 20 | bi_decl(bi_1pin_with_name(GPIO_CSR, "Read")); 21 | bi_decl(bi_1pin_with_name(GPIO_CSW, "Write")); 22 | bi_decl(bi_1pin_with_name(GPIO_MODE, "Mode")); 23 | bi_decl(bi_1pin_with_name(GPIO_INT, "Interrupt")); 24 | 25 | bi_decl(bi_1pin_with_name(GPIO_RESET, "Host Reset")); 26 | bi_decl(bi_1pin_with_name(GPIO_MODE1, "Mode 1 (V9938)")); 27 | -------------------------------------------------------------------------------- /test/host/clocks.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | .program clock 13 | pull block 14 | .wrap_target 15 | set pins, 1 16 | mov x, osr 17 | onDelay: 18 | jmp x-- onDelay 19 | set pins, 0 20 | mov x, osr 21 | offDelay: 22 | jmp x-- offDelay 23 | .wrap 24 | 25 | 26 | % c-sdk { 27 | 28 | void clock_program_init(PIO pio, uint sm, uint offset, uint pin) { 29 | pio_gpio_init(pio, pin); 30 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); 31 | pio_sm_config c = clock_program_get_default_config(offset); 32 | sm_config_set_set_pins(&c, pin, 1); 33 | pio_sm_init(pio, sm, offset, &c); 34 | } 35 | %} 36 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Pico", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${userHome}/.pico-sdk/sdk/2.1.1/**" 8 | ], 9 | "forcedInclude": [ 10 | "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", 11 | "${userHome}/.pico-sdk/sdk/2.1.1/src/common/pico_base_headers/include/pico.h" 12 | ], 13 | "defines": [], 14 | "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc", 15 | "compileCommands": "${workspaceFolder}/build/compile_commands.json", 16 | "cStandard": "c17", 17 | "cppStandard": "c++14", 18 | "intelliSenseMode": "linux-gcc-arm" 19 | } 20 | ], 21 | "version": 4 22 | } -------------------------------------------------------------------------------- /configtool/src/pletter/logoBottom.bin.plet5.bas: -------------------------------------------------------------------------------- 1 | DATA BYTE $18, $ff, $00, $f8, $cc, $00, $06, $30 2 | DATA BYTE $fe, $00, $00, $f3, $e3, $39, $c3, $03 3 | DATA BYTE $00, $e7, $00, $00, $e3, $e3, $e1, $e0 4 | DATA BYTE $e0, $c0, $c0, $61, $e0, $1b, $7f, $1f 5 | DATA BYTE $d0, $1b, $21, $fc, $f8, $3e, $03, $3e 6 | DATA BYTE $3f, $1f, $1f, $0f, $03, $9d, $0e, $c0 7 | DATA BYTE $3b, $10, $0c, $3f, $fe, $16, $f0, $80 8 | DATA BYTE $18, $01, $07, $00, $07, $08, $10, $20 9 | DATA BYTE $3f, $5c, $24, $01, $2a, $04, $10, $84 10 | DATA BYTE $84, $08, $0f, $60, $80, $60, $82, $39 11 | DATA BYTE $1f, $20, $40, $80, $13, $10, $fe, $02 12 | DATA BYTE $fc, $16, $01, $fe, $10, $58, $10, $24 13 | DATA BYTE $0f, $00, $6c, $84, $00, $fc, $07, $40 14 | DATA BYTE $83, $84, $83, $80, $3a, $25, $fc, $1f 15 | DATA BYTE $37, $3a, $20, $10, $00, $37, $ff, $ff 16 | DATA BYTE $ff, $ff, $c0 17 | -------------------------------------------------------------------------------- /src/palconv.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2025 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | /* convert 16-bit ggggbbbbxxxxrrrr to 16-bit 0000bbbbggggrrrr */ 13 | 14 | .program palconv 15 | .wrap_target // | osr | x | isr | 16 | pull // | GBxR | | | 17 | out x, 8 // | GB | xR | | 18 | out isr, 4 // | G | xR | B | 19 | in osr, 4 // | G | xR | BG | 20 | in x, 4 // | G | xR | BGR | 21 | mov x, isr // | G | BGR | | 22 | in x, 16 // | G | BGR | 0BGR0BGR | 23 | // auto-push 24 | .wrap 25 | 26 | 27 | .program crt 28 | .wrap_target 29 | out null, 1 30 | out x, 3 31 | in null, 1 32 | in x, 3 33 | .wrap 34 | -------------------------------------------------------------------------------- /configtool/src/pletter/logoTop.bin.plet5.bas: -------------------------------------------------------------------------------- 1 | DATA BYTE $01, $1f, $3f, $7f, $ff, $00, $8c, $00 2 | DATA BYTE $ff, $00, $00, $03, $01, $01, $03, $03 3 | DATA BYTE $c3, $e3, $f3, $e0, $00, $e0, $e0, $e1 4 | DATA BYTE $e3, $22, $e3, $e7, $00, $1f, $7f, $17 5 | DATA BYTE $00, $f8, $e0, $c0, $c0, $ff, $fe, $fc 6 | DATA BYTE $f8, $70, $00, $00, $03, $0f, $1f, $8a 7 | DATA BYTE $33, $3e, $3e, $16, $00, $60, $c0, $0e 8 | DATA BYTE $80, $f0, $fc, $10, $fe, $fe, $3f, $12 9 | DATA BYTE $07, $18, $20, $03, $20, $41, $42, $41 10 | DATA BYTE $20, $ff, $a8, $4b, $01, $17, $60, $10 11 | DATA BYTE $00, $08, $05, $85, $85, $04, $1f, $60 12 | DATA BYTE $80, $01, $80, $07, $08, $07, $80, $fe 13 | DATA BYTE $01, $08, $17, $fc, $02, $fe, $04, $81 14 | DATA BYTE $42, $11, $24, $17, $10, $00, $fc, $04 15 | DATA BYTE $33, $00, $84, $00, $86, $1f, $83, $84 16 | DATA BYTE $43, $93, $37, $1f, $fc, $b0, $37, $00 17 | DATA BYTE $20, $40, $ff, $ff, $ff, $ff, $c0 18 | -------------------------------------------------------------------------------- /src/vga/vga-modes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 - vga 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "vga.h" 15 | 16 | typedef enum 17 | { 18 | VGA_640_480_60HZ, 19 | VGA_640_400_70HZ, 20 | VGA_800_600_60HZ, 21 | VGA_1024_768_60HZ, 22 | VGA_1280_1024_60HZ, 23 | RGBS_PAL_720_576i_50HZ, 24 | RGBS_NTSC_720_480i_60HZ, 25 | } VgaMode; 26 | 27 | 28 | /* 29 | * get the vga parameters for known modes 30 | */ 31 | VgaParams vgaGetParams(VgaMode mode); 32 | 33 | /* 34 | * set the scale/multiplier of virtual pixel size 35 | */ 36 | bool setVgaParamsScale(VgaParams* params, int pixelScale); 37 | bool setVgaParamsScaleXY(VgaParams* params, int pixelScaleX, int pixelScaleY); 38 | bool setVgaParamsScaleX(VgaParams* params, int pixelScale); 39 | bool setVgaParamsScaleY(VgaParams* params, int pixelScale); -------------------------------------------------------------------------------- /src/temperature.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #include "hardware/adc.h" 13 | #include "pico/divider.h" 14 | 15 | 16 | #include 17 | 18 | /* 19 | * initialise temperature hardware 20 | */ 21 | void initTemperature() 22 | { 23 | adc_init(); 24 | adc_set_temp_sensor_enabled(true); 25 | 26 | #if PICO_RP2040 27 | adc_select_input(4); // Temperature sensor 28 | #else 29 | adc_select_input(8); // RP2350 QFN80 package only... 30 | #endif 31 | } 32 | 33 | /* 34 | * read temperature (celsius) 35 | */ 36 | float coreTemperatureC() 37 | { 38 | int v = adc_read(); 39 | const float vref = 3.3f; 40 | float t = 27.0f - ((v * vref / 4096.0f) - 0.706f) / 0.001721f; // From the datasheet 41 | return t; 42 | } 43 | 44 | /* 45 | * read temperature (fahrenheit) 46 | */ 47 | float coreTemperatureF() 48 | { 49 | return coreTemperatureC() * (9.0f / 5.0f) + 32.0f; 50 | } -------------------------------------------------------------------------------- /LICENSE_FIRMWARE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Troy Schrapel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /configtool/src/banksel.bas: -------------------------------------------------------------------------------- 1 | #if BANK_SIZE 2 | 3 | g_currentBank = 0 4 | 5 | DEF FN BANKSEL(BANK) = g_currentBank = BANK : GOSUB selectBank 6 | 7 | selectBank0: PROCEDURE 8 | BANK SELECT 0 9 | END 10 | 11 | selectBank1: PROCEDURE 12 | BANK SELECT 1 13 | END 14 | 15 | selectBank2: PROCEDURE 16 | BANK SELECT 2 17 | END 18 | 19 | selectBank3: PROCEDURE 20 | BANK SELECT 3 21 | END 22 | 23 | selectBank4: PROCEDURE 24 | BANK SELECT 4 25 | END 26 | 27 | selectBank5: PROCEDURE 28 | BANK SELECT 5 29 | END 30 | 31 | selectBank6: PROCEDURE 32 | BANK SELECT 6 33 | END 34 | 35 | selectBank7: PROCEDURE 36 | BANK SELECT 7 37 | END 38 | 39 | selectBank8: PROCEDURE 40 | BANK SELECT 8 41 | END 42 | 43 | selectBank9: PROCEDURE 44 | BANK SELECT 9 45 | END 46 | 47 | selectBank10: PROCEDURE 48 | BANK SELECT 10 49 | END 50 | 51 | selectBank11: PROCEDURE 52 | BANK SELECT 11 53 | END 54 | 55 | selectBank12: PROCEDURE 56 | BANK SELECT 12 57 | END 58 | 59 | selectBank: PROCEDURE 60 | ON g_currentBank FAST GOSUB selectBank0,selectBank1,selectBank2,selectBank3,selectBank4,selectBank5,selectBank6,selectBank7,selectBank8,selectBank9,selectBank10,selectBank11,selectBank12 61 | END 62 | 63 | #else ' No banking 64 | DEF FN BANKSEL(BANK) = B = BANK 65 | #endif -------------------------------------------------------------------------------- /src/pio-utils/pio_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico-56 - pio utilities 3 | * 4 | * Copyright (c) 2023 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico-56 9 | * 10 | */ 11 | 12 | #include "pio_utils.h" 13 | 14 | #define BITS_PER_ITER 4 15 | #define ITERATIONS (32 / BITS_PER_ITER) 16 | #define BIT_MASK ((1 << BITS_PER_ITER) - 1) 17 | 18 | #if BITS_PER_ITER * ITERATIONS != 32 19 | # error "BITS_PER_ITER must be a multiple of 32" 20 | #endif 21 | 22 | 23 | /* 24 | * set a pio state machine x or y register 25 | */ 26 | static void pio_set_xy(PIO pio, uint sm, uint32_t val, enum pio_src_dest dest) 27 | { 28 | // shift in 4 bits at a time (8 times) 29 | // we can only shift in up to 5 bits at a time 30 | // but doing 4 as it's easier (factor of 32) 31 | for (int i = 0; i < ITERATIONS; ++i, val >>= BITS_PER_ITER) 32 | { 33 | // set dest to lowest 4 bits of value 34 | pio_sm_exec(pio, sm, pio_encode_set(dest, val & BIT_MASK)); 35 | 36 | // shift the 4 bits into isr 37 | pio_sm_exec(pio, sm, pio_encode_in(dest, BITS_PER_ITER)); 38 | } 39 | 40 | // copy isr into dest register 41 | pio_sm_exec(pio, sm, pio_encode_mov(dest, pio_isr)); 42 | } 43 | 44 | /* 45 | * set the pio state machine y register 46 | */ 47 | void pio_set_y(PIO pio, uint sm, uint32_t y) 48 | { 49 | pio_set_xy(pio, sm, y, pio_y); 50 | } -------------------------------------------------------------------------------- /configtool/src/pico9918conf.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator (TI-99) 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | ' The TI-99 implementation only has 8kB Banks 14 | ' Other implementations have 16kB banks. 15 | 16 | 17 | #if TI994A 18 | BANK ROM 128 19 | CONST BANK_SIZE = 8 20 | #INFO "TI-99/4A - 8KB BANK SIZE" 21 | #elif NABU 22 | CONST BANK_SIZE = 0 23 | #INFO "NABU - No banking" 24 | #elif CREATIVISION 25 | CONST BANK_SIZE = 0 26 | #INFO "CreatiVision - No banking" 27 | #elif SG1000 28 | CONST BANK_SIZE = 0 29 | #INFO "SG-1000/SC-3000 - No banking" 30 | #else 31 | BANK ROM 128 32 | CONST BANK_SIZE = 16 33 | #INFO "Other - 16KB BANK SIZE" 34 | #endif 35 | 36 | #if CREATIVISION 37 | CONST SPRITE_TEST = 0 38 | #INFO "CreatiVision - No sprite test" 39 | #else 40 | CONST SPRITE_TEST = 1 41 | #endif 42 | 43 | #if TI994A 44 | INCLUDE "firmware_8k.h.bas" 45 | #else 46 | INCLUDE "firmware_16k.h.bas" 47 | #endif 48 | 49 | 50 | #if F18A_TESTING 51 | #INFO "F18A testing mode" 52 | #endif 53 | 54 | GOTO main 55 | 56 | INCLUDE "banksel.bas" 57 | INCLUDE "core.bas" 58 | 59 | #if TI994A 60 | INCLUDE "firmware_8k.bas" 61 | #elif BANK_SIZE 62 | INCLUDE "firmware_16k.bas" 63 | #endif 64 | -------------------------------------------------------------------------------- /configtool/src/menu-diag.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | ' Master switch 14 | ' Registers 15 | ' Performance 16 | ' Temperature 17 | ' Palette 18 | 19 | diagMenu: PROCEDURE 20 | 21 | DRAW_TITLE("DIAGNOSTICS") 22 | 23 | MENU_INDEX_OFFSET = 16 24 | MENU_INDEX_COUNT = 5 25 | g_currentMenuIndex = MENU_INDEX_OFFSET 26 | 27 | GOSUB renderMenu 28 | 29 | GOSUB delay 30 | 31 | WHILE 1 32 | 33 | WAIT 34 | 35 | GOSUB menuLoop 36 | 37 | IF valueChanged THEN 38 | RENDER_MENU_ROW(g_currentMenuIndex) 39 | WAIT 40 | vdpOptId = MENU_DATA(g_currentMenuIndex, CONF_INDEX) 41 | IF vdpOptId < 200 THEN 42 | VDP_CONFIG(vdpOptId) = currentValueIndex 43 | END IF 44 | 45 | optionIndex = MENU_DATA(g_currentMenuIndex, CONF_INDEX) 46 | IF optionIndex = CONF_MENU_CANCEL THEN EXIT WHILE 47 | GOSUB delay 48 | END IF 49 | 50 | IF NAV(NAV_CANCEL) THEN EXIT WHILE 51 | 52 | WEND 53 | 54 | g_currentMenuIndex = oldIndex 55 | SET_MENU(MENU_ID_MAIN) 56 | 57 | g_diagDirty = FALSE 58 | FOR I = CONF_DIAG_REGISTERS TO CONF_DIAG_ADDRESS 59 | IF savedConfigValues(I) <> tempConfigValues(I) THEN g_diagDirty = TRUE 60 | NEXT I 61 | 62 | END 63 | -------------------------------------------------------------------------------- /src/vga/vga.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 - vga 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico-56 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | typedef struct 18 | { 19 | uint32_t displayPixels; 20 | uint32_t frontPorchPixels; 21 | uint32_t syncPixels; 22 | uint32_t backPorchPixels; 23 | uint32_t totalPixels; 24 | float freqHz; 25 | bool syncHigh; 26 | } VgaSyncParams; 27 | 28 | typedef struct 29 | { 30 | uint32_t pixelClockKHz; 31 | VgaSyncParams hSyncParams; 32 | VgaSyncParams vSyncParams; 33 | uint32_t hPixelScale; 34 | uint32_t vPixelScale; 35 | uint32_t hVirtualPixels; 36 | uint32_t vVirtualPixels; 37 | float pioDivider; 38 | float pioFreqKHz; 39 | float pioClocksPerPixel; 40 | float pioClocksPerScaledPixel; 41 | } VgaParams; 42 | 43 | 44 | typedef void (*vgaScanlineRgbFn)(uint16_t y, VgaParams* params, uint16_t* pixels); 45 | typedef void (*vgaEndOfFrameFn)(uint32_t frameNumber); 46 | typedef void (*vgaPorchFn)(); 47 | typedef void (*vgaInitFn)(); 48 | typedef void (*vgaEndOfScanlineFn)(); 49 | 50 | extern uint32_t vgaMinimumPioClockKHz(VgaParams* params); 51 | 52 | typedef struct 53 | { 54 | VgaParams params; 55 | vgaInitFn initFn; 56 | vgaScanlineRgbFn scanlineFn; 57 | vgaEndOfFrameFn endOfFrameFn; 58 | vgaEndOfScanlineFn endOfScanlineFn; 59 | vgaPorchFn porchFn; 60 | bool scanlines; 61 | } VgaInitParams; 62 | 63 | void vgaLoop(); 64 | 65 | void vgaInit(VgaInitParams params); 66 | 67 | VgaInitParams *vgaCurrentParams(); 68 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #define PICO9918_SW_VERSION ((PICO9918_MAJOR_VER << 4) | PICO9918_MINOR_VER) 18 | 19 | typedef enum 20 | { 21 | // not settable via registers 22 | CONF_PICO_MODEL = 0, 23 | CONF_HW_VERSION = 1, 24 | CONF_SW_VERSION = 2, 25 | CONF_SW_PATCH_VERSION = 3, 26 | CONF_CLOCK_TESTED = 4, 27 | CONF_DISP_DRIVER = 5, 28 | CONF_FLASH_STATUS = 6, 29 | 30 | // settable via registers 31 | CONF_CRT_SCANLINES = 8, 32 | CONF_SCANLINE_SPRITES = 9, 33 | CONF_CLOCK_PRESET_ID = 10, 34 | 35 | CONF_DIAG = 16, 36 | CONF_DIAG_REGISTERS = 17, 37 | CONF_DIAG_PERFORMANCE = 18, 38 | CONF_DIAG_PALETTE = 19, 39 | CONF_DIAG_ADDRESS = 20, 40 | 41 | CONF_PALETTE_IDX_0 = 128, 42 | CONF_PALETTE_IDX_15 = CONF_PALETTE_IDX_0 + 32, // 16x 2 bytes 43 | 44 | CONF_SAVE_TO_FLASH = 255, 45 | } Pico9918Options; 46 | 47 | typedef enum 48 | { 49 | HWVer_0_3 = 0x03, 50 | HWVer_1_x = 0x10, 51 | } Pico9918HardwareVersion; 52 | 53 | #define CONFIG_BYTES 256 54 | 55 | /* get hardware version (v0.3 or v0.4/v1.0+) */ 56 | Pico9918HardwareVersion currentHwVersion(); 57 | 58 | /* read configuration data from flash */ 59 | void readConfig(uint8_t config[CONFIG_BYTES]); 60 | 61 | /* write configuration data to flash */ 62 | bool writeConfig(uint8_t config[CONFIG_BYTES]); 63 | 64 | /* apply configuration data to vdp instance */ 65 | void applyConfig(); -------------------------------------------------------------------------------- /configtool/tools/bin2cvb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Project: pico9918 3 | # 4 | # PICO9918 Configurator .binary to CVBasic data converter 5 | # 6 | # Copyright (c) 2025 Troy Schrapel 7 | # 8 | # This code is licensed under the MIT license 9 | # 10 | # https://github.com/visrealm/pico9918 11 | # 12 | 13 | 14 | import sys 15 | import argparse 16 | import glob 17 | 18 | def main() -> int: 19 | parser = argparse.ArgumentParser( 20 | description='Convert binary files to CVBasic source files of binary data.', 21 | epilog="GitHub: https://github.com/visrealm/pico9918") 22 | parser.add_argument('-o', '--outfile', help='output file - defaults to base input file name with .bas extension') 23 | parser.add_argument('binfile', nargs='+', help='binary file or pattern to match multiple files') 24 | args = vars(parser.parse_args()) 25 | 26 | bin_files = [] 27 | for pattern in args['binfile']: 28 | bin_files.extend(glob.glob(pattern)) 29 | 30 | if not bin_files: 31 | print("No matching files found.") 32 | sys.exit(1) 33 | 34 | for filename in bin_files: 35 | output_filename = args['outfile'] if args['outfile'] else filename + ".bas" 36 | 37 | with open(output_filename, mode='w') as output: 38 | try: 39 | with open(filename, mode='rb') as binFile: 40 | inpbuf = binFile.read(8) 41 | while inpbuf: 42 | byteStr = ["${:02x}".format(b) for b in inpbuf] 43 | output.write(" DATA BYTE {0}\n".format(", ".join(byteStr))) 44 | inpbuf = binFile.read(8) 45 | 46 | except FileNotFoundError: 47 | print(f"The file '{filename}' was not found.") 48 | continue 49 | except IOError: 50 | print(f"An error occurred while reading the file '{filename}'.") 51 | continue 52 | 53 | return 54 | 55 | # program entry 56 | if __name__ == "__main__": 57 | sys.exit(main()) -------------------------------------------------------------------------------- /configtool/src/input.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | CONST NAV_NONE = 0 14 | CONST NAV_DOWN = 1 15 | CONST NAV_UP = 2 16 | CONST NAV_LEFT = 4 17 | CONST NAV_RIGHT = 8 18 | CONST NAV_OK = 16 19 | CONST NAV_CANCEL = 32 20 | 21 | DEF FN NAV(v) = (g_nav AND (v)) 22 | 23 | ' ----------------------------------------------------------------------------- 24 | ' centralised navigation handling for kb and joystick 25 | ' ----------------------------------------------------------------------------- 26 | updateNavInput: PROCEDURE 27 | g_nav = NAV_NONE 28 | g_key = CONT1.key 29 | 30 | IF g_key >= 48 AND g_key <= 57 THEN 31 | g_key = g_key - 48 32 | ELSEIF g_key >= 65 AND g_key <= 90 THEN 33 | g_key = g_key - 55 34 | ELSEIF g_key > 9 THEN 35 | g_key = 0 36 | END IF 37 | 38 | ' or 39 | IF CONT.DOWN OR (CONT1.KEY = "X") THEN g_nav = g_nav OR NAV_DOWN 40 | 41 | ' or 42 | IF CONT.UP OR (CONT1.KEY = "E") THEN g_nav = g_nav OR NAV_UP 43 | 44 | ' or or (<.> [>]) 45 | IF CONT.RIGHT OR (CONT1.KEY = "D") OR (CONT1.KEY = ".") THEN g_nav = g_nav OR NAV_RIGHT 46 | 47 | ' or or (<,> [<]) 48 | IF CONT.LEFT OR (CONT1.KEY = "S") OR (CONT1.KEY = ",") THEN g_nav = g_nav OR NAV_LEFT 49 | 50 | ' or OR 51 | #if TI994A ' TI-99: CTRL is CONT1.BUTTON2, FCTN is CTRL2.BUTTON2. Let's just ... not 52 | IF (CONT1.KEY = " ") OR (CONT1.KEY = 11) THEN g_nav = g_nav OR NAV_OK 53 | #else 54 | IF CONT.BUTTON2 OR (CONT1.KEY = " ") OR (CONT1.KEY = 11) THEN g_nav = g_nav OR NAV_OK 55 | #endif 56 | 57 | ' or OR 58 | IF CONT.BUTTON OR (CONT1.KEY = "Q") OR (CONT1.KEY = 27) THEN g_nav = g_nav OR NAV_CANCEL 59 | END 60 | 61 | 62 | waitForInput: PROCEDURE 63 | WHILE 1 64 | WAIT 65 | 66 | GOSUB updateNavInput 67 | 68 | IF (g_nav) THEN EXIT WHILE 69 | WEND 70 | END -------------------------------------------------------------------------------- /pcb/v0.3/README.md: -------------------------------------------------------------------------------- 1 | ## PICO9918 v0.3 (2024-06-07) 2 | 3 | This is the first "public" version and is powered by a piggy-backed USB-C Pi Pico module. I have never produced or sold this version beyond the initial few prototypes. 4 | 5 |

PICO9918 v0.3

6 | 7 | 8 | ### Schematic 9 | 10 |

PICO9918 v0.3 Schematic

11 | 12 | 13 | ### PCB 14 | 15 |

PICO9918 v0.3 PCB

16 | 17 | * [PICO9918 v0.3 Gerber](pico9918_v0_3_gerber.zip) 18 | * [PICO9918 v0.3 BOM](pico9918_v0_3_bom.xlsx) 19 | * [PICO9918 v0.3 Pick and place](pico9918_v0_3_picknplace.csv) 20 | 21 | #### VGA dongle PCB 22 | 23 |

PICO9918 v0.3 VGA PCB

24 | 25 | * [PICO9918 v0.3 VGA Gerber](pico9918_v0_3_vga_gerber.zip) 26 | * [VGA connector](https://www.lcsc.com/product-detail/D-Sub-DVI-HDMI-Connectors_TXGA-FDB1519-F0DB300U1KA_C2834384.html) 27 | 28 | ### Changelog 29 | - Switched to use USB-C Pi Pico module instead of genuine Pi Pico. 30 | - Added CPUCLK and GROMCLK. 31 | 32 | ### PCB v0.3 Notes 33 | 34 | There are a number of 0 Ohm resistors (jumpers). You may need to omit the RST resistor. On some machines, the extra time is required to bootstrap the Pico. This will be changed to a soft reset on v0.4. 35 | 36 | ### Raspberry Pi Pico Module 37 | 38 | Note: Due to GROMCLK and CPUCLK using GPIO23 and GPIO29, a genuine Raspberry Pi Pico can't be used. v0.3 of the PCB is designed for the DWEII? RP2040 USB-C module which exposes these additional GPIOs. A future pico9918 revision will do without an external RP2040 board and use the RP2040 directly. 39 | 40 | Purchase links: 41 | * https://www.amazon.com/RP2040-Board-Type-C-Raspberry-Micropython/dp/B0CG9BY48X 42 | * https://www.aliexpress.com/item/1005007066733934.html 43 | 44 | I could reduce the VGA bit depth to 9-bit or 10-bit to allow the use of a genuine Raspberry Pi Pico board, but given the longer-term plan is to use the RP2040 directly, I've decided to go this way for the prototype. 45 | -------------------------------------------------------------------------------- /src/splash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #include "display.h" 13 | 14 | #include "splash.h" 15 | 16 | #if !PICO9918_NO_SPLASH 17 | #include "bmp_splash.h" 18 | #endif 19 | 20 | static int logoOffset = 100; 21 | static bool canHideSplash = false; 22 | 23 | /* 24 | * reset the splash popup (after... reset) 25 | */ 26 | void resetSplash() 27 | { 28 | logoOffset = 100; 29 | } 30 | 31 | void allowSplashHide() 32 | { 33 | canHideSplash = true; 34 | } 35 | 36 | /* 37 | * output the PICO9918 splash logo / firmware version at the bottom of the screen 38 | */ 39 | void outputSplash(uint16_t y, uint32_t frameCount, uint32_t vBorder, uint32_t vPixels, uint16_t* pixels) 40 | { 41 | #if !PICO9918_NO_SPLASH 42 | 43 | if (y == 0) 44 | { 45 | if (frameCount & 0x01) 46 | { 47 | if (frameCount < 200 && logoOffset > (22 - splashHeight)) --logoOffset; 48 | else if (canHideSplash && frameCount > 500) ++logoOffset; 49 | } 50 | } 51 | 52 | if (y < (VIRTUAL_PIXELS_Y - 1)) 53 | { 54 | y -= vBorder + vPixels + logoOffset; 55 | if (y < splashHeight) 56 | { 57 | /* the source image is 2bpp, so 4 pixels in a byte 58 | * this doesn't need to be overly performant as it only 59 | * gets called in the first few seconds of startup (or reset) 60 | */ 61 | const int leftBorderPx = 4; 62 | const int splashBpp = 2; 63 | const int splashPixPerByte = 8 / splashBpp; 64 | uint8_t* splashPtr = splash + (y * splashWidth / splashPixPerByte); 65 | 66 | for (int x = leftBorderPx; x < leftBorderPx + splashWidth; x += splashPixPerByte) 67 | { 68 | uint8_t c = *(splashPtr++); 69 | uint8_t pixMask = 0xc0; 70 | uint8_t offset = 6; 71 | 72 | for (int px = 0; px < 4; ++px, offset -= 2, pixMask >>= 2) 73 | { 74 | uint8_t palIndex = (c & pixMask) >> offset; 75 | if (palIndex) pixels[x + px] = splash_pal[palIndex]; 76 | } 77 | } 78 | } 79 | } 80 | #endif 81 | } 82 | -------------------------------------------------------------------------------- /nocut/README.md: -------------------------------------------------------------------------------- 1 | ## TI-99/4A no-cut mod 2 | 3 | The no-cut mod for the TI-99/4A consists of a custom PCB which replaces the original A/V DIN socket: 4 | 5 | ![ti99 no cut](./ti99/img/nocut-ti99-installed-pcb.jpg) 6 | 7 | The PCB is the supported by the printed enclosure: 8 | 9 | ![ti99 no cut](./ti99/img/nocut-ti99-installed-enclosure.jpg) 10 | 11 | ### PCB 12 | 13 | There is now a single PCB (v1.3) for both JST and FFC connector types. 14 | 15 | #### Configuration 16 | 17 | The 5P/6P jumper is used to connect the correct DIN pin to the ground plane of the no-cut PCB. For 5-pin DINs (most US models), place a jumper between the 5P and middle pin. For 6-pin DINs (most EU/AUS models), place a jumper between the 6P and middle pin. 18 | You can run the board with no jumper at all however the ground for the audio connectors will then be provided via the PICO9918 cable. 19 | 20 | See [ti99/pcb/](ti99/pcb/) 21 | 22 | To install the PCB: 23 | 1. Print and assemble the PCB spacer. 24 | - Print the 3D printed spacer: [stl/pico9918-nocut-ti99-pcb-spacer.stl](stl/pico9918-nocut-ti99-pcb-spacer.stl) 25 | - Print 2x copies of the assembly spacer jig: [stl/pico9918-nocut-ti99-pcb-spacer-jig.stl](stl/pico9918-nocut-ti99-pcb-spacer-jig.stl) 26 | - Insert 8x standard dupont pins from a male pin header into the spacer's 8x holes. 27 | - Using the assembly jigs on either side of the spacer, clamp the entire assembly together in a vice. This will space the pins centrally in the spacer. 28 | 2. Fit and solder the spacer assembly to the BOTTOM side of the no-cut PCB's DIN1 socket. 29 | 3. Desolder and remove the A/V connector from your TI-99/4A main board. 30 | 4. Fit and solder the original A/V connector to the TOP side no-cut PCB DIN2 socket. 31 | 5. Fit and solder the entire no-cut assembly to your TI-99/4A main board via the spacer. 32 | 33 | ### Enclosure 34 | 35 | The vent holes on the black versus beige TI-99/4As are slightly different. For that reason, find either the beige or black version of the enclosure top and the generic enclosure bottom. They should be printed like this: 36 | 37 | ![ti99 no cut print layout](./ti99/img/pico9918-nocut-ti99-build-plate.png) 38 | 39 | The case is held together with 4x screws. The screw hole diameter is 2.4mm (3/32"). I use 4G x 3/8 self-tapping screws, but there is some flexibility in screw sizes. 40 | 41 | See [ti99/stl/](ti99/stl/) 42 | -------------------------------------------------------------------------------- /visrealm_tools.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | 4 | set(IMG_CONV ${CMAKE_SOURCE_DIR}/tools/img2carray.py) 5 | set(BIN_CONV ${CMAKE_SOURCE_DIR}/tools/bin2carray.py) 6 | set(PYTHON python3) 7 | 8 | # custom function to generate source code from images using tools/img2carray.py 9 | function(visrealm_generate_image_source TARGET DST ROMSRC) 10 | set(fullSrc ${CMAKE_CURRENT_SOURCE_DIR}/${ROMSRC}) 11 | cmake_path(GET fullSrc PARENT_PATH srcPath) 12 | 13 | set (RAMSRCARG) 14 | set (extra_args ${ARGN}) 15 | list(LENGTH extra_args extra_count) 16 | if (${extra_count} GREATER 0) 17 | list(GET extra_args 0 RAMSRC) 18 | set(RAMSRCARG -r ${CMAKE_CURRENT_SOURCE_DIR}/${RAMSRC}) 19 | endif() 20 | add_custom_command( 21 | OUTPUT ${DST}.c ${DST}.h 22 | COMMAND ${PYTHON} ${IMG_CONV} -i ${CMAKE_CURRENT_SOURCE_DIR}/${ROMSRC} ${RAMSRCARG} -o ${DST}.c 23 | DEPENDS ${IMG_CONV} ${srcPath} 24 | ) 25 | target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 26 | target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${DST}.c) 27 | endfunction() 28 | 29 | # custom function to generate source code from images using tools/img2carray.py 30 | function(visrealm_generate_image_source_ram TARGET DST RAMSRC) 31 | set(fullSrc ${CMAKE_CURRENT_SOURCE_DIR}/${RAMSRC}) 32 | cmake_path(GET fullSrc PARENT_PATH srcPath) 33 | 34 | add_custom_command( 35 | OUTPUT ${DST}.c ${DST}.h 36 | COMMAND ${PYTHON} ${IMG_CONV} -r ${CMAKE_CURRENT_SOURCE_DIR}/${RAMSRC} -o ${DST}.c 37 | DEPENDS ${IMG_CONV} ${srcPath} 38 | ) 39 | target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 40 | target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${DST}.c) 41 | endfunction() 42 | 43 | 44 | 45 | 46 | # custom function to generate source code from images using tools/img2carray.py 47 | function(visrealm_generate_bindata_source TARGET DST SRC) 48 | set(fullSrc ${CMAKE_CURRENT_SOURCE_DIR}/${SRC}) 49 | cmake_path(GET fullSrc PARENT_PATH srcPath) 50 | 51 | add_custom_command( 52 | OUTPUT ${DST}.c ${DST}.h 53 | COMMAND ${PYTHON} ${BIN_CONV} -i ${CMAKE_CURRENT_SOURCE_DIR}/${SRC} -o ${DST}.c 54 | DEPENDS ${BIN_CONV} ${srcPath} 55 | ) 56 | target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 57 | target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${DST}.c) 58 | endfunction() 59 | 60 | -------------------------------------------------------------------------------- /src/boards/pico9918.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | // ----------------------------------------------------- 8 | // NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO 9 | // SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES 10 | // ----------------------------------------------------- 11 | 12 | // This header may be included by other board headers as "boards/pico.h" 13 | 14 | #pragma once 15 | 16 | // For board detection 17 | #define PICO9918 18 | 19 | #ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER 20 | #define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 32 21 | #endif 22 | 23 | // --- UART --- 24 | #ifndef PICO_DEFAULT_UART 25 | #define PICO_DEFAULT_UART 0 26 | #endif 27 | #ifndef PICO_DEFAULT_UART_TX_PIN 28 | #define PICO_DEFAULT_UART_TX_PIN 0 29 | #endif 30 | #ifndef PICO_DEFAULT_UART_RX_PIN 31 | #define PICO_DEFAULT_UART_RX_PIN 1 32 | #endif 33 | 34 | // --- LED --- 35 | #ifndef PICO_DEFAULT_LED_PIN 36 | #define PICO_DEFAULT_LED_PIN 25 37 | #endif 38 | // no PICO_DEFAULT_WS2812_PIN 39 | 40 | // --- I2C --- 41 | #ifndef PICO_DEFAULT_I2C 42 | #define PICO_DEFAULT_I2C 0 43 | #endif 44 | #ifndef PICO_DEFAULT_I2C_SDA_PIN 45 | #define PICO_DEFAULT_I2C_SDA_PIN 4 46 | #endif 47 | #ifndef PICO_DEFAULT_I2C_SCL_PIN 48 | #define PICO_DEFAULT_I2C_SCL_PIN 5 49 | #endif 50 | 51 | // --- SPI --- 52 | #ifndef PICO_DEFAULT_SPI 53 | #define PICO_DEFAULT_SPI 0 54 | #endif 55 | #ifndef PICO_DEFAULT_SPI_SCK_PIN 56 | #define PICO_DEFAULT_SPI_SCK_PIN 18 57 | #endif 58 | #ifndef PICO_DEFAULT_SPI_TX_PIN 59 | #define PICO_DEFAULT_SPI_TX_PIN 19 60 | #endif 61 | #ifndef PICO_DEFAULT_SPI_RX_PIN 62 | #define PICO_DEFAULT_SPI_RX_PIN 16 63 | #endif 64 | #ifndef PICO_DEFAULT_SPI_CSN_PIN 65 | #define PICO_DEFAULT_SPI_CSN_PIN 17 66 | #endif 67 | 68 | // --- FLASH --- 69 | 70 | #define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 71 | 72 | #ifndef PICO_FLASH_SPI_CLKDIV 73 | #define PICO_FLASH_SPI_CLKDIV 2 74 | #endif 75 | 76 | #ifndef PICO_FLASH_SIZE_BYTES 77 | #define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024) 78 | #endif 79 | 80 | // Drive high to force power supply into PWM mode (lower ripple on 3V3 at light loads) 81 | #define PICO_SMPS_MODE_PIN 23 82 | 83 | #ifndef PICO_RP2040_B0_SUPPORTED 84 | #define PICO_RP2040_B0_SUPPORTED 1 85 | #endif 86 | 87 | // The GPIO Pin used to read VBUS to determine if the device is battery powered. 88 | #ifndef PICO_VBUS_PIN 89 | #define PICO_VBUS_PIN 24 90 | #endif 91 | 92 | // The GPIO Pin used to monitor VSYS. Typically you would use this with ADC. 93 | // There is an example in adc/read_vsys in pico-examples. 94 | #ifndef PICO_VSYS_PIN 95 | #define PICO_VSYS_PIN 29 96 | #endif 97 | -------------------------------------------------------------------------------- /src/vga/vga.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico-56 9 | * 10 | */ 11 | 12 | 13 | ; ----------------------------------------------------------------------------- 14 | ; vga_sync - provides the vga hsync and vsync signals 15 | ; 16 | ; sync data passed-in via dma. each scanline will have four records 17 | ; one for active, front porch, sync and back porch sections 18 | ; 19 | ; +-------------------------+---------+---------+----------------------+ 20 | ; | 31 - 16 | 15 | 14 | 13 - 0 | 21 | ; | instruction | vsync | hsync | delay | 22 | ; +-------------------------+---------+---------+----------------------+ 23 | ; 24 | ; instruction: an instruction to run at the start of each segment (usually nop) 25 | ; vsync : vsync signal (1 for high) 26 | ; hsync : hsync signal (1 for high) 27 | ; delay : time in pio ticks to hold the current sync configuration 28 | 29 | .program vga_sync 30 | 31 | .define public SETUP_OVERHEAD 5 ; pio ticks between segments 32 | .define public WORD_VSYNC_OFFSET 15 33 | .define public WORD_HSYNC_OFFSET 14 34 | .define public WORD_EXEC_OFFSET 16 ; bit offset to instruction data 35 | .define WORD_DELAY_BITS 14 36 | .define WORD_SYNC_BITS 2 37 | .define WORD_EXEC_BITS 16 38 | 39 | ; auto-pull 40 | out x, WORD_DELAY_BITS ; setup delay 41 | out pins, WORD_SYNC_BITS ; output sync pins 42 | out exec, WORD_EXEC_BITS ; execute given instruction 43 | loop: 44 | jmp x-- loop ; fulfil the delay 45 | .wrap 46 | 47 | 48 | 49 | ; ----------------------------------------------------------------------------- 50 | ; vga_rgb - provides the vga rgb data 51 | ; 52 | ; output the vga rgb data. up to 16 bits per pixel supported 53 | ; rgb data passed-in via dma. 54 | ; 55 | ; before using this program, the y register of the state machine must be set 56 | ; to one less than the number of visible pixels in the scanline. 57 | ; additionally, an appropriate delay should be added to the instruction 58 | ; following the loop label. this delay will be the number of pio ticks 59 | ; to wait between pixels less the LOOP_TICKS overhead 60 | ; 61 | ; the program discards any unused bits of rgb data 62 | 63 | .program vga_rgb 64 | .define public LOOP_TICKS 2 65 | .define public DELAY_INSTR loop 66 | .define public RGB_IRQ 4 67 | 68 | mov pins, null 69 | wait 1 irq RGB_IRQ 70 | mov x, y [0] 71 | loop: 72 | ; auto-pull 73 | out pins, 16 ; here, we apply a dynamic delay 74 | jmp x-- loop 75 | .wrap 76 | -------------------------------------------------------------------------------- /configtool/src/conf-scanline-sprites.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | #if SPRITE_TEST 14 | 15 | spriteIndices: 16 | DATA BYTE 0,1,2,3,4,4,5,6 17 | ' P I C O 9 9 1 8 18 | 19 | #endif 20 | 21 | ' ----------------------------------------------------------------------------- 22 | ' initialise the sprite attributes 23 | ' ----------------------------------------------------------------------------- 24 | initSprites: PROCEDURE 25 | 26 | #if SPRITE_TEST 27 | CONST NUM_SPRITES = 16 28 | DIM spriteAttr(NUM_SPRITES * 4) 29 | 30 | xPos = 16 31 | 32 | FOR I = 0 TO NUM_SPRITES - 1 33 | spriteAttrIdx = I * 4 34 | spritePattIndex = spriteIndices(I AND 7) 35 | spriteAttr(spriteAttrIdx) = $d0 36 | spriteAttr(spriteAttrIdx + 1) = xPos 37 | spriteAttr(spriteAttrIdx + 2) = spritePattIndex * 4 38 | spriteAttr(spriteAttrIdx + 3) = 15 39 | xPos = xPos + logoSpriteWidths(spritePattIndex) + 1 40 | IF (I AND 7) = 7 THEN xPos = xPos + 8 ' small gap 41 | NEXT I 42 | #endif 43 | 44 | END 45 | 46 | 47 | ' ----------------------------------------------------------------------------- 48 | ' animate the sprites for 'scanline sprites' option 49 | ' ----------------------------------------------------------------------------- 50 | animateSprites: PROCEDURE 51 | 52 | #if SPRITE_TEST 53 | 54 | CONST spritePosY = 127 55 | 56 | ' "static" values 57 | s_startAnimIndex = s_startAnimIndex + 3 58 | 59 | ' update all y positions 60 | FOR I = 0 TO NUM_SPRITES - 1 61 | spriteAttrIdx = I * 4 62 | spriteAttr(spriteAttrIdx) = spritePosY + sine((s_startAnimIndex + spriteAttr(spriteAttrIdx + 1)) AND $7f) 63 | NEXT I 64 | 65 | s_startSpriteIndex = s_startSpriteIndex + 1 66 | if s_startSpriteIndex >= NUM_SPRITES THEN s_startSpriteIndex = 0 67 | 68 | WAIT 69 | 70 | ' dump it to vram (sprite attribute table) 71 | DEFINE VRAM #VDP_SPRITE_ATTR, (NUM_SPRITES - s_startSpriteIndex) * 4, VARPTR spriteAttr(s_startSpriteIndex * 4) 72 | IF s_startSpriteIndex > 0 THEN 73 | DEFINE VRAM #VDP_SPRITE_ATTR + (NUM_SPRITES - s_startSpriteIndex) * 4, s_startSpriteIndex * 4, VARPTR spriteAttr(0) 74 | END IF 75 | 76 | SPRITE NUM_SPRITES, $d0, 0,0,0 77 | #endif 78 | 79 | END 80 | 81 | 82 | ' ----------------------------------------------------------------------------- 83 | ' hide the sprites when 'scanline sprites' option no longer selected 84 | ' ----------------------------------------------------------------------------- 85 | hideSprites: PROCEDURE 86 | SPRITE 0,$d0,0,0,0 87 | SPRITE 1,$d0,0,0,0 88 | END 89 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | Any custom tools required for the project go here: 2 | 3 | # [img2carray.py](img2carray.py) 4 | 5 | An image converter. Converts images into C arrays for direct use in a PICO-56 program. 6 | 7 | The format of the image data will be `const uint16_t[]` for 24 or 32 bit images and will be a combined `const uint16_t[]` for the palette and a `const uint8_t[]` at 4 bits per pixel for 16 color paletized images and 8 bits per pixel for 256 color paletized images. 8 | 9 | Alpha values are also supported. 10 | 11 | The output format is `0bAAAABBBBGGGGRRRR` or `0xABGR`. 12 | 13 | ## Dependencies 14 | 15 | The script requires the [Pillow Imaging Library](https://pypi.org/project/Pillow/). 16 | 17 | Installation: 18 | 19 | ```sh 20 | python3 -m pip install --upgrade Pillow 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```sh 26 | python3 img2carray.py [-h] [-v] [-p PREFIX] [-o OUT] [-r RAM [RAM ...]] [-i IN [IN ...]] 27 | 28 | Convert images into C-style arrays for use with the PICO-56. 29 | 30 | options: 31 | -h, --help show this help message and exit 32 | -v, --verbose verbose output 33 | -p PREFIX, --prefix PREFIX 34 | array variable prefix 35 | -o OUT, --out OUT output file - defaults to base input file name with .c extension 36 | -r RAM [RAM ...], --ram RAM [RAM ...] 37 | input file(s) to store in Pi Pico RAM - can use wildcards 38 | -i IN [IN ...], --in IN [IN ...] 39 | input file(s) to store in Pi Pico ROM - can use wildcards 40 | ``` 41 | 42 | ### input 43 | 44 | A filename or glob (wildcards) to convert. By default, the arrays will be stored in the Pi Pico flash/ROM. TO have an image array assigned to be stored in RAM, pass it in using the -r / --ram command-line prefix. 45 | 46 | ### output 47 | 48 | Optional parameter to specify a single output file. 49 | 50 | By default, the output files will be named the same as the input file(s) with a .c/.h extension. This option allows you to combine multiple images into a single C source. A header file of the same name is also generated. 51 | 52 | ## Example usage 53 | 54 | ```sh 55 | python img2carray.py -i res/*.png -o images.c 56 | ``` 57 | 58 | Will generate images.c and images.h containing all .png images in the res directory. 59 | 60 | ## CMake integration 61 | 62 | [The root CMakeLists.txt](../CMakeLists.txt) contains a `visrealm_generate_image_source()` function which can be used to integrate this tool into your build process. 63 | 64 | ```sh 65 | visrealm_generate_image_source( []) 66 | ``` 67 | 68 | Here is an example usage in your project's CMakeLists.txt: 69 | 70 | ```sh 71 | visrealm_generate_image_source(${PROGRAM} images res/*.png res/myramimage.png) 72 | ``` 73 | 74 | This function will generate the C source file(s) from the input images and also add the .c file to the `target_sources()`. The generated file(s) will be placed in yout project's build directory. -------------------------------------------------------------------------------- /configtool/src/pletter/font.bin.plet5.bas: -------------------------------------------------------------------------------- 1 | DATA BYTE $3e, $00, $39, $00, $18, $00, $00, $2d 2 | DATA BYTE $01, $6c, $00, $25, $0f, $06, $fe, $01 3 | DATA BYTE $40, $0b, $11, $7e, $c0, $7c, $06, $fc 4 | DATA BYTE $40, $18, $10, $c6, $cc, $18, $30, $66 5 | DATA BYTE $00, $c6, $00, $38, $6c, $38, $76, $dc 6 | DATA BYTE $cc, $06, $76, $00, $30, $30, $60, $8a 7 | DATA BYTE $27, $0c, $15, $00, $2a, $18, $0c, $0f 8 | DATA BYTE $03, $00, $a0, $0b, $10, $66, $3c, $ff 9 | DATA BYTE $3c, $75, $66, $50, $39, $7d, $50, $5c 10 | DATA BYTE $c3, $18, $00, $7e, $00, $58, $6c, $00 11 | DATA BYTE $06, $a0, $2c, $41, $c0, $80, $00, $7c 12 | DATA BYTE $02, $ce, $de, $f6, $e6, $c6, $7c, $12 13 | DATA BYTE $65, $38, $7f, $32, $00, $0f, $c6, $06 14 | DATA BYTE $7c, $c0, $c0, $fe, $00, $05, $fc, $06 15 | DATA BYTE $06, $3c, $06, $78, $22, $5f, $cc, $00 16 | DATA BYTE $fe, $0c, $5f, $33, $fe, $c0, $11, $29 17 | DATA BYTE $27, $1c, $08, $c6, $57, $07, $0f, $0d 18 | DATA BYTE $57, $7a, $2f, $0c, $71, $02, $07, $7e 19 | DATA BYTE $06, $76, $3a, $5b, $f3, $03, $30, $b8 20 | DATA BYTE $66, $9f, $ee, $7e, $80, $33, $0b, $91 21 | DATA BYTE $38, $00, $a3, $71, $05, $ef, $25, $37 22 | DATA BYTE $de, $00, $c0, $19, $12, $d7, $40, $fe 23 | DATA BYTE $02, $ae, $77, $03, $02, $c5, $17, $c0 24 | DATA BYTE $00, $85, $57, $f8, $cc, $0d, $0a, $fa 25 | DATA BYTE $f8, $7f, $0d, $71, $f8, $97, $b3, $07 26 | DATA BYTE $c0, $46, $1f, $ce, $5d, $1f, $1d, $36 27 | DATA BYTE $46, $37, $ec, $c3, $bf, $06, $b8, $00 28 | DATA BYTE $17, $cc, $d8, $13, $f0, $d8, $cc, $17 29 | DATA BYTE $c0, $a8, $00, $37, $0f, $ee, $fe, $28 30 | DATA BYTE $fe, $d6, $27, $07, $e6, $f6, $55, $de 31 | DATA BYTE $37, $07, $3f, $d7, $00, $27, $6d, $6c 32 | DATA BYTE $4f, $13, $00, $d6, $de, $ea, $dd, $0f 33 | DATA BYTE $37, $8e, $5f, $7c, $47, $75, $ff, $57 34 | DATA BYTE $00, $dc, $67, $6a, $00, $d9, $36, $38 35 | DATA BYTE $d5, $07, $2f, $a0, $63, $07, $6c, $87 36 | DATA BYTE $ba, $00, $43, $15, $d6, $e0, $1e, $97 37 | DATA BYTE $f1, $23, $27, $3c, $30, $93, $00, $3c 38 | DATA BYTE $7f, $60, $84, $f1, $02, $0f, $0c, $e9 39 | DATA BYTE $00, $0f, $10, $5a, $2d, $2c, $d1, $00 40 | DATA BYTE $5e, $24, $24, $e5, $a5, $27, $6e, $7e 41 | DATA BYTE $c2, $8e, $af, $ff, $3a, $0f, $81, $2e 42 | DATA BYTE $7f, $cf, $2c, $16, $17, $e8, $0f, $fb 43 | DATA BYTE $81, $0f, $1c, $36, $30, $78, $30, $67 44 | DATA BYTE $02, $0f, $46, $16, $ae, $99, $2e, $7f 45 | DATA BYTE $74, $44, $c0, $3c, $9b, $2f, $00, $34 46 | DATA BYTE $80, $17, $80, $e3, $bb, $a7, $15, $4c 47 | DATA BYTE $17, $00, $cc, $80, $59, $d6, $07, $b7 48 | DATA BYTE $2f, $70, $4f, $ff, $00, $f1, $81, $f2 49 | DATA BYTE $4f, $3e, $b9, $0f, $f9, $ee, $5f, $f8 50 | DATA BYTE $f6, $c6, $cc, $18, $0e, $0f, $ee, $36 51 | DATA BYTE $87, $36, $ff, $3b, $07, $8c, $ff, $07 52 | DATA BYTE $e7, $80, $6b, $17, $3f, $0b, $b8, $ad 53 | DATA BYTE $60, $38, $ff, $3f, $01, $60, $cf, $d8 54 | DATA BYTE $d8, $cf, $60, $3f, $cf, $bb, $63, $03 55 | DATA BYTE $87, $96, $00, $f1, $76, $78, $dc, $f8 56 | DATA BYTE $79, $00, $ff, $ff, $ff, $ff, $80 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.13) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | 9 | # Initialise pico_sdk from installed location 10 | # (note this can come from environment, CMake cache etc) 11 | 12 | # == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == 13 | if(WIN32) 14 | set(USERHOME $ENV{USERPROFILE}) 15 | else() 16 | set(USERHOME $ENV{HOME}) 17 | endif() 18 | set(sdkVersion 2.1.1) 19 | set(toolchainVersion 14_2_Rel1) 20 | set(picotoolVersion 2.1.1) 21 | set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) 22 | if (EXISTS ${picoVscode}) 23 | include(${picoVscode}) 24 | endif() 25 | # ==================================================================================== 26 | 27 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR}/src/boards ) 28 | set(PICO_BOARD pico9918 CACHE STRING "Board type") 29 | #set(PICO_BOARD pimoroni_pga2350 CACHE STRING "Board type") 30 | 31 | # Check if building configurator only (skip Pico SDK setup) 32 | if(NOT CONFIGURATOR_ONLY) 33 | # Pull in Raspberry Pi Pico SDK (must be before project) 34 | include(pico_sdk_import.cmake) 35 | endif() 36 | 37 | # pull in helpers for my custom tools 38 | include(visrealm_tools.cmake) 39 | include(visrealm_cvbasic.cmake) 40 | 41 | project(pico9918 C CXX ASM) 42 | 43 | # PICO9918 Version Information 44 | set(PICO9918_MAJOR_VER 1) 45 | set(PICO9918_MINOR_VER 0) 46 | set(PICO9918_PATCH_VER 2) 47 | set(PICO9918_VERSION "${PICO9918_MAJOR_VER}.${PICO9918_MINOR_VER}.${PICO9918_PATCH_VER}") 48 | string(REPLACE "." "-" PICO9918_VERSION_STR "${PICO9918_VERSION}") 49 | 50 | # PICO9918 Build Options 51 | option(PICO9918_SCART_RGBS "Enable SCART RGBs output (default: VGA)" OFF) 52 | option(PICO9918_SCART_PAL "Use PAL 576i timing (default: NTSC 480i)" OFF) 53 | option(PICO9918_NO_SPLASH "Disable splash screen" OFF) 54 | option(PICO9918_DIAG "Enable diagnostic mode" OFF) 55 | 56 | add_definitions(-DPICO_BUILD=1) 57 | add_definitions(-DVR_EMU_TMS9918_SINGLE_INSTANCE=1) 58 | add_definitions(-DVR_EMU_TMS9918_MODE=1) 59 | 60 | 61 | add_definitions(-DSYS_CLK_HZ=252000000) 62 | add_definitions(-DPLL_SYS_VCO_FREQ_HZ=1512000000) 63 | add_definitions(-DPLL_SYS_POSTDIV1=6) 64 | add_definitions(-DPLL_SYS_POSTDIV2=1) 65 | 66 | # SDK Flash write routines exit XIP mode and use boot2 to reenter it, so 67 | # boot2 needs to be properly configured for whatever speed we end up at. 68 | add_definitions(-DPICO_FLASH_SPI_CLKDIV=4) # This could be 2, but then we can't write to firmware at higher clocks 69 | add_definitions(-DPICO_XOSC_STARTUP_DELAY_MULTIPLIER=8) 70 | 71 | 72 | if(CONFIGURATOR_ONLY) 73 | add_subdirectory(configtool) 74 | else() 75 | # Initialise the Raspberry Pi Pico SDK 76 | pico_sdk_init() 77 | 78 | add_subdirectory(submodules/vrEmuTms9918) 79 | add_subdirectory(src) 80 | add_subdirectory(test) 81 | add_subdirectory(configtool) 82 | endif() -------------------------------------------------------------------------------- /src/gpio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | /* 15 | * Pin mapping 16 | * 17 | * Pico Pin | GPIO (v0.3) | GPIO (v0.4+) | Name | TMS9918A Pin 18 | * ----------+-------------+--------------+-----------+------------- 19 | * 19 | 14 | 14 | CD7 | 24 20 | * 20 | 15 | 15 | CD6 | 23 21 | * 21 | 16 | 16 | CD5 | 22 22 | * 22 | 17 | 17 | CD4 | 21 23 | * 24 | 18 | 18 | CD3 | 20 24 | * 25 | 19 | 19 | CD2 | 19 25 | * 26 | 20 | 20 | CD1 | 18 26 | * 27 | 21 | 21 | CD0 | 17 27 | * 29 | 22 | 22 | /INT | 16 28 | * 30 | RUN | 23 | RST | 34 29 | * 31 | 26 | 26 | /CSR | 15 30 | * 32 | 27 | 27 | /CSW | 14 31 | * 34 | 28 | 28 | MODE | 13 32 | * -- | -- | 29 | MODE 1 | -- 33 | * 35 | 29 | 25 | GROMCLK | 37 34 | * 37 | 23 | 24 | CPUCLK | 38 35 | * 36 | * 37 | * Note: Due to GROMCLK and CPUCLK using GPIO23 and GPIO29 38 | * a genuine Raspberry Pi Pico can't be used. 39 | * v0.3 of the PCB is designed for the DWEII? 40 | * RP2040 USB-C module which exposes these additional 41 | * GPIOs. A future pico9918 revision (v0.4+) will do without 42 | * an external RP2040 board and use the RP2040 directly. 43 | * 44 | * Note: Hardware v0.3 has different GPIO mappings for GROMCL and CPUCL 45 | * Hardware v0.3 doesn't have a soft reset GPIO either 46 | * 47 | * Purchase links for v0.3 Pi Pico module: 48 | * https://www.amazon.com/RP2040-Board-Type-C-Raspberry-Micropython/dp/B0CG9BY48X 49 | * https://www.aliexpress.com/item/1005007066733934.html 50 | */ 51 | 52 | #pragma once 53 | 54 | #include "tms9918.pio.h" 55 | 56 | #define GPIO_CD7 14 57 | #define GPIO_CSR tmsRead_CSR_PIN // defined in tms9918.pio.h 58 | #define GPIO_CSW tmsWrite_CSW_PIN // defined in tms9918.pio.h 59 | #define GPIO_MODE 28 60 | #define GPIO_MODE1 29 61 | #define GPIO_INT 22 62 | #define GPIO_RESET 23 63 | 64 | // default mappings (v0.4+) 65 | #define GPIO_GROMCL 25 66 | #define GPIO_CPUCL 24 67 | 68 | // v0.3-specific pins mappings 69 | #define GPIO_GROMCL_V03 29 70 | #define GPIO_CPUCL_V03 23 71 | 72 | // gpio masks 73 | #define GPIO_CD_MASK (0xff << GPIO_CD7) 74 | #define GPIO_CSR_MASK (0x01 << GPIO_CSR) 75 | #define GPIO_CSW_MASK (0x01 << GPIO_CSW) 76 | #define GPIO_MODE_MASK (0x01 << GPIO_MODE) 77 | #define GPIO_MODE1_MASK (0x01 << GPIO_MODE1) 78 | #define GPIO_INT_MASK (0x01 << GPIO_INT) 79 | #define GPIO_RESET_MASK (0x01 << GPIO_RESET) 80 | 81 | -------------------------------------------------------------------------------- /src/tms9918.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | ; ----------------------------------------------------------------------------- 13 | ; tmsRead - monitor the CSR pin and send either status or data value 14 | ; 15 | ; due to the read-ahead nature of the TMS9918, we have the 16 | ; possible read values already. Each tx FIFO word contains 17 | ; the current values for status, read data and pin direction 18 | ; 19 | ; fifo osr 0b|xxxxxxxx|ssssssss|dddddddd|11111111| 20 | ; | ignore | status | data | pindir | 21 | ; 22 | ; fifo isr 0b|m|w|r| 23 | ; |o|r|e| 24 | ; |d|i|a| 25 | ; |e|t|d| 26 | ; | |e| | 27 | ; 28 | 29 | .program tmsRead 30 | .define public CSR_PIN 26 31 | 32 | pull block 33 | mov x, osr ; ensure we have a valid fifo value in x 34 | 35 | .wrap_target 36 | wait 1 gpio CSR_PIN ; wait for CSR to go high (inactive) 37 | mov osr, null ; change CD0-7 pindirs to inputs 38 | out pindirs, 8 39 | mov isr, null 40 | 41 | pullLoop: 42 | pull noblock ; continuously empty the tx fifo 43 | mov x, osr ; since we want the latest value 44 | jmp pin pullLoop 45 | 46 | in pins, 1 ; hacky way to jmp based on the CSR pin 47 | mov y, isr ; since we can only have one jmp pin 48 | 49 | out pindirs, 8 ; set up CD0-7 as outputs 50 | out pins, 8 ; mode is low, send the data byte 51 | 52 | jmp !y, endPart ; still 1? loop, otherwise, let's read 53 | 54 | readStatus: 55 | out pins, 8 ; output the status byte 56 | mov isr, x ; make this status value available 57 | endPart: 58 | in pins, 1 ; push CSR, CSW and MODE back through fifo 59 | push ; push ^^^ back to cpu to process 60 | .wrap 61 | 62 | ; ----------------------------------------------------------------------------- 63 | ; tmsWrite - monitor the CSW pin and pass on pin state via FIFO 64 | ; 65 | ; very simple grab the data and send it through... 66 | ; state at CSW going low in the high 16-bits 67 | ; state at CSW going high in the low 16-bits 68 | ; 69 | ; fifo isr 0b|x|m|w|r|xxxx|dddddddd|x|m|w|r|xxxx|dddddddd| 70 | ; | |o|r|e| | CD0-7 | | | | | | CD0-7 | 71 | ; | |d|i|a| | ignore | | | | | | | 72 | ; | |e|t|d| | | | | | | | | 73 | ; | | |e| | | | | | | | | | 74 | 75 | .program tmsWrite 76 | .define public CSW_PIN 27 77 | 78 | wait 0 gpio CSW_PIN [4] ; wait for CSW to go active (low) 79 | in pins, 16 ; grab the initial (mode) state 80 | pollWriteLoop: 81 | mov x, pins ; continuously grab pin state 82 | jmp pin captureWrite ; and wait for csw high 83 | jmp pollWriteLoop 84 | captureWrite: 85 | in x, 16 [4] ; grab the final state prior to CSRW high 86 | .wrap 87 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.options.statusBarVisibility": "hidden", 3 | "cmake.options.advanced": { 4 | "build": { 5 | "statusBarVisibility": "hidden" 6 | }, 7 | "launch": { 8 | "statusBarVisibility": "hidden" 9 | }, 10 | "debug": { 11 | "statusBarVisibility": "hidden" 12 | } 13 | }, 14 | "cmake.configureOnEdit": false, 15 | "cmake.automaticReconfigure": false, 16 | "cmake.configureOnOpen": false, 17 | "cmake.generator": "Ninja", 18 | "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", 19 | "cmake.configureArgs": [ 20 | "-DPICO9918_SCART_RGBS=OFF", 21 | "-DPICO9918_SCART_PAL=OFF", 22 | "-DPICO9918_NO_SPLASH=OFF", 23 | "-DPICO9918_DIAG=OFF" 24 | ], 25 | "C_Cpp.debugShortcut": false, 26 | "terminal.integrated.env.windows": { 27 | "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.1.1", 28 | "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", 29 | "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.0.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" 30 | }, 31 | "terminal.integrated.env.osx": { 32 | "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.1.1", 33 | "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", 34 | "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.0.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" 35 | }, 36 | "terminal.integrated.env.linux": { 37 | "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.1.1", 38 | "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", 39 | "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.0.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" 40 | }, 41 | "raspberry-pi-pico.cmakeAutoConfigure": true, 42 | "raspberry-pi-pico.useCmakeTools": false, 43 | "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", 44 | "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", 45 | "raspberry-pi-pico.python3Path": "${HOME}/.pico-sdk/python/3.12.1/python.exe", 46 | "files.associations": { 47 | "version.h": "c", 48 | "system_error": "c", 49 | "vremutms9918.h": "c", 50 | "cstdint": "c", 51 | "array": "c", 52 | "string": "c", 53 | "string_view": "c", 54 | "pico.h": "c", 55 | "platform.h": "c", 56 | "gpu.h": "c", 57 | "random": "c", 58 | "dma.h": "c", 59 | "ssi.h": "c", 60 | "vremutms9918priv.h": "c", 61 | "tms9918.pio.h": "c", 62 | "temperature.h": "c", 63 | "bmp_splash.h": "c", 64 | "flash.h": "c", 65 | "string.h": "c", 66 | "bmp_font.h": "c", 67 | "compare": "c", 68 | "algorithm": "c", 69 | "format": "c", 70 | "span": "c" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /configtool/src/vdp-utils.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | ' VDP constants 14 | CONST #VDP_NAME_TAB = $1800 15 | CONST #VDP_SPRITE_ATTR = $1B00 16 | CONST #VDP_FIRMWARE_DATA = $1D00 17 | CONST #VDP_SPRITE_PATT = $3800 18 | 19 | 20 | CONST #VDP_PATT_TAB1 = $0000 21 | CONST #VDP_PATT_TAB2 = #VDP_PATT_TAB1 + $0800 22 | CONST #VDP_PATT_TAB3 = #VDP_PATT_TAB2 + $0800 23 | 24 | CONST #VDP_COLOR_TAB1 = $2000 25 | CONST #VDP_COLOR_TAB2 = #VDP_COLOR_TAB1 + $0800 26 | CONST #VDP_COLOR_TAB3 = #VDP_COLOR_TAB2 + $0800 27 | 28 | 29 | #if TMS9918_TESTING 30 | DEF FN VDP_REG(VR) = IF (VR < 8) THEN VDP(VR) 31 | DEF FN VDP_STATUS = 0 32 | #else 33 | DEF FN VDP_REG(VR) = VDP(VR) 34 | DEF FN VDP_STATUS = USR RDVST 35 | #endif 36 | 37 | DEF FN VDP_CONFIG(I) = VDP_REG(58) = I : VDP_REG(59) ' = xxx 38 | DEF FN VDP_STATUS_REG = VDP_REG(15) 39 | DEF FN VDP_STATUS_REG0 = VDP_STATUS_REG = 0 40 | 41 | ' VDP helpers 42 | DEF FN VDP_DISABLE_INT = VDP_REG(1) = $C2 43 | DEF FN VDP_ENABLE_INT = VDP_REG(1) = $E2 44 | DEF FN VDP_DISABLE_INT_DISP_OFF = VDP_REG(1) = $82 45 | DEF FN VDP_ENABLE_INT_DISP_OFF = VDP_REG(1) = $A2 46 | ' name table helpers 47 | DEF FN XY(X, Y) = ((Y) * 32 + (X)) ' PRINT AT XY(1, 2), ... 48 | 49 | DEF FN NAME_TAB_XY(X, Y) = (#VDP_NAME_TAB + XY(X, Y)) ' DEFINE VRAM NAME_TAB_XY(1, 2), ... 50 | DEF FN PUT_XY(X, Y) = VPOKE NAME_TAB_XY(X, Y) ' place a byte in the name table 51 | DEF FN GET_XY(X, Y) = VPEEK(NAME_TAB_XY(X, Y)) ' read a byte from the name table 52 | 53 | 54 | ' ----------------------------------------------------------------------------- 55 | ' detect the vdp type. sets isF18ACompatible 56 | ' ----------------------------------------------------------------------------- 57 | vdpDetect: PROCEDURE 58 | GOSUB vdpUnlock 59 | DEFINE VRAM $3F00, 6, vdpGpuDetect 60 | VDP_REG($36) = $3F ' set gpu start address msb 61 | VDP_REG($37) = $00 ' set gpu start address lsb (triggers) 62 | isF18ACompatible = VPEEK($3F00) = 0 ' check result 63 | isV9938 = FALSE 64 | IF isF18ACompatible = FALSE THEN 65 | VDP_STATUS_REG = 4 66 | isV9938 = ((VDP_STATUS AND $fe) = $fe) 67 | VDP_STATUS_REG0 68 | END IF 69 | END 70 | 71 | ' ----------------------------------------------------------------------------- 72 | ' unlock F18A mode 73 | ' ----------------------------------------------------------------------------- 74 | vdpUnlock: PROCEDURE 75 | VDP_DISABLE_INT_DISP_OFF 76 | VDP_REG(57) = $1C ' unlock 77 | VDP_REG(57) = $1C ' unlock... again 78 | VDP_ENABLE_INT_DISP_OFF 79 | END 80 | 81 | ' ----------------------------------------------------------------------------- 82 | ' TMS9900 machine code (for PICO9918 GPU) to write $00 to VDP $3F00 83 | ' ----------------------------------------------------------------------------- 84 | vdpGpuDetect: 85 | DATA BYTE $04, $E0 ' CLR @>3F00 86 | DATA BYTE $3F, $00 87 | DATA BYTE $03, $40 ' IDLE 88 | 89 | defaultReg: ' default VDP register values 90 | DATA BYTE $02, $82, $06, $FF, $03, $36, $07, $00 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug (Cortex-Debug)", 6 | "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", 7 | "executable": "${command:raspberry-pi-pico.launchTargetPath}", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "openocd", 11 | "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", 12 | "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", 13 | "device": "${command:raspberry-pi-pico.getChipUppercase}", 14 | "configFiles": [ 15 | "interface/cmsis-dap.cfg", 16 | "target/${command:raspberry-pi-pico.getTarget}.cfg" 17 | ], 18 | "svdFile": "${userHome}/.pico-sdk/sdk/2.1.1/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", 19 | "runToEntryPoint": "main", 20 | // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected 21 | // Also works fine for flash binaries 22 | "overrideLaunchCommands": [ 23 | "monitor reset init", 24 | "load \"${command:raspberry-pi-pico.launchTargetPath}\"" 25 | ], 26 | "openOCDLaunchCommands": [ 27 | "adapter speed 5000" 28 | ] 29 | }, 30 | { 31 | "name": "Pico Debug (Cortex-Debug with external OpenOCD)", 32 | "cwd": "${workspaceRoot}", 33 | "executable": "${command:raspberry-pi-pico.launchTargetPath}", 34 | "request": "launch", 35 | "type": "cortex-debug", 36 | "servertype": "external", 37 | "gdbTarget": "localhost:3333", 38 | "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", 39 | "device": "${command:raspberry-pi-pico.getChipUppercase}", 40 | "svdFile": "${userHome}/.pico-sdk/sdk/2.1.1/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", 41 | "runToEntryPoint": "main", 42 | // Give restart the same functionality as runToEntryPoint - main 43 | "postRestartCommands": [ 44 | "break main", 45 | "continue" 46 | ] 47 | }, 48 | { 49 | "name": "Pico Debug (C++ Debugger)", 50 | "type": "cppdbg", 51 | "request": "launch", 52 | "cwd": "${workspaceRoot}", 53 | "program": "${command:raspberry-pi-pico.launchTargetPath}", 54 | "MIMode": "gdb", 55 | "miDebuggerPath": "${command:raspberry-pi-pico.getGDBPath}", 56 | "miDebuggerServerAddress": "localhost:3333", 57 | "debugServerPath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", 58 | "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/${command:raspberry-pi-pico.getTarget}.cfg -c \"adapter speed 5000\"", 59 | "serverStarted": "Listening on port .* for gdb connections", 60 | "filterStderr": true, 61 | "hardwareBreakpoints": { 62 | "require": true, 63 | "limit": 4 64 | }, 65 | "preLaunchTask": "Flash", 66 | "svdPath": "${userHome}/.pico-sdk/sdk/2.1.1/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd" 67 | }, 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /configtool/src/config.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | ' ----------------------------------------------------------------------------- 14 | ' reset options to defaults 15 | ' ----------------------------------------------------------------------------- 16 | resetOptions: PROCEDURE 17 | VDP_DISABLE_INT 18 | FOR I = 0 TO CONF_COUNT - 1 19 | tempConfigValues(I) = 0 20 | NEXT I 21 | 22 | g_paletteDirty = FALSE 23 | FOR I = 0 TO 31 24 | tempConfigValues(128 + I) = defPal(I) 25 | IF tempConfigValues(128 + I) <> savedConfigValues(128 + I) THEN g_paletteDirty = TRUE 26 | NEXT I 27 | 28 | g_diagDirty = FALSE 29 | FOR I = CONF_DIAG TO CONF_DIAG_ADDRESS 30 | IF tempConfigValues(I) <> savedConfigValues(I) THEN g_diagDirty = TRUE 31 | NEXT I 32 | 33 | FOR I = 0 TO CONF_COUNT - 1 34 | VDP_CONFIG(I) = tempConfigValues(I) 35 | NEXT I 36 | 37 | GOSUB applyConfigValues 38 | GOSUB renderMainMenu 39 | VDP_ENABLE_INT 40 | END 41 | 42 | ' ----------------------------------------------------------------------------- 43 | ' save the current config to PICO9918 flash 44 | ' ----------------------------------------------------------------------------- 45 | saveOptions: PROCEDURE 46 | 47 | ' instruct the pico9918 to commit config to flash 48 | VDP_CONFIG(CONF_SAVE_TO_FLASH) = 1 49 | 50 | clockChanged = savedConfigValues(CONF_CLOCK_PRESET_ID) <> tempConfigValues(CONF_CLOCK_PRESET_ID) 51 | 52 | ' update device values again 53 | FOR I = 0 TO CONF_COUNT - 1 54 | savedConfigValues(I) = tempConfigValues(I) 55 | NEXT I 56 | 57 | g_paletteDirty = FALSE 58 | g_diagDirty = FALSE 59 | 60 | GOSUB renderMainMenu 61 | 62 | END 63 | 64 | ' ----------------------------------------------------------------------------- 65 | ' load config values from VDP to tempConfigValues() and savedConfigValues() arrays 66 | ' ----------------------------------------------------------------------------- 67 | vdpLoadConfigValues: PROCEDURE 68 | #if F18A_TESTING 69 | FOR I = 0 TO CONF_COUNT - 1 70 | tempConfigValues(I) = 0 71 | savedConfigValues(I) = 0 72 | NEXT I 73 | FOR I = 0 TO 31 74 | tempConfigValues(128 + I) = defPal(I) 75 | savedConfigValues(128 + I) = defPal(I) 76 | NEXT I 77 | #else 78 | VDP_STATUS_REG = 12 ' read config register 79 | FOR I = 0 TO CONF_COUNT - 1 80 | VDP_REG(58) = I 81 | optValue = VDP_STATUS 82 | tempConfigValues(I) = optValue 83 | savedConfigValues(I) = optValue 84 | NEXT I 85 | VDP_STATUS_REG0 86 | #endif 87 | END 88 | 89 | ' ----------------------------------------------------------------------------- 90 | ' apply current options to the PICO9918 91 | ' ----------------------------------------------------------------------------- 92 | applyConfigValues: PROCEDURE 93 | VDP_REG(50) = tempConfigValues(CONF_CRT_SCANLINES) * 4 ' set crt scanlines 94 | VDP_REG(30) = pow2(tempConfigValues(CONF_SCANLINE_SPRITES) + 2) ' set scanline sprites 95 | 96 | VDP_REG(47) = $c0 97 | DEFINE VRAM 0, 32, VARPTR tempConfigValues(128) 98 | VDP_REG(47) = $40 99 | 100 | END 101 | -------------------------------------------------------------------------------- /configtool/src/lib/cvbasic_epilogue.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; CVBasic epilogue (BASIC compiler for Colecovision) 3 | ; 4 | ; by Oscar Toledo G. 5 | ; https://nanochess.org/ 6 | ; 7 | ; Creation date: Feb/27/2024. 8 | ; Revision date: Feb/29/2024. Added joystick, keypad, frame, random, and 9 | ; read_pointer variables. 10 | ; Revision date: Mar/04/2024. Added music player. 11 | ; Revision date: Mar/05/2024. Added support for Sega SG1000. 12 | ; Revision date: Mar/12/2024. Added support for MSX. 13 | ; Revision date: Mar/13/2024. Added Pletter decompressor. 14 | ; Revision date: Mar/19/2024. Added support for sprite flicker. 15 | ; Revision date: Apr/11/2024. Added support for Super Game Module. 16 | ; Revision date: Apr/13/2024. Updates LFSR in interruption handler. 17 | ; Revision date: Apr/26/2024. All code moved to cvbasic_prologue.asm so it 18 | ; can remain accessible in bank 0 (bank switching). 19 | ; Revision date: Aug/02/2024. Added rom_end label for Memotech. 20 | ; Revision date: Aug/15/2024. Added support for Tatung Einstein. 21 | ; Revision date: Nov/12/2024. Added vdp_status. 22 | ; Revision date: Feb/03/2025. Round final ROM size to 8K multiples. 23 | ; 24 | 25 | rom_end: 26 | 27 | ; ROM final size rounding 28 | if MSX+COLECO+SG1000+SMS+SVI+SORD 29 | TIMES (($+$1FFF)&$1e000)-$ DB $ff 30 | endif 31 | if MEMOTECH+EINSTEIN+NABU 32 | ; Align following data to a 256-byte page. 33 | TIMES $100-($&$ff) DB $4f 34 | endif 35 | if PV2000 36 | TIMES $10000-$ DB $ff 37 | endif 38 | if SG1000+SMS 39 | if CVBASIC_BANK_SWITCHING 40 | forg CVBASIC_BANK_ROM_SIZE*1024-1 ; Force final ROM size 41 | db $ff 42 | endif 43 | forg $7FF0 44 | org $7FF0 45 | db "TMR SEGA" 46 | db 0,0 47 | db 0,0 ; Checksum 48 | db $11,$78 ; Product code 49 | db $00 ; Version 50 | db $4c ; SMS Export + 32KB for checksum 51 | endif 52 | if COLECO+SG1000+SMS+MSX+SVI+SORD+PV2000 53 | org BASE_RAM 54 | endif 55 | ram_start: 56 | 57 | sprites: 58 | if SMS 59 | rb 256 60 | else 61 | rb 128 62 | endif 63 | sprite_data: 64 | rb 4 65 | frame: 66 | rb 2 67 | read_pointer: 68 | rb 2 69 | cursor: 70 | rb 2 71 | lfsr: 72 | rb 2 73 | mode: 74 | rb 1 75 | flicker: 76 | rb 1 77 | joy1_data: 78 | rb 1 79 | joy2_data: 80 | rb 1 81 | key1_data: 82 | rb 1 83 | key2_data: 84 | rb 1 85 | ntsc: 86 | rb 1 87 | vdp_status: 88 | rb 1 89 | if NABU 90 | nabu_data0: rb 1 91 | nabu_data1: rb 1 92 | nabu_data2: rb 1 93 | endif 94 | 95 | if CVBASIC_MUSIC_PLAYER 96 | music_tick: rb 1 97 | music_mode: rb 1 98 | 99 | if CVBASIC_BANK_SWITCHING 100 | music_bank: rb 1 101 | endif 102 | music_start: rb 2 103 | music_pointer: rb 2 104 | music_playing: rb 1 105 | music_timing: rb 1 106 | music_note_counter: rb 1 107 | music_instrument_1: rb 1 108 | music_counter_1: rb 1 109 | music_note_1: rb 1 110 | music_instrument_2: rb 1 111 | music_counter_2: rb 1 112 | music_note_2: rb 1 113 | music_instrument_3: rb 1 114 | music_counter_3: rb 1 115 | music_note_3: rb 1 116 | music_counter_4: rb 1 117 | music_drum: rb 1 118 | 119 | audio_freq1: rb 2 120 | audio_freq2: rb 2 121 | audio_freq3: rb 2 122 | audio_noise: rb 1 123 | audio_mix: rb 1 124 | audio_vol1: rb 1 125 | audio_vol2: rb 1 126 | audio_vol3: rb 1 127 | 128 | audio_control: rb 1 129 | audio_vol4hw: rb 1 130 | endif 131 | 132 | if SGM 133 | org $2000 ; Start for variables. 134 | endif 135 | -------------------------------------------------------------------------------- /configtool/src/menu-info.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | 14 | deviceInfoMenu: PROCEDURE 15 | const PICO_MODEL_RP2040 = 1 16 | const PICO_MODEL_RP2350 = 2 17 | 18 | g_menuTopRow = MENU_TITLE_ROW + 3 19 | 20 | 21 | DRAW_TITLE("DEVICE INFO") 22 | 23 | oldMenuTopRow = g_menuTopRow 24 | oldIndex = g_currentMenuIndex 25 | 26 | g_menuTopRow = MENU_TITLE_ROW + 14 27 | MENU_INDEX_OFFSET = 13 28 | MENU_INDEX_COUNT = 1 29 | MENU_START_X = 6 30 | g_currentMenuIndex = MENU_INDEX_OFFSET 31 | 32 | GOSUB renderMenu 33 | 34 | g_menuTopRow = oldMenuTopRow 35 | 36 | #addr = XY(2, g_menuTopRow) 37 | PRINT AT #addr, "Processor family : " 38 | PRINT AT #addr + 32, "Hardware version : " 39 | PRINT AT #addr + 64, "Software version : " 40 | PRINT AT #addr + 96, "Display driver : " 41 | PRINT AT #addr + 128, "Resolution : " 42 | PRINT AT #addr + 160, "F18A version : " 43 | PRINT AT #addr + 192, "Core temperature : Error" 44 | 45 | VDP_STATUS_REG = 12 ' config 46 | 47 | VDP_REG(58) = CONF_PICO_MODEL 48 | optValue = VDP_STATUS 49 | #addr = XY(21, g_menuTopRow + 0) 50 | PRINT AT #addr, "RP2" 51 | IF optValue = PICO_MODEL_RP2350 THEN 52 | PRINT "350" 53 | ELSE 54 | PRINT "040" 55 | END IF 56 | 57 | VDP_REG(58) = CONF_HW_VERSION 58 | optValue = VDP_STATUS 59 | tmpMajor = optValue / 16 60 | tmpMinor = optValue AND $0f 61 | PRINT AT #addr + 32, tmpMajor, ".", tmpMinor 62 | IF verMajor = 1 THEN PRINT AT XY(24, g_menuTopRow + 1), "+" 63 | 64 | VDP_REG(58) = CONF_SW_VERSION 65 | optValue = VDP_STATUS 66 | tmpMajor = optValue / 16 67 | tmpMinor = optValue AND $0f 68 | VDP_REG(58) = CONF_SW_PATCH_VERSION 69 | tmpPatch = VDP_STATUS 70 | PRINT AT #addr + 64, tmpMajor, ".", tmpMinor, ".", tmpPatch 71 | 72 | VDP_REG(58) = CONF_DISP_DRIVER 73 | optValue = VDP_STATUS 74 | #addr = #addr + 96 75 | IF optValue = 1 THEN 76 | PRINT AT #addr, "RGBs NTSC" 77 | PRINT AT #addr + 32, "480i 60Hz" 78 | ELSEIF optValue = 2 THEN 79 | PRINT AT #addr, "RGBs PAL" 80 | PRINT AT #addr + 32, "576i 50Hz" 81 | ELSE 82 | PRINT AT #addr, "VGA" 83 | PRINT AT #addr + 32, "480p 60Hz" 84 | END IF 85 | 86 | VDP_STATUS_REG = 14 ' SR14: Version 87 | optValue = VDP_STATUS 88 | tmpMajor = optValue / 16 89 | tmpMinor = optValue AND $0f 90 | PRINT AT XY(21, g_menuTopRow + 5), CHR$(hexChar(tmpMajor)), ".", CHR$(hexChar(tmpMinor)) 91 | VDP_STATUS_REG0 92 | 93 | GOSUB delay 94 | 95 | WHILE 1 96 | WAIT 97 | 98 | GOSUB updateNavInput 99 | IF NAV(NAV_CANCEL OR NAV_OK OR NAV_LEFT OR NAV_RIGHT) THEN EXIT WHILE 100 | IF CONT.KEY > 0 AND CONT.KEY <= MENU_INDEX_COUNT THEN EXIT WHILE 101 | 102 | VDP_DISABLE_INT 103 | 104 | VDP_STATUS_REG = 13 ' SR13: Temperature 105 | optValue = VDP_STATUS 106 | VDP_STATUS_REG0 107 | 108 | IF optValue > 0 THEN 109 | tempC = optValue / 4 110 | tempDec = optValue AND $03 111 | tempDec = tempDec * 25 112 | 113 | PRINT AT XY(21, g_menuTopRow + 6), tempC, ".", <2>tempDec, "`C " 114 | 115 | #optValueF = optValue 116 | #optValueF = #optValueF * 9 117 | #optValueF = #optValueF / 5 118 | 119 | #tempC = #optValueF / 4 + 32 120 | #tempDec = #optValueF AND $03 121 | #tempDec = #tempDec * 25 122 | 123 | PRINT AT XY(19, g_menuTopRow + 7), ": ",#tempC, ".", <2>#tempDec, "`F " 124 | END IF 125 | 126 | VDP_ENABLE_INT 127 | WEND 128 | 129 | g_currentMenuIndex = oldIndex 130 | 131 | SET_MENU(MENU_ID_MAIN) 132 | 133 | END -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Compile Project", 6 | "type": "process", 7 | "isBuildCommand": true, 8 | "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", 9 | "args": ["-C", "${workspaceFolder}/build"], 10 | "group": "build", 11 | "presentation": { 12 | "reveal": "always", 13 | "panel": "dedicated" 14 | }, 15 | "problemMatcher": "$gcc", 16 | "windows": { 17 | "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" 18 | } 19 | }, 20 | { 21 | "label": "Run Project", 22 | "type": "process", 23 | "command": "${env:HOME}/.pico-sdk/picotool/2.0.0/picotool/picotool", 24 | "args": [ 25 | "load", 26 | "${command:raspberry-pi-pico.launchTargetPath}", 27 | "-fx" 28 | ], 29 | "presentation": { 30 | "reveal": "always", 31 | "panel": "dedicated" 32 | }, 33 | "problemMatcher": [], 34 | "windows": { 35 | "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.0.0/picotool/picotool.exe" 36 | } 37 | }, 38 | { 39 | "label": "Flash", 40 | "type": "process", 41 | "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", 42 | "args": [ 43 | "-s", 44 | "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", 45 | "-f", 46 | "interface/cmsis-dap.cfg", 47 | "-f", 48 | "target/${command:raspberry-pi-pico.getTarget}.cfg", 49 | "-c", 50 | "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" 51 | ], 52 | "problemMatcher": [], 53 | "windows": { 54 | "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", 55 | } 56 | }, 57 | { 58 | "label": "Build All Configurator ROMs", 59 | "type": "shell", 60 | "command": "cmake", 61 | "args": ["--build", "./build", "--target", "configurator_all"], 62 | "group": "build", 63 | "presentation": { 64 | "reveal": "always", 65 | "panel": "shared" 66 | }, 67 | "problemMatcher": "$gcc", 68 | "dependsOn": "Compile Project" 69 | }, 70 | { 71 | "label": "Build TI-99 Configurator", 72 | "type": "shell", 73 | "command": "cmake", 74 | "args": ["--build", "./build", "--target", "ti99"], 75 | "group": "build", 76 | "presentation": { 77 | "reveal": "always", 78 | "panel": "shared" 79 | }, 80 | "problemMatcher": "$gcc", 81 | "dependsOn": "Compile Project" 82 | }, 83 | { 84 | "label": "Build ColecoVision Configurator", 85 | "type": "shell", 86 | "command": "cmake", 87 | "args": ["--build", "./build", "--target", "coleco"], 88 | "group": "build", 89 | "presentation": { 90 | "reveal": "always", 91 | "panel": "shared" 92 | }, 93 | "problemMatcher": "$gcc", 94 | "dependsOn": "Compile Project" 95 | }, 96 | { 97 | "label": "Build MSX Configurator", 98 | "type": "shell", 99 | "command": "cmake", 100 | "args": ["--build", "./build", "--target", "msx_asc16"], 101 | "group": "build", 102 | "presentation": { 103 | "reveal": "always", 104 | "panel": "shared" 105 | }, 106 | "problemMatcher": "$gcc", 107 | "dependsOn": "Compile Project" 108 | } 109 | ] 110 | } 111 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | 4 | # compile-time options 5 | 6 | # Version numbers and build options now defined in root CMakeLists.txt 7 | 8 | # end compile-time options 9 | 10 | 11 | # set up variables for the build process 12 | 13 | execute_process( 14 | COMMAND git symbolic-ref --short HEAD 15 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 16 | OUTPUT_VARIABLE PICO9918_GIT_BRANCH 17 | OUTPUT_STRIP_TRAILING_WHITESPACE 18 | ) 19 | 20 | if (${PICO9918_SCART_RGBS}) 21 | if (${PICO9918_SCART_PAL}) 22 | set(PICO9918_OUTPUT_STR "rgbs-pal576i") 23 | else() 24 | set(PICO9918_OUTPUT_STR "rgbs-ntsc480i") 25 | endif() 26 | else() 27 | set(PICO9918_OUTPUT_STR "vga") 28 | endif() 29 | 30 | set(PICO9918_VERSION "${PICO9918_MAJOR_VER}.${PICO9918_MINOR_VER}.${PICO9918_PATCH_VER}") 31 | string(REPLACE "." "-" PICO9918_VERSION_STR "${PICO9918_VERSION}") 32 | 33 | if (NOT ${PICO_BOARD} STREQUAL "pico9918") 34 | set(PICO_BOARD_STR -${PICO_BOARD}) 35 | endif() 36 | 37 | set(PICO9918_BINARY_SUFFIX ${PICO_BOARD_STR}-${PICO9918_OUTPUT_STR}-build-v${PICO9918_VERSION_STR}) 38 | 39 | if (${PICO9918_DIAG}) 40 | set(PICO9918_BINARY_SUFFIX ${PICO9918_BINARY_SUFFIX}-diag) 41 | endif() 42 | 43 | set(PROGRAM pico9918${PICO9918_BINARY_SUFFIX}) 44 | 45 | add_executable(${PROGRAM} ) 46 | 47 | target_sources(${PROGRAM} PRIVATE main.c config.c diag.c flash.c gpio.c splash.c temperature.c clocks.pio.h tms9918.pio.h palconv.pio.h) 48 | 49 | pico_set_program_name(${PROGRAM} "pico9918") 50 | pico_set_program_version(${PROGRAM} ${PICO9918_VERSION}) 51 | pico_set_program_description(${PROGRAM} "PICO9918 VDP") 52 | pico_set_program_url(${PROGRAM} "https://github.com/visrealm/pico9918") 53 | 54 | # generate image array source files from png images 55 | if (NOT PICO9918_NO_SPLASH) 56 | visrealm_generate_image_source_ram(${PROGRAM} bmp_splash res/splash.png ) 57 | endif() 58 | 59 | visrealm_generate_image_source_ram(${PROGRAM} bmp_font res/font.png ) 60 | 61 | # generate header file from pio 62 | pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/clocks.pio) 63 | pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/palconv.pio) 64 | pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/tms9918.pio) 65 | 66 | # Modify the below lines to enable/disable output over UART/USB 67 | pico_enable_stdio_uart(${PROGRAM} 0) 68 | pico_enable_stdio_usb(${PROGRAM} 0) 69 | 70 | pico_set_binary_type(${PROGRAM} copy_to_ram) 71 | 72 | target_compile_definitions(${PROGRAM} PRIVATE 73 | PICO9918_NO_SPLASH=$ 74 | PICO9918_SCART_RGBS=$ 75 | PICO9918_SCART_PAL=$ 76 | PICO9918_DIAG=$ 77 | PICO9918_VERSION="${PICO9918_VERSION}" 78 | PICO9918_MAJOR_VER=${PICO9918_MAJOR_VER} 79 | PICO9918_MINOR_VER=${PICO9918_MINOR_VER} 80 | PICO9918_PATCH_VER=${PICO9918_PATCH_VER} 81 | PICO_DISABLE_SHARED_IRQ_HANDLERS=1 82 | PICO_PANIC_FUNCTION= 83 | ) 84 | 85 | 86 | target_link_libraries(${PROGRAM} PUBLIC 87 | pico_stdlib 88 | pico_multicore 89 | hardware_dma 90 | hardware_pio 91 | hardware_adc 92 | hardware_flash 93 | pico9918-vga 94 | pico9918-gpu 95 | vrEmuTms9918) 96 | 97 | # Set the executable suffix to .elf for all platforms 98 | set_target_properties(${PROGRAM} PROPERTIES SUFFIX .elf) 99 | 100 | pico_add_extra_outputs(${PROGRAM}) 101 | 102 | # Copy final artifacts to dist directory for unified output 103 | add_custom_command( 104 | OUTPUT ${CMAKE_BINARY_DIR}/dist/${PROGRAM}.uf2 105 | COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/dist 106 | COMMAND ${CMAKE_COMMAND} -E copy $/${PROGRAM}.uf2 ${CMAKE_BINARY_DIR}/dist/ 107 | DEPENDS ${PROGRAM} 108 | COMMENT "Copying ${PROGRAM}.uf2 to dist/" 109 | ) 110 | 111 | # Create a target for the dist uf2 file 112 | add_custom_target(${PROGRAM}_dist DEPENDS ${CMAKE_BINARY_DIR}/dist/${PROGRAM}.uf2) 113 | 114 | # Create a generic firmware target for easier building 115 | add_custom_target(firmware DEPENDS ${CMAKE_BINARY_DIR}/dist/${PROGRAM}.uf2) 116 | 117 | add_subdirectory(pio-utils) 118 | add_subdirectory(vga) 119 | add_subdirectory(gpu) -------------------------------------------------------------------------------- /configtool/README.md: -------------------------------------------------------------------------------- 1 | # PICO9918 Configurator 2 | 3 | The configurator is a software tool used to modify PICO9918 configuration options, including: 4 | 5 | * Clock rate 6 | * Scanline CRT effect 7 | * Scanline sprite limit 8 | * Default palette 9 | * Diagnostics overlays 10 | 11 | Additionally, firmware updates can be provided via the Configurator. The full configurator is available for the **TI-99/4A**, **ColecoVision** and **MSX**. With cut-down builds (without firmware updates) available for several other machines. 12 | 13 | See the configurator in action: 14 | 15 | [![PICO9918 Configurator - ColecoVision](https://img.visualrealmsoftware.com/youtube/thumb/PBArYupT9qM)](https://youtu.be/PBArYupT9qM?t=9) 16 | 17 | The configurator was written in a [custom fork of CVBasic](https://github.com/visrealm/CVBasic). 18 | 19 | ## Building 20 | 21 | ### Prerequisites 22 | 23 | #### Option 1: Use existing tools 24 | - CMake 3.13 or later 25 | - Python 3 (accessible as `python3`) 26 | - CVBasic compiler (`cvbasic.exe`) 27 | - GASM80 assembler (`gasm80.exe`) 28 | - XDT99 XAS99 assembler (for TI-99 builds) 29 | 30 | #### Option 2: Auto-build tools (Linux/cross-platform) 31 | - CMake 3.13 or later 32 | - Python 3 (accessible as `python3`) 33 | - Git (for checking out tool repositories) 34 | - C compiler (GCC or Clang for building tools) 35 | 36 | ### Usage 37 | 38 | #### Integrated Build (Recommended) 39 | The configurator is now integrated into the main PICO9918 build system: 40 | 41 | ```bash 42 | # Build from project root 43 | mkdir build && cd build 44 | cmake .. 45 | make # Build firmware 46 | make configurator_all # Build all configurator ROMs 47 | ``` 48 | 49 | All final artifacts will be in `build/dist/`: 50 | - **Firmware**: `pico9918-vga-build-v1-0-2.uf2` 51 | - **Configurator ROMs**: `pico9918_v1-0-2_*.rom` / `pico9918_v1-0-2_*.bin` 52 | 53 | #### Standalone Build (Legacy) 54 | ```bash 55 | cd configtool 56 | mkdir build && cd build 57 | cmake .. [-DBUILD_TOOLS_FROM_SOURCE=ON] 58 | make configurator_all 59 | ``` 60 | 61 | #### Build Individual Platforms 62 | ```bash 63 | make ti99 # TI-99/4A 64 | make ti99_f18a # TI-99/4A F18A Testing 65 | make coleco # ColecoVision 66 | make msx_asc16 # MSX ASCII16 mapper 67 | make msx_konami # MSX Konami mapper 68 | make sg1000 # SG1000/SC3000 69 | make nabu # NABU 70 | make creativision # CreatiVision 71 | make nabu_mame_package # NABU MAME (.npz) 72 | ``` 73 | 74 | #### Build with Ninja (faster) 75 | ```bash 76 | cmake .. -G Ninja [-DBUILD_TOOLS_FROM_SOURCE=ON] 77 | ninja configurator_all 78 | ``` 79 | 80 | ## How it Works 81 | 82 | 1. **Firmware Dependency**: Checks for pre-built firmware in `../build/src/` 83 | 2. **UF2 Conversion**: Converts firmware to CVBasic data using `uf2cvb.py` 84 | 3. **CVBasic Compilation**: Compiles `.bas` sources for each target platform 85 | 4. **Assembly**: Uses GASM80 (most platforms) or XAS99 (TI-99) for final ROM creation 86 | 5. **Packaging**: Creates platform-specific ROM files (.bin, .rom, .nabu, etc.) 87 | 88 | ## Platform Support 89 | 90 | | Platform | Mapper/Banking | Output Format | Assembly Tool | 91 | |----------|----------------|---------------|---------------| 92 | | TI-99/4A | 8KB banks | .bin cartridge | XAS99 + linkticart | 93 | | ColecoVision | 16KB banks | .rom | GASM80 | 94 | | MSX | ASCII16/Konami | .rom | GASM80 | 95 | | NABU | No banking | .nabu/.npz | GASM80 | 96 | | CreatiVision | No banking | .bin | GASM80 | 97 | 98 | ## Tool Detection 99 | 100 | ### Default Mode (existing tools) 101 | The build system automatically searches for tools in: 102 | 1. `configtool/tools/cvbasic/` (bundled tools) 103 | 2. `../CVBasic/build/Release/` (local CVBasic build) 104 | 3. System PATH 105 | 4. `c:/tools/xdt99/` (Windows) or `/usr/local/bin`, `/opt/xdt99` (Linux) 106 | 107 | ### Auto-build Mode (`-DBUILD_TOOLS_FROM_SOURCE=ON`) 108 | When enabled, CMake will: 109 | 1. Clone CVBasic from https://github.com/visrealm/CVBasic.git 110 | 2. Clone gasm80 from https://github.com/visrealm/gasm80.git 111 | 3. Clone XDT99 from https://github.com/endlos99/xdt99.git 112 | 4. Build CVBasic and gasm80 from source using their CMake systems 113 | 5. Install tools to `build/external/` directory 114 | 6. Use the locally-built tools for ROM generation 115 | 116 | This mode enables fully cross-platform builds without pre-installed tools. 117 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) 22 | set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) 23 | message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") 24 | endif () 25 | 26 | if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) 27 | set(PICO_SDK_FETCH_FROM_GIT_TAG "master") 28 | message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") 29 | endif() 30 | 31 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 32 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 33 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 34 | set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE STRING "release tag for SDK") 35 | 36 | if (NOT PICO_SDK_PATH) 37 | if (PICO_SDK_FETCH_FROM_GIT) 38 | include(FetchContent) 39 | # Handle FetchContent deprecation warning gracefully 40 | if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.0") 41 | cmake_policy(SET CMP0169 NEW) 42 | endif() 43 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 44 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 45 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 46 | endif () 47 | # GIT_SUBMODULES_RECURSE was added in 3.17 48 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 49 | FetchContent_Declare( 50 | pico_sdk 51 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 52 | GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} 53 | GIT_SUBMODULES_RECURSE FALSE 54 | PATCH_COMMAND ${CMAKE_COMMAND} -E chdir git apply --ignore-whitespace --ignore-space-change --3way ${CMAKE_SOURCE_DIR}/picosdk-2.0.0-visrealm-fastboot.patch || ${CMAKE_COMMAND} -E echo "PICO9918: SDK patch failed or already applied (this is normal)" 55 | ) 56 | else () 57 | FetchContent_Declare( 58 | pico_sdk 59 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 60 | GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} 61 | PATCH_COMMAND ${CMAKE_COMMAND} -E chdir git apply --ignore-whitespace --ignore-space-change --3way ${CMAKE_SOURCE_DIR}/picosdk-2.0.0-visrealm-fastboot.patch || ${CMAKE_COMMAND} -E echo "PICO9918: SDK patch failed or already applied (this is normal)" 62 | ) 63 | endif () 64 | 65 | if (NOT pico_sdk) 66 | message("Downloading Raspberry Pi Pico SDK") 67 | FetchContent_MakeAvailable(pico_sdk) 68 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 69 | endif () 70 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 71 | else () 72 | message(FATAL_ERROR 73 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 74 | ) 75 | endif () 76 | endif () 77 | 78 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 79 | if (NOT EXISTS ${PICO_SDK_PATH}) 80 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 81 | endif () 82 | 83 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 84 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 85 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 86 | endif () 87 | 88 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 89 | 90 | include(${PICO_SDK_INIT_CMAKE_FILE}) 91 | -------------------------------------------------------------------------------- /configtool/src/ui.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | DEF FN DRAW_TITLE_AT(T, R) = a_titleLen = LEN(T) : PRINT AT XY((32 - a_titleLen) / 2, R), T : GOSUB drawTitleBox 14 | DEF FN DRAW_TITLE(T) = DRAW_TITLE_AT(T, MENU_TITLE_ROW) 15 | 16 | DEF FN DRAW_POPUP_W(T, H, W) = a_titleLen = LEN(T) : a_popupHeight = H : a_popupWidth = W : a_popupTop = (23 - a_popupHeight) / 2 : GOSUB drawPopup : PRINT AT XY((32 - a_titleLen) / 2, a_popupTop), T 17 | DEF FN DRAW_POPUP(T, H) = DRAW_POPUP_W(T, H, LEN(T)) 18 | 19 | ' ----------------------------------------------------------------------------- 20 | ' draw an empty row at R 21 | ' ----------------------------------------------------------------------------- 22 | emptyRowR: PROCEDURE 23 | DEFINE VRAM NAME_TAB_XY(0, R), 32, emptyRow 24 | END 25 | 26 | ' ----------------------------------------------------------------------------- 27 | ' draw a horizontal bar 28 | ' ----------------------------------------------------------------------------- 29 | horzBarR: 30 | BW = 32 31 | horzBarRW: 32 | BX = 0 33 | horzBarRWX: PROCEDURE 34 | DEFINE VRAM NAME_TAB_XY(BX, BR), BW, horzBar 35 | END 36 | 37 | ' ----------------------------------------------------------------------------- 38 | ' clear the screen (rows 3 to 19 anyway...) 39 | ' ----------------------------------------------------------------------------- 40 | clearScreen: PROCEDURE 41 | BR = 2 : GOSUB horzBarR 42 | FOR R = 3 TO 19 43 | GOSUB emptyRowR 44 | NEXT R 45 | END 46 | 47 | ' ----------------------------------------------------------------------------- 48 | ' draw a title box (at the top of the screen) 49 | ' ----------------------------------------------------------------------------- 50 | drawTitleBox: PROCEDURE 51 | X = (32 - a_titleLen) / 2 52 | 53 | BW = a_titleLen + 2 54 | BX = X - 1 55 | BR = MENU_TITLE_ROW - 1 : GOSUB horzBarRWX 56 | BR = MENU_TITLE_ROW + 1 : GOSUB horzBarRWX 57 | 58 | x1 = X - 2 : x2 = X + a_titleLen + 1 59 | 60 | #addr = NAME_TAB_XY(x1, MENU_TITLE_ROW) 61 | VPOKE #addr, PATT_IDX_BORDER_V 62 | VPOKE #addr - 32, PATT_IDX_BORDER_HD 63 | VPOKE #addr + 32, PATT_IDX_BORDER_BL 64 | 65 | #addr = NAME_TAB_XY(x2, MENU_TITLE_ROW) 66 | VPOKE #addr, PATT_IDX_BORDER_V 67 | VPOKE #addr - 32, PATT_IDX_BORDER_HD 68 | VPOKE #addr + 32, PATT_IDX_BORDER_BR 69 | 70 | END 71 | 72 | ' ----------------------------------------------------------------------------- 73 | ' render a popup window 74 | ' ----------------------------------------------------------------------------- 75 | drawPopup: PROCEDURE 76 | X = (32 - a_popupWidth) / 2 77 | 78 | BW = a_popupWidth 79 | BX = X 80 | 81 | BR = a_popupTop - 1: GOSUB horzBarRWX 82 | #addr = NAME_TAB_XY(X - 1, a_popupTop) 83 | FOR Y = 0 TO a_popupHeight 84 | DEFINE VRAM #addr, a_popupWidth, vBar 85 | VPOKE #addr + a_popupWidth + 1, PATT_IDX_BORDER_V 86 | #addr = #addr + 32 87 | NEXT Y 88 | BR = BR + 2: GOSUB horzBarRWX 89 | BR = BR + a_popupHeight: GOSUB horzBarRWX 90 | 91 | x2 = X + a_popupWidth 92 | #addr = NAME_TAB_XY(X - 1, a_popupTop + 1) 93 | VPOKE #addr - 64, PATT_IDX_BORDER_TL 94 | VPOKE #addr, PATT_IDX_BORDER_VR 95 | VPOKE #addr + a_popupHeight * 32, PATT_IDX_BORDER_BL 96 | #addr = NAME_TAB_XY(X + a_popupWidth, a_popupTop + 1) 97 | VPOKE #addr - 64, PATT_IDX_BORDER_TR 98 | VPOKE #addr, PATT_IDX_BORDER_VL 99 | VPOKE #addr + a_popupHeight * 32, PATT_IDX_BORDER_BR 100 | 101 | END 102 | 103 | ' ----------------------------------------------------------------------------- 104 | ' set up the menu header (and footer) 105 | ' ----------------------------------------------------------------------------- 106 | setupHeader: PROCEDURE 107 | 108 | ' output top-left logo 109 | CONST LOGO_WIDTH = 19 110 | #addr = #VDP_NAME_TAB 111 | FOR I = 1 TO LOGO_WIDTH 112 | VPOKE #addr, I 113 | VPOKE #addr + 32, I + 128 114 | #addr = #addr + 1 115 | NEXT I 116 | 117 | PRINT AT XY(26, 0),"v",FIRMWARE_MAJOR_VER,".",FIRMWARE_MINOR_VER,".",FIRMWARE_PATCH_VER 118 | PRINT AT XY(20, 1),"Configurator" 119 | PRINT AT XY(6, 23), "{}",#FIRMWARE_YEAR," Troy Schrapel" 120 | 121 | BR = 2: GOSUB horzBarR 122 | BR = 20: GOSUB horzBarR 123 | BR = 22: GOSUB horzBarR 124 | 125 | END 126 | 127 | ' ----------------------------------------------------------------------------- 128 | ' update the PICO9918 palette (shades of blue) 129 | ' ----------------------------------------------------------------------------- 130 | updatePalette: PROCEDURE 131 | WAIT 132 | VDP_REG(47) = $c0 + 16 ' palette data port from index #2 133 | DEFINE VRAM 0, 32, defPal 134 | VDP_REG(47) = $40 135 | END -------------------------------------------------------------------------------- /pcb/README.md: -------------------------------------------------------------------------------- 1 | # PICO9918 PCBs 2 | 3 | Here you will find schematics and gerbers for all working revisions of the PICO9918. There are two main variants of the hardware. Due to a minor difference in RP2040 GPIO pinouts, firmware binaries aren't compatible between the two, however both are fully supported with latest firmware updates and separate firmware packages are provided for each. 4 | 5 | ### DIY Piggybacked board (v0.3) 6 | 7 | This version is relatively cheap and easy to build and is powered by a piggybacked Pi Pico USB-C module. Recommended for a DIY project. 8 | 9 |

PICO9918 v0.3

10 | 11 | See [PICO9918 v0.3](v0.3) 12 | 13 | ### Fully integrated single board (v0.4 - v1.3+) 14 | 15 | From v0.4 the RP2040 has been integrated onto the PICO9918 PCB, making a much smaller small form factor. This revision has many small (0402) components and can be challenging (and more expensive) to build. They can be purchased too. 16 | 17 |

PICO9918 v1.0

19 | 20 | # Revision history 21 | 22 | ## [v1.3 (2025-07-02)](v1.2) 23 | 24 | ### Changelog 25 | - Added LM66200 dual perfect diode to allow connecting USB while installed. 26 | - Replaced 74HC08 with a 74ACT126 to make data and interrupt lines float on reset. 27 | - Data lines remain active slightly longer after /CSR goes high. 28 | 29 | ## [v1.2 (2024-11-26)](v1.2) 30 | 31 |

PICO9918 v1.2

32 | 33 | ### Changelog 34 | - Added 12-pin FFC connector with separate VGA dongle PCB 35 | 36 | ## [v1.1 (2024-09-12)](v1.1) 37 | 38 | ### Changelog 39 | - Removed reset button. 40 | - Default CPUCLK and GROMCLK jumper pads to closed. 41 | - Minor positioning adjustments of bottom-side resistors to clear header pins. 42 | 43 | ## [v1.0 (2024-08-01)](v1.0) 44 | 45 | First version available for sale on Tindie and ArcadeShopper 46 | 47 |

PICO9918 v1.0

48 | 49 | ### Changelog 50 | - Added reset button. 51 | - Moved CPUCLK and GROMCLK jumper pads to the top of the board. 52 | - Added SWD and SWC jumper pads to allow SWD (debugging) 53 | - Flipped top labels and graphics so it doesn't look upside-down. 54 | 55 | ## [v0.4 (2024-07-16)](v0.4) 56 | 57 | This is the first fully-integrated single-board version and is powered by an RP2040 directly. This version was never released, however I hand-built 9 of them and sent most of the to various retro enthusiasts from AtariAge. 58 | 59 |

PICO9918 v0.4

60 | 61 | ### Changelog 62 | - Removed piggy backed Pi Pico module. 63 | - Added RP2040 and all of its dependencies. 64 | - Switched out resisitor networks for discrete resistors. 65 | - Shrinkified the package to something resembling the first version for sale (v1.0). 66 | - Updated VGA connector to 6p 1.25mm JST. 67 | 68 | ## [v0.3 (2024-06-07)](v0.3) 69 | 70 | This is the first "public" version and is powered by a piggy-backed USB-C Pi Pico module. I have never produced or sold this version beyond the initial few prototypes. 71 | 72 |

PICO9918 v0.3

73 | 74 | ### Changelog 75 | - Switched to use USB-C Pi Pico module instead of genuine Pi Pico. 76 | - Added CPUCLK and GROMCLK. 77 | 78 | ### PCB v0.3 Notes 79 | 80 | There are a number of 0 Ohm resistors (jumpers). You may need to omit the RST resistor. On some machines, the extra time is required to bootstrap the Pico. This will be changed to a soft reset on v0.4. 81 | 82 | ### Raspberry Pi Pico Module 83 | 84 | Note: Due to GROMCLK and CPUCLK using GPIO23 and GPIO29, a genuine Raspberry Pi Pico can't be used. v0.3 of the PCB is designed for the DWEII? RP2040 USB-C module which exposes these additional GPIOs. A future pico9918 revision will do without an external RP2040 board and use the RP2040 directly. 85 | 86 | Purchase links: 87 | * https://www.amazon.com/RP2040-Board-Type-C-Raspberry-Micropython/dp/B0CG9BY48X 88 | * https://www.aliexpress.com/item/1005007066733934.html 89 | 90 | I could reduce the VGA bit depth to 9-bit or 10-bit to allow the use of a genuine Raspberry Pi Pico board, but given the longer-term plan is to use the RP2040 directly, I've decided to go this way for the prototype. 91 | 92 | 93 | ## What happened to v0.1 and v0.2? 94 | 95 | For the curious amongst you, v0.1 was the only version that wan't usable. The TMS9918A socket interface was 0.1" too narrow. Rookie error! Fortunately, I noticed within hours of ordering the PCBs, so ordered v0.2 long before v0.1 arrived. 96 | 97 | v0.2 was very usable and is the version used in the initial "It freaking works!" video. The only reason it isn't published is because it lacked the GROMCLK and CPUCLK signals required for many systems. In the video, you can see the GROMCLK signal is being provided by a second Pi Pico. 98 | -------------------------------------------------------------------------------- /test/host/font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const uint8_t tmsFont[] = { 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, // ! 7 | 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, // " 8 | 0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, // # 9 | 0x18, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x18, 0x00, // $ 10 | 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00, // % 11 | 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00, // & 12 | 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // ' 13 | 0x0c, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, // ( 14 | 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00, // ) 15 | 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00, // * 16 | 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, // + 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, // , 18 | 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, // - 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // . 20 | 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00, // / 21 | 0x7c, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0x7c, 0x00, // 0 22 | 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, // 1 23 | 0x7c, 0xc6, 0x06, 0x7c, 0xc0, 0xc0, 0xfe, 0x00, // 2 24 | 0xfc, 0x06, 0x06, 0x3c, 0x06, 0x06, 0xfc, 0x00, // 3 25 | 0x0c, 0xcc, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x00, // 4 26 | 0xfe, 0xc0, 0xfc, 0x06, 0x06, 0xc6, 0x7c, 0x00, // 5 27 | 0x7c, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0x7c, 0x00, // 6 28 | 0xfe, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00, // 7 29 | 0x7c, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0x7c, 0x00, // 8 30 | 0x7c, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x7c, 0x00, // 9 31 | 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, // : 32 | 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, // ; 33 | 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, // < 34 | 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00, // = 35 | 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, // > 36 | 0x3c, 0x66, 0x0c, 0x18, 0x18, 0x00, 0x18, 0x00, // ? 37 | 0x7c, 0xc6, 0xde, 0xde, 0xde, 0xc0, 0x7e, 0x00, // @ 38 | 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x00, // A 39 | 0xfc, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, // B 40 | 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, // C 41 | 0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0x00, // D 42 | 0xfe, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xfe, 0x00, // E 43 | 0xfe, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0x00, // F 44 | 0x7c, 0xc6, 0xc0, 0xc0, 0xce, 0xc6, 0x7c, 0x00, // G 45 | 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, // H 46 | 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, // I 47 | 0x06, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, // J 48 | 0xc6, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, // K 49 | 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, // L 50 | 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0x00, // M 51 | 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00, // N 52 | 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, // O 53 | 0xfc, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0x00, // P 54 | 0x7c, 0xc6, 0xc6, 0xc6, 0xd6, 0xde, 0x7c, 0x06, // Q 55 | 0xfc, 0xc6, 0xc6, 0xfc, 0xd8, 0xcc, 0xc6, 0x00, // R 56 | 0x7c, 0xc6, 0xc0, 0x7c, 0x06, 0xc6, 0x7c, 0x00, // S 57 | 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // T 58 | 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0x00, // U 59 | 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x38, 0x00, // V 60 | 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0x6c, 0x00, // W 61 | 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, // X 62 | 0xc6, 0xc6, 0xc6, 0x7c, 0x18, 0x30, 0xe0, 0x00, // Y 63 | 0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xfe, 0x00, // Z 64 | 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, // [ 65 | 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00, // 66 | 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, // ] 67 | 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, // ^ 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // _ 69 | 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, // ` 70 | 0x00, 0x00, 0x7c, 0x06, 0x7e, 0xc6, 0x7e, 0x00, // a 71 | 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, // b 72 | 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, // c 73 | 0x06, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0x7e, 0x00, // d 74 | 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0x7c, 0x00, // e 75 | 0x1c, 0x36, 0x30, 0x78, 0x30, 0x30, 0x78, 0x00, // f 76 | 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0x7e, 0x06, 0xfc, // g 77 | 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, // h 78 | 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3c, 0x00, // i 79 | 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, // j 80 | 0xc0, 0xc0, 0xcc, 0xd8, 0xf8, 0xcc, 0xc6, 0x00, // k 81 | 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, // l 82 | 0x00, 0x00, 0xcc, 0xfe, 0xfe, 0xd6, 0xd6, 0x00, // m 83 | 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, // n 84 | 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, // o 85 | 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, // p 86 | 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0x7e, 0x06, 0x06, // q 87 | 0x00, 0x00, 0xfc, 0xc6, 0xc0, 0xc0, 0xc0, 0x00, // r 88 | 0x00, 0x00, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x00, // s 89 | 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x0e, 0x00, // t 90 | 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, // u 91 | 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x7c, 0x38, 0x00, // v 92 | 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xfe, 0x6c, 0x00, // w 93 | 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00, // x 94 | 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0xfc, // y 95 | 0x00, 0x00, 0xfe, 0x0c, 0x38, 0x60, 0xfe, 0x00, // z 96 | 0x0e, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0e, 0x00, // { 97 | 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, // | 98 | 0x70, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x70, 0x00, // } 99 | 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ~ 100 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 101 | }; 102 | 103 | const size_t tmsFontBytes = sizeof(tmsFont); -------------------------------------------------------------------------------- /configtool/tools/cvpletter.py: -------------------------------------------------------------------------------- 1 | # cvpletter.py 2 | # 3 | # Converts raw CVBasic DATA sections into Pletter-compressed chunks. 4 | # 5 | # Copyright (c) 2025 Troy Schrapel 6 | # 7 | # This code is licensed under the MIT license 8 | # 9 | # https://github.com/visrealm/pico9918 10 | # 11 | 12 | import re 13 | import sys 14 | import subprocess 15 | from pathlib import Path 16 | from tempfile import TemporaryDirectory 17 | 18 | # Determine script directory and path to pletter.exe 19 | scriptDir = Path(__file__).parent.resolve() 20 | PLETTER_EXE = scriptDir / 'cvbasic' / 'pletter.exe' 21 | 22 | def extractLabelsAndData(basPath): 23 | """Parses a .bas file and extracts labeled DATA BYTE sequences.""" 24 | currentLabel = None 25 | labelDataMap = {} 26 | dataBuffer = [] 27 | 28 | with open(basPath, 'r') as f: 29 | for line in f: 30 | stripped = line.strip() 31 | 32 | if not stripped or stripped.startswith("'"): 33 | continue # skip empty and comment lines 34 | 35 | # detect label declarations (e.g., LabelName:) 36 | codeOnly = stripped.split("'", 1)[0].strip() 37 | if re.match(r'^[a-zA-Z_][\w]*\s*:$', codeOnly): 38 | 39 | if currentLabel and dataBuffer: 40 | labelDataMap[currentLabel] = dataBuffer 41 | currentLabel = codeOnly[:-1] 42 | dataBuffer = [] 43 | continue 44 | 45 | # remove comments and extract DATA BYTE declarations 46 | match = re.search(r'DATA\s+BYTE\s+(.*)', codeOnly, re.IGNORECASE) 47 | if match and currentLabel: 48 | byteValues = match.group(1).split(',') 49 | for byte in byteValues: 50 | byte = byte.strip() 51 | if byte.startswith('$'): 52 | dataBuffer.append(int(byte[1:], 16)) 53 | elif byte.lower().startswith('0x'): 54 | dataBuffer.append(int(byte, 16)) 55 | else: 56 | dataBuffer.append(int(byte)) 57 | 58 | if currentLabel and dataBuffer: 59 | labelDataMap[currentLabel] = dataBuffer 60 | 61 | return labelDataMap 62 | 63 | def compressDataViaPletter(data, tempDir, label): 64 | """writes data to disk, compresses it via pletter.exe, and reads back output bytes.""" 65 | binPath = tempDir / f"{label}.bin" 66 | pletterPath = tempDir / f"{label}.pletter.bin" 67 | 68 | with open(binPath, 'wb') as f: 69 | f.write(bytearray(data)) 70 | 71 | result = subprocess.run([str(PLETTER_EXE), str(binPath), str(pletterPath)], 72 | capture_output=True, text=True) 73 | 74 | if result.returncode != 0: 75 | raise RuntimeError(f"[✗] Pletter compression failed for {label}:\n{result.stderr}") 76 | 77 | with open(pletterPath, 'rb') as f: 78 | return list(f.read()) 79 | 80 | def writeFinalBas(inputBas, basOutputPath, compressedBlocks, sourceSizes): 81 | """generates the output .pletter.bas with compressed data blocks.""" 82 | 83 | totalSourceBytes = 0 84 | totalCompressedBytes = 0 85 | basOutputFile = Path(basOutputPath).name 86 | 87 | with open(basOutputPath, 'w') as f: 88 | f.write("' ====================================================\n") 89 | f.write("' This file was generated using cvpletter.py\n") 90 | f.write("' \n") 91 | f.write("' Copyright (c) 2025 Troy Schrapel (visrealm)\n") 92 | f.write("' \n") 93 | f.write(f"' source: {inputBas}\n") 94 | f.write(f"' cmd: python cvpletter.py {inputBas}\n") 95 | f.write(f"' output: {basOutputFile}\n") 96 | f.write("' \n") 97 | f.write("' ====================================================\n") 98 | f.write("' WARNING! Do NOT edit this file. Edit source file\n") 99 | f.write("' ====================================================\n\n") 100 | 101 | for label, compressed in compressedBlocks.items(): 102 | inSize = sourceSizes[label] 103 | outSize = len(compressed) 104 | totalSourceBytes += inSize 105 | totalCompressedBytes += outSize 106 | 107 | f.write(f" {label}Pletter: ' source: {inSize} bytes. compressed: {outSize} bytes\n") 108 | for i in range(0, outSize, 8): 109 | group = compressed[i:i+8] 110 | line = ', '.join(f"${b:02x}" for b in group) 111 | f.write(f" DATA BYTE {line}\n") 112 | f.write("\n") 113 | 114 | print(f" {label + 'Pletter:':<25} - in: {str(inSize) + 'B':>6} - out: {str(outSize) + 'B':>6} - saved: {str(inSize - outSize)+'B':>6}") 115 | 116 | print(f"{basOutputFile:<27} - in: {str(totalSourceBytes) + 'B':>6} - out: {str(totalCompressedBytes) + 'B':>6} - saved: {str(totalSourceBytes - totalCompressedBytes)+ 'B':>6}\n") 117 | 118 | def main(): 119 | if len(sys.argv) != 2: 120 | print("Usage: python cvpletter.py ") 121 | sys.exit(1) 122 | 123 | inputBas = Path(sys.argv[1]) 124 | if not inputBas.exists(): 125 | print(f"[X] File not found: {inputBas}") 126 | sys.exit(1) 127 | 128 | if not PLETTER_EXE.exists(): 129 | print(f"[X] Missing pletter.exe at: {PLETTER_EXE}") 130 | sys.exit(1) 131 | 132 | print(f"Processing {inputBas}...") 133 | 134 | labelsData = extractLabelsAndData(inputBas) 135 | compressedBlocks = {} 136 | sourceSizes = {} 137 | 138 | with TemporaryDirectory() as tmp: 139 | tempDir = Path(tmp) 140 | for label, data in labelsData.items(): 141 | compressedBlocks[label] = compressDataViaPletter(data, tempDir, label) 142 | sourceSizes[label] = len(data) 143 | 144 | outputBasPath = Path.cwd() / f"{inputBas.stem}.pletter.bas" 145 | writeFinalBas(inputBas, outputBasPath, compressedBlocks, sourceSizes) 146 | 147 | if __name__ == '__main__': 148 | main() -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #include "impl/vrEmuTms9918Priv.h" 13 | 14 | #include "gpio.h" 15 | #include "vga.h" 16 | #include "config.h" 17 | 18 | #include "hardware/flash.h" 19 | 20 | #include 21 | 22 | #if PICO9918_SCART_RGBS // 0 = VGA, 1 = NTSC, 2 = PAL 23 | #define PICO9918_DISP_DRIVER (1 + PICO9918_SCART_PAL) 24 | #else 25 | #define PICO9918_DISP_DRIVER 0 26 | #endif 27 | 28 | #if PICO_RP2040 29 | #define PICO_MODEL 1 30 | #elif PICO_RP2350 31 | #define PICO_MODEL 2 32 | #endif 33 | 34 | static Pico9918HardwareVersion hwVersion = HWVer_1_x; 35 | static bool hwVersionDetected = false; 36 | 37 | /* 38 | * detect the hardware version (v0.3 vs v0.4+) 39 | */ 40 | static Pico9918HardwareVersion detectHardwareVersion() 41 | { 42 | Pico9918HardwareVersion version = HWVer_1_x; 43 | 44 | #if PICO_RP2040 45 | // check if RESET pin is being driven externally (on v0.4+, it is, on v0.3 it isn't since it's CPUCL) 46 | gpio_set_dir_masked(GPIO_RESET_MASK, 0 << GPIO_RESET); // reset input 47 | gpio_pull_up(GPIO_RESET); 48 | sleep_us(100); 49 | if (gpio_get(GPIO_RESET)) // following pull... ok 50 | { 51 | gpio_pull_down(GPIO_RESET); 52 | sleep_us(100); 53 | if (!gpio_get(GPIO_RESET)) // still following pull... must be v0.3 54 | { 55 | version = HWVer_0_3; 56 | } 57 | } 58 | #endif 59 | 60 | return version; 61 | } 62 | 63 | /* 64 | * current (detected) hardware version 65 | */ 66 | Pico9918HardwareVersion currentHwVersion() 67 | { 68 | if (!hwVersionDetected) 69 | { 70 | hwVersion = detectHardwareVersion(); 71 | hwVersionDetected = true; 72 | } 73 | return hwVersion; 74 | } 75 | 76 | /* 77 | * apply current configuration to the VDP 78 | */ 79 | void applyConfig() 80 | { 81 | vgaCurrentParams()->scanlines = tms9918->config[CONF_CRT_SCANLINES]; 82 | 83 | if (tms9918->config[CONF_CRT_SCANLINES]) 84 | TMS_REGISTER(tms9918, 50) |= 0x04; 85 | else 86 | TMS_REGISTER(tms9918, 50) &= ~0x04; 87 | 88 | TMS_REGISTER(tms9918, 30) = 1 << (tms9918->config[CONF_SCANLINE_SPRITES] + 2); 89 | 90 | // apply default palette 91 | for (int i = 0; i < 16; ++i) 92 | { 93 | uint16_t rgb = (tms9918->config[CONF_PALETTE_IDX_0 + (i * 2)] << 8) | 94 | tms9918->config[CONF_PALETTE_IDX_0 + (i * 2) + 1]; 95 | tms9918->vram.map.pram[i] = __builtin_bswap16(rgb); 96 | } 97 | 98 | tms9918->config[CONF_DIAG] = tms9918->config[CONF_DIAG_ADDRESS] || 99 | tms9918->config[CONF_DIAG_PALETTE] || 100 | tms9918->config[CONF_DIAG_PERFORMANCE] || 101 | tms9918->config[CONF_DIAG_REGISTERS]; 102 | } 103 | 104 | #define CONFIG_FLASH_OFFSET (0x200000 - 0x1000) // in the top 4kB of a 2MB flash 105 | #define CONFIG_FLASH_ADDR (uint8_t*)(XIP_BASE + CONFIG_FLASH_OFFSET) 106 | 107 | /* 108 | * read current configuration from flash 109 | */ 110 | void readConfig(uint8_t config[CONFIG_BYTES]) 111 | { 112 | memcpy(config, CONFIG_FLASH_ADDR, CONFIG_BYTES); 113 | 114 | if (config[CONF_PICO_MODEL] != PICO_MODEL || 115 | config[CONF_DISP_DRIVER] != PICO9918_DISP_DRIVER || 116 | config[CONF_CLOCK_PRESET_ID] > 2 || 117 | config[CONF_CRT_SCANLINES] > 1 || 118 | config[CONF_SCANLINE_SPRITES] > 3 || 119 | config[CONF_PALETTE_IDX_0] != 0x00 || 120 | (config[CONF_PALETTE_IDX_0 + 2] & 0xf0) != 0xf0) // not initialised 121 | { 122 | memset(config, 0, CONFIG_BYTES); 123 | 124 | config[CONF_PICO_MODEL] = PICO_MODEL; 125 | config[CONF_SW_VERSION] = PICO9918_SW_VERSION; 126 | config[CONF_HW_VERSION] = currentHwVersion(); 127 | config[CONF_DISP_DRIVER] = PICO9918_DISP_DRIVER; 128 | config[CONF_CLOCK_TESTED] = 0; 129 | 130 | config[CONF_CRT_SCANLINES] = 0; 131 | config[CONF_SCANLINE_SPRITES] = 0; 132 | config[CONF_CLOCK_PRESET_ID] = 0; 133 | 134 | config[CONF_PALETTE_IDX_0] = 0; 135 | config[CONF_PALETTE_IDX_0 + 1] = 0; 136 | for (int i = 1; i < 16; ++i) 137 | { 138 | uint16_t rgb = 0xf000 | vrEmuTms9918DefaultPalette(i); 139 | config[CONF_PALETTE_IDX_0 + (i * 2)] = rgb >> 8; 140 | config[CONF_PALETTE_IDX_0 + (i * 2) + 1] = rgb & 0xff; 141 | } 142 | } 143 | 144 | config[CONF_SAVE_TO_FLASH] = 0; 145 | 146 | // looks like we've just upgraded 147 | if (config[CONF_SW_VERSION] != PICO9918_SW_VERSION || 148 | config[CONF_SW_PATCH_VERSION] != PICO9918_PATCH_VER) 149 | { 150 | config[CONF_SW_VERSION] = PICO9918_SW_VERSION; 151 | config[CONF_SW_PATCH_VERSION] = PICO9918_PATCH_VER; 152 | config[CONF_SAVE_TO_FLASH] = 1; 153 | } 154 | 155 | tms9918->configDirty = true; // so we apply it 156 | } 157 | 158 | /* 159 | * write configuration to flash 160 | */ 161 | bool writeConfig(uint8_t config[CONFIG_BYTES]) 162 | { 163 | flash_range_erase(CONFIG_FLASH_OFFSET, 0x1000); 164 | 165 | config[CONF_PICO_MODEL] = PICO_MODEL; 166 | config[CONF_HW_VERSION] = currentHwVersion(); 167 | config[CONF_SW_VERSION] = PICO9918_SW_VERSION; 168 | 169 | // sanity checking the palette 0 always 0, others always alpha 0xf 170 | config[CONF_PALETTE_IDX_0] = 0; 171 | config[CONF_PALETTE_IDX_0 + 1] = 0; 172 | for (int i = 1; i < 16; ++i) 173 | { 174 | config[CONF_PALETTE_IDX_0 + (i * 2)] |= 0xf0; 175 | } 176 | 177 | bool success = false; 178 | 179 | int attempts = 5; 180 | while (attempts--) 181 | { 182 | flash_range_program(CONFIG_FLASH_OFFSET, (const void*)tms9918->config, 256); 183 | 184 | // flush 185 | int i = 0; 186 | while (i < 256) { 187 | *((volatile uint32_t *)(CONFIG_FLASH_ADDR) + i) = 0; 188 | i += sizeof(uint32_t); 189 | } 190 | 191 | if (memcmp(CONFIG_FLASH_ADDR, (const void*)tms9918->config, 256) == 0) 192 | { 193 | success = true; 194 | break; 195 | } 196 | } 197 | 198 | return success; 199 | } -------------------------------------------------------------------------------- /tools/bin2carray.py: -------------------------------------------------------------------------------- 1 | # bin2carray.py 2 | # 3 | # Convert binary files into C arrays 4 | # 5 | # Copyright (c) 2023 Troy Schrapel 6 | # 7 | # This code is licensed under the MIT license 8 | # 9 | # https://github.com/visrealm/pico-56 10 | # 11 | # 12 | 13 | import os 14 | import sys 15 | import glob 16 | import re 17 | import argparse 18 | import datetime 19 | 20 | 21 | def main() -> int: 22 | """ 23 | main program entry-point 24 | """ 25 | parser = argparse.ArgumentParser( 26 | description='Convert binary files into C-style arrays for use with the PICO-56.', 27 | epilog="GitHub: https://github.com/visrealm/pico-56") 28 | parser.add_argument('-v', '--verbose', 29 | help='verbose output', action='store_true') 30 | parser.add_argument( 31 | '-p', '--prefix', help='array variable prefix', default='') 32 | parser.add_argument( 33 | '-o', '--out', help='output file - defaults to base input file name with .c extension') 34 | parser.add_argument('-i', '--in', nargs='+', 35 | help='input file(s) to store in Pi Pico ROM - can use wildcards') 36 | args = vars(parser.parse_args()) 37 | 38 | outSourceFileName = args['out'] 39 | outSourceFile = None 40 | outHeaderFileName = '' 41 | outHeaderFile = None 42 | 43 | inFileNames = [] 44 | 45 | for inGlob in args['in']: 46 | for inFileName in glob.glob(inGlob): 47 | inFileNames.append(inFileName) 48 | 49 | if outSourceFileName: 50 | outSourceFile = open(outSourceFileName, "w") 51 | outHeaderFileName = os.path.splitext(outSourceFileName)[0] + ".h" 52 | outHeaderFile = open(outHeaderFileName, "w") 53 | 54 | outSourceFile.write(getFileHeader( 55 | outSourceFileName, inFileNames, args, isHeaderFile=False)) 56 | outHeaderFile.write(getFileHeader( 57 | outHeaderFileName, inFileNames, args, isHeaderFile=True)) 58 | 59 | for infile in inFileNames: 60 | processFile(infile, outSourceFile, 61 | outHeaderFile, args) 62 | 63 | outSourceFile.close() 64 | outHeaderFile.close() 65 | 66 | fileList = "" 67 | for infile in inFileNames: 68 | fileList += os.path.split(infile)[1] + ", " 69 | 70 | print(sys.argv[0] + " generated C data arrays in " + 71 | os.path.join(os.getcwd(), outSourceFile.name) + " from (" + fileList.rstrip(", ") + ")") 72 | 73 | return 0 74 | 75 | 76 | def getFileHeader(fileName, fileList, args, isHeaderFile) -> str: 77 | """ 78 | write the header at the top of the .c/.h file 79 | """ 80 | timestamp = datetime.datetime.now() 81 | hdrText = ( 82 | f"/*\n" 83 | f" * Data file generated by {os.path.split(sys.argv[0])[1]}\n" 84 | f" * Copyright (c) {timestamp.strftime('%Y')} Troy Schrapel\n" 85 | f" *\n" 86 | f" * Generated using the following command:\n" 87 | f" * > cd {os.getcwd()}\n" 88 | f" * > python3 {' '.join(sys.argv[:])}\n" 89 | f" *\n" 90 | f" * Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}\n" 91 | f" *\n" 92 | f" * Contains the following images:\n" 93 | f" *\n") 94 | 95 | for infile in fileList: 96 | hdrText += f" * - {os.path.split(infile)[1]}\n" 97 | 98 | hdrText += " */\n\n" 99 | 100 | if isHeaderFile: 101 | baseName = args['prefix'] + "_" + os.path.basename(fileName) 102 | sanitizedFile = re.sub('[^0-9a-zA-Z]+', '_', baseName.upper()) 103 | hdrText += f"#pragma once\n\n" 104 | else: 105 | hdrText += "#include \"pico.h\"\n" 106 | hdrText += "#include " 107 | return hdrText 108 | 109 | 110 | def generateArrayComment(infile, src) -> str: 111 | """ 112 | generate a comment bloack for an image giving some metadata 113 | """ 114 | comment = "\n\n/* source: " + infile + "\n" 115 | 116 | src.seek(0, os.SEEK_END) 117 | 118 | comment += " * size : " + str(src.tell()) + " bytes */\n" 119 | 120 | src.seek(0, os.SEEK_SET) 121 | 122 | return comment 123 | 124 | 125 | def generateProto(varName, isHeader) -> str: 126 | """ 127 | generate an array prototype (or definition) 128 | """ 129 | typePrefix = ("extern " if isHeader else " ") 130 | varNamePrefix = "" if isHeader else ("__aligned(4) ") 131 | dataType = "const uint8_t " 132 | suffix = ";" if isHeader else " = " 133 | proto = "" 134 | 135 | # output the image array prototype 136 | proto += typePrefix + dataType + varNamePrefix + varName + "[]" + suffix 137 | return proto 138 | 139 | 140 | def dataToArrayContents(src) -> str: 141 | """ 142 | return a string containing a c-style array of the image pixels 143 | """ 144 | values = [] 145 | data = bytearray(src.read()) 146 | for b in data: 147 | values.append("{0:#0{1}x}".format(b, 4)) 148 | 149 | return "\n " + (", ".join(values)) 150 | 151 | 152 | def processFile(infile, srcOutput, hdrOutput, args) -> None: 153 | """ 154 | process a single image and output it to the header and source files 155 | """ 156 | outPathWithoutExt = os.path.splitext(infile)[0] 157 | closeFile = False 158 | if srcOutput == None: 159 | srcOutput = open(outPathWithoutExt + ".c", "w") 160 | srcOutput.write(getFileHeader(srcOutput.name, [ 161 | infile], args, isHeaderFile=False)) 162 | hdrOutput = open(outPathWithoutExt + ".h", "w") 163 | hdrOutput.write(getFileHeader(hdrOutput.name, [ 164 | infile], args, isHeaderFile=True)) 165 | closeFile = True 166 | 167 | varName = os.path.split(infile)[1] 168 | varName = re.sub('[^0-9a-zA-Z]+', '_', args['prefix'] + varName) 169 | 170 | try: 171 | src = open(infile, "rb") 172 | 173 | comment = generateArrayComment(infile, src) 174 | 175 | hdrOutput.write(comment) 176 | hdrOutput.write(generateProto( 177 | varName, isHeader=True) + "\n") 178 | 179 | srcOutput.write(comment) 180 | srcOutput.write(generateProto( 181 | varName, isHeader=False) + "{") 182 | srcOutput.write(dataToArrayContents(src) + "};") 183 | 184 | src.close() 185 | 186 | except IOError: 187 | print("cannot convert", infile) 188 | 189 | if closeFile: 190 | srcOutput.close() 191 | hdrOutput.close() 192 | 193 | return 194 | 195 | 196 | # program entry 197 | if __name__ == "__main__": 198 | sys.exit(main()) 199 | -------------------------------------------------------------------------------- /test/qc/qc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | /* 13 | * External pins 14 | * 15 | * Pin | GPIO | Name | TMS9918A Pin 16 | * -----+------+--------+------------- 17 | * 19 | 14 | CD0 | 24 18 | * 20 | 15 | CD1 | 23 19 | * 21 | 16 | CD2 | 22 20 | * 22 | 17 | CD3 | 21 21 | * 24 | 18 | CD4 | 20 22 | * 25 | 19 | CD5 | 19 23 | * 26 | 20 | CD6 | 18 24 | * 27 | 21 | CD7 | 17 25 | * 29 | 22 | /INT | 16 26 | * 30 | RUN | RST | 34 27 | * 31 | 26 | /CSR | 15 28 | * 32 | 27 | /CSW | 14 29 | * 34 | 28 | MODE | 13 30 | */ 31 | 32 | #include "pico/stdlib.h" 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | /* 39 | * Pin mapping (PCB v0.3) 40 | * 41 | * Pin | GPIO | Name | TMS9918A Pin 42 | * -----+------+-----------+------------- 43 | * 19 | 14 | CD0 | 24 44 | * 20 | 15 | CD1 | 23 45 | * 21 | 16 | CD2 | 22 46 | * 22 | 17 | CD3 | 21 47 | * 24 | 18 | CD4 | 20 48 | * 25 | 19 | CD5 | 19 49 | * 26 | 20 | CD6 | 18 50 | * 27 | 21 | CD7 | 17 51 | * 29 | 22 | /INT | 16 52 | * 30 | RUN | RST | 34 53 | * 31 | 26 | /CSR | 15 54 | * 32 | 27 | /CSW | 14 55 | * 34 | 28 | MODE | 13 56 | * 35 | 29 | GROMCLK | 37 57 | * 37 | 23 | CPUCLK | 38 58 | * 59 | * Note: Due to GROMCLK and CPUCLK using GPIO23 and GPIO29 60 | * a genuine Raspberry Pi Pico can't be used. 61 | * v0.3 of the PCB is designed for the DWEII? 62 | * RP2040 USB-C module which exposes these additional 63 | * GPIOs. A future pico9918 revision (v0.4+) will do without 64 | * an external RP2040 board and use the RP2040 directly. 65 | * 66 | * Purchase links: 67 | * https://www.amazon.com/RP2040-Board-Type-C-Raspberry-Micropython/dp/B0CG9BY48X 68 | * https://www.aliexpress.com/item/1005007066733934.html 69 | */ 70 | 71 | #define PCB_MAJOR_VERSION 0 72 | #define PCB_MINOR_VERSION 4 73 | 74 | #define GPIO_CD0 14 75 | #define GPIO_CSR 26 76 | #define GPIO_CSW 27 77 | #define GPIO_MODE 28 78 | #define GPIO_INT 22 79 | 80 | #if PCB_MAJOR_VERSION != 0 81 | #error "Time traveller?" 82 | #endif 83 | 84 | // pin-mapping for gromclk and cpuclk changed in PCB v0.4 85 | // in order to have MODE and MODE1 sequential 86 | #if PCB_MINOR_VERSION < 4 87 | #error "Not for v0.3 yet" 88 | #define GPIO_GROMCL 29 89 | #define GPIO_CPUCL 23 90 | #else 91 | #define GPIO_GROMCL 25 92 | #define GPIO_CPUCL 24 93 | #define GPIO_RESET 23 94 | #define GPIO_MODE1 29 95 | #endif 96 | 97 | #define GPIO_CD_MASK (0xff << GPIO_CD0) 98 | #define GPIO_CSR_MASK (0x01 << GPIO_CSR) 99 | #define GPIO_CSW_MASK (0x01 << GPIO_CSW) 100 | #define GPIO_MODE_MASK (0x01 << GPIO_MODE) 101 | #define GPIO_INT_MASK (0x01 << GPIO_INT) 102 | #define GPIO_GROMCL_MASK (0x01 << GPIO_GROMCL) 103 | #define GPIO_CPUCL_MASK (0x01 << GPIO_CPUCL) 104 | 105 | // Quality control wiring: 106 | // CPUCLK to CSR 107 | // GROMCLK to CSW 108 | // INT to MODE 109 | 110 | int main(void) 111 | { 112 | stdio_init_all(); 113 | gpio_init_mask(GPIO_CD_MASK | GPIO_GROMCL_MASK | GPIO_CPUCL_MASK | GPIO_CSR_MASK | GPIO_CSW_MASK | GPIO_MODE_MASK | GPIO_INT_MASK); 114 | gpio_set_dir_all_bits(GPIO_CD_MASK | GPIO_GROMCL_MASK | GPIO_CPUCL_MASK | GPIO_INT_MASK); // set r, w, mode to outputs 115 | 116 | start: 117 | gpio_put_all(0); 118 | 119 | printf("PICO9918 QC Tool\n"); 120 | 121 | bool initialCheckOk; 122 | 123 | initialCheckOk = true; 124 | 125 | 126 | printf("Testing CPUCLK and CSR\n"); 127 | 128 | printf("Set CPUCLK 1\n"); 129 | gpio_put(GPIO_CPUCL, 1); 130 | sleep_ms(1); 131 | bool val = gpio_get(GPIO_CSR); 132 | if (!val) initialCheckOk = false; 133 | printf("Check CSR %d - %s\n", val, val ? "OK" : "FAILED!"); 134 | sleep_ms(500); 135 | 136 | printf("Set CPUCLK 0\n"); 137 | gpio_put(GPIO_CPUCL, 0); 138 | sleep_ms(1); 139 | val = gpio_get(GPIO_CSR); 140 | if (val) initialCheckOk = false; 141 | printf("Check CSR %d - %s\n", val, !val ? "OK" : "FAILED!"); 142 | sleep_ms(500); 143 | 144 | 145 | printf("Testing GROMCLK and CSW\n"); 146 | 147 | printf("Set GROMCLK 1\n"); 148 | gpio_put(GPIO_GROMCL, 1); 149 | sleep_ms(1); 150 | val = gpio_get(GPIO_CSW); 151 | if (!val) initialCheckOk = false; 152 | printf("Check CSW %d - %s\n", val, val ? "OK" : "FAILED!"); 153 | sleep_ms(500); 154 | 155 | printf("Set GROMCLK 0\n"); 156 | gpio_put(GPIO_GROMCL, 0); 157 | sleep_ms(1); 158 | val = gpio_get(GPIO_CSW); 159 | if (val) initialCheckOk = false; 160 | printf("Check CSW %d - %s\n", val, !val ? "OK" : "FAILED!"); 161 | sleep_ms(500); 162 | 163 | 164 | 165 | printf("Testing INT and MODE\n"); 166 | 167 | printf("Set INT 1\n"); 168 | gpio_put(GPIO_INT, 1); 169 | sleep_ms(1); 170 | val = gpio_get(GPIO_MODE); 171 | if (!val) initialCheckOk = false; 172 | printf("Check MODE %d - %s\n", val, val ? "OK" : "FAILED!"); 173 | sleep_ms(500); 174 | 175 | printf("Set INT 0\n"); 176 | gpio_put(GPIO_INT, 0); 177 | sleep_ms(1); 178 | val = gpio_get(GPIO_MODE); 179 | if (val) initialCheckOk = false; 180 | printf("Check MODE %d - %s\n", val, !val ? "OK" : "FAILED!"); 181 | sleep_ms(500); 182 | 183 | if (!initialCheckOk) 184 | { 185 | printf("Initial check failed. Halting."); 186 | while (1) 187 | { 188 | tight_loop_contents(); 189 | } 190 | } 191 | else 192 | { 193 | printf("Initial check passed. Continuing..."); 194 | } 195 | 196 | uint8_t gpioOut = 1; 197 | 198 | printf("CD bit cycle start\n"); 199 | 200 | for (int i = 0; i < 3 * 8; ++i) 201 | { 202 | uint32_t out = GPIO_GROMCL_MASK; // read low (active), write high (inactive) 203 | out |= (uint32_t)gpioOut << GPIO_CD0; 204 | 205 | gpio_put_all(out); 206 | 207 | printf("%02x\n", gpioOut); 208 | 209 | gpioOut <<= 1; 210 | if (gpioOut == 0) gpioOut= 1; 211 | 212 | sleep_ms(250); 213 | } 214 | 215 | printf("CD bit cycle end\n"); 216 | printf("CD bit cycle OE disabled start\n"); 217 | 218 | for (int i = 0; i < 3 * 8; ++i) 219 | { 220 | uint32_t out = GPIO_GROMCL_MASK | GPIO_CPUCL_MASK; // read low (active), write high (inactive) 221 | out |= (uint32_t)gpioOut << GPIO_CD0; 222 | 223 | gpio_put_all(out); 224 | 225 | printf("%02x\n", gpioOut); 226 | 227 | gpioOut <<= 1; 228 | if (gpioOut == 0) gpioOut= 1; 229 | 230 | sleep_ms(250); 231 | } 232 | printf("CD bit cycle OE disabled end\n"); 233 | 234 | goto start; 235 | 236 | 237 | 238 | 239 | return 0; 240 | } 241 | -------------------------------------------------------------------------------- /.github/workflows/build-common.yml: -------------------------------------------------------------------------------- 1 | name: Common Build Steps 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | build-firmware: 7 | description: 'Whether to build firmware' 8 | required: false 9 | type: boolean 10 | default: true 11 | build-configurator: 12 | description: 'Whether to build configurator' 13 | required: false 14 | type: boolean 15 | default: false 16 | artifact-name-suffix: 17 | description: 'Suffix for artifact names' 18 | required: true 19 | type: string 20 | run-windows: 21 | description: 'Whether to run Windows job' 22 | required: false 23 | type: boolean 24 | default: true 25 | run-linux: 26 | description: 'Whether to run Linux job' 27 | required: false 28 | type: boolean 29 | default: true 30 | run-macos: 31 | description: 'Whether to run macOS job' 32 | required: false 33 | type: boolean 34 | default: true 35 | 36 | jobs: 37 | windows: 38 | runs-on: windows-latest 39 | if: ${{ inputs.run-windows }} 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | submodules: recursive 45 | 46 | - name: Install Python dependencies 47 | run: | 48 | python -m pip install --upgrade pip 49 | pip install pillow 50 | 51 | - name: Install ARM GNU Toolchain 52 | run: | 53 | $url = "https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/download/v13.2.1-1.1/xpack-arm-none-eabi-gcc-13.2.1-1.1-win32-x64.zip" 54 | $output = "arm-toolchain.zip" 55 | Invoke-WebRequest -Uri $url -OutFile $output 56 | Expand-Archive -Path $output -DestinationPath "C:\arm-toolchain" 57 | echo "C:\arm-toolchain\xpack-arm-none-eabi-gcc-13.2.1-1.1\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 58 | 59 | - name: Configure CMake (with automatic SDK fetch and patch) 60 | run: | 61 | mkdir build 62 | cd build 63 | cmake -S .. -B . -G Ninja -DPICO_SDK_FETCH_FROM_GIT=ON "-DPICO_SDK_FETCH_FROM_GIT_TAG=2.1.1" "-DPICO_BOARD_HEADER_DIRS=../src/boards" -DPICO_BOARD=pico9918 -DBUILD_TOOLS_FROM_SOURCE=ON 64 | 65 | - name: Build Firmware 66 | if: ${{ inputs.build-firmware }} 67 | run: | 68 | cd build 69 | cmake --build . --target firmware 70 | 71 | - name: Build Configurator 72 | if: ${{ inputs.build-configurator }} 73 | run: | 74 | cd build 75 | cmake --build . --target configurator_all 76 | 77 | - name: Upload Firmware Artifacts 78 | if: ${{ inputs.build-firmware }} 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: firmware-${{ inputs.artifact-name-suffix }} 82 | path: build/dist/*.uf2 83 | 84 | - name: Upload Configurator Artifacts 85 | if: ${{ inputs.build-configurator }} 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: configurator-${{ inputs.artifact-name-suffix }} 89 | path: | 90 | build/dist/*.rom 91 | build/dist/*.bin 92 | build/dist/*.nabu 93 | build/dist/*.sg 94 | 95 | linux: 96 | runs-on: ubuntu-latest 97 | if: ${{ inputs.run-linux }} 98 | 99 | steps: 100 | - uses: actions/checkout@v4 101 | with: 102 | submodules: recursive 103 | 104 | - name: Install dependencies 105 | run: | 106 | sudo apt-get update 107 | sudo apt-get install -y build-essential cmake python3 python3-pip git gcc-arm-none-eabi 108 | pip3 install pillow 109 | 110 | - name: Configure CMake (with automatic SDK fetch and patch) 111 | run: | 112 | mkdir build 113 | cd build 114 | cmake -S .. -B . -G Ninja -DPICO_SDK_FETCH_FROM_GIT=ON "-DPICO_SDK_FETCH_FROM_GIT_TAG=2.1.1" "-DPICO_BOARD_HEADER_DIRS=../src/boards" -DPICO_BOARD=pico9918 -DBUILD_TOOLS_FROM_SOURCE=ON 115 | 116 | - name: Build Firmware 117 | if: ${{ inputs.build-firmware }} 118 | run: | 119 | cd build 120 | cmake --build . --target firmware 121 | 122 | - name: Build Configurator 123 | if: ${{ inputs.build-configurator }} 124 | run: | 125 | cd build 126 | cmake --build . --target configurator_all 127 | 128 | - name: Upload Firmware Artifacts 129 | if: ${{ inputs.build-firmware }} 130 | uses: actions/upload-artifact@v4 131 | with: 132 | name: firmware-${{ inputs.artifact-name-suffix }} 133 | path: build/dist/*.uf2 134 | 135 | - name: Upload Configurator Artifacts 136 | if: ${{ inputs.build-configurator }} 137 | uses: actions/upload-artifact@v4 138 | with: 139 | name: configurator-${{ inputs.artifact-name-suffix }} 140 | path: | 141 | build/dist/*.rom 142 | build/dist/*.bin 143 | build/dist/*.nabu 144 | build/dist/*.sg 145 | 146 | macos: 147 | runs-on: macos-latest 148 | if: ${{ inputs.run-macos }} 149 | 150 | steps: 151 | - uses: actions/checkout@v4 152 | with: 153 | submodules: recursive 154 | 155 | - name: Install dependencies 156 | run: | 157 | brew install cmake ninja python3 git 158 | pip3 install pillow --break-system-packages 159 | 160 | - name: Install ARM GNU Toolchain 161 | run: | 162 | # Use the same version as other platforms for consistency 163 | curl -L "https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/download/v13.2.1-1.1/xpack-arm-none-eabi-gcc-13.2.1-1.1-darwin-arm64.tar.gz" -o arm-toolchain.tar.gz 164 | sudo tar -xzf arm-toolchain.tar.gz -C /opt 165 | echo "/opt/xpack-arm-none-eabi-gcc-13.2.1-1.1/bin" >> $GITHUB_PATH 166 | 167 | - name: Configure CMake (with automatic SDK fetch and patch) 168 | run: | 169 | mkdir build 170 | cd build 171 | cmake -S .. -B . -G Ninja -DPICO_SDK_FETCH_FROM_GIT=ON "-DPICO_SDK_FETCH_FROM_GIT_TAG=2.1.1" "-DPICO_BOARD_HEADER_DIRS=../src/boards" -DPICO_BOARD=pico9918 -DBUILD_TOOLS_FROM_SOURCE=ON 172 | 173 | - name: Build Firmware 174 | if: ${{ inputs.build-firmware }} 175 | run: | 176 | cd build 177 | cmake --build . --target firmware 178 | 179 | - name: Build Configurator 180 | if: ${{ inputs.build-configurator }} 181 | run: | 182 | cd build 183 | cmake --build . --target configurator_all 184 | 185 | - name: Upload Firmware Artifacts 186 | if: ${{ inputs.build-firmware }} 187 | uses: actions/upload-artifact@v4 188 | with: 189 | name: firmware-${{ inputs.artifact-name-suffix }} 190 | path: build/dist/*.uf2 191 | 192 | - name: Upload Configurator Artifacts 193 | if: ${{ inputs.build-configurator }} 194 | uses: actions/upload-artifact@v4 195 | with: 196 | name: configurator-${{ inputs.artifact-name-suffix }} 197 | path: | 198 | build/dist/*.rom 199 | build/dist/*.bin 200 | build/dist/*.nabu 201 | build/dist/*.sg 202 | -------------------------------------------------------------------------------- /src/vga/vga-modes.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: pico9918 - vga 3 | * 4 | * Copyright (c) 2024 Troy Schrapel 5 | * 6 | * This code is licensed under the MIT license 7 | * 8 | * https://github.com/visrealm/pico9918 9 | * 10 | */ 11 | 12 | #include "vga-modes.h" 13 | 14 | void vgaUpdateTotalPixels(VgaSyncParams* params); 15 | 16 | /* 17 | * populate VgaParams for known vga modes 18 | */ 19 | VgaParams vgaGetParams(VgaMode mode) 20 | { 21 | VgaParams params; 22 | 23 | switch (mode) 24 | { 25 | case VGA_640_480_60HZ: // http://tinyvga.com/vga-timing/640x480@60Hz 26 | params.pixelClockKHz = 25175; 27 | params.hSyncParams.displayPixels = 640; 28 | params.hSyncParams.frontPorchPixels = 16; 29 | params.hSyncParams.syncPixels = 96; 30 | params.hSyncParams.backPorchPixels = 48; 31 | params.hSyncParams.syncHigh = false; 32 | 33 | params.vSyncParams.displayPixels = 480; 34 | params.vSyncParams.frontPorchPixels = 10; 35 | params.vSyncParams.syncPixels = 2; 36 | params.vSyncParams.backPorchPixels = 33; 37 | params.vSyncParams.syncHigh = false; 38 | break; 39 | 40 | case VGA_640_400_70HZ: // http://tinyvga.com/vga-timing/640x400@70Hz 41 | params.pixelClockKHz = 25175; 42 | params.hSyncParams.displayPixels = 640; 43 | params.hSyncParams.frontPorchPixels = 16; 44 | params.hSyncParams.syncPixels = 96; 45 | params.hSyncParams.backPorchPixels = 48; 46 | params.hSyncParams.syncHigh = false; 47 | 48 | params.vSyncParams.displayPixels = 400; 49 | params.vSyncParams.frontPorchPixels = 12; 50 | params.vSyncParams.syncPixels = 2; 51 | params.vSyncParams.backPorchPixels = 35; 52 | params.vSyncParams.syncHigh = true; 53 | break; 54 | 55 | case VGA_800_600_60HZ: // http://tinyvga.com/vga-timing/800x600@60Hz 56 | params.pixelClockKHz = 40000; 57 | params.hSyncParams.displayPixels = 800; 58 | params.hSyncParams.frontPorchPixels = 40; 59 | params.hSyncParams.syncPixels = 128; 60 | params.hSyncParams.backPorchPixels = 88; 61 | params.hSyncParams.syncHigh = true; 62 | 63 | params.vSyncParams.displayPixels = 600; 64 | params.vSyncParams.frontPorchPixels = 1; 65 | params.vSyncParams.syncPixels = 4; 66 | params.vSyncParams.backPorchPixels = 23; 67 | params.vSyncParams.syncHigh = true; 68 | break; 69 | 70 | case VGA_1024_768_60HZ: // http://tinyvga.com/vga-timing/1024x768@60Hz 71 | params.pixelClockKHz = 65000; 72 | params.hSyncParams.displayPixels = 1024; 73 | params.hSyncParams.frontPorchPixels = 24; 74 | params.hSyncParams.syncPixels = 136; 75 | params.hSyncParams.backPorchPixels = 160; 76 | params.hSyncParams.syncHigh = false; 77 | 78 | params.vSyncParams.displayPixels = 768; 79 | params.vSyncParams.frontPorchPixels = 5; 80 | params.vSyncParams.syncPixels = 6; 81 | params.vSyncParams.backPorchPixels = 27; 82 | params.vSyncParams.syncHigh = false; 83 | break; 84 | 85 | case VGA_1280_1024_60HZ: // http://tinyvga.com/vga-timing/1280x1024@60Hz 86 | params.pixelClockKHz = 108000; 87 | params.hSyncParams.displayPixels = 1280; 88 | params.hSyncParams.frontPorchPixels = 48; 89 | params.hSyncParams.syncPixels = 112; 90 | params.hSyncParams.backPorchPixels = 248; 91 | params.hSyncParams.syncHigh = true; 92 | 93 | params.vSyncParams.displayPixels = 1024; 94 | params.vSyncParams.frontPorchPixels = 1; 95 | params.vSyncParams.syncPixels = 3; 96 | params.vSyncParams.backPorchPixels = 38; 97 | params.vSyncParams.syncHigh = true; 98 | break; 99 | 100 | case RGBS_PAL_720_576i_50HZ: 101 | params.pixelClockKHz = 13500; 102 | params.hSyncParams.displayPixels = 720; 103 | params.hSyncParams.frontPorchPixels = 22; 104 | params.hSyncParams.syncPixels = 64; 105 | params.hSyncParams.backPorchPixels = 74; 106 | params.hSyncParams.syncHigh = false; 107 | 108 | params.vSyncParams.displayPixels = 576 / 2; // halving since... interlaced 109 | params.vSyncParams.frontPorchPixels = 8; 110 | params.vSyncParams.syncPixels = 3; // this should be 2.5 111 | params.vSyncParams.backPorchPixels = 14; 112 | params.vSyncParams.syncHigh = false; 113 | break; 114 | 115 | case RGBS_NTSC_720_480i_60HZ: 116 | params.pixelClockKHz = 13500; 117 | params.hSyncParams.displayPixels = 720; 118 | params.hSyncParams.frontPorchPixels = 22; 119 | params.hSyncParams.syncPixels = 64; 120 | params.hSyncParams.backPorchPixels = 74; 121 | params.hSyncParams.syncHigh = false; 122 | 123 | params.vSyncParams.displayPixels = 480 / 2; // halving since... interlaced 124 | params.vSyncParams.frontPorchPixels = 3; 125 | params.vSyncParams.syncPixels = 3; 126 | params.vSyncParams.backPorchPixels = 17; // this should be 16.5 127 | params.vSyncParams.syncHigh = false; 128 | break; 129 | 130 | } 131 | 132 | if (params.pixelClockKHz && params.hSyncParams.displayPixels && params.vSyncParams.displayPixels) 133 | { 134 | vgaUpdateTotalPixels(¶ms.hSyncParams); 135 | vgaUpdateTotalPixels(¶ms.vSyncParams); 136 | 137 | float scanlineTimeSeconds = 1.0f / (params.pixelClockKHz * 1000.0f) * params.hSyncParams.totalPixels; 138 | float frameTimeSeconds = scanlineTimeSeconds * params.vSyncParams.totalPixels; 139 | 140 | params.hSyncParams.freqHz = 1.0f / scanlineTimeSeconds; 141 | params.vSyncParams.freqHz = 1.0f / frameTimeSeconds; 142 | } 143 | 144 | setVgaParamsScale(¶ms, 1); 145 | 146 | return params; 147 | } 148 | 149 | /* 150 | * set the scale/multiplier of virtual pixel size 151 | */ 152 | bool setVgaParamsScale(VgaParams* params, int pixelScale) 153 | { 154 | return setVgaParamsScaleX(params, pixelScale) && 155 | setVgaParamsScaleY(params, pixelScale); 156 | } 157 | 158 | bool setVgaParamsScaleXY(VgaParams* params, int pixelScaleX, int pixelScaleY) 159 | { 160 | return setVgaParamsScaleX(params, pixelScaleX) && 161 | setVgaParamsScaleY(params, pixelScaleY); 162 | } 163 | 164 | bool setVgaParamsScaleX(VgaParams* params, int pixelScale) 165 | { 166 | if (!params || pixelScale < 1) return false; 167 | 168 | params->hPixelScale = pixelScale; 169 | params->hVirtualPixels = (params->hSyncParams.displayPixels / params->hPixelScale); 170 | return true; 171 | } 172 | 173 | bool setVgaParamsScaleY(VgaParams* params, int pixelScale) 174 | { 175 | if (!params || pixelScale < 1) return false; 176 | 177 | params->vPixelScale = pixelScale; 178 | params->vVirtualPixels = (params->vSyncParams.displayPixels / params->vPixelScale); 179 | return true; 180 | } 181 | 182 | /* 183 | * update total number of pixels 184 | */ 185 | void vgaUpdateTotalPixels(VgaSyncParams* params) 186 | { 187 | if (params) 188 | { 189 | params->totalPixels = params->displayPixels; 190 | params->totalPixels += params->frontPorchPixels; 191 | params->totalPixels += params->syncPixels; 192 | params->totalPixels += params->backPorchPixels; 193 | } 194 | } -------------------------------------------------------------------------------- /configtool/src/menu-firmware.bas: -------------------------------------------------------------------------------- 1 | ' 2 | ' Project: pico9918 3 | ' 4 | ' PICO9918 Configurator 5 | ' 6 | ' Copyright (c) 2024 Troy Schrapel 7 | ' 8 | ' This code is licensed under the MIT license 9 | ' 10 | ' https://github.com/visrealm/pico9918 11 | ' 12 | 13 | #if BANK_SIZE 14 | ' convert .UF2 block number to name table location for visualization 15 | DEF FN BLOCKPOS(#I) = XY((#I) % 30 + 1, (#I) / 30 + a_popupTop + 2) 16 | 17 | #endif 18 | 19 | CONST FWROWS = (#FIRMWARE_BLOCKS - 1) / 30 + 2 20 | 21 | ' ----------------------------------------------------------------------------- 22 | ' open the firmware menu 23 | ' ----------------------------------------------------------------------------- 24 | firmwareMenu: PROCEDURE 25 | 26 | 27 | g_menuTopRow = MENU_TITLE_ROW + 3 ' WTF? For some reason I need this line twice???? At least on TI-99 28 | 29 | DRAW_TITLE("FIRMWARE UPDATE") 30 | 31 | PRINT AT XY(4, g_menuTopRow + 0), "Current version : v",verMajor, ".", verMinor, ".", verPatch 32 | PRINT AT XY(4, g_menuTopRow + 1), "New version : v",FIRMWARE_MAJOR_VER,".",FIRMWARE_MINOR_VER,".",FIRMWARE_PATCH_VER 33 | 34 | 35 | #if BANK_SIZE 36 | 37 | GOSUB verifyCartridgeFirmware 38 | 39 | IF STATUS THEN 40 | 41 | DRAW_POPUP_W("Update firmware?", 5, 20) 42 | 43 | GOSUB confirmationMenuLoop 44 | 45 | R = 19: GOSUB emptyRowR 46 | 47 | IF confirm THEN 48 | 49 | DRAW_POPUP_W("Upgrading firmware : ", FWROWS, 30) 50 | 51 | WAIT 52 | 53 | FOR #FWBLOCK = 0 TO #FIRMWARE_BLOCKS - 1 54 | PRINT AT BLOCKPOS(#FWBLOCK), CHR$(1) 55 | NEXT #FWBLOCK 56 | 57 | GOSUB firmwareWriteAndVerify 58 | END IF 59 | END IF 60 | #endif 61 | 62 | SET_MENU(MENU_ID_MAIN) 63 | END 64 | 65 | #if BANK_SIZE 66 | 67 | ' ----------------------------------------------------------------------------- 68 | ' verify the firmware on this cart can be read in full 69 | ' ----------------------------------------------------------------------------- 70 | verifyCartridgeFirmware: PROCEDURE 71 | 72 | #FWBLOCK = 0 73 | STATUS = 1 74 | 75 | VDP_DISABLE_INT 76 | 77 | PRINT AT XY(2, g_menuTopRow + 11), "Verifying new firmware data..." 78 | 79 | I = 0 80 | FOR B = 1 TO FIRMWARE_BANKS 81 | BANKSEL(B) 82 | #FWOFFSET = 0 83 | PRINT AT XY(8, g_menuTopRow + 5), "Checking Bank: ", B 84 | 85 | IF bank1Start(0) <> B THEN 86 | STATUS = 0 87 | PRINT AT XY(2, g_menuTopRow + 5), "Bank marker mismatch: ", bank1Start(0), " <> ", B 88 | ELSE 89 | FOR BL = 1 TO FIRMWARE_BLOCKS_PER_BANK 90 | 91 | blockFailed = FALSE 92 | 93 | FOR #UF2OFFSET = 0 TO 8 94 | IF bank1Data(#FWOFFSET + #UF2OFFSET) <> uf2Header(#UF2OFFSET) THEN 95 | blockFailed = TRUE 96 | PRINT AT XY(2, g_menuTopRow + 7), "Block start marker not found" 97 | EXIT FOR 98 | END IF 99 | NEXT #UF2OFFSET 100 | 101 | #UF2BLOCK = bank1Data(#FWOFFSET + 20) + (bank1Data(#FWOFFSET + 21) * 256) 102 | 103 | IF #UF2BLOCK <> #FWBLOCK THEN 104 | PRINT AT XY(1, g_menuTopRow + 7), "Block seq. mismatch: ", #UF2BLOCK, " <> ", #FWBLOCK 105 | blockFailed = TRUE 106 | END IF 107 | 108 | FOR #UF2OFFSET = #FIRMWARE_BLOCK_BYTES - 4 TO #FIRMWARE_BLOCK_BYTES - 1 109 | IF bank1Data(#FWOFFSET + #UF2OFFSET) <> uf2Header(#UF2OFFSET - 256) THEN 110 | blockFailed = TRUE 111 | PRINT AT XY(2, g_menuTopRow + 7), "Block end marker not found" 112 | EXIT FOR 113 | END IF 114 | NEXT #UF2OFFSET 115 | 116 | IF blockFailed THEN 117 | PRINT AT XY(2, g_menuTopRow + 5), "Bank: ", B, ", Block: ", #FWBLOCK, " FAILED!" 118 | STATUS = 0 119 | END IF 120 | 121 | IF STATUS = 0 THEN EXIT FOR 122 | 123 | #FWOFFSET = #FWOFFSET + #FIRMWARE_BLOCK_BYTES 124 | #FWBLOCK = #FWBLOCK + 1 125 | IF #FWBLOCK = #FIRMWARE_BLOCKS THEN EXIT FOR 126 | NEXT BL 127 | 128 | END IF 129 | IF STATUS = 0 THEN EXIT FOR 130 | NEXT B 131 | 132 | BANKSEL(0) 133 | 134 | IF STATUS = 1 THEN 135 | PRINT AT XY(1, g_menuTopRow + 11), " New firmware data is valid " 136 | ELSE 137 | PRINT AT XY(1, g_menuTopRow + 11), " New firmware data is invalid " 138 | END IF 139 | 140 | VDP_ENABLE_INT 141 | 142 | END 143 | 144 | ' ----------------------------------------------------------------------------- 145 | ' write the firmware 146 | ' ----------------------------------------------------------------------------- 147 | firmwareWriteAndVerify: PROCEDURE 148 | 149 | #FWBLOCK = 0 150 | 151 | STATUS = 1 152 | 153 | FOR B = 1 TO FIRMWARE_BANKS 154 | BANKSEL(B) 155 | #FWOFFSET = 0 156 | FOR BL = 1 TO FIRMWARE_BLOCKS_PER_BANK 157 | VDP_DISABLE_INT 158 | 159 | DEFINE VRAM #VDP_FIRMWARE_DATA, #FIRMWARE_BLOCK_BYTES, VARPTR bank1Data(#FWOFFSET) 160 | PRINT AT XY(23, a_popupTop), <3>(#FWBLOCK + 1),"/",#FIRMWARE_BLOCKS 161 | 162 | FWST = $c0 OR (#VDP_FIRMWARE_DATA / 256) 163 | 164 | VDP_REG($3F) = FWST 165 | R = 0 166 | WHILE (FWST AND $80) 167 | VDP_STATUS_REG = 2 168 | FWST = VDP_STATUS 169 | VDP_STATUS_REG0 170 | R = R + 1 171 | WEND 172 | 173 | IF FWST AND $1c THEN 174 | PRINT AT BLOCKPOS(#FWBLOCK), CHR$(2) 175 | STATUS = 0 176 | ELSE 177 | PRINT AT BLOCKPOS(#FWBLOCK), CHR$(0) 178 | END IF 179 | 180 | 181 | VDP_ENABLE_INT 182 | 183 | I = I + 1 184 | 185 | WAIT 186 | 187 | #FWOFFSET = #FWOFFSET + #FIRMWARE_BLOCK_BYTES 188 | #FWBLOCK = #FWBLOCK + 1 189 | IF #FWBLOCK = #FIRMWARE_BLOCKS THEN EXIT FOR 190 | NEXT BL 191 | NEXT B 192 | 193 | BANKSEL(0) 194 | 195 | 'PRINT AT XY(5, a_popupTop), "Firmware write " 196 | IF STATUS THEN 197 | ' PRINT "success" 198 | clockChanged = TRUE 199 | GOSUB successMessage 200 | ELSE 201 | ' PRINT "failed" 202 | GOSUB failedMessage 203 | END IF 204 | 205 | GOSUB waitForInput 206 | 207 | END 208 | 209 | uf2Header: 210 | DATA BYTE $55, $46, $32, $0a ' magic start 0 211 | DATA BYTE $57, $51, $5d, $9e ' magic start 1 212 | 213 | DATA BYTE $00, $20, $00, $00 ' flags 214 | DATA BYTE $00, $00, $00, $10 ' target address 215 | DATA BYTE $00, $01, $00, $00 ' payload size (256) 216 | DATA BYTE $00, $00, $00, $00 ' block No 217 | DATA BYTE $ef, $00, $00, $00 ' block count 218 | DATA BYTE $56, $ff, $8b, $e4 ' family Id 219 | 220 | DATA BYTE $30, $6f, $b1, $0a ' magic end 221 | 222 | #endif -------------------------------------------------------------------------------- /configtool/tools/uf2cvb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Project: pico9918 3 | # 4 | # PICO9918 Configurator .UF2 to CVBasic converter 5 | # 6 | # Copyright (c) 2024 Troy Schrapel 7 | # 8 | # This code is licensed under the MIT license 9 | # 10 | # https://github.com/visrealm/pico9918 11 | # 12 | 13 | import os 14 | import re 15 | import sys 16 | import struct 17 | import argparse 18 | from datetime import datetime 19 | 20 | 21 | UF2_MAGIC_START0 = 0x0A324655 22 | UF2_MAGIC_START1 = 0x9E5D5157 23 | UF2_MAGIC_END = 0x0AB16F30 24 | 25 | def isUf2(buf): 26 | w = struct.unpack(" int: 31 | parser = argparse.ArgumentParser( 32 | description='Convert .UF2 file to banked CVBasic source file of binary data.', 33 | epilog="GitHub: https://github.com/visrealm/pico9918") 34 | parser.add_argument('-b', '--banksize', help='bank size (0, 8 or 16)', default=8, type=int, choices=[0,8,16]) 35 | parser.add_argument('-o', '--outfile', help='output file - defaults to base input file name with .bas extension', default='firmware') 36 | parser.add_argument('uf2file') 37 | args = vars(parser.parse_args()) 38 | 39 | filename = args['uf2file'] 40 | 41 | # uint32_t magicStart0; 42 | # uint32_t magicStart1; 43 | # uint32_t flags; 44 | # uint32_t targetAddr; 45 | # uint32_t payloadSize; 46 | # uint32_t blockNo; 47 | # uint32_t numBlocks; 48 | # uint32_t familyID; 49 | # uint8_t data [476]; 50 | # uint32_t magicEnd; 51 | 52 | BLOCK_SIZE = 9 * 4 + 256 # 9 ints and 256 bytes of data 53 | BANK_OVERHEAD = 48 # bank overhead. we can't use it all :( 54 | if (args['banksize']): 55 | BANK_SIZE = (1024 * args['banksize']) - BANK_OVERHEAD 56 | else: 57 | BANK_SIZE = 1024 * 128 58 | BLOCKS_PER_BANK = int((BANK_SIZE - 1) / BLOCK_SIZE) 59 | 60 | majorVer = 0 61 | minorVer = 0 62 | patchVer = 0 63 | 64 | match = re.search(r".*-v(\d*)-(\d*)-(\d*)", filename) 65 | if (match): 66 | majorVer = match.group(1) 67 | minorVer = match.group(2) 68 | patchVer = match.group(3) 69 | 70 | print("Output file : {0}".format(args['outfile'] + ".bas")) 71 | print("Firmware version: {0}.{1}.{2}".format(majorVer, minorVer, patchVer)) 72 | print("Bank size : {0} bytes".format(BANK_SIZE)) 73 | print("Block size : {0} bytes".format(BLOCK_SIZE)) 74 | print("Blocks per bank : {0}".format(BLOCKS_PER_BANK)) 75 | 76 | nettBytes = 0 77 | grossBytes = 0 78 | 79 | with open(args['outfile'] + ".h.bas", mode='w') as header: 80 | with open(args['outfile'] + ".bas", mode='w') as output: 81 | 82 | try: 83 | with open(filename, mode='rb') as uf2: 84 | blocksRead = 0 85 | bank = 1 86 | 87 | inpbuf = uf2.read(512) 88 | w = struct.unpack(" 18 | #include "pico.h" // For PICO_RP2040 19 | 20 | #include // memcpy 21 | 22 | #include "../flash.h" 23 | #include "../config.h" 24 | 25 | 26 | /* run9900() implemented in Thumb9900.S */ 27 | uint16_t run9900(uint8_t * memory, uint16_t pc, uint16_t wp, uint8_t * regx38); 28 | 29 | 30 | /* data to pre-load into the GPU RAM */ 31 | static uint8_t preload [] = { 32 | 0x02, 0x0F, 0x47, 0xFE, 0x10, 0x0D, 0x40, 0x36, 0x40, 0x5A, 0x40, 0x94, 0x40, 0xB4, 0x40, 0xFA, 33 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 34 | 0x0C, 0xA0, 0x41, 0x1C, 0x03, 0x40, 0x04, 0xC1, 0xD0, 0x60, 0x3F, 0x00, 0x09, 0x71, 0xC0, 0x21, 35 | 0x40, 0x06, 0x06, 0x90, 0x10, 0xF7, 0xC0, 0x20, 0x3F, 0x02, 0xC0, 0x60, 0x3F, 0x04, 0xC0, 0xA0, 36 | 0x3F, 0x06, 0xD0, 0xE0, 0x3F, 0x01, 0x13, 0x05, 0xD0, 0x10, 0xDC, 0x40, 0x06, 0x02, 0x16, 0xFD, 37 | 0x10, 0x03, 0xDC, 0x70, 0x06, 0x02, 0x16, 0xFD, 0x04, 0x5B, 0x0D, 0x0B, 0x06, 0xA0, 0x40, 0xB4, 38 | 0x0F, 0x0B, 0xC1, 0xC7, 0x13, 0x16, 0x04, 0xC0, 0xD0, 0x20, 0x60, 0x04, 0x0A, 0x30, 0xC0, 0xC0, 39 | 0x04, 0xC1, 0x02, 0x02, 0x04, 0x00, 0xCC, 0x01, 0x06, 0x02, 0x16, 0xFD, 0x04, 0xC0, 0xD0, 0x20, 40 | 0x41, 0x51, 0x06, 0xC0, 0x0A, 0x30, 0xA0, 0x03, 0x0C, 0xA0, 0x41, 0xAE, 0xD8, 0x20, 0x41, 0x51, 41 | 0xB0, 0x00, 0x04, 0x5B, 0xD8, 0x20, 0x41, 0x1A, 0x3F, 0x00, 0x02, 0x00, 0x41, 0xD6, 0xC8, 0x00, 42 | 0x3F, 0x02, 0x02, 0x00, 0x40, 0x06, 0xC8, 0x00, 0x3F, 0x04, 0x02, 0x00, 0x40, 0x10, 0xC8, 0x00, 43 | 0x3F, 0x06, 0x04, 0x5B, 0x04, 0xC7, 0xD0, 0x20, 0x3F, 0x01, 0x13, 0x13, 0xC0, 0x20, 0x41, 0x18, 44 | 0x06, 0x00, 0x0C, 0xA0, 0x41, 0x52, 0x02, 0x04, 0x00, 0x05, 0x02, 0x05, 0x3F, 0x02, 0x02, 0x06, 45 | 0x41, 0x42, 0x8D, 0xB5, 0x16, 0x03, 0x06, 0x04, 0x16, 0xFC, 0x10, 0x09, 0x06, 0x00, 0x16, 0xF1, 46 | 0x10, 0x09, 0xC0, 0x20, 0x3F, 0x02, 0x0C, 0xA0, 0x41, 0x52, 0x80, 0x40, 0x14, 0x03, 0x0C, 0xA0, 47 | 0x41, 0x9A, 0x05, 0x47, 0xD8, 0x07, 0xB0, 0x00, 0x04, 0x5B, 0x0D, 0x0B, 0x06, 0xA0, 0x40, 0xB4, 48 | 0x0F, 0x0B, 0xC1, 0xC7, 0x13, 0x04, 0xC0, 0x20, 0x3F, 0x0C, 0x0C, 0xA0, 0x41, 0xAE, 0x04, 0x5B, 49 | 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x41, 0x10, 50 | 0x02, 0x01, 0x41, 0x15, 0x02, 0x02, 0x0B, 0x00, 0x03, 0xA0, 0x32, 0x02, 0x32, 0x30, 0x32, 0x30, 51 | 0x32, 0x30, 0x36, 0x00, 0x02, 0x02, 0x00, 0x06, 0x36, 0x31, 0x06, 0x02, 0x16, 0xFD, 0x03, 0xC0, 52 | 0x0C, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x88, 0x00, 0x41, 0x18, 0x1A, 0x03, 0xC0, 0x60, 0x41, 0x18, 0x0C, 0x00, 0x0D, 0x00, 54 | 0x0A, 0x40, 0x02, 0x01, 0x0B, 0x00, 0xA0, 0x20, 0x41, 0x16, 0x17, 0x01, 0x05, 0x81, 0xA0, 0x60, 55 | 0x41, 0x14, 0x02, 0x03, 0x41, 0x42, 0x02, 0x02, 0x00, 0x10, 0x03, 0xA0, 0x32, 0x01, 0x06, 0xC1, 56 | 0x32, 0x01, 0x32, 0x00, 0x06, 0xC0, 0x32, 0x00, 0x36, 0x00, 0x36, 0x33, 0x06, 0x02, 0x16, 0xFD, 57 | 0x03, 0xC0, 0x0F, 0x00, 0xC0, 0x60, 0x41, 0x18, 0x0C, 0x00, 0x02, 0x00, 0x3F, 0x00, 0x02, 0x01, 58 | 0x41, 0x42, 0x02, 0x02, 0x00, 0x08, 0xCC, 0x31, 0x06, 0x02, 0x16, 0xFD, 0x0C, 0x00, 0x02, 0x01, 59 | 0x41, 0x4C, 0xD0, 0xA0, 0x41, 0x50, 0x06, 0xC2, 0xD0, 0xA0, 0x41, 0x4F, 0x02, 0x03, 0x0B, 0x00, 60 | 0x03, 0xA0, 0x32, 0x03, 0x32, 0x31, 0x32, 0x31, 0x32, 0x31, 0x36, 0x01, 0x36, 0x30, 0x06, 0x02, 61 | 0x16, 0xFD, 0x03, 0xC0, 0x0C, 0x00, 0x03, 0x40 62 | }; 63 | 64 | 65 | static int didFault = 0; 66 | 67 | /* 68 | * hardfault handler (triggered by MPU for GPU DMA requests) 69 | */ 70 | void isr_hardfault () 71 | { 72 | didFault = 1; 73 | TMS_REGISTER(tms9918, 0x38) = 0; // Stop the GPU 74 | mpu_hw->ctrl = 0; // Turn off memory protection - all models 75 | } 76 | 77 | /* 78 | * run a gpu dma job 79 | */ 80 | static void triggerGpuDma() 81 | { 82 | uint32_t srcVramAddr = __builtin_bswap16(*(uint16_t*)(tms9918->vram.bytes + 0x8000)); 83 | uint32_t dstVramAddr = __builtin_bswap16(*(uint16_t*)(tms9918->vram.bytes + 0x8002)); 84 | uint32_t width = tms9918->vram.bytes[0x8004]; 85 | uint32_t height = tms9918->vram.bytes[0x8005]; 86 | uint32_t stride = tms9918->vram.bytes[0x8006]; 87 | uint32_t params = tms9918->vram.bytes[0x8007]; 88 | *(uint16_t*)(tms9918->vram.bytes + 0x8008) = 0; 89 | 90 | int32_t dstInc = params & 0x02 ? -1 : 1; 91 | int32_t srcInc = params & 0x01 ? 0 : dstInc; 92 | 93 | uint8_t *srcPtr = tms9918->vram.bytes + srcVramAddr; 94 | uint8_t *dstPtr = tms9918->vram.bytes + dstVramAddr; 95 | for (int y = 0; y < height; ++y) 96 | { 97 | for (int x = 0; x < width; ++x, srcPtr += srcInc, dstPtr += dstInc) 98 | *dstPtr = *srcPtr; 99 | srcPtr += (stride - width) * srcInc; 100 | dstPtr += (stride - width) * dstInc; 101 | } 102 | } 103 | 104 | 105 | /* 106 | * set up the MPU to guard a 32 byte page from a given address 107 | */ 108 | static void guard(void* a) 109 | { 110 | uintptr_t addr = (uintptr_t)a; 111 | #if PICO_RP2040 // Old memory protection unit 112 | mpu_hw->rbar = (addr & (uint)~0xff) | M0PLUS_MPU_RBAR_VALID_BITS | 0; 113 | mpu_hw->rasr = 1 | (0x07 << 1) | (0xfe << 8) | 0x10000000; // 0xfe = initial 32 bytes only 114 | #else 115 | mpu_hw->rnr = 0; 116 | mpu_hw->rbar = (addr & (uint)~31u) | (2u << M33_MPU_RBAR_AP_LSB) | M33_MPU_RBAR_XN_BITS; 117 | mpu_hw->rlar = (addr & (uint)~31u) | M33_MPU_RLAR_EN_BITS; 118 | #endif 119 | } 120 | 121 | /* 122 | * TMS9900 GPU main loop implementation 123 | */ 124 | static void __attribute__ ((noinline)) volatileHack () 125 | { 126 | tms9918->restart = 0; 127 | if ((tms9918->gpuAddress & 1) == 0) // Odd addresses will cause the RP2040 to crash 128 | { 129 | uint16_t lastAddress = tms9918->gpuAddress; 130 | restart: 131 | TMS_REGISTER(tms9918, 0x38) = 1; 132 | TMS_STATUS(tms9918, 2) |= 0x80; // Running 133 | 134 | #if PICO_RP2040 // Old memory protection unit 135 | mpu_hw->ctrl = M0PLUS_MPU_CTRL_PRIVDEFENA_BITS | M0PLUS_MPU_CTRL_ENABLE_BITS; // (=5) Turn on memory protection 136 | #else 137 | mpu_hw->ctrl = M33_MPU_CTRL_PRIVDEFENA_BITS | M33_MPU_CTRL_ENABLE_BITS; // (=5) Turn on memory protection 138 | #endif 139 | 140 | lastAddress = run9900 (tms9918->vram.bytes, lastAddress, 0xFFFE, &TMS_REGISTER(tms9918, 0x38)); 141 | mpu_hw->ctrl = 0; // Turn off memory protection - all models 142 | 143 | if (TMS_REGISTER(tms9918, 0x38) & 1) // GPU program decided to stop itself? 144 | { 145 | tms9918->gpuAddress = lastAddress; 146 | tms9918->restart = 0; 147 | } 148 | if (tms9918->vram.bytes[0x8008]) 149 | { 150 | triggerGpuDma(); 151 | } 152 | if (didFault) 153 | { 154 | didFault = 0; 155 | goto restart; 156 | } 157 | } 158 | TMS_STATUS(tms9918, 2) &= ~0x80; // Stopped 159 | TMS_REGISTER(tms9918, 0x38) = 0; 160 | } 161 | 162 | /* 163 | * initialize the TMS9900 GPU 164 | */ 165 | void gpuInit() 166 | { 167 | /* copy pre-load code to GPU ram at >4000 ...and >4800 ?*/ 168 | memcpy (tms9918->vram.map.gram1, preload, sizeof (preload)); 169 | memcpy (tms9918->vram.map.gram1 + 0x800, preload, sizeof (preload)); 170 | 171 | tms9918->gpuAddress = 0x4000; 172 | 173 | guard(&(tms9918->vram.bytes [0x8000])); 174 | } 175 | 176 | bool reportedBack = 1; 177 | uint32_t gpuTimeUs = 0; 178 | 179 | /* GPU runtime in microseconds */ 180 | uint32_t gpuTime(uint32_t totalTime) 181 | { 182 | if (!reportedBack) 183 | return totalTime; 184 | return gpuTimeUs; 185 | } 186 | 187 | /* reset the internal gpu time to 0 */ 188 | void resetGpuTime() 189 | { 190 | gpuTimeUs = 0; 191 | } 192 | 193 | /* 194 | * TMS9900 GPU main loop 195 | */ 196 | void gpuLoop() 197 | { 198 | while (1) 199 | { 200 | if (tms9918->restart) 201 | { 202 | reportedBack = 0; 203 | uint32_t gpuStart = time_us_32(); 204 | volatileHack(); 205 | gpuTimeUs += time_us_32() - gpuStart; 206 | } 207 | reportedBack = 1; 208 | 209 | if (tms9918->flash) 210 | { 211 | flashSector(); 212 | } 213 | 214 | if (tms9918->config[CONF_SAVE_TO_FLASH]) 215 | { 216 | tms9918->config[CONF_SAVE_TO_FLASH] = 0; 217 | writeConfig(tms9918->config); 218 | } 219 | } 220 | } -------------------------------------------------------------------------------- /configtool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | # PICO9918 Configurator CMake Build 4 | # Converts the batch-based build to cross-platform CMake 5 | 6 | project(pico9918-configurator) 7 | 8 | # Include CVBasic build functions 9 | include(${CMAKE_SOURCE_DIR}/visrealm_cvbasic.cmake) 10 | 11 | set(PYTHON python3) 12 | 13 | # Version information (from root CMakeLists.txt) 14 | set(VERSION "v${PICO9918_VERSION_STR}") 15 | set(FRIENDLYVER "${PICO9918_VERSION}") 16 | 17 | # Setup CVBasic toolchain (auto-build or find existing tools) 18 | setup_cvbasic_tools() 19 | 20 | # Firmware dependency (checked at build time, not configure time) 21 | set(FIRMWARE_FILE "${CMAKE_BINARY_DIR}/dist/pico9918-vga-build-${VERSION}.uf2") 22 | if(NOT CONFIGURATOR_ONLY) 23 | set(FIRMWARE_TARGET "firmware") # Target that builds the firmware 24 | else() 25 | set(FIRMWARE_TARGET "") # No target dependency when building configurator only 26 | endif() 27 | 28 | # Custom function to convert UF2 to CVBasic data 29 | function(convert_uf2_to_cvbasic BANK_SIZE OUTPUT_PREFIX) 30 | set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/intermediate") 31 | add_custom_command( 32 | OUTPUT "${OUTPUT_DIR}/${OUTPUT_PREFIX}.h.bas" "${OUTPUT_DIR}/${OUTPUT_PREFIX}.bas" 33 | COMMAND ${PYTHON} "${CMAKE_SOURCE_DIR}/configtool/tools/uf2cvb.py" 34 | -b ${BANK_SIZE} 35 | -o "${OUTPUT_DIR}/${OUTPUT_PREFIX}" 36 | "${FIRMWARE_FILE}" 37 | DEPENDS "${CMAKE_SOURCE_DIR}/configtool/tools/uf2cvb.py" "${FIRMWARE_FILE}" 38 | COMMENT "Converting UF2 to CVBasic data (${BANK_SIZE}KB banks)" 39 | VERBATIM 40 | ) 41 | endfunction() 42 | 43 | # Generate firmware data files 44 | convert_uf2_to_cvbasic(8 "firmware_8k") 45 | convert_uf2_to_cvbasic(16 "firmware_16k") 46 | convert_uf2_to_cvbasic(0 "firmware") 47 | 48 | # Custom function to compile CVBasic for a platform 49 | function(add_cvbasic_target TARGET_NAME PLATFORM_FLAG DEFINES ASM_OUTPUT ROM_OUTPUT DESCRIPTION) 50 | set(SOURCE_DIR "${CMAKE_BINARY_DIR}/intermediate/${TARGET_NAME}/src") 51 | set(ASM_DIR "${CMAKE_BINARY_DIR}/intermediate/${TARGET_NAME}/asm") 52 | set(DIST_DIR "${CMAKE_BINARY_DIR}/dist") 53 | 54 | # Create directories 55 | file(MAKE_DIRECTORY "${SOURCE_DIR}") 56 | file(MAKE_DIRECTORY "${ASM_DIR}") 57 | file(MAKE_DIRECTORY "${DIST_DIR}") 58 | 59 | # Collect all CVBasic source files for dependency tracking 60 | # This ensures any change to .bas files or .asm libraries triggers a rebuild 61 | file(GLOB CVBASIC_SOURCE_FILES 62 | "${CMAKE_SOURCE_DIR}/configtool/src/*.bas" 63 | "${CMAKE_SOURCE_DIR}/configtool/src/lib/*.asm" 64 | ) 65 | 66 | # Collect tools that affect the build 67 | # Changes to these Python tools will trigger rebuilds 68 | file(GLOB CVBASIC_TOOLS 69 | "${CMAKE_SOURCE_DIR}/configtool/tools/*.py" 70 | ) 71 | 72 | # Copy source files and generated firmware data 73 | add_custom_command( 74 | OUTPUT "${SOURCE_DIR}/pico9918conf.bas" 75 | COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/configtool/src" "${SOURCE_DIR}" 76 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.h.bas" "${SOURCE_DIR}/" 77 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.bas" "${SOURCE_DIR}/" 78 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.h.bas" "${SOURCE_DIR}/" 79 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.bas" "${SOURCE_DIR}/" 80 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware.h.bas" "${SOURCE_DIR}/" 81 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/intermediate/firmware.bas" "${SOURCE_DIR}/" 82 | DEPENDS ${CVBASIC_SOURCE_FILES} ${CVBASIC_TOOLS} 83 | "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.h.bas" "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.bas" 84 | "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.h.bas" "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.bas" 85 | "${CMAKE_BINARY_DIR}/intermediate/firmware.h.bas" "${CMAKE_BINARY_DIR}/intermediate/firmware.bas" 86 | COMMENT "Copying CVBasic source files and firmware data" 87 | ) 88 | 89 | # Platform-specific dependencies 90 | set(FIRMWARE_DEPS) 91 | if("${PLATFORM_FLAG}" STREQUAL "--ti994a") 92 | list(APPEND FIRMWARE_DEPS "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.h.bas" "${CMAKE_BINARY_DIR}/intermediate/firmware_8k.bas") 93 | else() 94 | list(APPEND FIRMWARE_DEPS "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.h.bas" "${CMAKE_BINARY_DIR}/intermediate/firmware_16k.bas") 95 | endif() 96 | 97 | # CVBasic compilation 98 | set(CVBASIC_COMMAND ${CVBASIC_EXE} ${PLATFORM_FLAG}) 99 | if(DEFINES) 100 | list(APPEND CVBASIC_COMMAND ${DEFINES}) 101 | endif() 102 | list(APPEND CVBASIC_COMMAND "pico9918conf.bas" "${ASM_DIR}/${ASM_OUTPUT}" "${CMAKE_SOURCE_DIR}/configtool/src/lib") 103 | 104 | add_custom_command( 105 | OUTPUT "${ASM_DIR}/${ASM_OUTPUT}" 106 | COMMAND ${CVBASIC_COMMAND} 107 | DEPENDS "${SOURCE_DIR}/pico9918conf.bas" ${FIRMWARE_DEPS} ${TOOL_DEPENDENCIES} 108 | WORKING_DIRECTORY "${SOURCE_DIR}" 109 | COMMENT "Compiling CVBasic for ${DESCRIPTION}" 110 | VERBATIM 111 | ) 112 | 113 | # Assembly step (platform specific) 114 | if("${PLATFORM_FLAG}" STREQUAL "--ti994a") 115 | if(XAS99_SCRIPT) 116 | # XAS99 generates files based on the assembly filename, not the target name 117 | get_filename_component(ASM_NAME "${ASM_OUTPUT}" NAME_WE) 118 | add_custom_command( 119 | OUTPUT "${DIST_DIR}/${ROM_OUTPUT}" 120 | COMMAND ${PYTHON} "${XAS99_SCRIPT}" -b -R "${ASM_DIR}/${ASM_OUTPUT}" 121 | COMMAND ${PYTHON} "${LINKTICART_SCRIPT}" 122 | "${ASM_DIR}/${ASM_NAME}_b00.bin" 123 | "${DIST_DIR}/${ROM_OUTPUT}" 124 | "PICO9918 V${FRIENDLYVER}" 125 | DEPENDS "${ASM_DIR}/${ASM_OUTPUT}" ${TOOL_DEPENDENCIES} 126 | WORKING_DIRECTORY "${ASM_DIR}" 127 | COMMENT "Assembling ${DESCRIPTION}" 128 | VERBATIM 129 | ) 130 | else() 131 | # Create a dummy target when XAS99 is not available 132 | add_custom_command( 133 | OUTPUT "${DIST_DIR}/${ROM_OUTPUT}" 134 | COMMAND ${CMAKE_COMMAND} -E echo "XAS99 not available, skipping ${DESCRIPTION}" 135 | COMMAND ${CMAKE_COMMAND} -E touch "${DIST_DIR}/${ROM_OUTPUT}" 136 | DEPENDS "${ASM_DIR}/${ASM_OUTPUT}" 137 | COMMENT "Skipping ${DESCRIPTION} (XAS99 not found)" 138 | ) 139 | endif() 140 | else() 141 | add_custom_command( 142 | OUTPUT "${DIST_DIR}/${ROM_OUTPUT}" 143 | COMMAND ${GASM80_EXE} "${ASM_DIR}/${ASM_OUTPUT}" -o "${DIST_DIR}/${ROM_OUTPUT}" 144 | DEPENDS "${ASM_DIR}/${ASM_OUTPUT}" ${TOOL_DEPENDENCIES} 145 | WORKING_DIRECTORY "${ASM_DIR}" 146 | COMMENT "Assembling ${DESCRIPTION}" 147 | VERBATIM 148 | ) 149 | endif() 150 | 151 | # Add firmware target dependency if building full project 152 | if(FIRMWARE_TARGET) 153 | add_custom_target(${TARGET_NAME} DEPENDS "${DIST_DIR}/${ROM_OUTPUT}" ${FIRMWARE_TARGET}) 154 | else() 155 | add_custom_target(${TARGET_NAME} DEPENDS "${DIST_DIR}/${ROM_OUTPUT}") 156 | endif() 157 | endfunction() 158 | 159 | # Define all platform targets 160 | add_cvbasic_target(ti99 "--ti994a" "" "pico9918_${VERSION}_ti99.a99" "pico9918_${VERSION}_ti99_8.bin" "TI-99/4A") 161 | add_cvbasic_target(ti99_f18a "--ti994a" "-dF18A_TESTING=1" "pico9918_${VERSION}_ti99_f18a.a99" "pico9918_${VERSION}_ti99_f18a_8.bin" "TI-99/4A F18A Testing") 162 | add_cvbasic_target(coleco "" "" "pico9918_${VERSION}_cv.asm" "pico9918_${VERSION}_cv.rom" "ColecoVision") 163 | add_cvbasic_target(msx_asc16 "--msx" "" "pico9918_${VERSION}_msx_asc16.asm" "pico9918_${VERSION}_msx_asc16.rom" "MSX ASCII16") 164 | add_cvbasic_target(msx_konami "--msx" "-konami" "pico9918_${VERSION}_msx_konami.asm" "pico9918_${VERSION}_msx_konami.rom" "MSX Konami") 165 | add_cvbasic_target(nabu "--nabu" "" "pico9918_${VERSION}_nabu.asm" "pico9918_${VERSION}.nabu" "NABU") 166 | add_cvbasic_target(creativision "--creativision" "-rom16" "pico9918_${VERSION}_crv.asm" "pico9918_${VERSION}_crv.bin" "CreatiVision") 167 | add_cvbasic_target(sg1000 "--sg1000" "" "pico9918_${VERSION}_sg1000.asm" "pico9918_${VERSION}_sg1000.sg" "SG-1000/SC-3000") 168 | 169 | # Special NABU MAME target (different defines) 170 | add_cvbasic_target(nabu_mame "--nabu" "-DTMS9918_TESTING=1" "pico9918_${VERSION}_nabu_mame.asm" "000001.nabu" "NABU MAME") 171 | 172 | # NABU MAME packaging 173 | add_custom_command( 174 | OUTPUT "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.npz" 175 | COMMAND ${CMAKE_COMMAND} -E tar "cf" "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.zip" --format=zip "000001.nabu" 176 | COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.zip" "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.npz" 177 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.zip" "000001.nabu" 178 | DEPENDS "${CMAKE_BINARY_DIR}/dist/000001.nabu" 179 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/dist" 180 | COMMENT "Packaging NABU MAME" 181 | VERBATIM 182 | ) 183 | 184 | add_custom_target(nabu_mame_package DEPENDS "${CMAKE_BINARY_DIR}/dist/pico9918_${VERSION}_nabu_mame.npz") 185 | 186 | # Master target to build all platforms 187 | add_custom_target(configurator_all DEPENDS ti99 ti99_f18a coleco msx_asc16 msx_konami nabu creativision sg1000 nabu_mame_package) 188 | 189 | # Print build information 190 | message(STATUS "PICO9918 Configurator CMake Configuration") 191 | message(STATUS "Version: ${FRIENDLYVER}") 192 | message(STATUS "CVBasic: ${CVBASIC_EXE}") 193 | message(STATUS "GASM80: ${GASM80_EXE}") 194 | message(STATUS "linkticart.py: ${LINKTICART_SCRIPT}") 195 | if(XAS99_SCRIPT) 196 | message(STATUS "XAS99: ${XAS99_SCRIPT}") 197 | else() 198 | message(STATUS "XAS99: NOT FOUND (TI-99 builds will be limited)") 199 | endif() 200 | message(STATUS "Firmware: ${FIRMWARE_FILE}") 201 | --------------------------------------------------------------------------------