/* // PicoPAC MultiCART by Andrea Ottaviani 2024 // // VIDEOPAC multicart based on Raspberry Pico board - // // More info on https://github.com/aotta/ // // parts of code are directly from the A8PicoCart project by Robin Edwards 2023 // // Needs to be a release NOT debug build for the cartridge emulation to work // // Edit myboard.h depending on the type of flash memory on the pico clone// // // v. 1.0 2024-08-05 : Initial version for Pi Pico // */ #include #include #include #include "pico/multicore.h" #include "hardware/gpio.h" #include "pico/platform.h" #include "pico/stdlib.h" #include "hardware/vreg.h" #include "pico/divider.h" #include "hardware/flash.h" #include "hardware/sync.h" #include "tusb.h" #include "ff.h" #include "fatfs_disk.h" // Pico pin usage definitions #define A0_PIN 0 #define A1_PIN 1 #define A2_PIN 2 #define A3_PIN 3 #define A4_PIN 4 #define A5_PIN 5 #define A6_PIN 6 #define A7_PIN 7 #define A8_PIN 8 #define A9_PIN 9 #define A10_PIN 10 #define A11_PIN 11 #define P11_PIN 12 #define P10_PIN 13 #define D0_PIN 14 #define D1_PIN 15 #define D2_PIN 16 #define D3_PIN 17 #define D4_PIN 18 #define D5_PIN 19 #define D6_PIN 20 #define D7_PIN 21 #define PSEN_PIN 22 #define WR_PIN 23 #define CS_PIN 24 // P14 #define LED_PIN 25 #define NOTCS_PIN 26 #define TD_PIN 27 // Pico pin usage masks #define A0_PIN_MASK 0x00000001L //gpio 0 #define A1_PIN_MASK 0x00000002L #define A2_PIN_MASK 0x00000004L #define A3_PIN_MASK 0x00000008L #define A4_PIN_MASK 0x00000010L #define A5_PIN_MASK 0x00000020L #define A6_PIN_MASK 0x00000040L #define A7_PIN_MASK 0x00000080L #define A8_PIN_MASK 0x00000100L #define A9_PIN_MASK 0x00000200L #define A10_PIN_MASK 0x00000400L #define A11_PIN_MASK 0x00000800L #define P11_PIN_MASK 0x00001000L // P11 high bank select #define P10_PIN_MASK 0x00002000L // P10 low bank select #define D0_PIN_MASK 0x00004000L //gpio 14 #define D1_PIN_MASK 0x00008000L #define D2_PIN_MASK 0x00010000L #define D3_PIN_MASK 0x00020000L #define D4_PIN_MASK 0x00040000L #define D5_PIN_MASK 0x00080000L // gpio 19 #define D6_PIN_MASK 0x00100000L #define D7_PIN_MASK 0x00200000L #define PSEN_PIN_MASK 0x00400000L //gpio 22 #define WR_PIN_MASK 0x00800000L //gpio 23 #define CS_PIN_MASK 0x01000000L //gpio 24 - P14 #define LED_PIN_MASK 0x02000000L //gpio 25 #define NOTCS_PIN_MASK 0x04000000L //gpio 26 #define TD_PIN_MASK 0x08000000L //gpio 27 // Aggregate Pico pin usage masks #define ALL_GPIO_MASK 0x0FFFFFFFL #define BUS_PIN_MASK 0x00003FFFL #define BANK_PIN_MASK 0x00003000L #define DATA_PIN_MASK 0x003FC000L #define FLAG_MASK 0x0FC00000L #define ALWAYS_IN_MASK (BUS_PIN_MASK | FLAG_MASK) #define ALWAYS_OUT_MASK (DATA_PIN_MASK ) #define SET_DATA_MODE_OUT gpio_set_dir_out_masked(DATA_PIN_MASK) #define SET_DATA_MODE_IN gpio_set_dir_in_masked(DATA_PIN_MASK) #define SET_LED_ON gpio_init(PICO_DEFAULT_LED_PIN);(PICO_DEFAULT_LED_PIN,GPIO_OUT);gpio_put(PICO_DEFAULT_LED_PIN,true); #define SET_LED_OFF gpio_init(PICO_DEFAULT_LED_PIN);gpio_set_dir(PICO_DEFAULT_LED_PIN,GPIO_OUT);gpio_put(PICO_DEFAULT_LED_PIN,false); // We're going to erase and reprogram a region 256k from the start of flash. // Once done, we can access this at XIP_BASE + 256k. char RBLo,RBHi; char gamelist[255][32]; #define BINLENGTH 1024*128+1 unsigned char rom_table[8][4096]; unsigned char new_rom_table[8][4096]; unsigned char extROM[1024]; unsigned char RAM[1024]; unsigned char files[256*255] = {0}; unsigned char nomefiles[32*25] = {0}; char curPath[256] = ""; char path[256]; volatile char cmd=0; char errorBuf[40]; bool cmd_executing; volatile int bankswitch; int bksw=0; int romsize; int lastpos; volatile u_int8_t bank_type=1; volatile u_int8_t new_bank_type=1; volatile char gamechoosen=0; volatile char newgame=0; char extram[0xff]; //////////////////////////////////////////////////////////////////////////////////// // Error(N) //////////////////////////////////////////////////////////////////////////////////// void error(int numblink){ multicore_lockout_start_blocking(); sleep_ms(1000); gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN,GPIO_OUT); while(1){ for(int i=0;i>D0_PIN); if (extram[0xff]==0xaa) { gamechoosen=extram[0xfe]; } } SET_DATA_MODE_IN; } SET_DATA_MODE_IN; switch (new_bank_type) { case 0: // standard 2k / 4k while(1) { pins=gpio_get_all(); addr = (pins & 0b0111111111111); if (gpio_get(P10_PIN)) { bank=0; } else { bank=1; } if (gpio_get(PSEN_PIN)==0) { SET_DATA_MODE_OUT; gpio_put_masked(DATA_PIN_MASK,(rom_table[bank][addr])<=0x80) { romlatch=((~(pins & DATA_PIN_MASK)>>D0_PIN)&7); } } else { if (gpio_get(P10_PIN)==0) { bank=romlatch; } else { bank=0; } if (gpio_get(PSEN_PIN)==0) { SET_DATA_MODE_OUT; gpio_put_masked(DATA_PIN_MASK,(rom_table[bank][addr])<isDir && !e2->isDir) return -1; else if (!e1->isDir && e2->isDir) return 1; else return strcasecmp(e1->long_filename, e2->long_filename); } char *get_filename_ext(char *filename) { char *dot = strrchr(filename, '.'); if(!dot || dot == filename) return ""; return dot + 1; } int is_valid_file(char *filename) { char *ext = get_filename_ext(filename); if (strcasecmp(ext, "BIN") == 0 || strcasecmp(ext, "ROM") == 0 || strcasecmp(ext, "CV") ) return 1; return 0; } FILINFO fno; char search_fname[FF_LFN_BUF + 1]; // polyfill :-) char *stristr(const char *str, const char *strSearch) { char *sors, *subs, *res = NULL; if ((sors = strdup (str)) != NULL) { if ((subs = strdup (strSearch)) != NULL) { res = strstr (strlwr (sors), strlwr (subs)); if (res != NULL) res = (char*)str + (res - sors); free (subs); } free (sors); } return res; } int scan_files(char *path, char *search) { FRESULT res; DIR dir; UINT i; res = f_opendir(&dir, path); if (res == FR_OK) { for (;;) { if (num_dir_entries == 255) break; res = f_readdir(&dir, &fno); if (res != FR_OK || fno.fname[0] == 0) break; if (fno.fattrib & (AM_HID | AM_SYS)) continue; if (fno.fattrib & AM_DIR) { i = strlen(path); strcat(path, "/"); if (fno.altname[0]) // no altname when lfn is 8.3 strcat(path, fno.altname); else strcat(path, fno.fname); if (strlen(path) >= 210) continue; // no more room for path in DIR_ENTRY res = scan_files(path, search); if (res != FR_OK) break; path[i] = 0; } else if (is_valid_file(fno.fname)) { char *match = stristr(fno.fname, search); if (match) { DIR_ENTRY *dst = (DIR_ENTRY *)&files[0]; dst += num_dir_entries; // fill out a record dst->isDir = (match == fno.fname) ? 1 : 0; // use this for a "score" strncpy(dst->long_filename, fno.fname, 31); dst->long_filename[31] = 0; // 8.3 name if (fno.altname[0]) strcpy(dst->filename, fno.altname); else { // no altname when lfn is 8.3 strncpy(dst->filename, fno.fname, 12); dst->filename[12] = 0; } // full path for search results strcpy(dst->full_path, path); num_dir_entries++; } } } f_closedir(&dir); } return res; } int search_directory(char *path, char *search) { char pathBuf[256]; strcpy(pathBuf, path); num_dir_entries = 0; int i; FATFS FatFs; if (f_mount(&FatFs, "", 1) == FR_OK) { if (scan_files(pathBuf, search) == FR_OK) { // sort by score, name qsort((DIR_ENTRY *)&files[0], num_dir_entries, sizeof(DIR_ENTRY), entry_compare); DIR_ENTRY *dst = (DIR_ENTRY *)&files[0]; // re-set the pointer back to 0 for (i=0; iisDir = fno.fattrib & AM_DIR ? 1 : 0; if (!dst->isDir) if (!is_valid_file(fno.fname)) continue; // copy file record to first ram block // long file name strncpy(dst->long_filename, fno.fname, 31); dst->long_filename[31] = 0; // 8.3 name if (fno.altname[0]) strcpy(dst->filename, fno.altname); else { // no altname when lfn is 8.3 strncpy(dst->filename, fno.fname, 12); dst->filename[12] = 0; } dst->full_path[0] = 0; // path only for search results dst++; num_dir_entries++; } f_closedir(&dir); } else strcpy(errorBuf, "Can't read directory"); f_mount(0, "", 1); qsort((DIR_ENTRY *)&files[0], num_dir_entries, sizeof(DIR_ENTRY), entry_compare); ret = 1; } else strcpy(errorBuf, "Can't read flash memory"); return ret; } int filesize(char *filename) { if (!fatfs_is_mounted()) mount_fatfs_disk(); FATFS FatFs; int car_file = 0; UINT br = 0; if (f_mount(&FatFs, "", 1) != FR_OK) { error(1); return 0; } FIL fil; if (f_open(&fil, filename, FA_READ) != FR_OK) { sleep_ms(100); error(2); goto cleanup; } int byteslong = f_size(&fil); romsize=byteslong; closefile: f_close(&fil); cleanup: f_mount(0, "", 1); return byteslong; } /* load file in ROM (rom_table) */ int load_file(char *filename) { FATFS FatFs; UINT br = 0; UINT bw = 0; int l,nb; memset(rom_table,0,1024*8*4); l=filesize(filename); if (f_mount(&FatFs, "", 1) != FR_OK) { error(1); } nb = l/2048; // nb = number of banks, l=file size) bank_type=0; if (nb==4) bank_type=1; if (nb>4) bank_type=3; FIL fil; if (f_open(&fil, filename, FA_READ) != FR_OK) { error(6); } // read the file to flash RAM for (int i = nb - 1; i >= 0; i--) { if (f_read(&fil,&rom_table[i][1024], 2048, &br)!= FR_OK) { error(9); } memcpy(&rom_table[i][3072], &rom_table[i][2048], 1024); /* simulate missing A10 */ } // mirror ROM in higher banks if (nb<2) memcpy(&rom_table[1],&rom_table[0],4096); if (nb<4) memcpy(&rom_table[2],&rom_table[0],8192); closefile: f_close(&fil); cleanup: f_mount(0, "", 1); return br; } int load_newfile(char *filename) { FATFS FatFs; UINT br = 0; UINT bw = 0; int l,nb; memset(new_rom_table,0,1024*8*4); l=filesize(filename); if (f_mount(&FatFs, "", 1) != FR_OK) { error(1); } FIL fil; if (f_open(&fil, filename, FA_READ) != FR_OK) { error(4); } nb = l/2048; // nb = number of banks, l=file size) if ((strcmp(filename,"vp_40.bin")==0)||((strcmp(filename,"vp_31.bin")==0))||((strcmp(filename,"4inarow.bin")==0))) { // 3k games new_bank_type=2; if (f_read(&fil, &extROM[0], 1024, &br) != FR_OK) { // error(5); } if (f_read(&fil, &new_rom_table[0][1024], 3072, &br) != FR_OK) { // error(7); } } else { new_bank_type=0; if (nb==4) new_bank_type=1; if (nb>4) new_bank_type=3; for (int i = nb - 1; i >= 0; i--) { if (f_read(&fil,&new_rom_table[i][1024], 2048, &br)!= FR_OK) { error(7); } memcpy(&new_rom_table[i][3072], &new_rom_table[i][2048], 1024); /* simulate missing A10 */ } } // mirror ROM in higher banks if (nb<2) memcpy(&new_rom_table[1],&new_rom_table[0],4096); if (nb<4) memcpy(&new_rom_table[2],&new_rom_table[0],8192); closefile: f_close(&fil); cleanup: f_mount(0, "", 1); return br; } void convert_ascii_file_to_VP(char* dest, char* source){ dest[0]=ASCII_to_VP(' '); for (int i=1; i<16; i++){ if (i-1 < strlen(source)) dest[i]=ASCII_to_VP(source[i-1]); else dest[i] = ASCII_to_VP(' '); } } //////////////////////////////////////////////////////////////////////////////////// // filelist //////////////////////////////////////////////////////////////////////////////////// void filelist(DIR_ENTRY* en,int da, int a) { char longfilename[32]; char tmp[32]; char VPfile[16]; unsigned long int pos; char block[0x80]; u_int8_t riga=0; u_int8_t blocco=0; u_int8_t selectori[1024*16]; FATFS FatFs; UINT br = 0; UINT bw = 0; uint numfiles=0; const int block_addr[] = // andrea { 0x1c00, 0x1d00, 0x1e00, 0x1f00, 0x1100, 0x1200, 0x1300, 0x1400, // 7 0x1500, 0x1600, 0x1700, 0x0900, 0x0a00, 0x0b00, 0x0c00, 0x0d00, //15 0x0e00, 0x0f00, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, //23 0x0700, 0x0800, 0x1c00, 0x1c00, 0x1c00, 0x1c00, 0x1c00, 0x1c00, }; if (f_mount(&FatFs, "", 1) != FR_OK) { error(1); } FIL fil,fil2; //open file "Selectgame.$$$" for input/output: if (f_open(&fil, "/selectgame.$$$", FA_READ) != FR_OK) { sleep_ms(100); error(2); goto cleanup; } for (int i=0;i<8;i++) { f_lseek(&fil,1024*i); if (f_read(&fil, &selectori[1024*i], 1024, &br)!= FR_OK) { error(i); } } f_close(&fil); // open file "Selectgame.bin" for input/output: if (f_open(&fil, "/selectgame.bin", FA_READ|FA_WRITE) != FR_OK) { sleep_ms(100); error(3); goto cleanup; } f_write(&fil,&selectori,4*1024,&bw); f_lseek(&fil,0); memset(block,10,sizeof(block)); SET_LED_ON; for(int n = 0;n0)) { dot=j; j--; } for (int i=0;inum_dir_entries) maxfile=num_dir_entries; filea=fileda+maxfile; filelist((DIR_ENTRY *)&files[0],fileda,filea); //sleep_ms(1400); } //////////////////////////////////////////////////////////////////////////////////// // PicoPAC Cart Main //////////////////////////////////////////////////////////////////////////////////// void picopac_cart_main() { uint32_t pins; uint32_t addr; uint32_t dataOut=0; uint16_t dataWrite=0; int ret=0; int l, nb; gpio_init_mask(ALL_GPIO_MASK); stdio_init_all(); // for serial output, via printf() printf("Start\n"); sleep_ms(400); memset(gamelist,'_',sizeof(gamelist)); multicore_launch_core1(core1_main); videopacMenu(); sleep_ms(160); load_file("/selectgame.bin"); // overclocking isn't necessary for most functions - but XEGS carts weren't working without it // I guess we might as well have it on all the time. set_sys_clock_khz(270000, true); //set_sys_clock_khz(170000, true); memset(extram,0,0xff); // Initial conditions curPath[0]=0; while (1) { cmd_executing=false; if ((gamechoosen>=1)) { sleep_ms(1400); load_newfile(gamelist[gamechoosen-1]); //reset(); memcpy(rom_table,new_rom_table,1024*32); newgame=1; } } }