diff --git a/firmware/build.sh b/firmware/build.sh new file mode 100644 index 0000000..20b39a1 --- /dev/null +++ b/firmware/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# This compiles the firmware into firmware.bin in RV32I format. +# It is to be loaded at address 0. + +# Set the path to the RISC-V gcc toolchain for x64 +CROSS=/opt/xpack-riscv-none-elf-gcc-13.2.0-2/bin/riscv-none-elf +CFLAGS="-O -mabi=ilp32 -march=rv32i -ffreestanding" + +$CROSS-as -mabi=ilp32 -march=rv32i -c -o start.o start.S +$CROSS-gcc $CFLAGS -c -o firmware.o firmware.c +$CROSS-gcc $CFLAGS -c -o picorv32.o picorv32.c +$CROSS-gcc $CFLAGS -c -o spi_sd.o spi_sd.c +$CROSS-gcc $CFLAGS -c -o spiflash.o spiflash.c + +# fatfs +$CROSS-gcc $CFLAGS -c -o fatfs/diskio.o fatfs/diskio.c +$CROSS-gcc $CFLAGS -c -o fatfs/ff.o fatfs/ff.c +$CROSS-gcc $CFLAGS -c -o fatfs/ffunicode.o fatfs/ffunicode.c + +$CROSS-gcc $CFLAGS -Wl,--build-id=none,-Bstatic,-T,baremetal.ld,--strip-debug \ + -nostdlib -o firmware.elf start.o firmware.o picorv32.o spiflash.o spi_sd.o \ + fatfs/diskio.o fatfs/ff.o fatfs/ffunicode.o -lgcc + +$CROSS-objcopy firmware.elf firmware.bin -O binary +# python bin2hexwords.py firmware.bin firmware.hex + +$CROSS-objdump -Mnumeric -D firmware.elf > firmware.elf.list \ No newline at end of file diff --git a/firmware/clean.sh b/firmware/clean.sh new file mode 100644 index 0000000..194ca81 --- /dev/null +++ b/firmware/clean.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +rm -f *.bin *.elf *.list *.o *.hex fatfs/*.o \ No newline at end of file diff --git a/firmware/firmware.c b/firmware/firmware.c index 8858b05..a1bc40f 100644 --- a/firmware/firmware.c +++ b/firmware/firmware.c @@ -5,37 +5,108 @@ // Use build.bat to build. Then burn firmware.bin to SPI flash address 0x500000 with Gowin programmer. #include +#include #include "picorv32.h" #include "fatfs/ff.h" #include "firmware.h" uint32_t CORE_ID; -#define CORE_NES 1 -#define CORE_SNES 2 +enum { + CORE_NES = 1, + CORE_SNES = 2, + CORE_GB = 3 +}core_ids; #define OPTION_FILE "/snestang.ini" #define OPTION_INVALID 2 #define OPTION_OSD_KEY_SELECT_START 1 #define OPTION_OSD_KEY_SELECT_RIGHT 2 +#define OPTION_OSD_KEY_HOME 3 + +#define CHEATS_MAX_NUMBER 16 + +#define MENU_OPTIONS_OFFSET_COL1_X 2 +#define MENU_OPTIONS_OFFSET_COL2_X 16 +#define MENU_OPTIONS_OFFSET_Y 12 + +enum { + MENU_OPTIONS_RETURN = 0, + MENU_OPTIONS_NOTHING = 1, + MENU_OPTIONS_OSD_HOT_KEY, + MENU_OPTIONS_BACKUP_BSRAM, + MENU_OPTIONS_ENHANCED_APU, + MENU_OPTIONS_CHEATS, + MENU_OPTIONS_SAVE_BSRAM, + MENU_OPTIONS_LOAD_BSRAM, + MENU_OPTIONS_SYSTEM, + MENU_OPTIONS_ASPECT, + MENU_OPTIONS_COUNT +}menu_options_values; + +enum{ + MAIN_OPTIONS_LOAD_ROM = 0, + MAIN_OPTIONS_LOAD_CORE, + MAIN_OPTIONS_OPTIONS, + MAIN_OPTIONS_COUNT +}; + +enum{ + MENU_CHEATS_RETURN = 0, + MENU_CHEATS_NOTHING, + MENU_CHEATS_ON, + MENU_CHEATS_LOAD, + MENU_CHEATS_COUNT, +}menu_cheats_options_values; // SNES BSRAM is mapped at address 7MB volatile uint8_t *SNES_BSRAM = (volatile uint8_t *)0x07000000; +volatile uint8_t *NES_BSRAM = (volatile uint8_t *)0x00006000; -int option_osd_key = OPTION_OSD_KEY_SELECT_RIGHT; -#define OSD_KEY_CODE (option_osd_key == OPTION_OSD_KEY_SELECT_START ? 0xC : 0x84) -bool option_backup_bsram = false; +uint8_t *nes_bsram_starting_address = (uint8_t *)0x006000; // directly read into BSRAM +const uint32_t nes_bsram_size = (0x8000 - 0x6000); + +int option_osd_key; +#define OSD_KEY_CODE (option_osd_key == OPTION_OSD_KEY_SELECT_START ? 0xC : (option_osd_key == OPTION_OSD_KEY_SELECT_RIGHT ? 0x84 : 0x24)) +bool option_backup_bsram; +bool option_enhanced_apu; +bool option_cheats_enabled; +bool option_sys_type_is_pal; + +bool flag_load_nes_bsram; + +uint32_t option_aspect_ratio; + +static bool rom_loaded = false; bool snes_running; int snes_ramsize; +bool nes_backup_valid; // whether it is okay to save bool snes_backup_valid; // whether it is okay to save char snes_backup_name[256]; +char nes_backup_name_bsram[256] = ""; +char nes_backup_save_str_bsram[] = "saves/"; +char nes_backup_path_bsram[266] = ""; +char snes_backup_path[266] = "saves/"; +uint16_t nes_bsram_crc16; uint16_t snes_bsram_crc16; uint32_t snes_backup_time; char load_fname[1024]; char load_buf[1024]; +int save_bsram(void); +int load_bsram(void); + +// Enhanced APU - enable +void enhanced_apu_enable(void){ + reg_enhanced_apu = 1; +} +// Enhanced APU - disable +void enhanced_apu_disable(void){ + reg_enhanced_apu = 0; +} + // return 0: success, 1: no option file found, 2: option file corrupt int load_option() { FIL f; @@ -59,21 +130,60 @@ int load_option() { key = trimwhitespace(line); value = trimwhitespace(s+1); // status(""); - uart_printf("key=%s, value=%s\n", key, value); + uart_printf("key=%s, value=%s\r\n", key, value); // message("see below",1); // now handle all key-value pairs if (strcmp(key, "osd_key") == 0) { option_osd_key = atoi(value); + uart_printf("osd_key: %d\r\n", option_osd_key); if (option_osd_key <= 0) { r = OPTION_INVALID; goto load_option_close; } } else if (strcmp(key, "backup_bsram") == 0) { + uart_printf("backup_bsram: %d\r\n", value); if (strcasecmp(value, "true") == 0) option_backup_bsram = true; else option_backup_bsram = false; + uart_printf("option_backup_bsram: %d\r\n", option_backup_bsram); + } else if (strcmp(key, "enhanced_apu") == 0) { + uart_printf("enhanced_apu: %d\r\n", value); + if (strcasecmp(value, "true") == 0) + option_enhanced_apu = true; + else + option_enhanced_apu = false; + uart_printf("option_enhanced_apu: %d\r\n", option_enhanced_apu); + reg_enhanced_apu = (uint32_t)option_enhanced_apu; + uart_printf("reg_enhanced_apu: %d\r\n", reg_enhanced_apu); + } else if (strcmp(key, "cheats_enabled") == 0) { + uart_printf("cheats_enabled: %d\r\n", value); + if (strcasecmp(value, "true") == 0) + option_cheats_enabled = true; + else + option_cheats_enabled = false; + uart_printf("option_cheats_enabled: %d\r\n", option_cheats_enabled); + reg_cheats_enabled = (uint32_t)option_cheats_enabled; + uart_printf("reg_cheats_enabled: %d\r\n", reg_cheats_enabled); + } else if (strcmp(key, "system") == 0) { + uart_printf("system: %d\r\n", value); + if (strcasecmp(value, "false") == 0) + option_sys_type_is_pal = false; + else + option_sys_type_is_pal = true; + uart_printf("option_sys_type_is_pal: %d\r\n", option_sys_type_is_pal); + reg_sys_type = (uint32_t)option_sys_type_is_pal; + uart_printf("reg_sys_type: %d\r\n", reg_sys_type); + } else if (strcmp(key, "aspect_ratio") == 0) { + uart_printf("aspect_ratio: %d\r\n", value); + if (strcasecmp(value, "0") == 0) + option_aspect_ratio = 0; + else + option_aspect_ratio = 1; + uart_printf("option_aspect_ratio: %d\r\n", option_aspect_ratio); + reg_aspect_ratio = option_aspect_ratio; + uart_printf("reg_aspect_ratio: %d\r\n", reg_aspect_ratio); } else { // just ignore unknown keys } @@ -98,15 +208,44 @@ int save_option() { } if (option_osd_key == OPTION_OSD_KEY_SELECT_START) f_puts("1\n", &f); - else + else if(option_osd_key == OPTION_OSD_KEY_SELECT_RIGHT) f_puts("2\n", &f); - + else + f_puts("3\n", &f); f_puts("backup_bsram=", &f); if (option_backup_bsram) f_puts("true\n", &f); else f_puts("false\n", &f); - + f_puts("enhanced_apu=", &f); + if (option_enhanced_apu){ + f_puts("true\n", &f); + } + else{ + f_puts("false\n", &f); + } + f_puts("cheats_enabled=", &f); + if (option_cheats_enabled){ + f_puts("true\n", &f); + } + else{ + f_puts("false\n", &f); + } + f_puts("system=", &f); + if (option_sys_type_is_pal){ + f_puts("true\n", &f); + } + else{ + f_puts("false\n", &f); + } + f_puts("aspect_ratio=", &f); + if (option_aspect_ratio){ + f_puts("1\n", &f); + } + else{ + f_puts("0\n", &f); + } + save_options_close: f_close(&f); // hide snestang.ini in dir list @@ -207,8 +346,9 @@ int file_len; // number of files on this page // set to valid entries on this page. // return: 0 if successful int load_dir(char *dir, int start, int len, int *count) { - DEBUG("load_dir: %s, start=%d, len=%d\n", dir, start, len); + DEBUG("load_dir: %s, start=%d, len=%d\r\n", dir, start, len); int cnt = 0; + int r = 0; DIR d; file_len = 0; // initiaze sd again to be sure @@ -255,7 +395,7 @@ int load_dir(char *dir, int start, int len, int *count) { } f_closedir(&d); *count = cnt; - DEBUG("load_dir: count=%d\n", cnt); + DEBUG("load_dir: count=%d\r\n", cnt); return 0; } @@ -287,6 +427,8 @@ int menu_loadrom(int *choice) { delay(300); while (1) { int r = joy_choice(TOPLINE, file_len, &active, OSD_KEY_CODE); + if(r == 4) + return 1; // return to main menu if (r == 1) { if (strcmp(pwd, "/") == 0 && page == 0 && active == 0) { // return to main menu @@ -364,19 +506,17 @@ void write_flash(uint8_t *corebuf, uint32_t addr, int cnt) { bool verify_flash(uint8_t *corebuf, uint32_t addr, int cnt) { // uart_printf("Verifying %d bytes at %x\n", cnt, addr); uint8_t buf[256]; - // uart_print("Reading flash\n"); spiflash_read(addr, buf, 256); - // uart_print("Done with flash read\n"); for (int j = 0; j < cnt; j++) { if (buf[j] != corebuf[j]) { - uart_printf("Verify error at %x: %d != %d. Data read:\n", addr+j, buf[j], corebuf[j]); + uart_printf("Verify error at %x: %d != %d. Data read:\r\n", addr+j, buf[j], corebuf[j]); for (int i = 0; i < 256; i++) { if (i > 0 && i % 16 == 0) - uart_print("\n"); + uart_print("\r\n"); uart_print_hex_digits(buf[i], 2); uart_print(" "); } - uart_print("\n"); + uart_print("\r\n"); return false; } } @@ -387,12 +527,13 @@ bool verify_flash(uint8_t *corebuf, uint32_t addr, int cnt) { return true; } -static unsigned int load_buf_off; // next available pos in load_buf -static unsigned int load_buf_len; // length of data in load_buf +static unsigned int load_buf_off; // next available pos in load_buf +static unsigned int load_buf_len; // length of data in load_buf // load a line into buf (max length *len), *len is updated to actual length of string // this uses load_buf[] internally void read_line(FIL *fp, char *buf, int *len) { + int br; int i = 0; bool done = false; while (!done) { @@ -422,9 +563,9 @@ void load_core(char *fname, int verify) { int binfile = strcasestr(fname, ".bin") != NULL; // 1: bin t_ready = 0; t_flash = 0, t_file = 0; t_parse = 0; if (binfile) - uart_printf("Loading bin file: %s\n", fname); + uart_printf("Loading bin file: %s\r\n", fname); else - uart_printf("Loading fs file: %s\n", fname); + uart_printf("Loading fs file: %s\r\n", fname); if (verify && !binfile) { message("Verify only supported for .bin files", 1); return; @@ -437,11 +578,11 @@ void load_core(char *fname, int verify) { int addr = 0; char *s = load_buf; unsigned int cnt = 0; + int bol = 1; + int br; while (!f_eof(&f) && (binfile || addr < 32*1024)) { // write only 32KB for .fs if (binfile) { - // uart_print("Checking cycles\n"); uint32_t t = cycle_counter(); - // uart_print("Reading file\n"); f_read(&f, corebuf, 256, &cnt); t_file += cycle_counter() - t; if (verify) { @@ -485,7 +626,7 @@ void load_core(char *fname, int verify) { uart_print_hex_digits(corebuf[j], 2); uart_print(" "); } - uart_print("\n"); + uart_print("\r\n"); write_flash(corebuf, addr, cnt); addr += cnt; cnt = 0; @@ -502,8 +643,8 @@ void load_core(char *fname, int verify) { f_close(&f); const uint32_t MS = 21500; - uart_printf("File read cycles: %d, parse cycles: %d, flash total cycles: %d, flash wait cycles: %d\n", t_file, t_parse, t_flash, t_ready); - uart_printf("File read time: %d ms, parse time: %d ms, flash total time: %d ms, flash wait time: %d ms\n", t_file / MS, t_parse / MS, t_flash / MS, t_ready / MS); + uart_printf("File read cycles: %d, parse cycles: %d, flash total cycles: %d, flash wait cycles: %d\r\n", t_file, t_parse, t_flash, t_ready); + uart_printf("File read time: %d ms, parse time: %d ms, flash total time: %d ms, flash wait time: %d ms\r\n", t_file / MS, t_parse / MS, t_flash / MS, t_ready / MS); if (verify) message("Core matches", 1); else @@ -511,7 +652,7 @@ void load_core(char *fname, int verify) { } void menu_select_core(int verify) { - int total, choice = 0, draw=1; + int total, choice=0, draw=1; int r = load_dir("/cores", 0, PAGESIZE, &total); if (r != 0) { clear(); @@ -532,8 +673,10 @@ void menu_select_core(int verify) { } draw = 0; } - // DEBUG("Calling joy_chocie\n"); - if (joy_choice(2, total, &choice, OSD_KEY_CODE) == 1) { + int r = joy_choice(2, total, &choice, OSD_KEY_CODE); + if(r == 4) + return; + if (r == 1) { if (choice == 0) return; else { @@ -554,78 +697,425 @@ void menu_select_core(int verify) { return; } } - // DEBUG("Done with joy_chocie\n"); } } void _menu_select_core() { uint8_t buf[256]; - uart_printf("begin select_core\n"); + uart_printf("begin select_core\r\n"); spiflash_read(0*1024*1024, buf, 256); for (int i = 0; i < 256; i++) { if (i > 0 && i % 16 == 0) - uart_printf("\n"); + uart_printf("\r\n"); uart_print_hex_digits(buf[i], 2); uart_print(" "); } - uart_printf("\n"); + uart_printf("\r\n"); status("Check UART for log"); - uart_printf("end select_core\n"); + uart_printf("end select_core\r\n"); } -void menu_options() { - int choice = 0; - uart_print("options\n"); - while (1) { - clear(); - cursor(8, 10); - print("--- Options ---"); - - cursor(2, 12); - print("<< Return to main menu"); - cursor(2, 14); - print("OSD hot key:"); - cursor(16, 14); - if (option_osd_key == OPTION_OSD_KEY_SELECT_START) - print("SELECT&START"); - else - print("SELECT&RIGHT"); - cursor(2, 15); - print("Backup BSRAM:"); - cursor(16, 15); - if (option_backup_bsram) - print("Yes"); - else - print("No"); +// load a cheats file. +// return 0 if successful +int load_cheats(int cheat_file) { + FIL f; + strncpy(load_fname, pwd, 1024); + strncat(load_fname, "/", 1024); + strncat(load_fname, file_names[cheat_file], 1024); + + DEBUG("load cheats start\r\n"); + + // check extension .sfc or .smc + char *p = strcasestr(file_names[cheat_file], ".cwz"); + if (p == NULL) { + status("Only .cwz supported"); + goto load_cheats_end; + } + + // initiaze sd again to be sure + if (sd_init() != 0) return 99; + + int r = f_open(&f, load_fname, FA_READ); + if (r) { + status("Cannot open file"); + reg_cheats_loaded = 0; + goto load_cheats_end; + } + int off = 0, br, total = 0; + int size = file_sizes[cheat_file]; + + // Parse here + BYTE s[16]; + UINT rc; + uint8_t cheat_counter = 0; + for(int i=0; i file_len-1) + active = file_len-1; + for (int i = 0; i < PAGESIZE; i++) { + int idx = page*PAGESIZE + i; + cursor(2, i+TOPLINE); + if (idx < total) { + print(file_names[i]); + if (idx != 0 && file_dir[i]) + print("/"); + } + } + delay(300); + while (1) { + int r = joy_choice(TOPLINE, file_len, &active, OSD_KEY_CODE); + if(r == 4) + return 1; // return to main menu + if (r == 1) { + if (strcmp(pwd, "/") == 0 && page == 0 && active == 0) { + // return to main menu + return 1; + } else if (file_dir[active]) { + if (file_names[active][0] == '.' && file_names[active][1] == '.') { + // return to parent dir + // message(file_names[active], 1); + char *slash = strrchr(pwd, '/'); + if (slash) + *slash = '\0'; + } else { // enter sub dir + strncat(pwd, "/", PWD_SIZE); + strncat(pwd, file_names[active], PWD_SIZE); + } + active = 0; + page = 0; + break; + } else { + // actually load a cheat file + *choice = active; + int res; + if (CORE_ID == 1) + res = load_cheats(active); + else + res = load_cheats(active); + if (res != 0) { + message("Cannot load cheat file",1); + break; + } + } + } + if (r == 2 && page < pages-1) { + page++; + break; + } else if (r == 3 && page > 0) { + page--; + break; + } + } + } else { + status("Error opening directory"); + printf(" %d", r); + return -1; + } + } +} - for (;;) { - if (joy_choice(12, 4, &choice, OSD_KEY_CODE) == 1) { - if (choice == 0) { - return; - } else if (choice == 1) { - // nothing - } else { - if (choice == 2) { - if (option_osd_key == OPTION_OSD_KEY_SELECT_START) - option_osd_key = OPTION_OSD_KEY_SELECT_RIGHT; - else - option_osd_key = OPTION_OSD_KEY_SELECT_START; - } else if (choice == 3) { - option_backup_bsram = !option_backup_bsram; - } - status("Saving options..."); - if (save_option()) { - message("Cannot save options to SD",1); - break; - } - break; // redraw UI - } - } +void menu_cheats_options() { + int choice = 0; + int cheat_file; + while (1) { + clear(); + cursor(8, 10); + print("--- Cheats ---"); + + cursor(MENU_OPTIONS_OFFSET_COL1_X, MENU_OPTIONS_OFFSET_Y); + print("<< Return to main menu"); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_CHEATS_ON)); + print("Cheats:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_CHEATS_ON)); + if (option_cheats_enabled) + print("On"); + else + print("Off"); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_CHEATS_LOAD)); + print("Load cheat file"); + + + delay(300); + + for (;;) { + int r = joy_choice(12, MENU_CHEATS_COUNT, &choice, OSD_KEY_CODE); + if(r == 4) + return; + if (r == 1) { + if (choice == MENU_CHEATS_RETURN) { + return; + } else if (choice == MENU_CHEATS_NOTHING) { + // nothing + } else { + if (choice == MENU_CHEATS_ON) { + option_cheats_enabled = !option_cheats_enabled; + reg_cheats_enabled = option_cheats_enabled; + } else if (choice == MENU_CHEATS_LOAD) { + delay(300); + menu_load_cheats(&cheat_file); + // continue; + } + status("Saving options..."); + if (save_option()) { + message("Cannot save options to SD",1); + break; + } + break; // redraw UI + } + } + } + } +} + +// Return 0 if success +int save_bsram(void){ + FIL f; + FILINFO fno; + int r = 0; + + if (f_stat(snes_backup_path, &fno) != FR_OK) { + if (f_mkdir(snes_backup_path) != FR_OK) { + status("Cannot create /saves"); + uart_printf("Cannot create /saves\r\n"); + return 1; } } + if(nes_backup_name_bsram == ""){ + status("ERROR: invalid name!"); + return 1; + } + if(nes_backup_path_bsram == ""){ + status("ERROR: invalid path!"); + return 1; + } + if (f_open(&f, nes_backup_path_bsram, (FA_WRITE | FA_CREATE_ALWAYS)) != FR_OK) { + status("Cannot write save file"); + uart_printf("Cannot write save file"); + return 1; + } + unsigned int bw; + if (f_write(&f, nes_bsram_starting_address, nes_bsram_size, &bw) != FR_OK || bw != nes_bsram_size) { + status("ERROR: BSRAM not saved!"); + uart_printf("Write failure, bw=%d\r\n", bw); + r = 2; + return 1; + } + status("BSRAM saved!"); + + f_close(&f); + return 0; +} + +// 1 if error +// 0 if OK +int load_bsram(void){ + nes_backup_valid = false; + FILINFO fno; + + reg_load_bsram = 1; + delay(250); + + if (f_stat(nes_backup_path_bsram, &fno) != FR_OK) { + if (f_mkdir(nes_backup_path_bsram) != FR_OK) { + status("Cannot create /saves"); + return 1; + } + } + uart_printf("Loading bsram from: %s\r\n", nes_backup_path_bsram); + FIL f; + if (f_open(&f, nes_backup_path_bsram, FA_READ) != FR_OK) { + nes_backup_valid = true; + status("Cannot open bsram file, assuming new"); + return 1; + } + + uint8_t *p = nes_bsram_starting_address; + unsigned int load = 0; + + while (load < nes_bsram_size) { + int br; + if (f_read(&f, p, 1024, &br) != FR_OK || br < 1024) + break; + p += br; + load += br; + } + nes_backup_valid = true; + f_close(&f); + int crc = gen_crc16(nes_bsram_starting_address, nes_bsram_size); + // uart_printf("Bsram backup loaded %d bytes CRC=%x.\n", load, crc); + status("BSRAM loaded!"); + + nes_bsram_crc16 = gen_crc16(nes_bsram_starting_address, nes_bsram_size); + + delay(250); + reg_load_bsram = 0; + return 0; +} + +void menu_options() { + int choice = 0; + while (1) { + clear(); + cursor(8, 10); + print("--- Options ---"); + + // Return to main menu + cursor(MENU_OPTIONS_OFFSET_COL1_X, MENU_OPTIONS_OFFSET_Y); + print("<< Return to main menu"); + // OSD hot key + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_OSD_HOT_KEY)); + print("OSD hot key:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_OSD_HOT_KEY)); + if (option_osd_key == OPTION_OSD_KEY_SELECT_START) + print("SELECT&START"); + else if(option_osd_key == OPTION_OSD_KEY_SELECT_RIGHT) + print("SELECT&RIGHT"); + else + print("HOME"); + // Backup BSRAM + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_BACKUP_BSRAM)); + print("Backup BSRAM:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_BACKUP_BSRAM)); + if (option_backup_bsram) + print("Yes"); + else + print("No"); + // Enhanced APU + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_ENHANCED_APU)); + print("Enhanced APU:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_ENHANCED_APU)); + if (option_enhanced_apu) + print("Yes"); + else + print("No"); + // Cheats + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_CHEATS)); + print("Cheats"); + // Save BSRAM + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_SAVE_BSRAM)); + print("Save BSRAM"); + // Load BSRAM + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_LOAD_BSRAM)); + print("Load BSRAM"); + // System - NTSC/DENDY or PAL + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_SYSTEM)); + print("System:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_SYSTEM)); + if(!option_sys_type_is_pal) + print("NTSC/DENDY"); + else + print("PAL"); + // Aspect Ratio + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_ASPECT)); + print("Aspect:"); + cursor(MENU_OPTIONS_OFFSET_COL2_X, (MENU_OPTIONS_OFFSET_Y+MENU_OPTIONS_ASPECT)); + if(!option_aspect_ratio) + print("1:1"); + else + print("8:7"); + + delay(300); + + for (;;) { + int r = joy_choice(12, 10, &choice, OSD_KEY_CODE); + if(r == 4) + return; + if (r == 1) { + if (choice == MENU_OPTIONS_RETURN) { + return; + } else if (choice == MENU_OPTIONS_NOTHING) { + // nothing + } else { + if (choice == MENU_OPTIONS_OSD_HOT_KEY) { + if (option_osd_key == OPTION_OSD_KEY_SELECT_START) + option_osd_key = OPTION_OSD_KEY_SELECT_RIGHT; + else if (option_osd_key == OPTION_OSD_KEY_SELECT_RIGHT) + option_osd_key = OPTION_OSD_KEY_HOME; + else + option_osd_key = OPTION_OSD_KEY_SELECT_START; + } else if (choice == MENU_OPTIONS_BACKUP_BSRAM) { + option_backup_bsram = !option_backup_bsram; + } else if (choice == MENU_OPTIONS_ENHANCED_APU) { + option_enhanced_apu = !option_enhanced_apu; + reg_enhanced_apu = !reg_enhanced_apu; + } else if (choice == MENU_OPTIONS_CHEATS) { + delay(300); + menu_cheats_options(); + //continue; + } else if (choice == MENU_OPTIONS_SAVE_BSRAM) { + delay(300); + save_bsram(); + //continue; + }else if (choice == MENU_OPTIONS_LOAD_BSRAM) { + delay(300); + load_bsram(); + //continue; + } else if (choice == MENU_OPTIONS_SYSTEM) { + option_sys_type_is_pal = !option_sys_type_is_pal; + reg_sys_type = (uint32_t)option_sys_type_is_pal; + } else if (choice == MENU_OPTIONS_ASPECT) { + option_aspect_ratio = !option_aspect_ratio; + reg_aspect_ratio = (uint32_t)option_aspect_ratio; + } + // + if((choice != MENU_OPTIONS_CHEATS)&&(choice != MENU_OPTIONS_SAVE_BSRAM)&&(choice != MENU_OPTIONS_LOAD_BSRAM)){ + status("Saving options..."); + if (save_option()) { + message("Cannot save options to SD",1); + break; + } + } + break; // redraw UI + } + } + } + } } int in_game; @@ -635,7 +1125,7 @@ int in_game; int parse_snes_header(FIL *fp, int pos, int file_size, int typ, char *hdr, int *map_ctrl, int *rom_type_header, int *rom_size, int *ram_size, int *company) { - unsigned int br; + int br; if (f_lseek(fp, pos)) return 1; f_read(fp, hdr, 64, &br); @@ -662,7 +1152,7 @@ int parse_snes_header(FIL *fp, int pos, int file_size, int typ, char *hdr, all_ascii = 0; score += all_ascii; - DEBUG("pos=%x, type=%d, map_ctrl=%d, rom=%d, ram=%d, checksum=%x, checksum_comp=%x, reset=%x, score=%d\n", + DEBUG("pos=%x, type=%d, map_ctrl=%d, rom=%d, ram=%d, checksum=%x, checksum_comp=%x, reset=%x, score=%d\r\n", pos, typ, mc, rom, ram, checksum, checksum_compliment, reset, score); if (rom < 14 && ram <= 7 && score >= 1 && @@ -685,7 +1175,7 @@ int parse_snes_header(FIL *fp, int pos, int file_size, int typ, char *hdr, // return 0 if successful int loadsnes(int rom) { FIL f; - int r = 1; + int r=1; strncpy(load_fname, pwd, 1024); strncat(load_fname, "/", 1024); strncat(load_fname, file_names[rom], 1024); @@ -717,7 +1207,7 @@ int loadsnes(int rom) { // parse SNES header from ROM file int off = size & 0x3ff; // rom header (0 or 512) int header_pos; - DEBUG("off=%d\n", off); + DEBUG("off=%d\r\n", off); header_pos = 0x7fc0 + off; if (parse_snes_header(&f, header_pos, size-off, 0, load_buf, &map_ctrl, &rom_type_header, &rom_size, &ram_size, &company)) { @@ -784,19 +1274,29 @@ int loadsnes(int rom) { loadsnes_close_file: f_close(&f); loadsnes_end: - return r; + return r; } // load a NES rom file. // return 0 if successful int loadnes(int rom) { FIL f; - int r = 1; + int r=1; strncpy(load_fname, pwd, 1024); strncat(load_fname, "/", 1024); strncat(load_fname, file_names[rom], 1024); - - DEBUG("loadnes start\n"); + int i=0; + for(int i=0; i<266; ++i) + nes_backup_path_bsram[i] = '\0'; + i = 0; + while(file_names[rom][i] != '\0') + ++i; + memset(nes_backup_path_bsram, '\0', 266); + strncpy(nes_backup_name_bsram, file_names[rom], (i-4)); + strcat(nes_backup_path_bsram, nes_backup_save_str_bsram); + strcat(nes_backup_path_bsram, nes_backup_name_bsram); + + DEBUG("loadnes start\r\n"); // check extension .sfc or .smc char *p = strcasestr(file_names[rom], ".nes"); @@ -823,61 +1323,71 @@ int loadnes(int rom) { // Send rom content to snes if ((r = f_lseek(&f, off)) != FR_OK) { status("Seek failure"); - goto loadnes_snes_end; + goto loadnes_nes_end; } + int bsram_counter = 0; do { if ((r = f_read(&f, load_buf, 1024, &br)) != FR_OK) break; + total += br; // bytes + // if((total >= 0x6000)&&(total < 0x8000)){ + // memset((nes_bsram_starting_address + bsram_counter), 0, br); + // bsram_counter++; + // } for (int i = 0; i < br; i += 4) { uint32_t *w = (uint32_t *)(load_buf + i); snes_data(*w); // send actual ROM data } - total += br; if ((total & 0xfff) == 0) { // display progress every 4KB status(""); printf("%d/%dK", total >> 10, size >> 10); } } while (br == 1024); + f_close(&f); - DEBUG("loadnes: %d bytes\n", total); + // Load BSRAM + if(option_backup_bsram){ + // uint32_t nes_bsram_local; + // memset(nes_bsram_starting_address, 0, 0x2000); + // r = load_bsram(); + } + + DEBUG("loadnes: %d bytes\r\n", total); status("Success"); snes_running = true; - overlay(0); // turn off OSD - -loadnes_snes_end: - snes_ctrl(0); // turn off game loading, this starts the core - f_close(&f); +loadnes_nes_end: + snes_ctrl(0); // turn off game loading, this starts the core loadnes_end: - return r; + overlay(0); // turn off OSD + return r; } void backup_load(char *name, int size) { snes_backup_valid = false; if (!option_backup_bsram || size == 0) return; - char path[266] = "/saves/"; FILINFO fno; uint8_t *bsram = (uint8_t *)0x700000; // directly read into BSRAM - if (f_stat(path, &fno) != FR_OK) { - if (f_mkdir(path) != FR_OK) { + if (f_stat(snes_backup_path, &fno) != FR_OK) { + if (f_mkdir(snes_backup_path) != FR_OK) { status("Cannot create /saves"); - uart_printf("Cannot create /saves\n"); + uart_printf("Cannot create /saves\r\n"); goto backup_load_crc; } } - strcat(path, snes_backup_name); - uart_printf("Loading bsram from: %s\n", snes_backup_name); + strcat(snes_backup_path, snes_backup_name); + uart_printf("Loading bsram from: %s\r\n", snes_backup_name); FIL f; - if (f_open(&f, path, FA_READ) != FR_OK) { - snes_backup_valid = true; // new save file, mark as valid - uart_printf("Cannot open bsram file, assuming new\n"); + if (f_open(&f, snes_backup_path, FA_READ) != FR_OK) { + nes_backup_valid = true; // new save file, mark as valid + uart_printf("Cannot open bsram file, assuming new\r\n"); goto backup_load_crc; } uint8_t *p = bsram; unsigned int load = 0; while (load < size) { - unsigned int br; + int br; if (f_read(&f, p, 1024, &br) != FR_OK || br < 1024) break; p += br; @@ -886,12 +1396,11 @@ void backup_load(char *name, int size) { snes_backup_valid = true; f_close(&f); int crc = gen_crc16(bsram, size); - uart_printf("Bsram backup loaded %d bytes CRC=%x.\n", load, crc); + uart_printf("Bsram backup loaded %d bytes CRC=%x.\r\n", load, crc); backup_load_crc: snes_bsram_crc16 = gen_crc16(bsram, size); - - return; + return; } // return 0: successfully saved, 1: BSRAM unchanged, 2: file write failure @@ -904,7 +1413,7 @@ int backup_save(char *name, int size) { // first check if BSRAM content is changed since last save int newcrc = gen_crc16(bsram, size); - uart_printf("New CRC: %x, size=%d\n", newcrc, size); + uart_printf("New CRC: %x, size=%d\r\n", newcrc, size); if (newcrc == snes_bsram_crc16) return 1; @@ -919,7 +1428,7 @@ int backup_save(char *name, int size) { // if (f_write(&f, bsram, 1024, &bw) != FR_OK) { if (f_write(&f, bsram, size, &bw) != FR_OK || bw != size) { status("Write failure"); - uart_printf("Write failure, bw=%d\n", bw); + uart_printf("Write failure, bw=%d\r\n", bw); r = 2; goto bsram_save_close; } @@ -927,18 +1436,24 @@ int backup_save(char *name, int size) { snes_bsram_crc16 = newcrc; bsram_save_close: - f_close(&f); - return r; + f_close(&f); + return r; } int backup_success_time; void backup_process() { - if (CORE_ID != CORE_SNES || !snes_running || !option_backup_bsram || snes_ramsize == 0) + if (!snes_running || !option_backup_bsram || snes_ramsize == 0) return; int t = time_millis(); if (t - snes_backup_time >= 10000) { // need to save - int r = backup_save(snes_backup_name, snes_ramsize); + int r; + if(CORE_ID == CORE_SNES ) + r = backup_save(snes_backup_name, snes_ramsize); + else if(CORE_ID == CORE_NES ) + r = save_bsram(); + else + return; if (r == 0) backup_success_time = t; if (backup_success_time != 0) { @@ -1010,9 +1525,15 @@ int main() { // initialize UART reg_uart_clkdiv = 187; // 21505400 / 115200; + // Init reg_load_bsram + reg_load_bsram = 0; + + // Init system type + reg_sys_type = (option_sys_type_is_pal ? (uint32_t)0x01 : (uint32_t)0x00); + sd_init(); delay(100); - DEBUG("CORE_ID=%d\n", CORE_ID); + DEBUG("CORE_ID=%d\r\n", CORE_ID); int mounted = 0; while(!mounted) { @@ -1038,22 +1559,33 @@ int main() { for (;;) { // main menu clear(); - cursor(2, 10); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y-2)); // 01234567890123456789012345678901 - if (CORE_ID == 1) + switch(CORE_ID){ + case 1: print("=== Welcome to NESTang ==="); - else + break; + case 2: print("~~~ Welcome to SNESTang ~~~"); + break; + case 3: + print("... Welcome to GBTang ..."); + break; + default: + print("ERR: undefined COREID"); + break; + } + - cursor(2, 12); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MAIN_OPTIONS_LOAD_ROM)); print("1) Load ROM from SD card\n"); - cursor(2, 13); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MAIN_OPTIONS_LOAD_CORE)); print("2) Select core\n"); - cursor(2, 14); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+MAIN_OPTIONS_OPTIONS)); print("3) Options\n"); // cursor(2, 15); // print("4) Verify core\n"); - cursor(2, 16); + cursor(MENU_OPTIONS_OFFSET_COL1_X, (MENU_OPTIONS_OFFSET_Y+4)); print("Version: "); print(__DATE__); @@ -1061,17 +1593,17 @@ int main() { int choice = 0; for (;;) { - int r = joy_choice(12, 3, &choice, OSD_KEY_CODE); + int r = joy_choice(12, MAIN_OPTIONS_COUNT, &choice, OSD_KEY_CODE); if (r == 1) break; } - if (choice == 0) { + if (choice == MAIN_OPTIONS_LOAD_ROM) { int rom; delay(300); menu_loadrom(&rom); - } else if (choice == 1) { + } else if (choice == MAIN_OPTIONS_LOAD_CORE) { menu_select_core(0); - } else if (choice == 2) { + } else if (choice == MAIN_OPTIONS_OPTIONS) { delay(300); menu_options(); continue; diff --git a/firmware/picorv32.c b/firmware/picorv32.c index 27ec13e..c198f2e 100644 --- a/firmware/picorv32.c +++ b/firmware/picorv32.c @@ -155,8 +155,10 @@ int uart_putchar(int c) { } int uart_print(const char *s) { +#if (UART_DEBUG_ENABLED == 1) while (*s) - _putchar(*(s++), 1); + _putchar(*(s++), 1); +#endif return 0; } @@ -222,6 +224,8 @@ int joy_choice(int start_line, int len, int *active, int overlay_key_code) { if ((joy1 & 0x20) || (joy2 & 0x20)) { if (*active < len-1) (*active)++; } + if ((joy1 & 0x02) || (joy2 & 0x02)) + return 4; // B button if ((joy1 & 0x40) || (joy2 & 0x40)) return 3; // previous page if ((joy1 & 0x80) || (joy2 & 0x80)) diff --git a/firmware/picorv32.h b/firmware/picorv32.h index fcce5a0..68a9e8d 100644 --- a/firmware/picorv32.h +++ b/firmware/picorv32.h @@ -5,23 +5,49 @@ #include #include // for atoi() +#define UART_DEBUG_ENABLED 0 + #define DEBUG(...) uart_printf(__VA_ARGS__) // #define DEBUG(...) do {} while(0) -#define reg_textdisp (*(volatile uint32_t*)0x02000000) -#define reg_uart_clkdiv (*(volatile uint32_t*)0x02000010) -#define reg_uart_data (*(volatile uint32_t*)0x02000014) -#define reg_spimaster_byte (*(volatile uint32_t*)0x02000020) -#define reg_spimaster_word (*(volatile uint32_t*)0x02000024) -#define reg_romload_ctrl (*(volatile uint32_t*)0x02000030) -#define reg_romload_data (*(volatile uint32_t*)0x02000034) -#define reg_joystick (*(volatile uint32_t*)0x02000040) -#define reg_time (*(volatile uint32_t*)0x02000050) -#define reg_cycle (*(volatile uint32_t*)0x02000054) -#define reg_core_id (*(volatile uint32_t*)0x02000060) -#define reg_spiflash_byte (*(volatile uint32_t*)0x02000070) -#define reg_spiflash_word (*(volatile uint32_t*)0x02000074) -#define reg_spiflash_ctrl (*(volatile uint32_t*)0x02000078) +#define reg_textdisp (*(volatile uint32_t*)0x02000000) +#define reg_uart_clkdiv (*(volatile uint32_t*)0x02000010) +#define reg_uart_data (*(volatile uint32_t*)0x02000014) +#define reg_spimaster_byte (*(volatile uint32_t*)0x02000020) +#define reg_spimaster_word (*(volatile uint32_t*)0x02000024) +#define reg_romload_ctrl (*(volatile uint32_t*)0x02000030) +#define reg_romload_data (*(volatile uint32_t*)0x02000034) +#define reg_joystick (*(volatile uint32_t*)0x02000040) +#define reg_time (*(volatile uint32_t*)0x02000050) +#define reg_cycle (*(volatile uint32_t*)0x02000054) +#define reg_core_id (*(volatile uint32_t*)0x02000060) +#define reg_spiflash_byte (*(volatile uint32_t*)0x02000070) +#define reg_spiflash_word (*(volatile uint32_t*)0x02000074) +#define reg_spiflash_ctrl (*(volatile uint32_t*)0x02000078) +// Enhanced APU +#define reg_enhanced_apu (*(volatile uint32_t*)0x02000080) +// Cheats +#define reg_cheats_enabled (*(volatile uint32_t*)0x020000A0) +#define reg_cheats_loaded (*(volatile uint32_t*)0x020000C0) +#define reg_cheats_3 (*(volatile uint32_t*)0x020000E0) +#define reg_cheats_2 (*(volatile uint32_t*)0x02000100) +#define reg_cheats_1 (*(volatile uint32_t*)0x02000120) +#define reg_cheats_0 (*(volatile uint32_t*)0x02000140) +#define reg_cheats_data_ready (*(volatile uint32_t*)0x02000160) +// BSRAM +#define reg_save_bsram (*(volatile uint32_t*)0x02000180) +#define reg_load_bsram (*(volatile uint32_t*)0x020001A0) +// System type +#define reg_sys_type (*(volatile uint32_t*)0x020001C0) +// Aspect Ratio +#define reg_aspect_ratio (*(volatile uint32_t*)0x020001E0) +// Timer Interrupts +#define reg_timer_interrupts (*(volatile uint32_t*)0x02000200) +// Timer0 Load Register +#define reg_timer0_load_value (*(volatile uint32_t*)0x02000220) + + + // Standard library for PicoRV32 RV32I softcore