├── source ├── Atari Boot ROM │ ├── convert.bat │ ├── xxd.exe │ ├── keytable.bin │ └── A8PicoCart.asm └── Pico VSCode Project │ └── a8_pico_cart │ ├── .vscode │ ├── cmake-kits.json │ ├── extensions.json │ ├── c_cpp_properties.json │ ├── tasks.json │ ├── settings.json │ └── launch.json │ ├── myboard.h │ ├── atari_cart.h │ ├── flash_fs.h │ ├── fatfs_disk.h │ ├── fatfs │ ├── 00readme.txt │ ├── diskio.h │ ├── diskio.c │ ├── ffsystem.c │ ├── ffconf.h │ ├── 00history.txt │ └── ff.h │ ├── CMakeLists.txt │ ├── fatfs_disk.c │ ├── pico_sdk_import.cmake │ ├── main.c │ ├── tusb_config.h │ ├── msc_disk.c │ ├── memmap_custom.ld │ ├── flash_fs.c │ └── usb_descriptors.c ├── .gitattributes ├── images ├── 800xl.jpg ├── menu.jpg ├── pcbs.jpg ├── pcbs2.jpg ├── USB_mode.png ├── 3d_print_case_1.jpg ├── 3d_print_case_2.jpg ├── 3d_print_case_v2.jpg ├── 3d_print_case_v2_end.jpg └── githubbutton_pp.svg ├── A8PicoCart Manual.pdf ├── firmware └── a8_pico_cart.uf2 ├── pcbs ├── XE version (no case) │ ├── logo.png │ ├── A8PicoCart.kicad_prl │ └── A8PicoCart.kicad_pro ├── XL or XE version (fits case) │ ├── logo.png │ ├── A8PicoCart-gerbers.zip │ ├── README.md │ ├── A8PicoCart.kicad_prl │ └── A8PicoCart.kicad_pro └── Previous Versions │ └── XL or XE version (fits case) │ ├── logo.png │ ├── A8PicoCart-gerbers.zip │ ├── README.md │ ├── A8PicoCart.kicad_prl │ └── A8PicoCart.kicad_pro ├── cartridge shell 3d print files ├── a8pico_logo.png ├── a8pico_logo.stl ├── a8pico_back_v2.stl ├── a8pico_front.stl ├── Previous Versions │ ├── a8pico_back.stl │ └── a8pico_back.scad ├── a8pico_front.scad └── a8pico_back_v2.scad └── README.md /source/Atari Boot ROM/convert.bat: -------------------------------------------------------------------------------- 1 | xxd -i A8PicoCart.rom > rom.h 2 | notepad rom.h -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /images/800xl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/800xl.jpg -------------------------------------------------------------------------------- /images/menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/menu.jpg -------------------------------------------------------------------------------- /images/pcbs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/pcbs.jpg -------------------------------------------------------------------------------- /images/pcbs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/pcbs2.jpg -------------------------------------------------------------------------------- /images/USB_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/USB_mode.png -------------------------------------------------------------------------------- /A8PicoCart Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/A8PicoCart Manual.pdf -------------------------------------------------------------------------------- /firmware/a8_pico_cart.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/firmware/a8_pico_cart.uf2 -------------------------------------------------------------------------------- /images/3d_print_case_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/3d_print_case_1.jpg -------------------------------------------------------------------------------- /images/3d_print_case_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/3d_print_case_2.jpg -------------------------------------------------------------------------------- /images/3d_print_case_v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/3d_print_case_v2.jpg -------------------------------------------------------------------------------- /source/Atari Boot ROM/xxd.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/source/Atari Boot ROM/xxd.exe -------------------------------------------------------------------------------- /images/3d_print_case_v2_end.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/images/3d_print_case_v2_end.jpg -------------------------------------------------------------------------------- /pcbs/XE version (no case)/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/pcbs/XE version (no case)/logo.png -------------------------------------------------------------------------------- /source/Atari Boot ROM/keytable.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/source/Atari Boot ROM/keytable.bin -------------------------------------------------------------------------------- /pcbs/XL or XE version (fits case)/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/pcbs/XL or XE version (fits case)/logo.png -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/cartridge shell 3d print files/a8pico_logo.png -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_logo.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/cartridge shell 3d print files/a8pico_logo.stl -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_back_v2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/cartridge shell 3d print files/a8pico_back_v2.stl -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_front.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/cartridge shell 3d print files/a8pico_front.stl -------------------------------------------------------------------------------- /pcbs/XL or XE version (fits case)/A8PicoCart-gerbers.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/pcbs/XL or XE version (fits case)/A8PicoCart-gerbers.zip -------------------------------------------------------------------------------- /pcbs/Previous Versions/XL or XE version (fits case)/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/pcbs/Previous Versions/XL or XE version (fits case)/logo.png -------------------------------------------------------------------------------- /cartridge shell 3d print files/Previous Versions/a8pico_back.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/cartridge shell 3d print files/Previous Versions/a8pico_back.stl -------------------------------------------------------------------------------- /pcbs/Previous Versions/XL or XE version (fits case)/A8PicoCart-gerbers.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinhedwards/A8PicoCart/HEAD/pcbs/Previous Versions/XL or XE version (fits case)/A8PicoCart-gerbers.zip -------------------------------------------------------------------------------- /pcbs/Previous Versions/XL or XE version (fits case)/README.md: -------------------------------------------------------------------------------- 1 | ## XL/XE version PCB (which fits the 3d printed case design) 2 | 3 | * KiCad Project 4 | * A zip file of the gerbers (Elecrow specification). 5 | 6 | 7 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/cmake-kits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Pico ARM GCC", 4 | "description": "Pico SDK Toolchain with GCC arm-none-eabi", 5 | "toolchainFile": "${env:PICO_SDK_PATH}/cmake/preload/toolchains/pico_arm_gcc.cmake" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "marus25.cortex-debug", 4 | "ms-vscode.cmake-tools", 5 | "ms-vscode.cpptools", 6 | "ms-vscode.cpptools-extension-pack", 7 | "ms-vscode.vscode-serial-monitor" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/myboard.h: -------------------------------------------------------------------------------- 1 | // myboard.h 2 | 3 | // setting first overrides the value in the default header 4 | 5 | //#define PICO_FLASH_SPI_CLKDIV 2 // use for winbond flash 6 | #define PICO_FLASH_SPI_CLKDIV 4 // use for slower flash (e.g. zbit) 7 | 8 | // pick up the rest of the settings 9 | #include "boards/pico.h" 10 | -------------------------------------------------------------------------------- /pcbs/XL or XE version (fits case)/README.md: -------------------------------------------------------------------------------- 1 | ## XL/XE version PCB (which fits the 3d printed case design) 2 | 3 | * KiCad Project 4 | * A zip file of the gerbers (Elecrow specification). 5 | * Updated version for pico pointing downwards towards cartridge slot 6 | 7 | PCB specifications: 8 | 1.6mm thickness 9 | Hard gold fingers with bevelled edge will make your PCB last longer, but at a much higher cost. 10 | 11 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Pico", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:PICO_SDK_PATH}/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "${env:PICO_INSTALL_PATH}/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe", 11 | "cStandard": "c11", 12 | "cppStandard": "c++11", 13 | "intelliSenseMode": "linux-gcc-arm", 14 | "configurationProvider": "ms-vscode.cmake-tools" 15 | } 16 | ], 17 | "version": 4 18 | } 19 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/atari_cart.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #ifndef __ATARI_CART_H__ 16 | #define __ATARI_CART_H__ 17 | 18 | #define ATARI_PHI2_PIN 22 // used on boot to check if we are plugged into an atari or usb 19 | 20 | void atari_cart_main(); 21 | 22 | #endif -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Flash", 6 | "type": "shell", 7 | "command": "openocd", 8 | "args": [ 9 | "-f", 10 | "interface/cmsis-dap.cfg", 11 | "-f", 12 | "target/rp2040.cfg", 13 | "-c", 14 | "adapter speed 1000; program {${command:cmake.launchTargetPath}} verify reset exit" 15 | ], 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "Build", 20 | "type": "cmake", 21 | "command": "build", 22 | "problemMatcher": "$gcc", 23 | "group": { 24 | "kind": "build", 25 | "isDefault": true 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/flash_fs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #ifndef __FLASH_FS_H__ 16 | #define __FLASH_FS_H__ 17 | 18 | #include 19 | 20 | int flash_fs_mount(); 21 | void flash_fs_create(); 22 | void flash_fs_sync(); 23 | void flash_fs_read_FAT_sector(uint16_t fat_sector, void *buffer); 24 | void flash_fs_write_FAT_sector(uint16_t fat_sector, const void *buffer); 25 | bool flash_fs_verify_FAT_sector(uint16_t fat_sector, const void *buffer); 26 | 27 | #endif -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs_disk.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #ifndef __FATFS_DISK_H__ 16 | #define __FATFS_DISK_H__ 17 | 18 | #include "flash_fs.h" 19 | 20 | #define SECTOR_NUM 30716 //2044 //1800 21 | #define SECTOR_SIZE 512 22 | 23 | void create_fatfs_disk(); 24 | bool mount_fatfs_disk(); 25 | bool fatfs_is_mounted(); 26 | uint32_t fatfs_disk_read(uint8_t* buff, uint32_t sector, uint32_t count); 27 | uint32_t fatfs_disk_write(const uint8_t* buff, uint32_t sector, uint32_t count); 28 | void fatfs_disk_sync(); 29 | 30 | #endif -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/00readme.txt: -------------------------------------------------------------------------------- 1 | FatFs Module Source Files R0.15 2 | 3 | 4 | FILES 5 | 6 | 00readme.txt This file. 7 | 00history.txt Revision history. 8 | ff.c FatFs module. 9 | ffconf.h Configuration file of FatFs module. 10 | ff.h Common include file for FatFs and application module. 11 | diskio.h Common include file for FatFs and disk I/O module. 12 | diskio.c An example of glue function to attach existing disk I/O module to FatFs. 13 | ffunicode.c Optional Unicode utility functions. 14 | ffsystem.c An example of optional O/S related functions. 15 | 16 | 17 | Low level disk I/O module is not included in this archive because the FatFs 18 | module is only a generic file system layer and it does not depend on any specific 19 | storage device. You need to provide a low level disk I/O module written to 20 | control the storage device that attached to the target system. 21 | 22 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // These settings tweaks to the cmake plugin will ensure 3 | // that you debug using cortex-debug instead of trying to launch 4 | // a Pico binary on the host 5 | "cmake.statusbar.advanced": { 6 | "debug": { 7 | "visibility": "hidden" 8 | }, 9 | "launch": { 10 | "visibility": "hidden" 11 | }, 12 | "build": { 13 | "visibility": "hidden" 14 | }, 15 | "buildTarget": { 16 | "visibility": "hidden" 17 | } 18 | }, 19 | "cmake.buildBeforeRun": true, 20 | "cmake.configureOnOpen": true, 21 | "cmake.configureSettings": { 22 | "CMAKE_MODULE_PATH": "${env:PICO_INSTALL_PATH}/pico-sdk-tools" 23 | }, 24 | "cmake.generator": "Ninja", 25 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 26 | "files.associations": { 27 | "stdlib.h": "c", 28 | "lfs_flash_handler.h": "c", 29 | "128k_rom.h": "c", 30 | "sync.h": "c", 31 | "string.h": "c", 32 | "reent.h": "c", 33 | "features.h": "c", 34 | "flash_fs.h": "c", 35 | "stdio.h": "c" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pcbs/XE version (no case)/A8PicoCart.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "fffffff_ffffffff", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "A8PicoCart.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pcbs/XL or XE version (fits case)/A8PicoCart.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "Front Layers", 5 | "auto_track_width": true, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "00290aa_00000001", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "A8PicoCart.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pcbs/Previous Versions/XL or XE version (fits case)/A8PicoCart.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "fffffff_ffffffff", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "A8PicoCart.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | # initialize the SDK based on PICO_SDK_PATH 4 | # note: this must happen before project() 5 | include(pico_sdk_import.cmake) 6 | 7 | project(a8_pico_cart) 8 | 9 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}) 10 | set(PICO_BOARD myboard) 11 | 12 | # initialize the Raspberry Pi Pico SDK 13 | pico_sdk_init() 14 | 15 | add_executable(a8_pico_cart) 16 | 17 | # 16megs of flash on purple pico clones 18 | target_compile_definitions(a8_pico_cart PRIVATE 19 | PICO_FLASH_SIZE_BYTES=16777216 20 | ) 21 | 22 | #pico_set_linker_script(a8_pico_cart ${CMAKE_CURRENT_SOURCE_DIR}/memmap_custom.ld) 23 | 24 | target_sources(a8_pico_cart PUBLIC 25 | ${CMAKE_CURRENT_LIST_DIR}/main.c 26 | ${CMAKE_CURRENT_LIST_DIR}/atari_cart.c 27 | ${CMAKE_CURRENT_LIST_DIR}/msc_disk.c 28 | ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c 29 | ${CMAKE_CURRENT_LIST_DIR}/fatfs_disk.c 30 | ${CMAKE_CURRENT_LIST_DIR}/flash_fs.c 31 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/ff.c 32 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/ffunicode.c 33 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/diskio.c 34 | ) 35 | 36 | target_include_directories(a8_pico_cart PUBLIC 37 | ${CMAKE_CURRENT_LIST_DIR} 38 | ${CMAKE_CURRENT_LIST_DIR}/fatfs 39 | ) 40 | 41 | # In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device 42 | # for TinyUSB device support 43 | target_link_libraries(a8_pico_cart PUBLIC pico_stdlib hardware_flash tinyusb_device) 44 | 45 | # create map/bin/hex/uf2 file in addition to ELF. 46 | pico_add_extra_outputs(a8_pico_cart) -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug (Cortex-Debug)", 6 | "cwd": "${workspaceFolder}", 7 | "executable": "${command:cmake.launchTargetPath}", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "openocd", 11 | "gdbPath": "arm-none-eabi-gdb", 12 | "device": "RP2040", 13 | "configFiles": [ 14 | "interface/cmsis-dap.cfg", 15 | "target/rp2040.cfg" 16 | ], 17 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 18 | "runToEntryPoint": "main", 19 | "openOCDLaunchCommands": [ 20 | "adapter speed 1000" 21 | ] 22 | }, 23 | { 24 | "name": "Pico Debug (Cortex-Debug with external OpenOCD)", 25 | "cwd": "${workspaceFolder}", 26 | "executable": "${command:cmake.launchTargetPath}", 27 | "request": "launch", 28 | "type": "cortex-debug", 29 | "servertype": "external", 30 | "gdbTarget": "localhost:3333", 31 | "gdbPath": "arm-none-eabi-gdb", 32 | "device": "RP2040", 33 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 34 | "runToEntryPoint": "main" 35 | }, 36 | { 37 | "name": "Pico Debug (C++ Debugger)", 38 | "type": "cppdbg", 39 | "request": "launch", 40 | "cwd": "${workspaceFolder}", 41 | "program": "${command:cmake.launchTargetPath}", 42 | "MIMode": "gdb", 43 | "miDebuggerPath": "arm-none-eabi-gdb", 44 | "miDebuggerServerAddress": "localhost:3333", 45 | "debugServerPath": "openocd", 46 | "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 1000\"", 47 | "serverStarted": "Listening on port .* for gdb connections", 48 | "filterStderr": true, 49 | "stopAtEntry": true, 50 | "hardwareBreakpoints": { 51 | "require": true, 52 | "limit": 4 53 | }, 54 | "preLaunchTask": "Flash", 55 | "svdPath": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/diskio.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------/ 2 | / Low level disk interface modlue include file (C)ChaN, 2019 / 3 | /-----------------------------------------------------------------------*/ 4 | 5 | #ifndef _DISKIO_DEFINED 6 | #define _DISKIO_DEFINED 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* Status of Disk Functions */ 13 | typedef BYTE DSTATUS; 14 | 15 | /* Results of Disk Functions */ 16 | typedef enum { 17 | RES_OK = 0, /* 0: Successful */ 18 | RES_ERROR, /* 1: R/W Error */ 19 | RES_WRPRT, /* 2: Write Protected */ 20 | RES_NOTRDY, /* 3: Not Ready */ 21 | RES_PARERR /* 4: Invalid Parameter */ 22 | } DRESULT; 23 | 24 | 25 | /*---------------------------------------*/ 26 | /* Prototypes for disk control functions */ 27 | 28 | 29 | DSTATUS disk_initialize (BYTE pdrv); 30 | DSTATUS disk_status (BYTE pdrv); 31 | DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); 32 | DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); 33 | DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); 34 | 35 | 36 | /* Disk Status Bits (DSTATUS) */ 37 | 38 | #define STA_NOINIT 0x01 /* Drive not initialized */ 39 | #define STA_NODISK 0x02 /* No medium in the drive */ 40 | #define STA_PROTECT 0x04 /* Write protected */ 41 | 42 | 43 | /* Command code for disk_ioctrl fucntion */ 44 | 45 | /* Generic command (Used by FatFs) */ 46 | #define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ 47 | #define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ 48 | #define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ 49 | #define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ 50 | #define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ 51 | 52 | /* Generic command (Not used by FatFs) */ 53 | #define CTRL_POWER 5 /* Get/Set power status */ 54 | #define CTRL_LOCK 6 /* Lock/Unlock media removal */ 55 | #define CTRL_EJECT 7 /* Eject media */ 56 | #define CTRL_FORMAT 8 /* Create physical format on the media */ 57 | 58 | /* MMC/SDC specific ioctl command */ 59 | #define MMC_GET_TYPE 10 /* Get card type */ 60 | #define MMC_GET_CSD 11 /* Get CSD */ 61 | #define MMC_GET_CID 12 /* Get CID */ 62 | #define MMC_GET_OCR 13 /* Get OCR */ 63 | #define MMC_GET_SDSTAT 14 /* Get SD status */ 64 | #define ISDIO_READ 55 /* Read data form SD iSDIO register */ 65 | #define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ 66 | #define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ 67 | 68 | /* ATA/CF specific ioctl command */ 69 | #define ATA_GET_REV 20 /* Get F/W revision */ 70 | #define ATA_GET_MODEL 21 /* Get model name */ 71 | #define ATA_GET_SN 22 /* Get serial number */ 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_front.scad: -------------------------------------------------------------------------------- 1 | /* A8PicoCart shell 2 | (c) R.Edwards 2023 3 | */ 4 | 5 | ridge_width = 0.5; 6 | cart_length=80; 7 | cart_width=65 - (ridge_width*2); 8 | cart_height=9.6; 9 | screw_sep = 44; 10 | front_thickness = 1.6; 11 | sides_thickness = 1.6 - 0.05; 12 | support_thickness = 1.2 - 0.05; 13 | support_height = 7.6; 14 | rim_height = 1; 15 | logo_width = 14.5; 16 | logo_height = 58; 17 | 18 | difference() { 19 | union() { 20 | difference() { 21 | // main shell 22 | cube([cart_length, cart_width, cart_height]); 23 | // rims 24 | translate([0, sides_thickness/2, cart_height-rim_height]) 25 | cube([cart_length-sides_thickness/2, 26 | cart_width-sides_thickness, rim_height]); 27 | // make hollow 28 | translate([0, sides_thickness, front_thickness]) 29 | cube([cart_length-sides_thickness, 30 | cart_width-sides_thickness*2, 31 | cart_height-front_thickness]); 32 | // cutout cart port 33 | translate([0, (cart_width-57)/2, 0]) 34 | cube([10, 57, cart_height]); 35 | // button 36 | translate([cart_length-15, 9, 0]) 37 | cylinder(h = front_thickness, r1 = 2.5, r2 = 2.5, $fs = 1); 38 | } 39 | // pcb supports 40 | difference() { 41 | translate([17, 0, 0]) 42 | cube([support_thickness, cart_width, cart_height-rim_height]); 43 | translate([17, (cart_width-55)/2, support_height]) 44 | cube([support_thickness, 55, cart_height-support_height]); 45 | } 46 | translate([60, 0, 0]) 47 | cube([support_thickness, 7, support_height]); 48 | translate([60, cart_width-7, 0]) 49 | cube([support_thickness, 7, support_height]); 50 | // screw holes 51 | translate([31, (cart_width-screw_sep)/2, 0]) 52 | difference() { 53 | cylinder(h = support_height + 2.5, r1 = 3.2, r2 = 2.8, $fs = 1); 54 | cylinder(h = support_height + 2.5, r1 = 1.25, r2 = 1.25, $fs = 1); 55 | } 56 | translate([31, cart_width-(cart_width-screw_sep)/2, 0]) 57 | difference() { 58 | cylinder(h = support_height + 2.5, r1 = 3.2, r2 = 2.8, $fs = 1); 59 | cylinder(h = support_height + 2.5, r1 = 1.25, r2 = 1.25, $fs = 1); 60 | } 61 | // ridges 62 | for (i = [15: 5: cart_length-5]) 63 | union() { 64 | translate([i, -ridge_width, 0]) 65 | cube([3, ridge_width, cart_height]); 66 | translate([i, cart_width, 0]) 67 | cube([3, ridge_width, cart_height]); 68 | } 69 | } 70 | // logo space 71 | translate([cart_length-logo_height-2, cart_width-logo_width-3.5, 0]) 72 | cube([logo_height, logo_width, 1]); 73 | } -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs_disk.c: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #include "ff.h" 16 | #include "diskio.h" 17 | #include "fatfs_disk.h" 18 | 19 | #include 20 | #include "pico/stdlib.h" 21 | #include "hardware/flash.h" 22 | 23 | bool flashfs_is_mounted = false; 24 | 25 | bool mount_fatfs_disk() 26 | { 27 | int err = flash_fs_mount(); 28 | if (err) 29 | return false; 30 | 31 | flashfs_is_mounted = true; 32 | return true; 33 | } 34 | 35 | bool fatfs_is_mounted() { return flashfs_is_mounted; } 36 | 37 | void create_fatfs_disk() 38 | { 39 | flash_fs_create(); 40 | flashfs_is_mounted = true; 41 | 42 | // now create a fatfs on the flash_fs filesystem :-) 43 | 44 | FATFS fs; /* Filesystem object */ 45 | FIL fil; /* File object */ 46 | FRESULT res; /* API result code */ 47 | BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */ 48 | 49 | /* Format the default drive with default parameters */ 50 | printf("making fatfs\n"); 51 | res = f_mkfs("", 0, work, sizeof work); 52 | f_mount(&fs, "", 0); 53 | f_setlabel("A8-PICOCART"); 54 | res = f_open(&fil, "WELCOME.TXT", FA_CREATE_NEW | FA_WRITE); 55 | f_puts("Atari 8-bit PicoCart\r\n(c)2023 Electrotrains\r\nDrag ROM,CAR & XEX files in here!\r\n", &fil); 56 | f_close(&fil); 57 | f_mount(0, "", 0); 58 | } 59 | 60 | uint32_t fatfs_disk_read(uint8_t* buff, uint32_t sector, uint32_t count) 61 | { 62 | // printf("fatfs_disk_read sector=%d, count=%d\n", sector, count); 63 | if (!flashfs_is_mounted) return RES_ERROR; 64 | if (sector < 0 || sector >= SECTOR_NUM) 65 | return RES_PARERR; 66 | 67 | /* copy data to buffer */ 68 | for (int i=0; i= SECTOR_NUM) 78 | return RES_PARERR; 79 | 80 | /* copy data to buffer */ 81 | for (int i=0; i/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 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | 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") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /cartridge shell 3d print files/Previous Versions/a8pico_back.scad: -------------------------------------------------------------------------------- 1 | /* A8PicoCart shell 2 | (c) R.Edwards 2023 3 | */ 4 | 5 | ridge_width = 0.5; 6 | cart_length=80; 7 | cart_width=65 - (ridge_width*2); 8 | cart_height=11.5; 9 | screw_sep = 44; 10 | front_thickness = 1.6; 11 | sides_thickness = 1.6 - 0.05; 12 | support_thickness = 1.2 - 0.05; 13 | support_height = 10.5; 14 | rim_height=1; 15 | 16 | difference() { 17 | union() { 18 | difference() { 19 | // main shell 20 | union() { 21 | cube([cart_length, cart_width, cart_height-rim_height]); 22 | // rim 23 | translate([0, sides_thickness/2, cart_height-rim_height]) 24 | cube([cart_length-sides_thickness/2, sides_thickness/2, rim_height]); 25 | translate([0, cart_width-sides_thickness, cart_height-rim_height]) 26 | cube([cart_length-sides_thickness/2, sides_thickness/2, rim_height]); 27 | translate([cart_length-sides_thickness, sides_thickness/2, cart_height-rim_height]) 28 | cube([sides_thickness/2, cart_width-sides_thickness, rim_height]); 29 | } 30 | // make hollow 31 | translate([0, sides_thickness, front_thickness]) 32 | cube([cart_length-sides_thickness, 33 | cart_width-sides_thickness*2, 34 | cart_height-front_thickness]); 35 | } 36 | // end 37 | cube([sides_thickness, cart_width, 4]); 38 | // pcb supports 39 | difference() { 40 | translate([17, sides_thickness, 0]) 41 | cube([support_thickness, cart_width-sides_thickness*2, cart_height]); 42 | translate([17, (cart_width-55)/2, support_height]) 43 | cube([support_thickness, 55, cart_height-support_height]); 44 | } 45 | translate([60, 0, 0]) 46 | cube([support_thickness, 7, support_height]); 47 | translate([60, cart_width-7, 0]) 48 | cube([support_thickness, 7, support_height]); 49 | // screw holes 50 | translate([31, (cart_width-screw_sep)/2, 0]) 51 | difference() { 52 | cylinder(h = support_height - 1.2, r1 = 4, r2 = 2.8, $fs = 1); 53 | cylinder(h = support_height - 1.2, r1 = 1.6, r2 = 1.6, $fs = 1); 54 | } 55 | translate([31, cart_width-(cart_width-screw_sep)/2, 0]) 56 | difference() { 57 | cylinder(h = support_height - 1.2, r1 = 4, r2 = 2.8, $fs = 1); 58 | cylinder(h = support_height - 1.2, r1 = 1.6, r2 = 1.6, $fs = 1); 59 | } 60 | // ridges 61 | for (i = [15: 5: cart_length-5]) 62 | union() { 63 | translate([i, -ridge_width, 0]) 64 | cube([3, ridge_width, cart_height-rim_height]); 65 | translate([i, cart_width, 0]) 66 | cube([3, ridge_width, cart_height-rim_height]); 67 | } 68 | } 69 | // screw head 70 | translate([31, (cart_width-screw_sep)/2, 0]) 71 | cylinder(h = 0.6, r1 = 3.5, r2 = 3.5, $fs = 1); 72 | translate([31, (cart_width-screw_sep)/2, 0.6]) 73 | cylinder(h = front_thickness-0.6, r1 = 3.5, r2 = 1.8, $fs = 1); 74 | 75 | translate([31, cart_width-(cart_width-screw_sep)/2, 0]) 76 | cylinder(h = 0.6, r1 = 3.5, r2 = 3.5, $fs = 1); 77 | translate([31, cart_width-(cart_width-screw_sep)/2, 0.6]) 78 | cylinder(h = front_thickness-0.6, r1 = 3.5, r2 = 1.8, $fs = 1); 79 | // cutout USB slot 80 | translate([40, -ridge_width, 0]) 81 | cube([16, 5, cart_height]); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /cartridge shell 3d print files/a8pico_back_v2.scad: -------------------------------------------------------------------------------- 1 | /* A8PicoCart shell 2 | (c) R.Edwards 2023 3 | */ 4 | 5 | ridge_width = 0.5; 6 | cart_length=80; 7 | cart_width=65 - (ridge_width*2); 8 | cart_height=11.5; 9 | screw_sep = 44; 10 | front_thickness = 1.6; 11 | sides_thickness = 1.6 - 0.05; 12 | support_thickness = 1.2 - 0.05; 13 | support_height = 10.5; 14 | rim_height=1; 15 | 16 | difference() { 17 | union() { 18 | difference() { 19 | // main shell 20 | union() { 21 | cube([cart_length, cart_width, cart_height-rim_height]); 22 | // rim 23 | translate([0, sides_thickness/2, cart_height-rim_height]) 24 | cube([cart_length-sides_thickness/2, sides_thickness/2, rim_height]); 25 | translate([0, cart_width-sides_thickness, cart_height-rim_height]) 26 | cube([cart_length-sides_thickness/2, sides_thickness/2, rim_height]); 27 | translate([cart_length-sides_thickness, sides_thickness/2, cart_height-rim_height]) 28 | cube([sides_thickness/2, cart_width-sides_thickness, rim_height]); 29 | } 30 | // make hollow 31 | translate([0, sides_thickness, front_thickness]) 32 | cube([cart_length-sides_thickness, 33 | cart_width-sides_thickness*2, 34 | cart_height-front_thickness]); 35 | } 36 | // end 37 | cube([sides_thickness, cart_width, 4]); 38 | // pcb supports 39 | difference() { 40 | translate([17, sides_thickness, 0]) 41 | cube([support_thickness, cart_width-sides_thickness*2, cart_height]); 42 | translate([17, (cart_width-55)/2, support_height]) 43 | cube([support_thickness, 55, cart_height-support_height]); 44 | } 45 | translate([60, 0, 0]) 46 | cube([support_thickness, 7, support_height]); 47 | translate([60, cart_width-7, 0]) 48 | cube([support_thickness, 7, support_height]); 49 | // screw holes 50 | translate([31, (cart_width-screw_sep)/2, 0]) 51 | difference() { 52 | cylinder(h = support_height - 1.2, r1 = 4, r2 = 2.8, $fs = 1); 53 | cylinder(h = support_height - 1.2, r1 = 1.6, r2 = 1.6, $fs = 1); 54 | } 55 | translate([31, cart_width-(cart_width-screw_sep)/2, 0]) 56 | difference() { 57 | cylinder(h = support_height - 1.2, r1 = 4, r2 = 2.8, $fs = 1); 58 | cylinder(h = support_height - 1.2, r1 = 1.6, r2 = 1.6, $fs = 1); 59 | } 60 | // ridges 61 | for (i = [15: 5: cart_length-5]) 62 | union() { 63 | translate([i, -ridge_width, 0]) 64 | cube([3, ridge_width, cart_height-rim_height]); 65 | translate([i, cart_width, 0]) 66 | cube([3, ridge_width, cart_height-rim_height]); 67 | } 68 | } 69 | // screw head 70 | translate([31, (cart_width-screw_sep)/2, 0]) 71 | cylinder(h = 0.6, r1 = 3.5, r2 = 3.5, $fs = 1); 72 | translate([31, (cart_width-screw_sep)/2, 0.6]) 73 | cylinder(h = front_thickness-0.6, r1 = 3.5, r2 = 1.8, $fs = 1); 74 | 75 | translate([31, cart_width-(cart_width-screw_sep)/2, 0]) 76 | cylinder(h = 0.6, r1 = 3.5, r2 = 3.5, $fs = 1); 77 | translate([31, cart_width-(cart_width-screw_sep)/2, 0.6]) 78 | cylinder(h = front_thickness-0.6, r1 = 3.5, r2 = 1.8, $fs = 1); 79 | // cutout USB slot 80 | translate([0, (cart_width-16)/2+0.5, front_thickness]) // pico is 0.5 mm off center 81 | cube([40, 16, cart_height]); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico (16MB clone with all GPIO) 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | * 14 | * Edit myboard.h depending on the type of flash memory on the pico clone 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "pico/stdlib.h" 22 | #include "pico/time.h" 23 | #include "tusb.h" 24 | 25 | #include "atari_cart.h" 26 | #include "fatfs_disk.h" 27 | 28 | void cdc_task(void); 29 | 30 | int main(void) 31 | { 32 | // check to see if we are plugged into Atari 8-bit 33 | // by checking for high on PHI2 gpio for 100ms 34 | gpio_init(ATARI_PHI2_PIN); 35 | gpio_set_dir(ATARI_PHI2_PIN, GPIO_IN); 36 | while (to_ms_since_boot(get_absolute_time()) < 100) 37 | { 38 | if (gpio_get(ATARI_PHI2_PIN)) 39 | atari_cart_main(); 40 | } 41 | 42 | // we are presumably powered from USB 43 | // enter USB mass storage mode 44 | 45 | stdio_init_all(); // for serial output, via printf() 46 | printf("Start up\n"); 47 | 48 | // init device stack on configured roothub port 49 | tud_init(BOARD_TUD_RHPORT); 50 | 51 | while (1) 52 | { 53 | tud_task(); // tinyusb device task 54 | 55 | cdc_task(); 56 | } 57 | 58 | return 0; 59 | } 60 | 61 | //--------------------------------------------------------------------+ 62 | // Device callbacks 63 | //--------------------------------------------------------------------+ 64 | 65 | // Invoked when device is mounted 66 | void tud_mount_cb(void) 67 | { 68 | printf("Device mounted\n"); 69 | if (!mount_fatfs_disk()) 70 | create_fatfs_disk(); 71 | } 72 | 73 | // Invoked when device is unmounted 74 | void tud_umount_cb(void) 75 | { 76 | printf("Device unmounted\n"); 77 | } 78 | 79 | // Invoked when usb bus is suspended 80 | // remote_wakeup_en : if host allow us to perform remote wakeup 81 | // Within 7ms, device must draw an average of current less than 2.5 mA from bus 82 | void tud_suspend_cb(bool remote_wakeup_en) 83 | { 84 | (void) remote_wakeup_en; 85 | // blink_interval_ms = BLINK_SUSPENDED; 86 | } 87 | 88 | // Invoked when usb bus is resumed 89 | void tud_resume_cb(void) 90 | { 91 | // blink_interval_ms = BLINK_MOUNTED; 92 | } 93 | 94 | 95 | //--------------------------------------------------------------------+ 96 | // USB CDC 97 | //--------------------------------------------------------------------+ 98 | void cdc_task(void) 99 | { 100 | // connected() check for DTR bit 101 | // Most but not all terminal client set this when making connection 102 | // if ( tud_cdc_connected() ) 103 | { 104 | // connected and there are data available 105 | if ( tud_cdc_available() ) 106 | { 107 | // read data 108 | char buf[64]; 109 | uint32_t count = tud_cdc_read(buf, sizeof(buf)); 110 | (void) count; 111 | 112 | // Echo back 113 | // Note: Skip echo by commenting out write() and write_flush() 114 | // for throughput test e.g 115 | // $ dd if=/dev/zero of=/dev/ttyACM0 count=10000 116 | tud_cdc_write(buf, count); 117 | tud_cdc_write_flush(); 118 | } 119 | } 120 | } 121 | 122 | // Invoked when cdc when line state changed e.g connected/disconnected 123 | void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) 124 | { 125 | (void) itf; 126 | (void) rts; 127 | 128 | // TODO set some indicator 129 | if ( dtr ) 130 | { 131 | // Terminal connected 132 | }else 133 | { 134 | // Terminal disconnected 135 | } 136 | } 137 | 138 | // Invoked when CDC interface received data from host 139 | void tud_cdc_rx_cb(uint8_t itf) 140 | { 141 | (void) itf; 142 | } 143 | 144 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //--------------------------------------------------------------------+ 34 | // Board Specific Configuration 35 | //--------------------------------------------------------------------+ 36 | 37 | // RHPort number used for device can be defined by board.mk, default to port 0 38 | #ifndef BOARD_TUD_RHPORT 39 | #define BOARD_TUD_RHPORT 0 40 | #endif 41 | 42 | // RHPort max operational speed can defined by board.mk 43 | #ifndef BOARD_TUD_MAX_SPEED 44 | #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED 45 | #endif 46 | 47 | //-------------------------------------------------------------------- 48 | // Common Configuration 49 | //-------------------------------------------------------------------- 50 | 51 | // defined by compiler flags for flexibility 52 | #ifndef CFG_TUSB_MCU 53 | #error CFG_TUSB_MCU must be defined 54 | #endif 55 | 56 | #ifndef CFG_TUSB_OS 57 | #define CFG_TUSB_OS OPT_OS_NONE 58 | #endif 59 | 60 | #ifndef CFG_TUSB_DEBUG 61 | #define CFG_TUSB_DEBUG 0 62 | #endif 63 | 64 | // Enable Device stack 65 | #define CFG_TUD_ENABLED 1 66 | 67 | // Default is max speed that hardware controller could support with on-chip PHY 68 | #define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED 69 | 70 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 71 | * Tinyusb use follows macros to declare transferring memory so that they can be put 72 | * into those specific section. 73 | * e.g 74 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 75 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 76 | */ 77 | #ifndef CFG_TUSB_MEM_SECTION 78 | #define CFG_TUSB_MEM_SECTION 79 | #endif 80 | 81 | #ifndef CFG_TUSB_MEM_ALIGN 82 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 83 | #endif 84 | 85 | //-------------------------------------------------------------------- 86 | // DEVICE CONFIGURATION 87 | //-------------------------------------------------------------------- 88 | 89 | #ifndef CFG_TUD_ENDPOINT0_SIZE 90 | #define CFG_TUD_ENDPOINT0_SIZE 64 91 | #endif 92 | 93 | //------------- CLASS -------------// 94 | #define CFG_TUD_CDC 1 95 | #define CFG_TUD_MSC 1 96 | #define CFG_TUD_HID 0 97 | #define CFG_TUD_MIDI 0 98 | #define CFG_TUD_VENDOR 0 99 | 100 | // CDC FIFO size of TX and RX 101 | #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 102 | #define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 103 | 104 | // CDC Endpoint transfer buffer size, more is faster 105 | #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 106 | 107 | // MSC Buffer size of Device Mass storage 108 | #define CFG_TUD_MSC_EP_BUFSIZE 512 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | #endif /* _TUSB_CONFIG_H_ */ 115 | -------------------------------------------------------------------------------- /images/githubbutton_pp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | Created with Sketch. 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 26 | 29 | 32 | 36 | 38 | 43 | 46 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/diskio.c: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*/ 2 | /* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */ 3 | /*-----------------------------------------------------------------------*/ 4 | /* If a working storage control module is available, it should be */ 5 | /* attached to the FatFs via a glue function rather than modifying it. */ 6 | /* This is an example of glue functions to attach various exsisting */ 7 | /* storage control modules to the FatFs module with a defined API. */ 8 | /*-----------------------------------------------------------------------*/ 9 | 10 | #include "ff.h" /* Obtains integer types */ 11 | #include "diskio.h" /* Declarations of disk functions */ 12 | 13 | /* Definitions of physical drive number for each drive */ 14 | #define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */ 15 | #define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */ 16 | #define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */ 17 | 18 | #include "fatfs_disk.h" 19 | 20 | /*-----------------------------------------------------------------------*/ 21 | /* Get Drive Status */ 22 | /*-----------------------------------------------------------------------*/ 23 | 24 | DSTATUS disk_status ( 25 | BYTE pdrv /* Physical drive nmuber to identify the drive */ 26 | ) 27 | { 28 | DSTATUS stat; 29 | int result; 30 | 31 | if (pdrv == 0) 32 | return 0; 33 | 34 | return STA_NOINIT; 35 | } 36 | 37 | 38 | 39 | /*-----------------------------------------------------------------------*/ 40 | /* Inidialize a Drive */ 41 | /*-----------------------------------------------------------------------*/ 42 | 43 | DSTATUS disk_initialize ( 44 | BYTE pdrv /* Physical drive nmuber to identify the drive */ 45 | ) 46 | { 47 | DSTATUS stat; 48 | int result; 49 | 50 | if (pdrv == 0) 51 | { 52 | return 0; 53 | } 54 | return STA_NOINIT; 55 | } 56 | 57 | 58 | 59 | /*-----------------------------------------------------------------------*/ 60 | /* Read Sector(s) */ 61 | /*-----------------------------------------------------------------------*/ 62 | 63 | DRESULT disk_read ( 64 | BYTE pdrv, /* Physical drive nmuber to identify the drive */ 65 | BYTE *buff, /* Data buffer to store read data */ 66 | LBA_t sector, /* Start sector in LBA */ 67 | UINT count /* Number of sectors to read */ 68 | ) 69 | { 70 | DRESULT res; 71 | int result; 72 | 73 | if (pdrv == 0) 74 | { 75 | res = fatfs_disk_read((uint8_t*)buff, sector, count); 76 | return res; 77 | } 78 | 79 | return RES_PARERR; 80 | } 81 | 82 | 83 | 84 | /*-----------------------------------------------------------------------*/ 85 | /* Write Sector(s) */ 86 | /*-----------------------------------------------------------------------*/ 87 | 88 | #if FF_FS_READONLY == 0 89 | 90 | DRESULT disk_write ( 91 | BYTE pdrv, /* Physical drive nmuber to identify the drive */ 92 | const BYTE *buff, /* Data to be written */ 93 | LBA_t sector, /* Start sector in LBA */ 94 | UINT count /* Number of sectors to write */ 95 | ) 96 | { 97 | DRESULT res; 98 | int result; 99 | 100 | if (pdrv == 0) 101 | { 102 | res = fatfs_disk_write((const uint8_t*)buff, sector, count); 103 | return res; 104 | } 105 | 106 | return RES_PARERR; 107 | } 108 | 109 | #endif 110 | 111 | 112 | /*-----------------------------------------------------------------------*/ 113 | /* Miscellaneous Functions */ 114 | /*-----------------------------------------------------------------------*/ 115 | 116 | DRESULT disk_ioctl ( 117 | BYTE pdrv, /* Physical drive nmuber (0..) */ 118 | BYTE cmd, /* Control code */ 119 | void *buff /* Buffer to send/receive control data */ 120 | ) 121 | { 122 | DRESULT res; 123 | int result; 124 | 125 | if (pdrv == 0) 126 | { 127 | switch(cmd) { 128 | case CTRL_SYNC: 129 | fatfs_disk_sync(); 130 | return RES_OK; 131 | case GET_SECTOR_COUNT: 132 | *(LBA_t*) buff = SECTOR_NUM; 133 | return RES_OK; 134 | case GET_SECTOR_SIZE: 135 | *(WORD*) buff = SECTOR_SIZE; 136 | return RES_OK; 137 | case GET_BLOCK_SIZE: 138 | *(DWORD*) buff = 1; 139 | return RES_OK; 140 | case CTRL_TRIM: 141 | return RES_OK; 142 | default: 143 | return RES_PARERR; 144 | } 145 | } 146 | 147 | return RES_PARERR; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/msc_disk.c: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #include "tusb.h" 16 | #include "fatfs_disk.h" 17 | 18 | // whether host does safe-eject 19 | static bool ejected = false; 20 | 21 | 22 | // Invoked when received SCSI_CMD_INQUIRY 23 | // Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively 24 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) 25 | { 26 | (void) lun; 27 | 28 | const char vid[] = "PicoCart"; 29 | const char pid[] = "Mass Storage"; 30 | const char rev[] = "1.0"; 31 | 32 | memcpy(vendor_id , vid, strlen(vid)); 33 | memcpy(product_id , pid, strlen(pid)); 34 | memcpy(product_rev, rev, strlen(rev)); 35 | } 36 | 37 | // Invoked when received Test Unit Ready command. 38 | // return true allowing host to read/write this LUN e.g SD card inserted 39 | bool tud_msc_test_unit_ready_cb(uint8_t lun) 40 | { 41 | (void) lun; 42 | 43 | // RAM disk is ready until ejected 44 | if (ejected) { 45 | // Additional Sense 3A-00 is NOT_FOUND 46 | tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | // Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size 54 | // Application update block count and block size 55 | void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) 56 | { 57 | (void) lun; 58 | 59 | *block_count = SECTOR_NUM; 60 | *block_size = SECTOR_SIZE; 61 | } 62 | 63 | // Invoked when received Start Stop Unit command 64 | // - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage 65 | // - Start = 1 : active mode, if load_eject = 1 : load disk storage 66 | bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) 67 | { 68 | (void) lun; 69 | (void) power_condition; 70 | 71 | if ( load_eject ) 72 | { 73 | if (start) 74 | { 75 | // load disk storage 76 | }else 77 | { 78 | // unload disk storage 79 | ejected = true; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | // Callback invoked when received READ10 command. 87 | // Copy disk's data to buffer (up to bufsize) and return number of copied bytes. 88 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) 89 | { 90 | (void) lun; 91 | 92 | if(offset != 0) return -1; 93 | if(bufsize != SECTOR_SIZE) return -1; 94 | 95 | uint32_t status = fatfs_disk_read(buffer, lba, 1); 96 | if(status != 0) return -1; 97 | return (int32_t) bufsize; 98 | } 99 | 100 | bool tud_msc_is_writable_cb (uint8_t lun) 101 | { 102 | (void) lun; 103 | return true; 104 | } 105 | 106 | // Callback invoked when received WRITE10 command. 107 | // Process data in buffer to disk's storage and return number of written bytes 108 | alarm_id_t alarm_id = -1; 109 | int64_t sync_callback(alarm_id_t id, void *user_data) 110 | { 111 | fatfs_disk_sync(); 112 | } 113 | 114 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) 115 | { 116 | (void) lun; 117 | 118 | if(offset != 0) return -1; 119 | if(bufsize != SECTOR_SIZE) return -1; 120 | 121 | uint32_t status = fatfs_disk_write(buffer, lba, 1); 122 | 123 | // we need to sync the flash but only do it when activity dies down :-) 124 | if(alarm_id >= 0) 125 | cancel_alarm(alarm_id); 126 | alarm_id = add_alarm_in_ms(250, sync_callback, NULL, false); 127 | 128 | if(status != 0) return -1; 129 | return (int32_t) bufsize; 130 | } 131 | 132 | // Callback invoked when received an SCSI command not in built-in list below 133 | // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE 134 | // - READ10 and WRITE10 has their own callbacks 135 | int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) 136 | { 137 | // read10 & write10 has their own callback and MUST not be handled here 138 | 139 | void const* response = NULL; 140 | int32_t resplen = 0; 141 | 142 | // most scsi handled is input 143 | bool in_xfer = true; 144 | 145 | switch (scsi_cmd[0]) 146 | { 147 | default: 148 | // Set Sense = Invalid Command Operation 149 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 150 | 151 | // negative means error -> tinyusb could stall and/or response with failed status 152 | resplen = -1; 153 | break; 154 | } 155 | 156 | // return resplen must not larger than bufsize 157 | if ( resplen > bufsize ) resplen = bufsize; 158 | 159 | if ( response && (resplen > 0) ) 160 | { 161 | if(in_xfer) 162 | { 163 | memcpy(buffer, response, (size_t) resplen); 164 | }else 165 | { 166 | // SCSI output 167 | } 168 | } 169 | 170 | return (int32_t) resplen; 171 | } 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A8PicoCart (UnoCart on a Raspberry Pi Pico clone) 2 | 3 | The A8PicoCart is a multi-cart for the Atari 8-bit (XL/XE) which you can make at home with only basic soldering skills. 4 | It is based on my earlier UnoCart design but uses a very inexpensive Raspberry Pi Pico clone (£2-3) for an easy build. 5 | 6 | When plugged into a PC by USB it becomes a Mass Storage device allowing ROM/CAR/XEX & ATR files to be copied to the cartridge. 7 | When plugged into an Atari these files are shown on the menu and the device will either emulate the selected cartridge type or 8 | act as a XEX file launcher. It also has (very) limited support for ATR files allowing you to do some programming and save your 9 | work to an ATR file. 10 | 11 | This page contains everything you need to build the project - firmware to flash to your purple Pico clone, Kicad PCB design files to make a PCB and 3d print files for a nice case. The source code for everything is also here. There is also a manual detailing the full capabilities of the cartridge. 12 | 13 | If you build an A8PicoCart consider donating to help fund this and my future projects: 14 | 15 | 16 | 17 | Cased A8PicoCart in a 800xl 18 | A8PicoCart Menu 19 | 20 | ## Quick Start 21 | 22 | Get yourself a purple Raspberry Pi Pico clone (see [this thread](https://forums.raspberrypi.com/viewtopic.php?t=337976) for details) and order a PCB using the KiCad board files provided. You have a choice of two PCB designs - the XE design is for use uncased, with the Pico on the top surface. The XL/XE version is desgined to be mounted in a case (though can be used uncased too) with the Pico on the bottom/back surface. Once you have the Pico clone and PCB, solder the pico as pictured below, depending on your PCB version. Program the firmware onto the device by pressing BOOTSEL on the board and connecting to USB on a PC and drag the firmware (UF2 file) onto the device. It should then remount as A8-PICOCART so you can copy Atari CAR & XEX files to it. Unmount, then plug into your Atari and play! Optionally solder a reset button to reset back to the menu. 23 | 24 | **Note**: this project does not work with the official green RPi Pico board. Although the official board has the same 25 | number of physical pins (and will therefore fit in the PCB) - the actual pin functions are different and attempting to use 26 | it may damage the Pico and/or your Atari. The purple clones are cheaper and have a greater number of usuable GPIO pins - 27 | this is required for the cartridge port emulation to work. 28 | 29 | **Warning**: always disconnect from PC/USB before plugging into your Atari or you will damage your Atari and/or PC. This warning mainly 30 | applies to the XE/uncased version of the PCB. The newest version of the XL/XE board makes it physically impossible to connect USB when the 31 | cartridge is inserted in an Atari. 32 | 33 | Purple Pico mounted on the PCBs 34 | USB Mass Storage mode 35 | 36 | ## Inserting the cartridge 37 | 38 | Depending on which PCB you choose the Pico clone may be mounted on the front or back of the PCB. When you insert 39 | the cartridge in the Atari make sure the "S..A" side of the cartridge edge (NOT the "1..15" side) is facing upwards (XE) or towards you (XL). 40 | 41 | ## Purple Pico clones 42 | 43 | The firmware is designed for the Purple Pico clones with USB C connector and 16Megs of flash and a single button (BOOTSEL). 44 | I've received these devices with two different types of SPI flash chips - Winbond and (slower) Z-bit. The firmware has been tested with both types. 45 | 46 | The project defines a custom board with a higher value for PICO_FLASH_SPI_CLKDIV so it supports the slower Z-bit flash. 47 | 48 | Be aware that it's possible you might get one with another type of flash memory chip that has not been tested. 49 | 50 | ## Reset button 51 | 52 | You can solder a push button to the cartridge to get back to the menu and avoid wear on your Atari power switch. 53 | When you reset the cartridge the Atari will crash - just push reset on the Atari itself to get back to the menu. 54 | 55 | The button used is a 6x6mm thru-hole tactile momentary switch. If you are going to print a case, then 9mm+ height is suitable. 56 | When you solder the button to the PCB - make sure the lower two holes in the PCB are bridged only when the button is pressed. 57 | 58 | It works simply by pulling the RUN pin on the pico clone to ground to reset. 59 | 60 | ## 3D print files 61 | 62 | The STL files can be used to print a 3 part (front, back and logo) cartridge shell. The 2-tone logo is achieved by 63 | changing the filament at the correct z-position when printing a8pico_logo.stl. You'll also need two M3 15mm screws. 64 | 65 | Cased A8PicoCart 66 | Cased A8PicoCart 67 | 68 | ## Changelog 69 | 70 | * 7 Oct 2023 - Add new PCB & case design files to make it impossible to insert USB when plugged into Atari. Add support for Microcart (CAR type 52) cartridges. Updated manual. 71 | * 29 Oct 2023 - Adds 3 new cartridge types (thanks to ascrnet) 72 | 73 | ## Credits 74 | 75 | * Design, hardware and firmware by Robin Edwards (electrotrains at atariage) 76 | * XEX loader and OS modifications by Jonathan Halliday (flashjazzcat at atariage) 77 | * Altirra LLE OS used with permision from Avery Lee (phaeron at atariage) 78 | 79 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/ffsystem.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------*/ 2 | /* A Sample Code of User Provided OS Dependent Functions for FatFs */ 3 | /*------------------------------------------------------------------------*/ 4 | 5 | #include "ff.h" 6 | 7 | 8 | #if FF_USE_LFN == 3 /* Use dynamic memory allocation */ 9 | 10 | /*------------------------------------------------------------------------*/ 11 | /* Allocate/Free a Memory Block */ 12 | /*------------------------------------------------------------------------*/ 13 | 14 | #include /* with POSIX API */ 15 | 16 | 17 | void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ 18 | UINT msize /* Number of bytes to allocate */ 19 | ) 20 | { 21 | return malloc((size_t)msize); /* Allocate a new memory block */ 22 | } 23 | 24 | 25 | void ff_memfree ( 26 | void* mblock /* Pointer to the memory block to free (no effect if null) */ 27 | ) 28 | { 29 | free(mblock); /* Free the memory block */ 30 | } 31 | 32 | #endif 33 | 34 | 35 | 36 | 37 | #if FF_FS_REENTRANT /* Mutal exclusion */ 38 | /*------------------------------------------------------------------------*/ 39 | /* Definitions of Mutex */ 40 | /*------------------------------------------------------------------------*/ 41 | 42 | #define OS_TYPE 0 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */ 43 | 44 | 45 | #if OS_TYPE == 0 /* Win32 */ 46 | #include 47 | static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */ 48 | 49 | #elif OS_TYPE == 1 /* uITRON */ 50 | #include "itron.h" 51 | #include "kernel.h" 52 | static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */ 53 | 54 | #elif OS_TYPE == 2 /* uc/OS-II */ 55 | #include "includes.h" 56 | static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */ 57 | 58 | #elif OS_TYPE == 3 /* FreeRTOS */ 59 | #include "FreeRTOS.h" 60 | #include "semphr.h" 61 | static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */ 62 | 63 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 64 | #include "cmsis_os.h" 65 | static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */ 66 | 67 | #endif 68 | 69 | 70 | 71 | /*------------------------------------------------------------------------*/ 72 | /* Create a Mutex */ 73 | /*------------------------------------------------------------------------*/ 74 | /* This function is called in f_mount function to create a new mutex 75 | / or semaphore for the volume. When a 0 is returned, the f_mount function 76 | / fails with FR_INT_ERR. 77 | */ 78 | 79 | int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */ 80 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 81 | ) 82 | { 83 | #if OS_TYPE == 0 /* Win32 */ 84 | Mutex[vol] = CreateMutex(NULL, FALSE, NULL); 85 | return (int)(Mutex[vol] != INVALID_HANDLE_VALUE); 86 | 87 | #elif OS_TYPE == 1 /* uITRON */ 88 | T_CMTX cmtx = {TA_TPRI,1}; 89 | 90 | Mutex[vol] = acre_mtx(&cmtx); 91 | return (int)(Mutex[vol] > 0); 92 | 93 | #elif OS_TYPE == 2 /* uC/OS-II */ 94 | OS_ERR err; 95 | 96 | Mutex[vol] = OSMutexCreate(0, &err); 97 | return (int)(err == OS_NO_ERR); 98 | 99 | #elif OS_TYPE == 3 /* FreeRTOS */ 100 | Mutex[vol] = xSemaphoreCreateMutex(); 101 | return (int)(Mutex[vol] != NULL); 102 | 103 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 104 | osMutexDef(cmsis_os_mutex); 105 | 106 | Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex)); 107 | return (int)(Mutex[vol] != NULL); 108 | 109 | #endif 110 | } 111 | 112 | 113 | /*------------------------------------------------------------------------*/ 114 | /* Delete a Mutex */ 115 | /*------------------------------------------------------------------------*/ 116 | /* This function is called in f_mount function to delete a mutex or 117 | / semaphore of the volume created with ff_mutex_create function. 118 | */ 119 | 120 | void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */ 121 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 122 | ) 123 | { 124 | #if OS_TYPE == 0 /* Win32 */ 125 | CloseHandle(Mutex[vol]); 126 | 127 | #elif OS_TYPE == 1 /* uITRON */ 128 | del_mtx(Mutex[vol]); 129 | 130 | #elif OS_TYPE == 2 /* uC/OS-II */ 131 | OS_ERR err; 132 | 133 | OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err); 134 | 135 | #elif OS_TYPE == 3 /* FreeRTOS */ 136 | vSemaphoreDelete(Mutex[vol]); 137 | 138 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 139 | osMutexDelete(Mutex[vol]); 140 | 141 | #endif 142 | } 143 | 144 | 145 | /*------------------------------------------------------------------------*/ 146 | /* Request a Grant to Access the Volume */ 147 | /*------------------------------------------------------------------------*/ 148 | /* This function is called on enter file functions to lock the volume. 149 | / When a 0 is returned, the file function fails with FR_TIMEOUT. 150 | */ 151 | 152 | int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */ 153 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 154 | ) 155 | { 156 | #if OS_TYPE == 0 /* Win32 */ 157 | return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0); 158 | 159 | #elif OS_TYPE == 1 /* uITRON */ 160 | return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK); 161 | 162 | #elif OS_TYPE == 2 /* uC/OS-II */ 163 | OS_ERR err; 164 | 165 | OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err)); 166 | return (int)(err == OS_NO_ERR); 167 | 168 | #elif OS_TYPE == 3 /* FreeRTOS */ 169 | return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE); 170 | 171 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 172 | return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK); 173 | 174 | #endif 175 | } 176 | 177 | 178 | 179 | /*------------------------------------------------------------------------*/ 180 | /* Release a Grant to Access the Volume */ 181 | /*------------------------------------------------------------------------*/ 182 | /* This function is called on leave file functions to unlock the volume. 183 | */ 184 | 185 | void ff_mutex_give ( 186 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 187 | ) 188 | { 189 | #if OS_TYPE == 0 /* Win32 */ 190 | ReleaseMutex(Mutex[vol]); 191 | 192 | #elif OS_TYPE == 1 /* uITRON */ 193 | unl_mtx(Mutex[vol]); 194 | 195 | #elif OS_TYPE == 2 /* uC/OS-II */ 196 | OSMutexPost(Mutex[vol]); 197 | 198 | #elif OS_TYPE == 3 /* FreeRTOS */ 199 | xSemaphoreGive(Mutex[vol]); 200 | 201 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 202 | osMutexRelease(Mutex[vol]); 203 | 204 | #endif 205 | } 206 | 207 | #endif /* FF_FS_REENTRANT */ 208 | 209 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/memmap_custom.ld: -------------------------------------------------------------------------------- 1 | /* Based on GCC ARM embedded samples. 2 | Defines the following symbols for use by code: 3 | __exidx_start 4 | __exidx_end 5 | __etext 6 | __data_start__ 7 | __preinit_array_start 8 | __preinit_array_end 9 | __init_array_start 10 | __init_array_end 11 | __fini_array_start 12 | __fini_array_end 13 | __data_end__ 14 | __bss_start__ 15 | __bss_end__ 16 | __end__ 17 | end 18 | __HeapLimit 19 | __StackLimit 20 | __StackTop 21 | __stack (== StackTop) 22 | */ 23 | 24 | MEMORY 25 | { 26 | FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k 27 | RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k 28 | SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 29 | SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 30 | } 31 | 32 | ENTRY(_entry_point) 33 | 34 | SECTIONS 35 | { 36 | /* Second stage bootloader is prepended to the image. It must be 256 bytes big 37 | and checksummed. It is usually built by the boot_stage2 target 38 | in the Raspberry Pi Pico SDK 39 | */ 40 | 41 | .flash_begin : { 42 | __flash_binary_start = .; 43 | } > FLASH 44 | 45 | .boot2 : { 46 | __boot2_start__ = .; 47 | KEEP (*(.boot2)) 48 | __boot2_end__ = .; 49 | } > FLASH 50 | 51 | ASSERT(__boot2_end__ - __boot2_start__ == 256, 52 | "ERROR: Pico second stage bootloader must be 256 bytes in size") 53 | 54 | /* The second stage will always enter the image at the start of .text. 55 | The debugger will use the ELF entry point, which is the _entry_point 56 | symbol if present, otherwise defaults to start of .text. 57 | This can be used to transfer control back to the bootrom on debugger 58 | launches only, to perform proper flash setup. 59 | */ 60 | 61 | .text : { 62 | __logical_binary_start = .; 63 | KEEP (*(.vectors)) 64 | KEEP (*(.binary_info_header)) 65 | __binary_info_header_end = .; 66 | KEEP (*(.reset)) 67 | /* TODO revisit this now memset/memcpy/float in ROM */ 68 | /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from 69 | * FLASH ... we will include any thing excluded here in .data below by default */ 70 | *(.init) 71 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) 72 | *(.fini) 73 | /* Pull all c'tors into .text */ 74 | *crtbegin.o(.ctors) 75 | *crtbegin?.o(.ctors) 76 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 77 | *(SORT(.ctors.*)) 78 | *(.ctors) 79 | /* Followed by destructors */ 80 | *crtbegin.o(.dtors) 81 | *crtbegin?.o(.dtors) 82 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 83 | *(SORT(.dtors.*)) 84 | *(.dtors) 85 | 86 | *(.eh_frame*) 87 | . = ALIGN(4); 88 | } > FLASH 89 | 90 | .rodata : { 91 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) 92 | . = ALIGN(4); 93 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) 94 | . = ALIGN(4); 95 | } > FLASH 96 | 97 | .ARM.extab : 98 | { 99 | *(.ARM.extab* .gnu.linkonce.armextab.*) 100 | } > FLASH 101 | 102 | __exidx_start = .; 103 | .ARM.exidx : 104 | { 105 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 106 | } > FLASH 107 | __exidx_end = .; 108 | 109 | /* Machine inspectable binary information */ 110 | . = ALIGN(4); 111 | __binary_info_start = .; 112 | .binary_info : 113 | { 114 | KEEP(*(.binary_info.keep.*)) 115 | *(.binary_info.*) 116 | } > FLASH 117 | __binary_info_end = .; 118 | . = ALIGN(4); 119 | 120 | /* End of .text-like segments */ 121 | __etext = .; 122 | 123 | .ram_vector_table (COPY): { 124 | *(.ram_vector_table) 125 | } > RAM 126 | 127 | .data : { 128 | __data_start__ = .; 129 | *(vtable) 130 | 131 | *(.time_critical*) 132 | 133 | /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ 134 | *(.text*) 135 | . = ALIGN(4); 136 | *(.rodata*) 137 | . = ALIGN(4); 138 | 139 | *(.data*) 140 | 141 | . = ALIGN(4); 142 | *(.after_data.*) 143 | . = ALIGN(4); 144 | /* preinit data */ 145 | PROVIDE_HIDDEN (__mutex_array_start = .); 146 | KEEP(*(SORT(.mutex_array.*))) 147 | KEEP(*(.mutex_array)) 148 | PROVIDE_HIDDEN (__mutex_array_end = .); 149 | 150 | . = ALIGN(4); 151 | /* preinit data */ 152 | PROVIDE_HIDDEN (__preinit_array_start = .); 153 | KEEP(*(SORT(.preinit_array.*))) 154 | KEEP(*(.preinit_array)) 155 | PROVIDE_HIDDEN (__preinit_array_end = .); 156 | 157 | . = ALIGN(4); 158 | /* init data */ 159 | PROVIDE_HIDDEN (__init_array_start = .); 160 | KEEP(*(SORT(.init_array.*))) 161 | KEEP(*(.init_array)) 162 | PROVIDE_HIDDEN (__init_array_end = .); 163 | 164 | . = ALIGN(4); 165 | /* finit data */ 166 | PROVIDE_HIDDEN (__fini_array_start = .); 167 | *(SORT(.fini_array.*)) 168 | *(.fini_array) 169 | PROVIDE_HIDDEN (__fini_array_end = .); 170 | 171 | *(.jcr) 172 | . = ALIGN(4); 173 | /* All data end */ 174 | __data_end__ = .; 175 | } > RAM AT> FLASH 176 | 177 | .uninitialized_data (COPY): { 178 | . = ALIGN(4); 179 | *(.uninitialized_data*) 180 | } > RAM 181 | 182 | /* Start and end symbols must be word-aligned */ 183 | .scratch_x : { 184 | __scratch_x_start__ = .; 185 | *(.scratch_x.*) 186 | . = ALIGN(4); 187 | __scratch_x_end__ = .; 188 | } > SCRATCH_X AT > FLASH 189 | __scratch_x_source__ = LOADADDR(.scratch_x); 190 | 191 | .scratch_y : { 192 | __scratch_y_start__ = .; 193 | *(.scratch_y.*) 194 | . = ALIGN(4); 195 | __scratch_y_end__ = .; 196 | } > SCRATCH_Y AT > FLASH 197 | __scratch_y_source__ = LOADADDR(.scratch_y); 198 | 199 | .bss : { 200 | . = ALIGN(4); 201 | __bss_start__ = .; 202 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 203 | *(COMMON) 204 | . = ALIGN(4); 205 | __bss_end__ = .; 206 | } > RAM 207 | 208 | .heap (COPY): 209 | { 210 | __end__ = .; 211 | end = __end__; 212 | *(.heap*) 213 | __HeapLimit = .; 214 | } > RAM 215 | 216 | /* .stack*_dummy section doesn't contains any symbols. It is only 217 | * used for linker to calculate size of stack sections, and assign 218 | * values to stack symbols later 219 | * 220 | * stack1 section may be empty/missing if platform_launch_core1 is not used */ 221 | 222 | /* by default we put core 0 stack at the end of scratch Y, so that if core 1 223 | * stack is not used then all of SCRATCH_X is free. 224 | */ 225 | .stack1_dummy (COPY): 226 | { 227 | *(.stack1*) 228 | } > SCRATCH_X 229 | .stack_dummy (COPY): 230 | { 231 | *(.stack*) 232 | } > SCRATCH_Y 233 | 234 | .flash_end : { 235 | __flash_binary_end = .; 236 | } > FLASH 237 | 238 | /* stack limit is poorly named, but historically is maximum heap ptr */ 239 | __StackLimit = ORIGIN(RAM) + LENGTH(RAM); 240 | __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); 241 | __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); 242 | __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); 243 | __StackBottom = __StackTop - SIZEOF(.stack_dummy); 244 | PROVIDE(__stack = __StackTop); 245 | 246 | /* Check if data + heap + stack exceeds RAM limit */ 247 | ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") 248 | 249 | ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") 250 | /* todo assert on extra code */ 251 | } 252 | 253 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/flash_fs.c: -------------------------------------------------------------------------------- 1 | /** 2 | * _ ___ ___ _ ___ _ 3 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 4 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 5 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 6 | * 7 | * 8 | * Atari 8-bit cartridge for Raspberry Pi Pico 9 | * 10 | * Robin Edwards 2023 11 | * 12 | * Needs to be a release NOT debug build for the cartridge emulation to work 13 | */ 14 | 15 | #include "pico/stdlib.h" 16 | #include "hardware/flash.h" 17 | #include "hardware/sync.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "flash_fs.h" 23 | 24 | // Implements 512 byte FAT sectors on 4096 byte flash sectors. 25 | // Doesn't really implement wear levelling (e.g. the fs_map) so not for heavy use but should be 26 | // fine for the intended use case. 27 | 28 | #define HW_FLASH_STORAGE_BASE (1024 * 1024) 29 | #define MAGIC_8_BYTES "RHE!FS30" 30 | 31 | #define NUM_FAT_SECTORS 30716 // 15megs / 512bytes = 30720, but we used 4 records for the header (8 bytes) 32 | #define NUM_FLASH_SECTORS 3840 // 15megs / 4096bytes = 3840 33 | 34 | typedef struct { 35 | uint8_t header[8]; 36 | uint16_t sectors[NUM_FAT_SECTORS]; // map FAT sectors -> flash sectors 37 | } sector_map; 38 | 39 | sector_map fs_map; 40 | bool fs_map_needs_written[15]; 41 | 42 | uint8_t used_bitmap[NUM_FLASH_SECTORS]; // we will use 256 flash sectors for 2048 fat sectors 43 | 44 | uint16_t write_sector = 0; // which flash sector we are writing to 45 | uint8_t write_sector_bitmap = 0; // 1 for each free 512 byte page on the sector 46 | 47 | // each sector entry in the sector map is: 48 | // 13 bits of sector (indexing 8192 4k flash sectors) 49 | // 3 bits of offset (0->7 512 byte FAT sectors in each 4k flash sector) 50 | uint16_t getMapSector(uint16_t mapEntry) { return (mapEntry & 0xFFF8) >> 3; } 51 | uint8_t getMapOffset(uint16_t mapEntry) { return mapEntry & 0x7; } 52 | uint16_t makeMapEntry(uint16_t sector, uint8_t offset) { return (sector << 3) | offset; }; 53 | 54 | // forward declns 55 | void flash_read_sector(uint16_t sector, uint8_t offset, void *buffer, uint16_t size); 56 | void flash_erase_sector(uint16_t sector); 57 | void flash_write_sector(uint16_t sector, uint8_t offset, const void *buffer, uint16_t size); 58 | void flash_erase_with_copy_sector(uint16_t sector, uint8_t preserve_bitmap); 59 | 60 | void debug_print_in_use() { 61 | return; 62 | // just shows first 1meg 63 | printf("IN USE-----------------------------------\n"); 64 | for (int i=0; i<16; i++) { 65 | printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", 66 | used_bitmap[i*16+0], used_bitmap[i*16+1], used_bitmap[i*16+2], used_bitmap[i*16+3], 67 | used_bitmap[i*16+4], used_bitmap[i*16+5], used_bitmap[i*16+6], used_bitmap[i*16+7], 68 | used_bitmap[i*16+8], used_bitmap[i*16+9], used_bitmap[i*16+10], used_bitmap[i*16+11], 69 | used_bitmap[i*16+12], used_bitmap[i*16+13], used_bitmap[i*16+14], used_bitmap[i*16+15]); 70 | } 71 | printf("END--------------------------------------\n"); 72 | } 73 | 74 | void write_fs_map() 75 | { 76 | debug_print_in_use(); 77 | for (int i=0; i<15; i++) { 78 | if (fs_map_needs_written[i]) { 79 | // printf("Writing FS Map %d\n", i); 80 | flash_erase_sector(i); 81 | flash_write_sector(i, 0, (uint8_t*)&fs_map+(4096*i), 4096); 82 | fs_map_needs_written[i] = false; 83 | } 84 | } 85 | } 86 | 87 | uint16_t getNextWriteSector() 88 | { 89 | static uint16_t search_start_pos = 0; 90 | int i; 91 | if (write_sector == 0 || write_sector_bitmap == 0) 92 | { // first try to find a completely free sector 93 | for (i=0; i 31 ) chr_count = 31; 276 | 277 | // Convert ASCII string into UTF-16 278 | for(uint8_t i=0; i= 1 with LFN enabled, string functions convert the character 73 | / encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE 74 | / to be read/written via those functions. 75 | / 76 | / 0: ANSI/OEM in current CP 77 | / 1: Unicode in UTF-16LE 78 | / 2: Unicode in UTF-16BE 79 | / 3: Unicode in UTF-8 80 | */ 81 | 82 | 83 | /*---------------------------------------------------------------------------/ 84 | / Locale and Namespace Configurations 85 | /---------------------------------------------------------------------------*/ 86 | 87 | #define FF_CODE_PAGE 437 88 | /* This option specifies the OEM code page to be used on the target system. 89 | / Incorrect code page setting can cause a file open failure. 90 | / 91 | / 437 - U.S. 92 | / 720 - Arabic 93 | / 737 - Greek 94 | / 771 - KBL 95 | / 775 - Baltic 96 | / 850 - Latin 1 97 | / 852 - Latin 2 98 | / 855 - Cyrillic 99 | / 857 - Turkish 100 | / 860 - Portuguese 101 | / 861 - Icelandic 102 | / 862 - Hebrew 103 | / 863 - Canadian French 104 | / 864 - Arabic 105 | / 865 - Nordic 106 | / 866 - Russian 107 | / 869 - Greek 2 108 | / 932 - Japanese (DBCS) 109 | / 936 - Simplified Chinese (DBCS) 110 | / 949 - Korean (DBCS) 111 | / 950 - Traditional Chinese (DBCS) 112 | / 0 - Include all code pages above and configured by f_setcp() 113 | */ 114 | 115 | 116 | #define FF_USE_LFN 1 117 | #define FF_MAX_LFN 255 118 | /* The FF_USE_LFN switches the support for LFN (long file name). 119 | / 120 | / 0: Disable LFN. FF_MAX_LFN has no effect. 121 | / 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. 122 | / 2: Enable LFN with dynamic working buffer on the STACK. 123 | / 3: Enable LFN with dynamic working buffer on the HEAP. 124 | / 125 | / To enable the LFN, ffunicode.c needs to be added to the project. The LFN function 126 | / requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and 127 | / additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. 128 | / The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can 129 | / be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN 130 | / specification. 131 | / When use stack for the working buffer, take care on stack overflow. When use heap 132 | / memory for the working buffer, memory management functions, ff_memalloc() and 133 | / ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ 134 | 135 | 136 | #define FF_LFN_UNICODE 0 137 | /* This option switches the character encoding on the API when LFN is enabled. 138 | / 139 | / 0: ANSI/OEM in current CP (TCHAR = char) 140 | / 1: Unicode in UTF-16 (TCHAR = WCHAR) 141 | / 2: Unicode in UTF-8 (TCHAR = char) 142 | / 3: Unicode in UTF-32 (TCHAR = DWORD) 143 | / 144 | / Also behavior of string I/O functions will be affected by this option. 145 | / When LFN is not enabled, this option has no effect. */ 146 | 147 | 148 | #define FF_LFN_BUF 255 149 | #define FF_SFN_BUF 12 150 | /* This set of options defines size of file name members in the FILINFO structure 151 | / which is used to read out directory items. These values should be suffcient for 152 | / the file names to read. The maximum possible length of the read file name depends 153 | / on character encoding. When LFN is not enabled, these options have no effect. */ 154 | 155 | 156 | #define FF_FS_RPATH 0 157 | /* This option configures support for relative path. 158 | / 159 | / 0: Disable relative path and remove related functions. 160 | / 1: Enable relative path. f_chdir() and f_chdrive() are available. 161 | / 2: f_getcwd() function is available in addition to 1. 162 | */ 163 | 164 | 165 | /*---------------------------------------------------------------------------/ 166 | / Drive/Volume Configurations 167 | /---------------------------------------------------------------------------*/ 168 | 169 | #define FF_VOLUMES 1 170 | /* Number of volumes (logical drives) to be used. (1-10) */ 171 | 172 | 173 | #define FF_STR_VOLUME_ID 0 174 | #define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" 175 | /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. 176 | / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive 177 | / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each 178 | / logical drives. Number of items must not be less than FF_VOLUMES. Valid 179 | / characters for the volume ID strings are A-Z, a-z and 0-9, however, they are 180 | / compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is 181 | / not defined, a user defined volume string table is needed as: 182 | / 183 | / const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... 184 | */ 185 | 186 | 187 | #define FF_MULTI_PARTITION 0 188 | /* This option switches support for multiple volumes on the physical drive. 189 | / By default (0), each logical drive number is bound to the same physical drive 190 | / number and only an FAT volume found on the physical drive will be mounted. 191 | / When this function is enabled (1), each logical drive number can be bound to 192 | / arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() 193 | / function will be available. */ 194 | 195 | 196 | #define FF_MIN_SS 512 197 | #define FF_MAX_SS 512 198 | /* This set of options configures the range of sector size to be supported. (512, 199 | / 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and 200 | / harddisk, but a larger value may be required for on-board flash memory and some 201 | / type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured 202 | / for variable sector size mode and disk_ioctl() function needs to implement 203 | / GET_SECTOR_SIZE command. */ 204 | 205 | 206 | #define FF_LBA64 0 207 | /* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) 208 | / To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ 209 | 210 | 211 | #define FF_MIN_GPT 0x10000000 212 | /* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and 213 | / f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ 214 | 215 | 216 | #define FF_USE_TRIM 0 217 | /* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) 218 | / To enable Trim function, also CTRL_TRIM command should be implemented to the 219 | / disk_ioctl() function. */ 220 | 221 | 222 | 223 | /*---------------------------------------------------------------------------/ 224 | / System Configurations 225 | /---------------------------------------------------------------------------*/ 226 | 227 | #define FF_FS_TINY 0 228 | /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) 229 | / At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. 230 | / Instead of private sector buffer eliminated from the file object, common sector 231 | / buffer in the filesystem object (FATFS) is used for the file data transfer. */ 232 | 233 | 234 | #define FF_FS_EXFAT 0 235 | /* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) 236 | / To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) 237 | / Note that enabling exFAT discards ANSI C (C89) compatibility. */ 238 | 239 | 240 | #define FF_FS_NORTC 1 241 | #define FF_NORTC_MON 1 242 | #define FF_NORTC_MDAY 1 243 | #define FF_NORTC_YEAR 2023 244 | /* The option FF_FS_NORTC switches timestamp feature. If the system does not have 245 | / an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the 246 | / timestamp feature. Every object modified by FatFs will have a fixed timestamp 247 | / defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. 248 | / To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be 249 | / added to the project to read current time form real-time clock. FF_NORTC_MON, 250 | / FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. 251 | / These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ 252 | 253 | 254 | #define FF_FS_NOFSINFO 0 255 | /* If you need to know correct free space on the FAT32 volume, set bit 0 of this 256 | / option, and f_getfree() function at the first time after volume mount will force 257 | / a full FAT scan. Bit 1 controls the use of last allocated cluster number. 258 | / 259 | / bit0=0: Use free cluster count in the FSINFO if available. 260 | / bit0=1: Do not trust free cluster count in the FSINFO. 261 | / bit1=0: Use last allocated cluster number in the FSINFO if available. 262 | / bit1=1: Do not trust last allocated cluster number in the FSINFO. 263 | */ 264 | 265 | 266 | #define FF_FS_LOCK 0 267 | /* The option FF_FS_LOCK switches file lock function to control duplicated file open 268 | / and illegal operation to open objects. This option must be 0 when FF_FS_READONLY 269 | / is 1. 270 | / 271 | / 0: Disable file lock function. To avoid volume corruption, application program 272 | / should avoid illegal open, remove and rename to the open objects. 273 | / >0: Enable file lock function. The value defines how many files/sub-directories 274 | / can be opened simultaneously under file lock control. Note that the file 275 | / lock control is independent of re-entrancy. */ 276 | 277 | 278 | #define FF_FS_REENTRANT 0 279 | #define FF_FS_TIMEOUT 1000 280 | /* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs 281 | / module itself. Note that regardless of this option, file access to different 282 | / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() 283 | / and f_fdisk() function, are always not re-entrant. Only file/directory access 284 | / to the same volume is under control of this featuer. 285 | / 286 | / 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect. 287 | / 1: Enable re-entrancy. Also user provided synchronization handlers, 288 | / ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give() 289 | / function, must be added to the project. Samples are available in ffsystem.c. 290 | / 291 | / The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick. 292 | */ 293 | 294 | 295 | 296 | /*--- End of configuration options ---*/ 297 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/00history.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | Revision history of FatFs module 3 | ---------------------------------------------------------------------------- 4 | 5 | R0.00 (February 26, 2006) 6 | 7 | Prototype. 8 | 9 | 10 | 11 | R0.01 (April 29, 2006) 12 | 13 | The first release. 14 | 15 | 16 | 17 | R0.02 (June 01, 2006) 18 | 19 | Added FAT12 support. 20 | Removed unbuffered mode. 21 | Fixed a problem on small (<32M) partition. 22 | 23 | 24 | 25 | R0.02a (June 10, 2006) 26 | 27 | Added a configuration option (_FS_MINIMUM). 28 | 29 | 30 | 31 | R0.03 (September 22, 2006) 32 | 33 | Added f_rename(). 34 | Changed option _FS_MINIMUM to _FS_MINIMIZE. 35 | 36 | 37 | 38 | R0.03a (December 11, 2006) 39 | 40 | Improved cluster scan algorithm to write files fast. 41 | Fixed f_mkdir() creates incorrect directory on FAT32. 42 | 43 | 44 | 45 | R0.04 (February 04, 2007) 46 | 47 | Added f_mkfs(). 48 | Supported multiple drive system. 49 | Changed some interfaces for multiple drive system. 50 | Changed f_mountdrv() to f_mount(). 51 | 52 | 53 | 54 | R0.04a (April 01, 2007) 55 | 56 | Supported multiple partitions on a physical drive. 57 | Added a capability of extending file size to f_lseek(). 58 | Added minimization level 3. 59 | Fixed an endian sensitive code in f_mkfs(). 60 | 61 | 62 | 63 | R0.04b (May 05, 2007) 64 | 65 | Added a configuration option _USE_NTFLAG. 66 | Added FSINFO support. 67 | Fixed DBCS name can result FR_INVALID_NAME. 68 | Fixed short seek (<= csize) collapses the file object. 69 | 70 | 71 | 72 | R0.05 (August 25, 2007) 73 | 74 | Changed arguments of f_read(), f_write() and f_mkfs(). 75 | Fixed f_mkfs() on FAT32 creates incorrect FSINFO. 76 | Fixed f_mkdir() on FAT32 creates incorrect directory. 77 | 78 | 79 | 80 | R0.05a (February 03, 2008) 81 | 82 | Added f_truncate() and f_utime(). 83 | Fixed off by one error at FAT sub-type determination. 84 | Fixed btr in f_read() can be mistruncated. 85 | Fixed cached sector is not flushed when create and close without write. 86 | 87 | 88 | 89 | R0.06 (April 01, 2008) 90 | 91 | Added fputc(), fputs(), fprintf() and fgets(). 92 | Improved performance of f_lseek() on moving to the same or following cluster. 93 | 94 | 95 | 96 | R0.07 (April 01, 2009) 97 | 98 | Merged Tiny-FatFs as a configuration option. (_FS_TINY) 99 | Added long file name feature. (_USE_LFN) 100 | Added multiple code page feature. (_CODE_PAGE) 101 | Added re-entrancy for multitask operation. (_FS_REENTRANT) 102 | Added auto cluster size selection to f_mkfs(). 103 | Added rewind option to f_readdir(). 104 | Changed result code of critical errors. 105 | Renamed string functions to avoid name collision. 106 | 107 | 108 | 109 | R0.07a (April 14, 2009) 110 | 111 | Septemberarated out OS dependent code on reentrant cfg. 112 | Added multiple sector size feature. 113 | 114 | 115 | 116 | R0.07c (June 21, 2009) 117 | 118 | Fixed f_unlink() can return FR_OK on error. 119 | Fixed wrong cache control in f_lseek(). 120 | Added relative path feature. 121 | Added f_chdir() and f_chdrive(). 122 | Added proper case conversion to extended character. 123 | 124 | 125 | 126 | R0.07e (November 03, 2009) 127 | 128 | Septemberarated out configuration options from ff.h to ffconf.h. 129 | Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH. 130 | Fixed name matching error on the 13 character boundary. 131 | Added a configuration option, _LFN_UNICODE. 132 | Changed f_readdir() to return the SFN with always upper case on non-LFN cfg. 133 | 134 | 135 | 136 | R0.08 (May 15, 2010) 137 | 138 | Added a memory configuration option. (_USE_LFN = 3) 139 | Added file lock feature. (_FS_SHARE) 140 | Added fast seek feature. (_USE_FASTSEEK) 141 | Changed some types on the API, XCHAR->TCHAR. 142 | Changed .fname in the FILINFO structure on Unicode cfg. 143 | String functions support UTF-8 encoding files on Unicode cfg. 144 | 145 | 146 | 147 | R0.08a (August 16, 2010) 148 | 149 | Added f_getcwd(). (_FS_RPATH = 2) 150 | Added sector erase feature. (_USE_ERASE) 151 | Moved file lock semaphore table from fs object to the bss. 152 | Fixed f_mkfs() creates wrong FAT32 volume. 153 | 154 | 155 | 156 | R0.08b (January 15, 2011) 157 | 158 | Fast seek feature is also applied to f_read() and f_write(). 159 | f_lseek() reports required table size on creating CLMP. 160 | Extended format syntax of f_printf(). 161 | Ignores duplicated directory separators in given path name. 162 | 163 | 164 | 165 | R0.09 (September 06, 2011) 166 | 167 | f_mkfs() supports multiple partition to complete the multiple partition feature. 168 | Added f_fdisk(). 169 | 170 | 171 | 172 | R0.09a (August 27, 2012) 173 | 174 | Changed f_open() and f_opendir() reject null object pointer to avoid crash. 175 | Changed option name _FS_SHARE to _FS_LOCK. 176 | Fixed assertion failure due to OS/2 EA on FAT12/16 volume. 177 | 178 | 179 | 180 | R0.09b (January 24, 2013) 181 | 182 | Added f_setlabel() and f_getlabel(). 183 | 184 | 185 | 186 | R0.10 (October 02, 2013) 187 | 188 | Added selection of character encoding on the file. (_STRF_ENCODE) 189 | Added f_closedir(). 190 | Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO) 191 | Added forced mount feature with changes of f_mount(). 192 | Improved behavior of volume auto detection. 193 | Improved write throughput of f_puts() and f_printf(). 194 | Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write(). 195 | Fixed f_write() can be truncated when the file size is close to 4GB. 196 | Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error. 197 | 198 | 199 | 200 | R0.10a (January 15, 2014) 201 | 202 | Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID) 203 | Added a configuration option of minimum sector size. (_MIN_SS) 204 | 2nd argument of f_rename() can have a drive number and it will be ignored. 205 | Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10) 206 | Fixed f_close() invalidates the file object without volume lock. 207 | Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10) 208 | Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07) 209 | 210 | 211 | 212 | R0.10b (May 19, 2014) 213 | 214 | Fixed a hard error in the disk I/O layer can collapse the directory entry. 215 | Fixed LFN entry is not deleted when delete/rename an object with lossy converted SFN. (appeared at R0.07) 216 | 217 | 218 | 219 | R0.10c (November 09, 2014) 220 | 221 | Added a configuration option for the platforms without RTC. (_FS_NORTC) 222 | Changed option name _USE_ERASE to _USE_TRIM. 223 | Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b) 224 | Fixed a potential problem of FAT access that can appear on disk error. 225 | Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08) 226 | 227 | 228 | 229 | R0.11 (February 09, 2015) 230 | 231 | Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND) 232 | Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c) 233 | Fixed _FS_NORTC option does not work properly. (appeared at R0.10c) 234 | 235 | 236 | 237 | R0.11a (September 05, 2015) 238 | 239 | Fixed wrong media change can lead a deadlock at thread-safe configuration. 240 | Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE) 241 | Removed some code pages actually not exist on the standard systems. (_CODE_PAGE) 242 | Fixed errors in the case conversion teble of code page 437 and 850 (ff.c). 243 | Fixed errors in the case conversion teble of Unicode (cc*.c). 244 | 245 | 246 | 247 | R0.12 (April 12, 2016) 248 | 249 | Added support for exFAT file system. (_FS_EXFAT) 250 | Added f_expand(). (_USE_EXPAND) 251 | Changed some members in FINFO structure and behavior of f_readdir(). 252 | Added an option _USE_CHMOD. 253 | Removed an option _WORD_ACCESS. 254 | Fixed errors in the case conversion table of Unicode (cc*.c). 255 | 256 | 257 | 258 | R0.12a (July 10, 2016) 259 | 260 | Added support for creating exFAT volume with some changes of f_mkfs(). 261 | Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed. 262 | f_forward() is available regardless of _FS_TINY. 263 | Fixed f_mkfs() creates wrong volume. (appeared at R0.12) 264 | Fixed wrong memory read in create_name(). (appeared at R0.12) 265 | Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD. 266 | 267 | 268 | 269 | R0.12b (September 04, 2016) 270 | 271 | Made f_rename() be able to rename objects with the same name but case. 272 | Fixed an error in the case conversion teble of code page 866. (ff.c) 273 | Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12) 274 | Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12) 275 | Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12) 276 | Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12) 277 | Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12) 278 | Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12) 279 | 280 | 281 | 282 | R0.12c (March 04, 2017) 283 | 284 | Improved write throughput at the fragmented file on the exFAT volume. 285 | Made memory usage for exFAT be able to be reduced as decreasing _MAX_LFN. 286 | Fixed successive f_getfree() can return wrong count on the FAT12/16 volume. (appeared at R0.12) 287 | Fixed configuration option _VOLUMES cannot be set 10. (appeared at R0.10c) 288 | 289 | 290 | 291 | R0.13 (May 21, 2017) 292 | 293 | Changed heading character of configuration keywords "_" to "FF_". 294 | Removed ASCII-only configuration, FF_CODE_PAGE = 1. Use FF_CODE_PAGE = 437 instead. 295 | Added f_setcp(), run-time code page configuration. (FF_CODE_PAGE = 0) 296 | Improved cluster allocation time on stretch a deep buried cluster chain. 297 | Improved processing time of f_mkdir() with large cluster size by using FF_USE_LFN = 3. 298 | Improved NoFatChain flag of the fragmented file to be set after it is truncated and got contiguous. 299 | Fixed archive attribute is left not set when a file on the exFAT volume is renamed. (appeared at R0.12) 300 | Fixed exFAT FAT entry can be collapsed when write or lseek operation to the existing file is done. (appeared at R0.12c) 301 | Fixed creating a file can fail when a new cluster allocation to the exFAT directory occures. (appeared at R0.12c) 302 | 303 | 304 | 305 | R0.13a (October 14, 2017) 306 | 307 | Added support for UTF-8 encoding on the API. (FF_LFN_UNICODE = 2) 308 | Added options for file name output buffer. (FF_LFN_BUF, FF_SFN_BUF). 309 | Added dynamic memory allocation option for working buffer of f_mkfs() and f_fdisk(). 310 | Fixed f_fdisk() and f_mkfs() create the partition table with wrong CHS parameters. (appeared at R0.09) 311 | Fixed f_unlink() can cause lost clusters at fragmented file on the exFAT volume. (appeared at R0.12c) 312 | Fixed f_setlabel() rejects some valid characters for exFAT volume. (appeared at R0.12) 313 | 314 | 315 | 316 | R0.13b (April 07, 2018) 317 | 318 | Added support for UTF-32 encoding on the API. (FF_LFN_UNICODE = 3) 319 | Added support for Unix style volume ID. (FF_STR_VOLUME_ID = 2) 320 | Fixed accesing any object on the exFAT root directory beyond the cluster boundary can fail. (appeared at R0.12c) 321 | Fixed f_setlabel() does not reject some invalid characters. (appeared at R0.09b) 322 | 323 | 324 | 325 | R0.13c (October 14, 2018) 326 | Supported stdint.h for C99 and later. (integer.h was included in ff.h) 327 | Fixed reading a directory gets infinite loop when the last directory entry is not empty. (appeared at R0.12) 328 | Fixed creating a sub-directory in the fragmented sub-directory on the exFAT volume collapses FAT chain of the parent directory. (appeared at R0.12) 329 | Fixed f_getcwd() cause output buffer overrun when the buffer has a valid drive number. (appeared at R0.13b) 330 | 331 | 332 | 333 | R0.14 (October 14, 2019) 334 | Added support for 64-bit LBA and GUID partition table (FF_LBA64 = 1) 335 | Changed some API functions, f_mkfs() and f_fdisk(). 336 | Fixed f_open() function cannot find the file with file name in length of FF_MAX_LFN characters. 337 | Fixed f_readdir() function cannot retrieve long file names in length of FF_MAX_LFN - 1 characters. 338 | Fixed f_readdir() function returns file names with wrong case conversion. (appeared at R0.12) 339 | Fixed f_mkfs() function can fail to create exFAT volume in the second partition. (appeared at R0.12) 340 | 341 | 342 | R0.14a (December 5, 2020) 343 | Limited number of recursive calls in f_findnext(). 344 | Fixed old floppy disks formatted with MS-DOS 2.x and 3.x cannot be mounted. 345 | Fixed some compiler warnings. 346 | 347 | 348 | 349 | R0.14b (April 17, 2021) 350 | Made FatFs uses standard library for copy, compare and search instead of built-in string functions. 351 | Added support for long long integer and floating point to f_printf(). (FF_STRF_LLI and FF_STRF_FP) 352 | Made path name parser ignore the terminating separator to allow "dir/". 353 | Improved the compatibility in Unix style path name feature. 354 | Fixed the file gets dead-locked when f_open() failed with some conditions. (appeared at R0.12a) 355 | Fixed f_mkfs() can create wrong exFAT volume due to a timing dependent error. (appeared at R0.12) 356 | Fixed code page 855 cannot be set by f_setcp(). 357 | Fixed some compiler warnings. 358 | 359 | 360 | 361 | R0.15 (November 6, 2022) 362 | Changed user provided synchronization functions in order to completely eliminate the platform dependency from FatFs code. 363 | FF_SYNC_t is removed from the configuration options. 364 | Fixed a potential error in f_mount when FF_FS_REENTRANT. 365 | Fixed file lock control FF_FS_LOCK is not mutal excluded when FF_FS_REENTRANT && FF_VOLUMES > 1 is true. 366 | Fixed f_mkfs() creates broken exFAT volume when the size of volume is >= 2^32 sectors. 367 | Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8). 368 | Fixed a compatibility issue in identification of GPT header. 369 | 370 | -------------------------------------------------------------------------------- /source/Pico VSCode Project/a8_pico_cart/fatfs/ff.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------/ 2 | / FatFs - Generic FAT Filesystem module R0.15 / 3 | /-----------------------------------------------------------------------------/ 4 | / 5 | / Copyright (C) 2022, ChaN, all right reserved. 6 | / 7 | / FatFs module is an open source software. Redistribution and use of FatFs in 8 | / source and binary forms, with or without modification, are permitted provided 9 | / that the following condition is met: 10 | 11 | / 1. Redistributions of source code must retain the above copyright notice, 12 | / this condition and the following disclaimer. 13 | / 14 | / This software is provided by the copyright holder and contributors "AS IS" 15 | / and any warranties related to this software are DISCLAIMED. 16 | / The copyright owner or contributors be NOT LIABLE for any damages caused 17 | / by use of this software. 18 | / 19 | /----------------------------------------------------------------------------*/ 20 | 21 | 22 | #ifndef FF_DEFINED 23 | #define FF_DEFINED 80286 /* Revision ID */ 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #include "ffconf.h" /* FatFs configuration options */ 30 | 31 | #if FF_DEFINED != FFCONF_DEF 32 | #error Wrong configuration file (ffconf.h). 33 | #endif 34 | 35 | 36 | /* Integer types used for FatFs API */ 37 | 38 | #if defined(_WIN32) /* Windows VC++ (for development only) */ 39 | #define FF_INTDEF 2 40 | #include 41 | typedef unsigned __int64 QWORD; 42 | #include 43 | #define isnan(v) _isnan(v) 44 | #define isinf(v) (!_finite(v)) 45 | 46 | #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ 47 | #define FF_INTDEF 2 48 | #include 49 | typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ 50 | typedef unsigned char BYTE; /* char must be 8-bit */ 51 | typedef uint16_t WORD; /* 16-bit unsigned integer */ 52 | typedef uint32_t DWORD; /* 32-bit unsigned integer */ 53 | typedef uint64_t QWORD; /* 64-bit unsigned integer */ 54 | typedef WORD WCHAR; /* UTF-16 character type */ 55 | 56 | #else /* Earlier than C99 */ 57 | #define FF_INTDEF 1 58 | typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ 59 | typedef unsigned char BYTE; /* char must be 8-bit */ 60 | typedef unsigned short WORD; /* 16-bit unsigned integer */ 61 | typedef unsigned long DWORD; /* 32-bit unsigned integer */ 62 | typedef WORD WCHAR; /* UTF-16 character type */ 63 | #endif 64 | 65 | 66 | /* Type of file size and LBA variables */ 67 | 68 | #if FF_FS_EXFAT 69 | #if FF_INTDEF != 2 70 | #error exFAT feature wants C99 or later 71 | #endif 72 | typedef QWORD FSIZE_t; 73 | #if FF_LBA64 74 | typedef QWORD LBA_t; 75 | #else 76 | typedef DWORD LBA_t; 77 | #endif 78 | #else 79 | #if FF_LBA64 80 | #error exFAT needs to be enabled when enable 64-bit LBA 81 | #endif 82 | typedef DWORD FSIZE_t; 83 | typedef DWORD LBA_t; 84 | #endif 85 | 86 | 87 | 88 | /* Type of path name strings on FatFs API (TCHAR) */ 89 | 90 | #if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ 91 | typedef WCHAR TCHAR; 92 | #define _T(x) L ## x 93 | #define _TEXT(x) L ## x 94 | #elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ 95 | typedef char TCHAR; 96 | #define _T(x) u8 ## x 97 | #define _TEXT(x) u8 ## x 98 | #elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ 99 | typedef DWORD TCHAR; 100 | #define _T(x) U ## x 101 | #define _TEXT(x) U ## x 102 | #elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) 103 | #error Wrong FF_LFN_UNICODE setting 104 | #else /* ANSI/OEM code in SBCS/DBCS */ 105 | typedef char TCHAR; 106 | #define _T(x) x 107 | #define _TEXT(x) x 108 | #endif 109 | 110 | 111 | 112 | /* Definitions of volume management */ 113 | 114 | #if FF_MULTI_PARTITION /* Multiple partition configuration */ 115 | typedef struct { 116 | BYTE pd; /* Physical drive number */ 117 | BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ 118 | } PARTITION; 119 | extern PARTITION VolToPart[]; /* Volume - Partition mapping table */ 120 | #endif 121 | 122 | #if FF_STR_VOLUME_ID 123 | #ifndef FF_VOLUME_STRS 124 | extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ 125 | #endif 126 | #endif 127 | 128 | 129 | 130 | /* Filesystem object structure (FATFS) */ 131 | 132 | typedef struct { 133 | BYTE fs_type; /* Filesystem type (0:not mounted) */ 134 | BYTE pdrv; /* Volume hosting physical drive */ 135 | BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */ 136 | BYTE n_fats; /* Number of FATs (1 or 2) */ 137 | BYTE wflag; /* win[] status (b0:dirty) */ 138 | BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */ 139 | WORD id; /* Volume mount ID */ 140 | WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ 141 | WORD csize; /* Cluster size [sectors] */ 142 | #if FF_MAX_SS != FF_MIN_SS 143 | WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ 144 | #endif 145 | #if FF_USE_LFN 146 | WCHAR* lfnbuf; /* LFN working buffer */ 147 | #endif 148 | #if FF_FS_EXFAT 149 | BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ 150 | #endif 151 | #if !FF_FS_READONLY 152 | DWORD last_clst; /* Last allocated cluster */ 153 | DWORD free_clst; /* Number of free clusters */ 154 | #endif 155 | #if FF_FS_RPATH 156 | DWORD cdir; /* Current directory start cluster (0:root) */ 157 | #if FF_FS_EXFAT 158 | DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ 159 | DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ 160 | DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ 161 | #endif 162 | #endif 163 | DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ 164 | DWORD fsize; /* Number of sectors per FAT */ 165 | LBA_t volbase; /* Volume base sector */ 166 | LBA_t fatbase; /* FAT base sector */ 167 | LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */ 168 | LBA_t database; /* Data base sector */ 169 | #if FF_FS_EXFAT 170 | LBA_t bitbase; /* Allocation bitmap base sector */ 171 | #endif 172 | LBA_t winsect; /* Current sector appearing in the win[] */ 173 | BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ 174 | } FATFS; 175 | 176 | 177 | 178 | /* Object ID and allocation information (FFOBJID) */ 179 | 180 | typedef struct { 181 | FATFS* fs; /* Pointer to the hosting volume of this object */ 182 | WORD id; /* Hosting volume's mount ID */ 183 | BYTE attr; /* Object attribute */ 184 | BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ 185 | DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ 186 | FSIZE_t objsize; /* Object size (valid when sclust != 0) */ 187 | #if FF_FS_EXFAT 188 | DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ 189 | DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ 190 | DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ 191 | DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ 192 | DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ 193 | #endif 194 | #if FF_FS_LOCK 195 | UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ 196 | #endif 197 | } FFOBJID; 198 | 199 | 200 | 201 | /* File object structure (FIL) */ 202 | 203 | typedef struct { 204 | FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ 205 | BYTE flag; /* File status flags */ 206 | BYTE err; /* Abort flag (error code) */ 207 | FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ 208 | DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ 209 | LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */ 210 | #if !FF_FS_READONLY 211 | LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ 212 | BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ 213 | #endif 214 | #if FF_USE_FASTSEEK 215 | DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ 216 | #endif 217 | #if !FF_FS_TINY 218 | BYTE buf[FF_MAX_SS]; /* File private data read/write window */ 219 | #endif 220 | } FIL; 221 | 222 | 223 | 224 | /* Directory object structure (DIR) */ 225 | 226 | typedef struct { 227 | FFOBJID obj; /* Object identifier */ 228 | DWORD dptr; /* Current read/write offset */ 229 | DWORD clust; /* Current cluster */ 230 | LBA_t sect; /* Current sector (0:Read operation has terminated) */ 231 | BYTE* dir; /* Pointer to the directory item in the win[] */ 232 | BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ 233 | #if FF_USE_LFN 234 | DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ 235 | #endif 236 | #if FF_USE_FIND 237 | const TCHAR* pat; /* Pointer to the name matching pattern */ 238 | #endif 239 | } DIR; 240 | 241 | 242 | 243 | /* File information structure (FILINFO) */ 244 | 245 | typedef struct { 246 | FSIZE_t fsize; /* File size */ 247 | WORD fdate; /* Modified date */ 248 | WORD ftime; /* Modified time */ 249 | BYTE fattrib; /* File attribute */ 250 | #if FF_USE_LFN 251 | TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */ 252 | TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ 253 | #else 254 | TCHAR fname[12 + 1]; /* File name */ 255 | #endif 256 | } FILINFO; 257 | 258 | 259 | 260 | /* Format parameter structure (MKFS_PARM) */ 261 | 262 | typedef struct { 263 | BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */ 264 | BYTE n_fat; /* Number of FATs */ 265 | UINT align; /* Data area alignment (sector) */ 266 | UINT n_root; /* Number of root directory entries */ 267 | DWORD au_size; /* Cluster size (byte) */ 268 | } MKFS_PARM; 269 | 270 | 271 | 272 | /* File function return code (FRESULT) */ 273 | 274 | typedef enum { 275 | FR_OK = 0, /* (0) Succeeded */ 276 | FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ 277 | FR_INT_ERR, /* (2) Assertion failed */ 278 | FR_NOT_READY, /* (3) The physical drive cannot work */ 279 | FR_NO_FILE, /* (4) Could not find the file */ 280 | FR_NO_PATH, /* (5) Could not find the path */ 281 | FR_INVALID_NAME, /* (6) The path name format is invalid */ 282 | FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ 283 | FR_EXIST, /* (8) Access denied due to prohibited access */ 284 | FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ 285 | FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ 286 | FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ 287 | FR_NOT_ENABLED, /* (12) The volume has no work area */ 288 | FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ 289 | FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ 290 | FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ 291 | FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ 292 | FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ 293 | FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ 294 | FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ 295 | } FRESULT; 296 | 297 | 298 | 299 | 300 | /*--------------------------------------------------------------*/ 301 | /* FatFs Module Application Interface */ 302 | /*--------------------------------------------------------------*/ 303 | 304 | FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ 305 | FRESULT f_close (FIL* fp); /* Close an open file object */ 306 | FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ 307 | FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ 308 | FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ 309 | FRESULT f_truncate (FIL* fp); /* Truncate the file */ 310 | FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ 311 | FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ 312 | FRESULT f_closedir (DIR* dp); /* Close an open directory */ 313 | FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ 314 | FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ 315 | FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ 316 | FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ 317 | FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ 318 | FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ 319 | FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */ 320 | FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ 321 | FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ 322 | FRESULT f_chdir (const TCHAR* path); /* Change current directory */ 323 | FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ 324 | FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ 325 | FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ 326 | FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ 327 | FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ 328 | FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ 329 | FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ 330 | FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ 331 | FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ 332 | FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ 333 | FRESULT f_setcp (WORD cp); /* Set current code page */ 334 | int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ 335 | int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ 336 | int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ 337 | TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ 338 | 339 | /* Some API fucntions are implemented as macro */ 340 | 341 | #define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) 342 | #define f_error(fp) ((fp)->err) 343 | #define f_tell(fp) ((fp)->fptr) 344 | #define f_size(fp) ((fp)->obj.objsize) 345 | #define f_rewind(fp) f_lseek((fp), 0) 346 | #define f_rewinddir(dp) f_readdir((dp), 0) 347 | #define f_rmdir(path) f_unlink(path) 348 | #define f_unmount(path) f_mount(0, path, 0) 349 | 350 | 351 | 352 | 353 | /*--------------------------------------------------------------*/ 354 | /* Additional Functions */ 355 | /*--------------------------------------------------------------*/ 356 | 357 | /* RTC function (provided by user) */ 358 | #if !FF_FS_READONLY && !FF_FS_NORTC 359 | DWORD get_fattime (void); /* Get current time */ 360 | #endif 361 | 362 | 363 | /* LFN support functions (defined in ffunicode.c) */ 364 | 365 | #if FF_USE_LFN >= 1 366 | WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ 367 | WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ 368 | DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ 369 | #endif 370 | 371 | 372 | /* O/S dependent functions (samples available in ffsystem.c) */ 373 | 374 | #if FF_USE_LFN == 3 /* Dynamic memory allocation */ 375 | void* ff_memalloc (UINT msize); /* Allocate memory block */ 376 | void ff_memfree (void* mblock); /* Free memory block */ 377 | #endif 378 | #if FF_FS_REENTRANT /* Sync functions */ 379 | int ff_mutex_create (int vol); /* Create a sync object */ 380 | void ff_mutex_delete (int vol); /* Delete a sync object */ 381 | int ff_mutex_take (int vol); /* Lock sync object */ 382 | void ff_mutex_give (int vol); /* Unlock sync object */ 383 | #endif 384 | 385 | 386 | 387 | 388 | /*--------------------------------------------------------------*/ 389 | /* Flags and Offset Address */ 390 | /*--------------------------------------------------------------*/ 391 | 392 | /* File access mode and open method flags (3rd argument of f_open) */ 393 | #define FA_READ 0x01 394 | #define FA_WRITE 0x02 395 | #define FA_OPEN_EXISTING 0x00 396 | #define FA_CREATE_NEW 0x04 397 | #define FA_CREATE_ALWAYS 0x08 398 | #define FA_OPEN_ALWAYS 0x10 399 | #define FA_OPEN_APPEND 0x30 400 | 401 | /* Fast seek controls (2nd argument of f_lseek) */ 402 | #define CREATE_LINKMAP ((FSIZE_t)0 - 1) 403 | 404 | /* Format options (2nd argument of f_mkfs) */ 405 | #define FM_FAT 0x01 406 | #define FM_FAT32 0x02 407 | #define FM_EXFAT 0x04 408 | #define FM_ANY 0x07 409 | #define FM_SFD 0x08 410 | 411 | /* Filesystem type (FATFS.fs_type) */ 412 | #define FS_FAT12 1 413 | #define FS_FAT16 2 414 | #define FS_FAT32 3 415 | #define FS_EXFAT 4 416 | 417 | /* File attribute bits for directory entry (FILINFO.fattrib) */ 418 | #define AM_RDO 0x01 /* Read only */ 419 | #define AM_HID 0x02 /* Hidden */ 420 | #define AM_SYS 0x04 /* System */ 421 | #define AM_DIR 0x10 /* Directory */ 422 | #define AM_ARC 0x20 /* Archive */ 423 | 424 | 425 | #ifdef __cplusplus 426 | } 427 | #endif 428 | 429 | #endif /* FF_DEFINED */ 430 | -------------------------------------------------------------------------------- /source/Atari Boot ROM/A8PicoCart.asm: -------------------------------------------------------------------------------- 1 | /* Boot ROM for A8PicoCart 2 | * by Robin Edwards/Electrotrains@AtariAge 3 | * This file builds with WUDSN/MADS into an 8K Atari ROM 4 | * The 8k ROM should be converted into a C include file using: 5 | * xxd -i A8PicoCart.ROM > rom.h 6 | */ 7 | 8 | /* 9 | Theory of Operation 10 | ------------------- 11 | Atari sends command to mcu on cart by writing to $D5DF ($D5E0-$D5FF = SDX) 12 | (extra paramters for the command in $D500-$D5DE) 13 | Atari must be running from RAM when it sends a command, since the mcu on the cart will 14 | go away at that point. 15 | Atari polls $D500 until it reads $11. At this point it knows the mcu is back 16 | and it is safe to rts back to code in cartridge ROM again. 17 | Results of the command are in $D501-$D5DF 18 | */ 19 | 20 | CART_CMD_OPEN_ITEM = $0 21 | CART_CMD_READ_CUR_DIR = $1 22 | CART_CMD_GET_DIR_ENTRY = $2 23 | CART_CMD_UP_DIR = $3 24 | CART_CMD_ROOT_DIR = $4 25 | CART_CMD_SEARCH = $5 26 | CART_CMD_LOAD_SOFT_OS = $10 27 | CART_CMD_SOFT_OS_CHUNK = $11 28 | CART_CMD_RESET_FLASH = $F0 29 | CART_CMD_NO_CART = $FE 30 | CART_CMD_ACTIVATE_CART = $FF 31 | 32 | DIR_START_ROW = 7 33 | DIR_END_ROW = 21 34 | ITEMS_PER_PAGE = DIR_END_ROW-DIR_START_ROW+1 35 | 36 | ;@com.wudsn.ide.asm.outputfileextension=.rom 37 | 38 | ;CARTCS = $bffa ;Start address vector, used if CARTFG has CARTFG_START_CART bit set 39 | ;CART = $bffc ;Flag, must be zero for modules 40 | ;CARTFG = $bffd ;Flags or-ed together, indicating how to start the module. 41 | ;CARTAD = $bffe ;Initialization address vector 42 | 43 | CARTFG_DIAGNOSTIC_CART = $80 ;Flag value: Directly jump via CARTAD during RESET. 44 | CARTFG_START_CART = $04 ;Flag value: Jump via CARTAD and then via CARTCS. 45 | CARTFG_BOOT = $01 ;Flag value: Boot peripherals, then start the module. 46 | 47 | COLDSV = $E477 ; Coldstart (powerup) entry point 48 | WARMSV = $E474 ; Warmstart entry point 49 | CH = $2FC ; Internal hardware value for the last key pressed 50 | BOOT = $09 51 | CASINI = $02 52 | OSROM = $C000 53 | PORTB = $D301 54 | NMIEN = $D40E 55 | PMBASE = $D407 56 | SDMCTL = $22F 57 | GPRIOR = $26F 58 | PCOLR0 = $2C0 59 | PCOLR1 = $2C1 60 | PCOLR2 = $2C2 61 | PCOLR3 = $2C3 62 | COLOR0 = $2C4 63 | COLOR1 = $2C5 64 | COLOR2 = $2C6 65 | COLOR3 = $2C7 66 | COLOR4 = $2C8 67 | STICK0 = $278 68 | 69 | HPosP0 equ $D000 70 | HPosP1 equ $D001 71 | HPosP2 equ $D002 72 | HPosP3 equ $D003 73 | HPosM0 equ $D004 74 | HPosM1 equ $D005 75 | HPosM2 equ $D006 76 | HPosM3 equ $D007 77 | SizeP0 equ $D008 78 | SizeP1 equ $D009 79 | SizeP2 equ $D00A 80 | SizeP3 equ $D00B 81 | SizeM equ $D00C 82 | GrafP0 equ $D00D 83 | GrafP1 equ $D00E 84 | GrafP2 equ $D00F 85 | GrafP3 equ $D010 86 | Trig0 equ $D010 87 | ColPM2 equ $D014 88 | GRACTL equ $D01D 89 | 90 | sm_ptr = $58 ; screen memory 91 | search_string = $600 92 | wait_for_cart = $620 ; routine copied here 93 | reboot_to_selected_cart = $630 ; routine copied here 94 | 95 | PMBuffer = $800 96 | Player0Data = $A00 97 | Player1Data = $A80 98 | Player2Data = $B00 99 | Player3Data = $B80 100 | 101 | ; ************************ VARIABLES **************************** 102 | num_dir_entries = $80 103 | dir_entry = $81 104 | ypos = $82 105 | cur_ypos = $83 106 | top_item = $84 107 | cur_item = $85 108 | search_text_len = $86 109 | search_results_mode = $87 110 | trigger_state = $88 111 | stick_state = $89 112 | stick_timer = $8a 113 | trigger_pressed = $8b 114 | stick_input = $8c 115 | tmp_ptr = $90 // word 116 | text_out_x = $92 // word 117 | text_out_y = $94 // word 118 | text_out_ptr = $96 // word 119 | text_out_len = $98 120 | cur_chunk = $99 121 | 122 | ; XEX loader stuff from Jon Halliday/FJC 123 | LoaderAddress equ $700 124 | VER_MAJ equ $01 125 | VER_MIN equ $02 126 | FMSZPG equ $43 127 | Critic equ $42 128 | IOPtr equ FMSZPG 129 | FileSize equ FMSZPG+2 ; .ds 4 130 | ptr1 equ FMSZPG 131 | ptr2 equ FMSZPG+2 132 | ptr3 equ FMSZPG+4 133 | DOSINI equ $0C 134 | MEMLO equ $02E7 135 | RunVec equ $02E0 136 | IniVec equ $02E2 137 | VCOUNT equ $D40B 138 | WSYNC equ $D40A 139 | GINTLK equ $03FA 140 | TRIG3 equ $D013 141 | SDLSTL equ $230 142 | CIOV equ $E456 143 | 144 | ; CIO Error Codes 145 | .enum IOErr 146 | AlreadyOpen = 129 147 | NotOpen = 133 148 | EOF = 136 149 | NAK = 139 150 | NoFunction = 146 151 | BadName = 165 152 | NotFound = 170 153 | .ende 154 | 155 | .struct IOCBlock 156 | ID .byte 157 | DevNum .byte 158 | Command .byte 159 | Status .byte 160 | Address .word 161 | Put .word ; put byte address 162 | Len .word 163 | Aux1 .byte 164 | Aux2 .byte 165 | Aux3 .byte 166 | Aux4 .byte 167 | Aux5 .byte 168 | Aux6 .byte 169 | .ends 170 | 171 | org $0340 172 | 173 | IOCB dta IOCBlock [8] 174 | 175 | ; CIO commands 176 | 177 | .enum IOCommand 178 | Open = $03 179 | GetText = $05 180 | Read = $07 181 | PutText = $09 182 | Write = $0B 183 | Close = $0C 184 | Status = $0D 185 | .ende 186 | 187 | ; ************************ CODE **************************** 188 | 189 | 190 | opt h- ;Disable Atari COM/XEX file headers 191 | 192 | org $a000 ;RD5 cartridge base 193 | opt f+ ;Activate fill mode 194 | 195 | ;Cartridge initalization 196 | ;Only the minimum of the OS initialization is complete, you don't want to code here normally. 197 | init .proc 198 | rts 199 | .endp ; proc init 200 | 201 | ;Cartridge start 202 | ;RAM, graphics 0 and IOCB no for the editor (E:) are ready 203 | start .proc 204 | lda ColPM2 205 | cmp #1 206 | beq pal_cols 207 | ntsc_cols 208 | mva #$6F COLOR1 209 | mva #$62 COLOR2 210 | jmp patch_boot 211 | pal_cols 212 | mva #$4F COLOR1 213 | mva #$42 COLOR2 214 | 215 | patch_boot 216 | mva #3 BOOT ; patch reset - from mapping the atari (revised) appendix 11 217 | mwa #reset_routine CASINI 218 | 219 | jsr display_boot_screen 220 | jsr copy_wait_for_cart 221 | jsr copy_reboot_to_selected_cart 222 | jsr setup_pmg 223 | jsr init_joystick 224 | 225 | ; check for trigger pressed on startup to reset flash on cartridge 226 | lda Trig0 227 | bne read_current_directory 228 | jsr reset_flash_prompt 229 | 230 | ; read directory 231 | read_current_directory 232 | mva #0 search_results_mode 233 | lda #CART_CMD_READ_CUR_DIR 234 | jsr wait_for_cart 235 | check_read_dir 236 | lda $D501 237 | cmp #1 ; check for error flag 238 | bne read_dir_ok 239 | jsr display_error_msg_from_cart 240 | jmp read_current_directory 241 | 242 | read_dir_ok 243 | lda $D502 244 | sta num_dir_entries 245 | mva #0 top_item 246 | mva #0 cur_item 247 | 248 | ; display_directory 249 | display_directory 250 | jsr output_header_text 251 | jsr clear_screen 252 | 253 | lda num_dir_entries 254 | bne dir_ok 255 | 256 | no_dir jsr output_empty_dir_msg 257 | jsr hide_pmg_cursor 258 | jmp main_loop 259 | 260 | dir_ok jsr output_directory 261 | jsr draw_cursor 262 | 263 | main_loop 264 | jsr wait_for_vsync 265 | jsr GetKey 266 | beq check_joystick 267 | cmp #$1C ; cur up 268 | beq up_pressed 269 | cmp #'-' 270 | beq up_pressed 271 | 272 | cmp #$1D ; cur down 273 | beq down_pressed 274 | cmp #'=' 275 | beq down_pressed 276 | 277 | cmp #'b' 278 | bne _1 279 | jmp back_pressed 280 | _1 cmp #$1E ; cur left 281 | bne _2 282 | jmp back_pressed 283 | 284 | _2 cmp #$9B ; ret 285 | bne _3 286 | jmp return_pressed 287 | 288 | _3 cmp #'x' 289 | bne _4 290 | jmp disable_pressed 291 | 292 | _4 cmp #$1B ; esc 293 | bne _5 294 | jmp search_pressed 295 | _5 296 | check_joystick 297 | jsr read_joystick 298 | lda trigger_pressed 299 | cmp #1 300 | beq return_pressed 301 | 302 | lda stick_input 303 | and #$01 304 | bne up_pressed 305 | 306 | lda stick_input 307 | and #$02 308 | bne down_pressed 309 | 310 | jmp main_loop 311 | 312 | down_pressed 313 | lda cur_item 314 | clc 315 | adc #1 316 | cmp num_dir_entries 317 | bcs main_loop 318 | ; single row down 319 | inc cur_item 320 | ; do we need to page down? 321 | lda cur_item 322 | sec 323 | sbc top_item 324 | clc 325 | cmp #ITEMS_PER_PAGE 326 | beq page_down 327 | jsr draw_cursor 328 | jmp main_loop 329 | page_down 330 | lda top_item 331 | clc 332 | adc #ITEMS_PER_PAGE 333 | sta top_item 334 | jmp display_directory 335 | 336 | up_pressed 337 | lda cur_item 338 | cmp #0 339 | beq main_loop 340 | ; single row up 341 | dec cur_item 342 | ; do we need to page up 343 | lda cur_item 344 | cmp top_item 345 | bmi page_up 346 | jsr draw_cursor 347 | jmp main_loop 348 | page_up 349 | lda top_item 350 | sec 351 | sbc #ITEMS_PER_PAGE 352 | sta top_item 353 | jmp display_directory 354 | 355 | return_pressed 356 | lda num_dir_entries 357 | bne return_pressed_ok ; check for empty dir 358 | jmp main_loop 359 | return_pressed_ok 360 | lda cur_item 361 | sta $D500 362 | lda #CART_CMD_OPEN_ITEM 363 | jsr wait_for_cart 364 | 365 | lda $D501 ; look at return code from cart 366 | cmp #0 367 | beq directory_changed 368 | cmp #1 369 | beq file_loaded 370 | cmp #2 371 | beq xex_loaded 372 | cmp #3 373 | beq atr_loaded 374 | ; if we get here, there was an error 375 | jsr display_error_msg_from_cart 376 | jmp read_current_directory 377 | directory_changed 378 | jmp read_current_directory 379 | file_loaded 380 | jmp reboot_to_selected_cart 381 | xex_loaded 382 | jmp launch_xex 383 | atr_loaded 384 | jmp launch_atr 385 | 386 | back_pressed 387 | lda search_results_mode 388 | cmp #1 389 | beq exit_search_results 390 | lda #CART_CMD_UP_DIR 391 | jsr wait_for_cart 392 | exit_search_results 393 | jmp read_current_directory 394 | 395 | disable_pressed 396 | lda #CART_CMD_NO_CART 397 | jsr wait_for_cart 398 | jmp reboot_to_selected_cart 399 | 400 | launch_xex 401 | jsr disable_pmg 402 | jsr copy_XEX_loader 403 | jmp LoadBinaryFile 404 | 405 | launch_atr 406 | jsr disable_pmg 407 | jsr copy_soft_rom 408 | cmp #0 409 | beq reboot_atr 410 | jmp read_current_directory 411 | reboot_atr 412 | jmp reboot_to_selected_cart 413 | 414 | search_pressed 415 | jsr output_search_box 416 | jsr get_search_string 417 | lda search_text_len 418 | cmp #0 419 | bne search 420 | jmp display_directory 421 | search 422 | ; copy search_text_len bytes from search_text to $D5xx 423 | ldy #0 424 | @ lda search_string,y 425 | sta $D500,y 426 | iny 427 | tya 428 | cmp search_text_len 429 | bcc @- 430 | ; null terminate 431 | lda #0 432 | sta $D500,y 433 | 434 | jsr clear_screen 435 | jsr output_searching_msg 436 | 437 | lda #CART_CMD_SEARCH 438 | jsr wait_for_cart 439 | mva #1 search_results_mode 440 | jmp check_read_dir 441 | .endp ; proc start 442 | 443 | 444 | ; ************************ SUBROUTINES **************************** 445 | .proc init_joystick 446 | mva #$01 trigger_state 447 | mva #$0F stick_state 448 | mva #0 stick_timer 449 | rts 450 | .endp 451 | 452 | .proc read_joystick 453 | mva #0 trigger_pressed 454 | mva #0 stick_input 455 | 456 | lda stick_timer 457 | beq @+ 458 | dec stick_timer 459 | 460 | @ 461 | lda Trig0 462 | cmp trigger_state 463 | bne trigger_change ; trigger change 464 | 465 | lda STICK0 466 | and #$0F 467 | cmp stick_state 468 | bne stick_change ; stick change 469 | ldy stick_timer 470 | beq stick_change 471 | rts 472 | stick_change 473 | sta stick_state 474 | EOR #$0F 475 | sta stick_input 476 | ldy #8 477 | sty stick_timer 478 | rts 479 | trigger_change ; trigger has either gone up or down 480 | sta trigger_state 481 | cmp #0 482 | bne done 483 | mva #1 trigger_pressed 484 | done 485 | rts 486 | .endp 487 | 488 | .proc wait_for_vsync 489 | lda VCount 490 | rne 491 | lda VCount 492 | req 493 | rts 494 | .endp 495 | 496 | .proc copy_soft_rom 497 | lda #CART_CMD_LOAD_SOFT_OS 498 | jsr wait_for_cart 499 | lda $D501 500 | cmp #1 ; check for error flag 501 | bne read_ok 502 | jsr display_error_msg_from_cart 503 | lda #1 504 | rts 505 | read_ok 506 | ; the following is from Appendix 12 of Mapping the Atari (revised), pg218 507 | swap php ; save processor status 508 | sei ; disable irqs 509 | lda NMIEN 510 | pha ; save NMIEN 511 | lda #0 512 | sta NMIEN 513 | ; set colors 514 | mva #$B2 $D017 515 | mva #$B2 $D018 516 | ; switch ROM to RAM 517 | LDA PORTB 518 | AND #$FE 519 | STA PORTB 520 | ; copy 521 | 522 | mwa #OSROM tmp_ptr 523 | mva #0 cur_chunk 524 | copy_page 525 | lda tmp_ptr+1 526 | cmp #$D0 527 | bne copy_first_half 528 | ; skip $D000-D800 529 | lda #$D8 530 | sta tmp_ptr+1 531 | lda #$30 532 | sta cur_chunk 533 | copy_first_half 534 | lda #$00 535 | sta tmp_ptr 536 | ; fetch the first 128 bytes from the cartridge 537 | lda cur_chunk 538 | sta $D500 539 | lda #CART_CMD_SOFT_OS_CHUNK 540 | jsr wait_for_cart 541 | ldy #0 542 | @ lda $D501,y 543 | sta (tmp_ptr),y 544 | iny 545 | cpy #128 546 | bne @- 547 | inc cur_chunk 548 | copy_second_half 549 | lda #$80 550 | sta tmp_ptr 551 | ; fetch the next 128 bytes from the cartridge 552 | lda cur_chunk 553 | sta $D500 554 | lda #CART_CMD_SOFT_OS_CHUNK 555 | jsr wait_for_cart 556 | ldy #0 557 | @ lda $D501,y 558 | sta (tmp_ptr),y 559 | iny 560 | cpy #128 561 | bne @- 562 | inc cur_chunk 563 | ; move to the next page 564 | inc tmp_ptr+1 565 | bne copy_page 566 | 567 | enable pla 568 | sta NMIEN 569 | plp 570 | lda #0 571 | rts 572 | .endp 573 | 574 | .proc get_search_string 575 | mva #0 search_text_len 576 | jmp output 577 | loop 578 | jsr GetKey 579 | beq loop 580 | cmp #$1B ; esc 581 | beq cancel 582 | cmp #$7E; del 583 | beq delete 584 | cmp #$9B ; ret 585 | beq done 586 | ldy search_text_len 587 | cpy #14 588 | beq loop 589 | jmp newchar 590 | delete 591 | lda search_text_len 592 | beq loop 593 | ; erase character 594 | clc 595 | adc #16 596 | sta text_out_x 597 | mwa #cursor_text text_out_ptr 598 | mva #1 text_out_len 599 | jsr output_text 600 | 601 | dec search_text_len 602 | jmp output 603 | newchar 604 | ldy search_text_len 605 | sta search_string,y 606 | inc search_text_len 607 | output 608 | mva #16 text_out_x 609 | mva #9 text_out_y 610 | mwa #search_string text_out_ptr 611 | mva search_text_len text_out_len 612 | jsr output_text 613 | ; draw cursor 614 | lda text_out_x 615 | clc 616 | adc text_out_len 617 | sta text_out_x 618 | mwa #cursor_text text_out_ptr 619 | mva #1 text_out_len 620 | jsr output_text_inverted 621 | 622 | jmp loop 623 | cancel mva #0 search_text_len 624 | done rts 625 | .endp 626 | .proc reset_routine 627 | mva #3 BOOT 628 | lda #CART_CMD_ROOT_DIR ; tell the mcu we've done a reset 629 | jsr wait_for_cart 630 | rts 631 | .endp 632 | 633 | .proc display_error_msg_from_cart 634 | jsr hide_pmg_cursor 635 | mva #1 text_out_x 636 | mva #8 text_out_y 637 | mwa #error_text1 text_out_ptr 638 | mva #(.len error_text1) text_out_len 639 | jsr output_text_internal 640 | inc text_out_y 641 | mwa #error_text2 text_out_ptr 642 | mva #(.len error_text2) text_out_len 643 | jsr output_text_internal 644 | inc text_out_y 645 | mwa #error_text3 text_out_ptr 646 | mva #(.len error_text3) text_out_len 647 | jsr output_text_internal 648 | ; display the actual errro 649 | mva #8 text_out_x 650 | mva #9 text_out_y 651 | mwa #$D502 text_out_ptr 652 | mva #30 text_out_len 653 | jsr output_text 654 | jsr wait_key 655 | rts 656 | .endp 657 | 658 | .proc reset_flash_prompt 659 | jsr hide_pmg_cursor 660 | mva #1 text_out_x 661 | mva #8 text_out_y 662 | mwa #reset_flash_text1 text_out_ptr 663 | mva #(.len reset_flash_text1) text_out_len 664 | jsr output_text_internal 665 | inc text_out_y 666 | mwa #reset_flash_text2 text_out_ptr 667 | mva #(.len reset_flash_text2) text_out_len 668 | jsr output_text_internal 669 | inc text_out_y 670 | mwa #reset_flash_text3 text_out_ptr 671 | mva #(.len reset_flash_text3) text_out_len 672 | jsr output_text_internal 673 | @ jsr GetKey 674 | beq @- 675 | cmp #'r' 676 | beq reset 677 | rts 678 | 679 | reset lda #CART_CMD_RESET_FLASH 680 | jsr wait_for_cart 681 | rts 682 | .endp 683 | 684 | .proc output_directory 685 | mva top_item dir_entry 686 | mva #DIR_START_ROW ypos 687 | next_entry 688 | ldy ypos 689 | dey 690 | tya 691 | cmp #DIR_END_ROW 692 | beq end_of_page 693 | lda dir_entry 694 | cmp num_dir_entries 695 | beq end_of_page 696 | sta $D500 697 | lda #CART_CMD_GET_DIR_ENTRY ; request from mcu 698 | jsr wait_for_cart 699 | 700 | ; output the directory entry 701 | mva ypos text_out_y 702 | mva #4 text_out_x 703 | ldx $D501 ; 0 = file, 1 = folder 704 | mwa #$D502 text_out_ptr 705 | ; mwa #test_text text_out_ptr 706 | mva #31 text_out_len 707 | cpx #1 708 | beq folder 709 | file jsr output_text 710 | jmp next 711 | folder jsr output_text 712 | mva #0 text_out_x 713 | mva #3 text_out_len 714 | mwa #folder_text text_out_ptr 715 | jsr output_text_inverted 716 | next inc ypos 717 | inc dir_entry 718 | jmp next_entry 719 | end_of_page 720 | rts 721 | .endp 722 | 723 | ; clear screen 724 | clear_screen .proc 725 | mwa sm_ptr tmp_ptr 726 | ldy #DIR_START_ROW 727 | @ dey 728 | bmi yend 729 | adw tmp_ptr #40 730 | jmp @- 731 | yend 732 | ldx #ITEMS_PER_PAGE ; number of lines to clear 733 | yloop lda #0 734 | ldy #39 735 | xloop sta (tmp_ptr),y 736 | dey 737 | bpl xloop 738 | adw tmp_ptr #40 739 | dex 740 | bne yloop 741 | rts 742 | .endp 743 | 744 | .proc draw_cursor 745 | lda cur_item 746 | sec 747 | sbc top_item 748 | clc 749 | adc #DIR_START_ROW 750 | sta cur_ypos 751 | jsr draw_pmg_cursor 752 | rts 753 | .endp 754 | 755 | ; Scan keyboard (returns N = 1 for no key pressed, else ASCII in A) 756 | .proc GetKey 757 | ldx CH 758 | cpx #$FF 759 | beq NoKey 760 | mva #$FF CH ; set last key pressed to none 761 | lda scancodes,x 762 | cmp #$FF 763 | NoKey 764 | rts 765 | .endp 766 | 767 | .proc setup_pmg 768 | mva #>PMBuffer PMBASE 769 | mva #$2E SDMCTL 770 | 771 | mva #$3 SizeP0 772 | mva #$48 PCOLR0 773 | mva #$40 HPosP0 774 | 775 | mva #$3 SizeP1 776 | mva #$48 PCOLR1 777 | mva #$60 HPosP1 778 | 779 | mva #$3 SizeP2 780 | mva #$48 PCOLR2 781 | mva #$80 HPosP2 782 | 783 | mva #$3 SizeP3 784 | mva #$48 PCOLR3 785 | mva #$A0 HPosP3 786 | 787 | mva #$1 GPRIOR 788 | rts 789 | .endp 790 | 791 | .proc disable_pmg 792 | mva #34 SDMCTL 793 | lda #0 794 | sta GRACTL 795 | ldy #$0c 796 | @ 797 | sta $D000,y 798 | dey 799 | bpl @- 800 | 801 | mva #$0 PCOLR0 802 | mva #$0 PCOLR1 803 | mva #$0 PCOLR2 804 | mva #$0 PCOLR3 805 | 806 | lda:cmp:req 20 ; wait vbl 807 | rts 808 | .endp 809 | 810 | .proc hide_pmg_cursor 811 | lda:cmp:req 20 ; wait vbl 812 | mva #$0 GRACTL 813 | rts 814 | .endp 815 | 816 | .proc draw_pmg_cursor 817 | lda:cmp:req 20 ; wait vbl 818 | mva #$3 GRACTL 819 | lda #0 ; clear pmg memory 820 | ldy #127 821 | @ sta Player0Data,y 822 | sta Player1Data,y 823 | sta Player2Data,y 824 | sta Player3Data,y 825 | dey 826 | bpl @- 827 | 828 | lda #$C ; offset row 0 829 | ldy cur_ypos 830 | clc 831 | @ adc #4 ; skip character lines 832 | dey 833 | bpl @- 834 | tay 835 | 836 | lda #$FF ; draw 837 | ldx #3 838 | @ sta Player0Data,y 839 | sta Player1Data,y 840 | sta Player2Data,y 841 | sta Player3Data,y 842 | iny 843 | dex 844 | bpl @- 845 | rts 846 | .endp 847 | 848 | .proc display_boot_screen 849 | mva #0 text_out_x 850 | mva #0 text_out_y 851 | mwa #menu_text1 text_out_ptr 852 | mva #(.len menu_text1) text_out_len 853 | jsr output_text_internal 854 | inc text_out_y 855 | mwa #menu_text2 text_out_ptr 856 | mva #(.len menu_text2) text_out_len 857 | jsr output_text_internal 858 | inc text_out_y 859 | mwa #menu_text3 text_out_ptr 860 | mva #(.len menu_text3) text_out_len 861 | jsr output_text_internal 862 | inc text_out_y 863 | mwa #menu_text4 text_out_ptr 864 | mva #(.len menu_text4) text_out_len 865 | jsr output_text_internal 866 | inc text_out_y 867 | mwa #menu_text5 text_out_ptr 868 | mva #(.len menu_text5) text_out_len 869 | jsr output_text_internal 870 | mva #23 text_out_y 871 | mwa #menu_text_bottom text_out_ptr 872 | mva #(.len menu_text_bottom) text_out_len 873 | jsr output_text_inverted 874 | rts 875 | .endp 876 | 877 | .proc output_header_text 878 | mva #9 text_out_x 879 | mva #DIR_START_ROW-2 text_out_y 880 | lda search_results_mode 881 | bne _2 882 | _1 mwa #directory_text text_out_ptr 883 | mva #(.len directory_text) text_out_len 884 | jmp out 885 | _2 mwa #search_results_text text_out_ptr 886 | mva #(.len search_results_text) text_out_len 887 | out jsr output_text_inverted 888 | rts 889 | .endp 890 | 891 | .proc output_empty_dir_msg 892 | mva #6 text_out_x 893 | mva #DIR_START_ROW+1 text_out_y 894 | mwa #empty_dir_text text_out_ptr 895 | mva #(.len empty_dir_text) text_out_len 896 | jsr output_text 897 | rts 898 | .endp 899 | 900 | .proc output_searching_msg 901 | mva #12 text_out_x 902 | mva #9 text_out_y 903 | mwa #searching_text text_out_ptr 904 | mva #(.len searching_text) text_out_len 905 | jsr output_text 906 | rts 907 | .endp 908 | 909 | .proc output_search_box 910 | jsr hide_pmg_cursor 911 | mva #8 text_out_x 912 | mva #8 text_out_y 913 | mwa #search_text1 text_out_ptr 914 | mva #(.len search_text1) text_out_len 915 | jsr output_text_internal 916 | inc text_out_y 917 | mwa #search_text2 text_out_ptr 918 | mva #(.len search_text2) text_out_len 919 | jsr output_text_internal 920 | inc text_out_y 921 | mwa #search_text3 text_out_ptr 922 | mva #(.len search_text3) text_out_len 923 | jsr output_text_internal 924 | rts 925 | .endp 926 | 927 | 928 | .proc wait_key 929 | mva #$FF CH ; set last key pressed to none 930 | @ ldx CH 931 | cpx #$FF 932 | beq @- 933 | mva #$FF CH 934 | rts 935 | .endp 936 | 937 | ; output text in text_out_ptr at (cur_x, cur_y) 938 | .proc output_text_internal 939 | mwa sm_ptr tmp_ptr 940 | ; add the cursor y offset 941 | ldy text_out_y 942 | yloop dey 943 | bmi yend 944 | adw tmp_ptr #40 945 | jmp yloop 946 | yend adw text_out_x tmp_ptr tmp_ptr ; add the cursor x offset 947 | ; text output loop 948 | ldy #0 949 | nextchar ; text output loop 950 | lda (text_out_ptr),y 951 | sta (tmp_ptr),y 952 | iny 953 | cpy text_out_len 954 | bne nextchar 955 | endoftext 956 | rts 957 | .endp 958 | 959 | ; output text in text_out_ptr at (cur_x, cur_y) 960 | .proc output_text 961 | lda text_out_len 962 | bne ok 963 | rts 964 | ok mwa sm_ptr tmp_ptr 965 | ; add the cursor y offset 966 | ldy text_out_y 967 | yloop dey 968 | bmi yend 969 | adw tmp_ptr #40 970 | jmp yloop 971 | yend adw text_out_x tmp_ptr tmp_ptr ; add the cursor x offset 972 | ; text output loop 973 | ldy #0 974 | nextchar ; text output loop 975 | lda (text_out_ptr),y 976 | beq endoftext ; end of line? 977 | cmp #96; convert ascii->internal 978 | bcs lower 979 | sec 980 | sbc #32 981 | lower sta (tmp_ptr),y 982 | iny 983 | cpy text_out_len 984 | bne nextchar 985 | endoftext 986 | rts 987 | .endp 988 | 989 | ; output text in text_out_ptr at (cur_x, cur_y) 990 | output_text_inverted .proc 991 | mwa sm_ptr tmp_ptr 992 | ; add the cursor y offset 993 | ldy text_out_y 994 | yloop dey 995 | bmi yend 996 | adw tmp_ptr #40 997 | jmp yloop 998 | yend adw text_out_x tmp_ptr tmp_ptr ; add the cursor x offset 999 | ; text output loop 1000 | ldy #0 1001 | nextchar ; text output loop 1002 | lda (text_out_ptr),y 1003 | beq endoftext ; end of line? 1004 | cmp #96; convert ascii->internal 1005 | bcs lower 1006 | sec 1007 | sbc #32 1008 | lower ora #$80 1009 | sta (tmp_ptr),y 1010 | iny 1011 | cpy text_out_len 1012 | bne nextchar 1013 | endoftext 1014 | rts 1015 | .endp 1016 | 1017 | .proc copy_wait_for_cart 1018 | ldy #.len[WaitForCartCode] 1019 | @ 1020 | lda WaitForCartCode-1,y 1021 | sta wait_for_cart-1,y 1022 | dey 1023 | bne @- 1024 | rts 1025 | .endp 1026 | 1027 | ; cmd is in Accumulator 1028 | .proc WaitForCartCode 1029 | sta $D5DF ; send cmd to the cart 1030 | @ lda $D500 1031 | cmp #$11 ; wait for the cart to signal it's back 1032 | bne @- 1033 | rts 1034 | .endp 1035 | 1036 | .proc copy_reboot_to_selected_cart 1037 | ldy #.len[RebootToSelectedCartCode] 1038 | @ 1039 | lda RebootToSelectedCartCode-1,y 1040 | sta reboot_to_selected_cart-1,y 1041 | dey 1042 | bne @- 1043 | rts 1044 | .endp 1045 | 1046 | .proc RebootToSelectedCartCode 1047 | sei ; prevent GINTLK check in deferred vbi 1048 | lda #CART_CMD_ACTIVATE_CART ; tell the cart we're ready for it switch ROM 1049 | sta $D5DF 1050 | jmp COLDSV 1051 | .endp 1052 | 1053 | ; ************************ XEX LOADER **************************** 1054 | 1055 | .proc copy_XEX_loader 1056 | mwa #LoaderCodeStart ptr1 1057 | mwa #LoaderAddress ptr2 1058 | mwa #[EndLoaderCode-LoaderCode] ptr3 1059 | jmp UMove 1060 | .endp 1061 | 1062 | ; Move bytes from ptr1 to ptr2, length ptr3 1063 | .proc UMove 1064 | lda ptr3 1065 | eor #$FF 1066 | adc #1 1067 | sta ptr3 1068 | lda ptr3+1 1069 | eor #$FF 1070 | adc #0 1071 | sta ptr3+1 1072 | 1073 | ldy #0 1074 | Loop 1075 | lda (ptr1),y 1076 | sta (ptr2),y 1077 | iny 1078 | bne @+ 1079 | inc ptr1+1 1080 | inc ptr2+1 1081 | @ 1082 | inc ptr3 1083 | bne Loop 1084 | inc ptr3+1 1085 | bne Loop 1086 | rts 1087 | .endp 1088 | 1089 | 1090 | ; ************************ DATA **************************** 1091 | .local menu_text1 1092 | .byte " _ ___ ___ _ ___ _ " 1093 | .endl 1094 | .local menu_text2 1095 | .byte " /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ " 1096 | .endl 1097 | .local menu_text3 1098 | .byte " / _ \/ _ \ _/ / _/_\ (__/ _' | '_| _|" 1099 | .endl 1100 | .local menu_text4 1101 | .byte "/_/ \_\___/_| |_\__\_/\___\__,_|_| \__|" 1102 | .endl 1103 | .local menu_text5 1104 | .byte " Electrotrains 2023" 1105 | .endl 1106 | .local menu_text_bottom 1107 | .byte 'CurUp/Dn/Retn=Sel B=Back X=Boot Esc=Find' 1108 | .endl 1109 | .local directory_text 1110 | .byte '[Directory contents]' 1111 | .endl 1112 | .local search_results_text 1113 | .byte '[ Search results ]' 1114 | .endl 1115 | 1116 | .local error_text1 1117 | .byte 81,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,69 1118 | .endl 1119 | .local error_text2 1120 | .byte 124,"Error:",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124 1121 | .endl 1122 | .local error_text3 1123 | .byte 90,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,"P"+$80,"r"+$80,"e"+$80,"s"+$80,"s"+$80," "+$80,"a"+$80," "+$80,"k"+$80,"e"+$80,"y"+$80,67 1124 | .endl 1125 | 1126 | .local search_text1 1127 | .byte 81,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,69 1128 | .endl 1129 | .local search_text2 1130 | .byte 124,"Search:",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124 1131 | .endl 1132 | .local search_text3 1133 | .byte 90,82,82,82,82,82,82,82,82,82,82,82,82,"E"+$80,"S"+$80,"C"+$80," "+$80,"C"+$80,"a"+$80,"n"+$80,"c"+$80,"e"+$80,"l"+$80,67 1134 | .endl 1135 | 1136 | .local reset_flash_text1 1137 | .byte 81,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,69 1138 | .endl 1139 | .local reset_flash_text2 1140 | .byte 124,"Reset flash memory? ",124 1141 | .endl 1142 | .local reset_flash_text3 1143 | .byte 90,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,"P"+$80,"r"+$80,"e"+$80,"s"+$80,"s"+$80," "+$80,"R"+$80," "+$80,"t"+$80,"o"+$80," "+$80, "r"+$80, "e"+$80, "s"+$80, "e"+$80, "t"+$80,67 1144 | .endl 1145 | 1146 | 1147 | .local folder_text 1148 | .byte 'DIR',0 1149 | .endl 1150 | 1151 | .local empty_dir_text 1152 | .byte 'No valid files to display' 1153 | .endl 1154 | 1155 | .local searching_text 1156 | .byte 'Searching....' 1157 | .endl 1158 | 1159 | .local cursor_text 1160 | .byte ' ' 1161 | .endl 1162 | 1163 | .local test_text 1164 | .byte 'Hello',0 1165 | .endl 1166 | scancodes 1167 | ins 'keytable.bin' 1168 | 1169 | 1170 | LoaderCodeStart 1171 | 1172 | opt f- 1173 | org LoaderAddress 1174 | opt f+ 1175 | 1176 | LoaderCode 1177 | .byte 'L' 1178 | .byte VER_MAJ 1179 | .byte VER_MIN 1180 | 1181 | .proc LoadBinaryFile 1182 | jsr InitLoader 1183 | Loop 1184 | mwa #Return IniVec ; reset init vector 1185 | jsr ReadBlock 1186 | bmi Error 1187 | cpw RunVec #Return 1188 | bne @+ 1189 | mwa BStart RunVec ; set run address to start of first block 1190 | @ 1191 | jsr DoInit 1192 | jmp Loop 1193 | Error 1194 | jmp (RunVec) 1195 | Return 1196 | rts 1197 | .endp 1198 | 1199 | 1200 | // 1201 | // Jump through init vector 1202 | // 1203 | 1204 | .proc DoInit 1205 | jmp (IniVec) 1206 | .endp 1207 | 1208 | 1209 | // 1210 | // Read block from executable 1211 | // 1212 | 1213 | .proc ReadBlock 1214 | jsr ReadWord 1215 | bmi Error 1216 | lda HeaderBuf 1217 | and HeaderBuf+1 1218 | cmp #$ff 1219 | bne NoSignature 1220 | jsr ReadWord 1221 | bmi Error 1222 | NoSignature 1223 | mwa HeaderBuf BStart 1224 | jsr ReadWord 1225 | bmi Error 1226 | sbw HeaderBuf BStart BLen 1227 | inw BLen 1228 | mwa BStart IOPtr 1229 | jsr ReadBuffer 1230 | Error 1231 | rts 1232 | .endp 1233 | 1234 | 1235 | 1236 | 1237 | // 1238 | // Read word from XEX 1239 | // 1240 | 1241 | .proc ReadWord 1242 | mwa #HeaderBuf IOPtr 1243 | mwa #2 BLen ; fall into ReadBuffer 1244 | .endp 1245 | 1246 | 1247 | 1248 | // 1249 | // Read buffer from XEX 1250 | // Returns Z=1 on EOF 1251 | // 1252 | 1253 | .proc ReadBuffer 1254 | jsr SetSegment 1255 | Loop 1256 | lda BLen 1257 | ora BLen+1 1258 | beq Done 1259 | 1260 | lda FileSize ; first ensure we're not at the end of the file 1261 | ora FileSize+1 1262 | ora FileSize+2 1263 | ora FileSize+3 1264 | beq EOF 1265 | 1266 | inc BufIndex 1267 | bne NoBurst ; don't burst unless we're at the end of the buffer 1268 | 1269 | BurstLoop 1270 | inc SegmentLo ; bump segment if we reached end of buffer 1271 | bne @+ 1272 | inc SegmentHi 1273 | @ 1274 | jsr SetSegment 1275 | 1276 | lda Blen+1 ; see if we can burst read the next 256 bytes 1277 | beq NoBurst 1278 | lda FileSize+1 ; ensure buffer and remaining bytes in file are both >= 256 1279 | ora FileSize+2 1280 | ora FileSize+3 1281 | beq NoBurst 1282 | 1283 | ldy #0 ; read a whole page into RAM 1284 | @ 1285 | lda $D500,y ; doesn't matter about speculative reads (?) 1286 | sta (IOPtr),y 1287 | iny 1288 | bne @- 1289 | inc IOPtr+1 ; bump address for next time 1290 | 1291 | ldx #3 ; y is already 0 1292 | sec 1293 | @ 1294 | lda FileSize,y ; reduce file size by 256 1295 | sbc L256,y 1296 | sta FileSize,y 1297 | iny 1298 | dex 1299 | bpl @- 1300 | dec Blen+1 ; reduce buffer length by 256 1301 | dec BufIndex 1302 | bne ReadBuffer 1303 | 1304 | NoBurst 1305 | lda $D500 1306 | BufIndex equ *-2 1307 | ldy #0 1308 | sta (IOPtr),y 1309 | inw IOPtr 1310 | dew BLen 1311 | 1312 | ldx #3 ; y is already 0 1313 | sec 1314 | @ 1315 | lda FileSize,y 1316 | sbc L1,y 1317 | sta FileSize,y 1318 | iny 1319 | dex 1320 | bpl @- 1321 | bmi Loop 1322 | 1323 | Done 1324 | ldy #1 1325 | rts 1326 | EOF 1327 | ldy #IOErr.EOF 1328 | rts 1329 | 1330 | SetSegment 1331 | ldy #0 1332 | SegmentLo equ *-1 1333 | ldx #0 1334 | SegmentHi equ *-1 1335 | sty $D500 1336 | stx $D501 1337 | rts 1338 | L1 1339 | .dword 1 1340 | L256 1341 | .dword 256 1342 | .endp 1343 | 1344 | 1345 | HeaderBuf .word 0 1346 | BStart .word 0 1347 | BLen .word 0 1348 | 1349 | 1350 | 1351 | ; Everything beyond here can be obliterated safely during the load 1352 | 1353 | // 1354 | // Loader initialisation 1355 | // 1356 | 1357 | .proc InitLoader 1358 | sei 1359 | lda #CART_CMD_ACTIVATE_CART 1360 | sta $D5DF 1361 | 1362 | jsr SetGintlk 1363 | jsr BasicOff 1364 | cli 1365 | jsr OpenEditor 1366 | mwa #EndLoaderCode MEMLO 1367 | mwa #LoadBinaryFile.Return RunVec ; reset run vector 1368 | ldy #0 1369 | tya 1370 | @ 1371 | sta $80,y 1372 | iny 1373 | bpl @- 1374 | jsr ClearRAM 1375 | 1376 | ldy #3 1377 | @ 1378 | lda $D500,y 1379 | sta FileSize,y 1380 | dey 1381 | bpl @- 1382 | mva #3 ReadBuffer.BufIndex 1383 | rts 1384 | .endp 1385 | 1386 | 1387 | 1388 | .proc BASICOff 1389 | mva #$01 $3f8 1390 | mva #$C0 $6A 1391 | lda portb 1392 | ora #$02 1393 | sta portb 1394 | rts 1395 | .endp 1396 | 1397 | 1398 | 1399 | .proc SetGintlk 1400 | sta WSYNC 1401 | sta WSYNC 1402 | lda TRIG3 1403 | sta GINTLK 1404 | rts 1405 | .endp 1406 | 1407 | 1408 | 1409 | .proc ClearRAM 1410 | mwa #EndLoaderCode ptr1 1411 | ; sbw $c000 ptr1 ptr2 1412 | sbw SDLSTL ptr1 ptr2 ; clear up to display list address 1413 | 1414 | lda ptr2 1415 | eor #$FF 1416 | clc 1417 | adc #1 1418 | sta ptr2 1419 | lda ptr2+1 1420 | eor #$FF 1421 | adc #0 1422 | sta ptr2+1 1423 | ldy #0 1424 | tya 1425 | Loop 1426 | sta (ptr1),y 1427 | iny 1428 | bne @+ 1429 | inc ptr1+1 1430 | @ 1431 | inc ptr2 1432 | bne Loop 1433 | inc ptr2+1 1434 | bne Loop 1435 | rts 1436 | .endp 1437 | 1438 | 1439 | 1440 | .proc OpenEditor 1441 | ldx #0 1442 | lda #$0c 1443 | sta iocb[0].Command 1444 | jsr ciov 1445 | mwa #EName iocb[0].Address 1446 | mva #$0C iocb[0].Aux1 1447 | mva #$00 iocb[0].Aux2 1448 | mva #$03 iocb[0].Command 1449 | jmp ciov 1450 | 1451 | EName 1452 | .byte 'E:',$9B 1453 | 1454 | .endp 1455 | 1456 | 1457 | 1458 | .if 0 1459 | // 1460 | // Wait for sync 1461 | // 1462 | 1463 | .proc WaitForSync2 1464 | lda VCount 1465 | rne 1466 | lda VCount 1467 | req 1468 | rts 1469 | .endp 1470 | 1471 | .endif 1472 | 1473 | 1474 | 1475 | 1476 | EndLoaderCode ; end of relocated code 1477 | 1478 | LoaderCodeSize = EndLoaderCode-LoaderCode 1479 | 1480 | opt f- 1481 | org LoaderCodeStart + LoaderCodeSize 1482 | opt f+ 1483 | 1484 | 1485 | ; ************************ CARTRIDGE CONTROL BLOCK ***************** 1486 | 1487 | org $bffa ;Cartridge control block 1488 | .word start ;CARTCS 1489 | .byte 0 ;CART 1490 | .byte CARTFG_START_CART ;CARTFG 1491 | .word init ;CARTAD 1492 | 1493 | --------------------------------------------------------------------------------