convert to platformio and clean up
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
build
|
build
|
||||||
uf2
|
uf2
|
||||||
|
.pio
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13...3.23)
|
|
||||||
|
|
||||||
include(pico_sdk_import.cmake)
|
|
||||||
|
|
||||||
project(RP2040_GB C CXX ASM)
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
|
|
||||||
pico_sdk_init()
|
|
||||||
|
|
||||||
add_subdirectory(ext/FatFs_SPI build)
|
|
||||||
|
|
||||||
add_executable(RP2040_GB
|
|
||||||
src/main.c
|
|
||||||
src/mk_ili9225.c
|
|
||||||
ext/minigb_apu/minigb_apu.c
|
|
||||||
ext/i2s/i2s.c
|
|
||||||
)
|
|
||||||
|
|
||||||
pico_generate_pio_header(RP2040_GB ${CMAKE_CURRENT_LIST_DIR}/ext/i2s/audio_i2s.pio)
|
|
||||||
|
|
||||||
target_include_directories(RP2040_GB PRIVATE inc ext/minigb_apu ext/i2s)
|
|
||||||
|
|
||||||
target_link_libraries(RP2040_GB
|
|
||||||
FatFs_SPI
|
|
||||||
pico_stdlib pico_stdio pico_bootrom pico_multicore pico_stdio pico_multicore
|
|
||||||
hardware_clocks hardware_pio hardware_vreg hardware_pio
|
|
||||||
hardware_sync hardware_pll hardware_spi hardware_irq hardware_dma
|
|
||||||
pico_binary_info)
|
|
||||||
target_compile_definitions(RP2040_GB PRIVATE
|
|
||||||
PARAM_ASSERTIONS_DISABLE_ALL=1
|
|
||||||
PICO_ENTER_USB_BOOT_ON_EXIT=1
|
|
||||||
PICO_STDIO_ENABLE_CRLF_SUPPORT=0
|
|
||||||
PICO_STDIO_DEFAULT_CRLF=0
|
|
||||||
PICO_PRINTF_SUPPORT_FLOAT=0
|
|
||||||
PICO_PRINTF_SUPPORT_EXPONENTIAL=0
|
|
||||||
PICO_PRINTF_SUPPORT_LONG_LONG=1
|
|
||||||
PICO_PRINTF_SUPPORT_PTRDIFF_T=0)
|
|
||||||
|
|
||||||
function(pico_add_verbose_dis_output TARGET)
|
|
||||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
|
||||||
COMMAND ${CMAKE_OBJDUMP} -h $<TARGET_FILE:${TARGET}> >$<IF:$<BOOL:$<TARGET_PROPERTY:${TARGET},OUTPUT_NAME>>,$<TARGET_PROPERTY:${TARGET},OUTPUT_NAME>,$<TARGET_PROPERTY:${TARGET},NAME>>.dis
|
|
||||||
COMMAND ${CMAKE_OBJDUMP} -drwCSl $<TARGET_FILE:${TARGET}> >>$<IF:$<BOOL:$<TARGET_PROPERTY:${TARGET},OUTPUT_NAME>>,$<TARGET_PROPERTY:${TARGET},OUTPUT_NAME>,$<TARGET_PROPERTY:${TARGET},NAME>>.dis
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
pico_set_binary_type(RP2040_GB copy_to_ram)
|
|
||||||
#pico_set_binary_type(RP2040_GB no_flash)
|
|
||||||
pico_enable_stdio_usb(RP2040_GB 1)
|
|
||||||
pico_enable_stdio_uart(RP2040_GB 0)
|
|
||||||
pico_add_verbose_dis_output(RP2040_GB)
|
|
||||||
pico_add_bin_output(RP2040_GB)
|
|
||||||
pico_add_uf2_output(RP2040_GB)
|
|
||||||
+11
-4
@@ -34,6 +34,11 @@
|
|||||||
|
|
||||||
typedef uint16_t palette_t[3][4];
|
typedef uint16_t palette_t[3][4];
|
||||||
|
|
||||||
|
void manual_assign_palette(palette_t palette, uint8_t selection);
|
||||||
|
void auto_assign_palette(uint16_t palette[3][4], uint8_t game_checksum, const char *game_title);
|
||||||
|
|
||||||
|
#ifndef GBCOLOR_HEADER_ONLY
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get an RGB565 colour palette by entry ID & shuffling flags
|
* Get an RGB565 colour palette by entry ID & shuffling flags
|
||||||
*
|
*
|
||||||
@@ -46,7 +51,7 @@ typedef uint16_t palette_t[3][4];
|
|||||||
*/
|
*/
|
||||||
void get_colour_palette(palette_t selected_palette,uint8_t table_entry,uint8_t shuffling_flags)
|
void get_colour_palette(palette_t selected_palette,uint8_t table_entry,uint8_t shuffling_flags)
|
||||||
{
|
{
|
||||||
printf("I get_colour_palette(table_entry=0x%02X,shuffling_flags=0x%02X)\n",
|
Serial.printf("I get_colour_palette(table_entry=0x%02X,shuffling_flags=0x%02X)\n",
|
||||||
table_entry,
|
table_entry,
|
||||||
shuffling_flags);
|
shuffling_flags);
|
||||||
if(table_entry==0x00 && shuffling_flags==0x01)
|
if(table_entry==0x00 && shuffling_flags==0x01)
|
||||||
@@ -571,7 +576,7 @@ void get_colour_palette(palette_t selected_palette,uint8_t table_entry,uint8_t s
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* default palette */
|
/* default palette */
|
||||||
printf("E get_colour_palette: No palette found for table_entry=0x%02X shuffling_flags=0x%02X\n",
|
Serial.printf("E get_colour_palette: No palette found for table_entry=0x%02X shuffling_flags=0x%02X\n",
|
||||||
table_entry,
|
table_entry,
|
||||||
shuffling_flags);
|
shuffling_flags);
|
||||||
/* Game Boy DMG palette (4 shades of green) */
|
/* Game Boy DMG palette (4 shades of green) */
|
||||||
@@ -600,7 +605,7 @@ void get_colour_palette(palette_t selected_palette,uint8_t table_entry,uint8_t s
|
|||||||
void auto_assign_palette(uint16_t palette[3][4], uint8_t game_checksum, const char *game_title)
|
void auto_assign_palette(uint16_t palette[3][4], uint8_t game_checksum, const char *game_title)
|
||||||
{
|
{
|
||||||
char disambiguation_character=game_title[3]; /* e.g. 'METROID' -> R */
|
char disambiguation_character=game_title[3]; /* e.g. 'METROID' -> R */
|
||||||
printf("I auto_assign_palette(0x%02X,%s)\n", game_checksum,game_title);
|
Serial.printf("I auto_assign_palette(0x%02X,%s)\n", game_checksum,game_title);
|
||||||
switch(game_checksum)
|
switch(game_checksum)
|
||||||
{
|
{
|
||||||
case 0x00:
|
case 0x00:
|
||||||
@@ -1239,7 +1244,7 @@ void auto_assign_palette(uint16_t palette[3][4], uint8_t game_checksum, const ch
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
printf("E auto_assign_palette: No palette found for checksum 0x%02X.\n", game_checksum);
|
Serial.printf("E auto_assign_palette: No palette found for checksum 0x%02X.\n", game_checksum);
|
||||||
/* Original Game Boy DMG color palette (monochrome 4-shades of green!) */
|
/* Original Game Boy DMG color palette (monochrome 4-shades of green!) */
|
||||||
get_colour_palette(palette,0xFF,0xFF);
|
get_colour_palette(palette,0xFF,0xFF);
|
||||||
break;
|
break;
|
||||||
@@ -1352,3 +1357,5 @@ void manual_assign_palette(palette_t palette, uint8_t selection)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
|
||||||
|
|
||||||
# This can be dropped into an external project to help locate this SDK
|
|
||||||
# It should be include()ed prior to project()
|
|
||||||
|
|
||||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
|
||||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
|
||||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
|
||||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
|
||||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
|
||||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
|
||||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
|
||||||
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")
|
|
||||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
|
||||||
|
|
||||||
if (NOT PICO_SDK_PATH)
|
|
||||||
if (PICO_SDK_FETCH_FROM_GIT)
|
|
||||||
include(FetchContent)
|
|
||||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
|
||||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
|
||||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
|
||||||
endif ()
|
|
||||||
FetchContent_Declare(
|
|
||||||
pico_sdk
|
|
||||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
|
||||||
GIT_TAG master
|
|
||||||
)
|
|
||||||
if (NOT pico_sdk)
|
|
||||||
message("Downloading Raspberry Pi Pico SDK")
|
|
||||||
FetchContent_Populate(pico_sdk)
|
|
||||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
|
||||||
endif ()
|
|
||||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
|
||||||
else ()
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
|
||||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
|
||||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
|
||||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
|
||||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
|
||||||
|
|
||||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
[env]
|
||||||
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
[pico-base]
|
||||||
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#651837d09a1a58c46bd1bcf4647468d8c5ed92c2
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
board_build.core = earlephilhower
|
||||||
|
board_build.filesystem_size = 512k
|
||||||
|
|
||||||
|
#upload_protocol = mbed
|
||||||
|
upload_protocol = cmsis-dap
|
||||||
|
debug_tool = cmsis-dap
|
||||||
|
|
||||||
|
build_src_filter =
|
||||||
|
+<*.cpp>
|
||||||
|
|
||||||
|
#lib_ldf_mode = chain+
|
||||||
|
lib_deps =
|
||||||
|
bodmer/TFT_eSPI@^2.5.43
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-I.
|
||||||
|
-Isrc/tft-espi-config/
|
||||||
|
-Iinc
|
||||||
|
-Iext/minigb_apu
|
||||||
|
|
||||||
|
[env:pico]
|
||||||
|
extends = pico-base
|
||||||
|
board = rpipico
|
||||||
|
|
||||||
|
[env:pico2]
|
||||||
|
extends = pico-base
|
||||||
|
board = rpipico2
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
IndentWidth: 2
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
Standard: Cpp11
|
||||||
|
CommentPragmas: '^// Copyright|^// SPDX-License-Identifier'
|
||||||
|
# Pointers aligned to the left
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterUnion: false
|
||||||
|
AfterExternBlock: true
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: false
|
||||||
|
SplitEmptyNamespace: false
|
||||||
|
SpaceInEmptyBlock: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 2
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IndentCaseLabels: false
|
||||||
|
# NamespaceIndentation: All
|
||||||
|
# SortIncludes: true
|
||||||
|
# SortUsingDeclarations: true
|
||||||
|
# SpaceAfterTemplateKeyword: true
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
UseTab: Never
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
#if ENABLE_SDCARD
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a save file from the SD card
|
||||||
|
*/
|
||||||
|
void read_cart_ram_file(struct gb_s* gb) {
|
||||||
|
char filename[16];
|
||||||
|
uint_fast32_t save_size;
|
||||||
|
UINT br;
|
||||||
|
|
||||||
|
gb_get_rom_name(gb, filename);
|
||||||
|
save_size = gb_get_save_size(gb);
|
||||||
|
if (save_size > 0) {
|
||||||
|
sd_card_t* pSD = sd_get_by_num(0);
|
||||||
|
FRESULT fr = f_mount(&pSD->fatfs, pSD->pcName, 1);
|
||||||
|
if (FR_OK != fr) {
|
||||||
|
printf("E f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FIL fil;
|
||||||
|
fr = f_open(&fil, filename, FA_READ);
|
||||||
|
if (fr == FR_OK) {
|
||||||
|
f_read(&fil, ram, f_size(&fil), &br);
|
||||||
|
} else {
|
||||||
|
printf("E f_open(%s) error: %s (%d)\n", filename, FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fr = f_close(&fil);
|
||||||
|
if (fr != FR_OK) {
|
||||||
|
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
f_unmount(pSD->pcName);
|
||||||
|
}
|
||||||
|
printf("I read_cart_ram_file(%s) COMPLETE (%lu bytes)\n", filename, save_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a save file to the SD card
|
||||||
|
*/
|
||||||
|
void write_cart_ram_file(struct gb_s* gb) {
|
||||||
|
char filename[16];
|
||||||
|
uint_fast32_t save_size;
|
||||||
|
UINT bw;
|
||||||
|
|
||||||
|
gb_get_rom_name(gb, filename);
|
||||||
|
save_size = gb_get_save_size(gb);
|
||||||
|
if (save_size > 0) {
|
||||||
|
sd_card_t* pSD = sd_get_by_num(0);
|
||||||
|
FRESULT fr = f_mount(&pSD->fatfs, pSD->pcName, 1);
|
||||||
|
if (FR_OK != fr) {
|
||||||
|
printf("E f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FIL fil;
|
||||||
|
fr = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE);
|
||||||
|
if (fr == FR_OK) {
|
||||||
|
f_write(&fil, ram, save_size, &bw);
|
||||||
|
} else {
|
||||||
|
printf("E f_open(%s) error: %s (%d)\n", filename, FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fr = f_close(&fil);
|
||||||
|
if (fr != FR_OK) {
|
||||||
|
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
f_unmount(pSD->pcName);
|
||||||
|
}
|
||||||
|
printf("I write_cart_ram_file(%s) COMPLETE (%lu bytes)\n", filename, save_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a .gb rom file in flash from the SD card
|
||||||
|
*/
|
||||||
|
void load_cart_rom_file(char* filename) {
|
||||||
|
UINT br;
|
||||||
|
uint8_t buffer[FLASH_SECTOR_SIZE];
|
||||||
|
bool mismatch = false;
|
||||||
|
sd_card_t* pSD = sd_get_by_num(0);
|
||||||
|
FRESULT fr = f_mount(&pSD->fatfs, pSD->pcName, 1);
|
||||||
|
if (FR_OK != fr) {
|
||||||
|
printf("E f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FIL fil;
|
||||||
|
fr = f_open(&fil, filename, FA_READ);
|
||||||
|
if (fr == FR_OK) {
|
||||||
|
uint32_t flash_target_offset = FLASH_TARGET_OFFSET;
|
||||||
|
for (;;) {
|
||||||
|
f_read(&fil, buffer, sizeof buffer, &br);
|
||||||
|
if (br == 0)
|
||||||
|
break; /* end of file */
|
||||||
|
|
||||||
|
printf("I Erasing target region...\n");
|
||||||
|
flash_range_erase(flash_target_offset, FLASH_SECTOR_SIZE);
|
||||||
|
printf("I Programming target region...\n");
|
||||||
|
flash_range_program(flash_target_offset, buffer, FLASH_SECTOR_SIZE);
|
||||||
|
|
||||||
|
/* Read back target region and check programming */
|
||||||
|
printf("I Done. Reading back target region...\n");
|
||||||
|
for (uint32_t i = 0; i < FLASH_SECTOR_SIZE; i++) {
|
||||||
|
if (rom[flash_target_offset + i] != buffer[i]) {
|
||||||
|
mismatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next sector */
|
||||||
|
flash_target_offset += FLASH_SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
if (mismatch) {
|
||||||
|
printf("I Programming successful!\n");
|
||||||
|
} else {
|
||||||
|
printf("E Programming failed!\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("E f_open(%s) error: %s (%d)\n", filename, FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fr = f_close(&fil);
|
||||||
|
if (fr != FR_OK) {
|
||||||
|
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
}
|
||||||
|
f_unmount(pSD->pcName);
|
||||||
|
|
||||||
|
printf("I load_cart_rom_file(%s) COMPLETE (%lu bytes)\n", filename, br);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used by the rom file selector to display one page of .gb rom files
|
||||||
|
*/
|
||||||
|
uint16_t rom_file_selector_display_page(char filename[22][256], uint16_t num_page) {
|
||||||
|
sd_card_t* pSD = sd_get_by_num(0);
|
||||||
|
DIR dj;
|
||||||
|
FILINFO fno;
|
||||||
|
FRESULT fr;
|
||||||
|
|
||||||
|
fr = f_mount(&pSD->fatfs, pSD->pcName, 1);
|
||||||
|
if (FR_OK != fr) {
|
||||||
|
printf("E f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear the filenames array */
|
||||||
|
for (uint8_t ifile = 0; ifile < 22; ifile++) {
|
||||||
|
strcpy(filename[ifile], "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* search *.gb files */
|
||||||
|
uint16_t num_file = 0;
|
||||||
|
fr = f_findfirst(&dj, &fno, "", "*.gb");
|
||||||
|
|
||||||
|
/* skip the first N pages */
|
||||||
|
if (num_page > 0) {
|
||||||
|
while (num_file < num_page * 22 && fr == FR_OK && fno.fname[0]) {
|
||||||
|
num_file++;
|
||||||
|
fr = f_findnext(&dj, &fno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* store the filenames of this page */
|
||||||
|
num_file = 0;
|
||||||
|
while (num_file < 22 && fr == FR_OK && fno.fname[0]) {
|
||||||
|
strcpy(filename[num_file], fno.fname);
|
||||||
|
num_file++;
|
||||||
|
fr = f_findnext(&dj, &fno);
|
||||||
|
}
|
||||||
|
f_closedir(&dj);
|
||||||
|
f_unmount(pSD->pcName);
|
||||||
|
|
||||||
|
/* display *.gb rom files on screen */
|
||||||
|
lcd_fill(0x0000);
|
||||||
|
for (uint8_t ifile = 0; ifile < num_file; ifile++) {
|
||||||
|
lcd_text(filename[ifile], 0, ifile * 8, 0xFFFF, 0x0000);
|
||||||
|
}
|
||||||
|
return num_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ROM selector displays pages of up to 22 rom files
|
||||||
|
* allowing the user to select which rom file to start
|
||||||
|
* Copy your *.gb rom files to the root directory of the SD card
|
||||||
|
*/
|
||||||
|
void rom_file_selector() {
|
||||||
|
uint16_t num_page;
|
||||||
|
char filename[22][256];
|
||||||
|
uint16_t num_file;
|
||||||
|
|
||||||
|
/* display the first page with up to 22 rom files */
|
||||||
|
num_file = rom_file_selector_display_page(filename, num_page);
|
||||||
|
|
||||||
|
/* select the first rom */
|
||||||
|
uint8_t selected = 0;
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||||
|
|
||||||
|
/* get user's input */
|
||||||
|
bool up, down, left, right, a, b, select, start;
|
||||||
|
while (true) {
|
||||||
|
up = gpio_get(GPIO_UP);
|
||||||
|
down = gpio_get(GPIO_DOWN);
|
||||||
|
left = gpio_get(GPIO_LEFT);
|
||||||
|
right = gpio_get(GPIO_RIGHT);
|
||||||
|
a = gpio_get(GPIO_A);
|
||||||
|
b = gpio_get(GPIO_B);
|
||||||
|
select = gpio_get(GPIO_SELECT);
|
||||||
|
start = gpio_get(GPIO_START);
|
||||||
|
if (!start) {
|
||||||
|
/* re-start the last game (no need to reprogram flash) */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!a | !b) {
|
||||||
|
/* copy the rom from the SD card to flash and start the game */
|
||||||
|
load_cart_rom_file(filename[selected]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!down) {
|
||||||
|
/* select the next rom */
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0x0000);
|
||||||
|
selected++;
|
||||||
|
if (selected >= num_file)
|
||||||
|
selected = 0;
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||||
|
sleep_ms(150);
|
||||||
|
}
|
||||||
|
if (!up) {
|
||||||
|
/* select the previous rom */
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0x0000);
|
||||||
|
if (selected == 0) {
|
||||||
|
selected = num_file - 1;
|
||||||
|
} else {
|
||||||
|
selected--;
|
||||||
|
}
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||||
|
sleep_ms(150);
|
||||||
|
}
|
||||||
|
if (!right) {
|
||||||
|
/* select the next page */
|
||||||
|
num_page++;
|
||||||
|
num_file = rom_file_selector_display_page(filename, num_page);
|
||||||
|
if (num_file == 0) {
|
||||||
|
/* no files in this page, go to the previous page */
|
||||||
|
num_page--;
|
||||||
|
num_file = rom_file_selector_display_page(filename, num_page);
|
||||||
|
}
|
||||||
|
/* select the first file */
|
||||||
|
selected = 0;
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||||
|
sleep_ms(150);
|
||||||
|
}
|
||||||
|
if ((!left) && num_page > 0) {
|
||||||
|
/* select the previous page */
|
||||||
|
num_page--;
|
||||||
|
num_file = rom_file_selector_display_page(filename, num_page);
|
||||||
|
/* select the first file */
|
||||||
|
selected = 0;
|
||||||
|
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||||
|
sleep_ms(150);
|
||||||
|
}
|
||||||
|
tight_loop_contents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define GBCOLOR_HEADER_ONLY
|
||||||
|
#include "gbcolors.h"
|
||||||
|
|
||||||
|
/* Multicore command structure. */
|
||||||
|
union core_cmd
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
/* Does nothing. */
|
||||||
|
#define CORE_CMD_NOP 0
|
||||||
|
/* Set line "data" on the LCD. Pixel data is in pixels_buffer. */
|
||||||
|
#define CORE_CMD_LCD_LINE 1
|
||||||
|
/* Control idle mode on the LCD. Limits colours to 2 bits. */
|
||||||
|
#define CORE_CMD_IDLE_SET 2
|
||||||
|
/* Set a specific pixel. For debugging. */
|
||||||
|
#define CORE_CMD_SET_PIXEL 3
|
||||||
|
uint8_t cmd;
|
||||||
|
uint8_t unused1;
|
||||||
|
uint8_t unused2;
|
||||||
|
uint8_t data;
|
||||||
|
};
|
||||||
|
uint32_t full;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern palette_t palette; // Colour palette
|
||||||
|
|
||||||
|
void lcd_draw_line(struct gb_s *gb, const uint8_t *pixels, const uint_fast8_t line);
|
||||||
|
|
||||||
|
void core1_init();
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
#define PEANUT_GB_HEADER_ONLY
|
||||||
|
#include "peanut_gb.h"
|
||||||
|
|
||||||
|
// Note: must be included before core
|
||||||
|
#include "gbcolors.h"
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
static TFT_eSPI tft = TFT_eSPI();
|
||||||
|
|
||||||
|
static uint8_t scaledLineOffsetTable[LCD_HEIGHT]; // scaled to 240 lines
|
||||||
|
|
||||||
|
/* Pixel data is stored in here. */
|
||||||
|
static uint8_t pixels_buffer[LCD_WIDTH];
|
||||||
|
|
||||||
|
palette_t palette; // Colour palette
|
||||||
|
|
||||||
|
static int lcd_line_busy = 0;
|
||||||
|
|
||||||
|
#define IS_LINE_REPEATED(line) ((line % 2) || (line % 6 == 0))
|
||||||
|
// #define IS_LINE_REPEATED(line) 0
|
||||||
|
|
||||||
|
static void calcExtraLineTable() {
|
||||||
|
uint8_t offset = 0;
|
||||||
|
for (uint8_t line = 0; line < LCD_HEIGHT; ++line) {
|
||||||
|
scaledLineOffsetTable[line] = offset;
|
||||||
|
offset += 1 + IS_LINE_REPEATED(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_init(void) {
|
||||||
|
tft.init();
|
||||||
|
// tft.initDMA();
|
||||||
|
tft.setRotation(1);
|
||||||
|
tft.fillScreen(TFT_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_draw_line(struct gb_s* gb, const uint8_t pixels[LCD_WIDTH],
|
||||||
|
const uint_fast8_t line) {
|
||||||
|
union core_cmd cmd;
|
||||||
|
|
||||||
|
/* Wait until previous line is sent. */
|
||||||
|
while (__atomic_load_n(&lcd_line_busy, __ATOMIC_SEQ_CST))
|
||||||
|
tight_loop_contents();
|
||||||
|
|
||||||
|
memcpy(pixels_buffer, pixels, LCD_WIDTH);
|
||||||
|
|
||||||
|
/* Populate command. */
|
||||||
|
cmd.cmd = CORE_CMD_LCD_LINE;
|
||||||
|
cmd.data = line;
|
||||||
|
|
||||||
|
__atomic_store_n(&lcd_line_busy, 1, __ATOMIC_SEQ_CST);
|
||||||
|
multicore_fifo_push_blocking(cmd.full);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_write_pixels(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
|
||||||
|
static uint16_t doubledPixels[320];
|
||||||
|
uint16_t pos = 0;
|
||||||
|
for (int i = 0; i < nmemb; ++i) {
|
||||||
|
doubledPixels[pos++] = pixels[i];
|
||||||
|
doubledPixels[pos++] = pixels[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t repeatedLines = IS_LINE_REPEATED(line);
|
||||||
|
// tft.setAddrWindow(0, scaledLineOffsetTable[line], nmemb * 2, 1 + repeatedLines);
|
||||||
|
tft.setAddrWindow(0, scaledLineOffsetTable[line], nmemb * 2, 1);
|
||||||
|
tft.pushColors((uint16_t*)doubledPixels, nmemb * 2, true);
|
||||||
|
if (repeatedLines) {
|
||||||
|
tft.setAddrWindow(0, scaledLineOffsetTable[line] + 1, nmemb * 2, 1);
|
||||||
|
tft.pushColors((uint16_t*)doubledPixels, nmemb * 2, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_fill(uint16_t color) {
|
||||||
|
tft.fillScreen(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
|
||||||
|
tft.fillRect(0, 0, tft.width(), tft.height(), TFT_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_text(char* s, uint8_t x, uint8_t y, uint16_t color, uint16_t bgcolor) {
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK); // TODO
|
||||||
|
tft.drawString(s, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_display_control(bool invert, int /*ili9225_color_mode_e*/ colour_mode) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void core1_lcd_draw_line(const uint_fast8_t line) {
|
||||||
|
static uint16_t fb[LCD_WIDTH];
|
||||||
|
|
||||||
|
for (unsigned int x = 0; x < LCD_WIDTH; x++) {
|
||||||
|
fb[x] = palette[(pixels_buffer[x] & LCD_PALETTE_ALL) >> 4]
|
||||||
|
[pixels_buffer[x] & 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
lcd_write_pixels(fb, line, LCD_WIDTH);
|
||||||
|
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void core1DispatchLoop() {
|
||||||
|
union core_cmd cmd;
|
||||||
|
|
||||||
|
/* Handle commands coming from core0. */
|
||||||
|
cmd.full = multicore_fifo_pop_blocking();
|
||||||
|
switch (cmd.cmd) {
|
||||||
|
case CORE_CMD_LCD_LINE:
|
||||||
|
core1_lcd_draw_line(cmd.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CORE_CMD_IDLE_SET:
|
||||||
|
lcd_display_control(true, cmd.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CORE_CMD_NOP:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void core1_init() {
|
||||||
|
/* Initialise and control LCD on core 1. */
|
||||||
|
lcd_init();
|
||||||
|
|
||||||
|
/* Clear LCD screen. */
|
||||||
|
lcd_fill(0x0000);
|
||||||
|
|
||||||
|
/* Set LCD window to DMG size. */
|
||||||
|
lcd_fill_rect(31, 16, LCD_WIDTH, LCD_HEIGHT, 0x0000);
|
||||||
|
|
||||||
|
calcExtraLineTable();
|
||||||
|
|
||||||
|
// Sleep used for debugging LCD window.
|
||||||
|
// sleep_ms(1000);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
core1DispatchLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
-902
@@ -1,902 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2022 by Mahyar Koshkouei <mk@deltabeard.com>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
||||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
* PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Peanut-GB emulator settings
|
|
||||||
#define ENABLE_LCD 1
|
|
||||||
#define ENABLE_SOUND 1
|
|
||||||
#define ENABLE_SDCARD 1
|
|
||||||
#define PEANUT_GB_HIGH_LCD_ACCURACY 1
|
|
||||||
#define PEANUT_GB_USE_BIOS 0
|
|
||||||
|
|
||||||
/* Use DMA for all drawing to LCD. Benefits aren't fully realised at the moment
|
|
||||||
* due to busy loops waiting for DMA completion. */
|
|
||||||
#define USE_DMA 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reducing VSYNC calculation to lower multiple.
|
|
||||||
* When setting a clock IRQ to DMG_CLOCK_FREQ_REDUCED, count to
|
|
||||||
* SCREEN_REFRESH_CYCLES_REDUCED to obtain the time required each VSYNC.
|
|
||||||
* DMG_CLOCK_FREQ_REDUCED = 2^18, and SCREEN_REFRESH_CYCLES_REDUCED = 4389.
|
|
||||||
* Currently unused.
|
|
||||||
*/
|
|
||||||
#define VSYNC_REDUCTION_FACTOR 16u
|
|
||||||
#define SCREEN_REFRESH_CYCLES_REDUCED (SCREEN_REFRESH_CYCLES/VSYNC_REDUCTION_FACTOR)
|
|
||||||
#define DMG_CLOCK_FREQ_REDUCED (DMG_CLOCK_FREQ/VSYNC_REDUCTION_FACTOR)
|
|
||||||
|
|
||||||
/* C Headers */
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* RP2040 Headers */
|
|
||||||
#include <hardware/pio.h>
|
|
||||||
#include <hardware/clocks.h>
|
|
||||||
#include <hardware/dma.h>
|
|
||||||
#include <hardware/spi.h>
|
|
||||||
#include <hardware/sync.h>
|
|
||||||
#include <hardware/flash.h>
|
|
||||||
#include <hardware/timer.h>
|
|
||||||
#include <hardware/vreg.h>
|
|
||||||
#include <pico/bootrom.h>
|
|
||||||
#include <pico/stdio.h>
|
|
||||||
#include <pico/stdlib.h>
|
|
||||||
#include <pico/multicore.h>
|
|
||||||
#include <sys/unistd.h>
|
|
||||||
#include <hardware/irq.h>
|
|
||||||
|
|
||||||
/* Project headers */
|
|
||||||
#include "hedley.h"
|
|
||||||
#include "minigb_apu.h"
|
|
||||||
#include "peanut_gb.h"
|
|
||||||
#include "mk_ili9225.h"
|
|
||||||
#include "sdcard.h"
|
|
||||||
#include "i2s.h"
|
|
||||||
#include "gbcolors.h"
|
|
||||||
|
|
||||||
/* GPIO Connections. */
|
|
||||||
#define GPIO_UP 2
|
|
||||||
#define GPIO_DOWN 3
|
|
||||||
#define GPIO_LEFT 4
|
|
||||||
#define GPIO_RIGHT 5
|
|
||||||
#define GPIO_A 6
|
|
||||||
#define GPIO_B 7
|
|
||||||
#define GPIO_SELECT 8
|
|
||||||
#define GPIO_START 9
|
|
||||||
#define GPIO_CS 17
|
|
||||||
#define GPIO_CLK 18
|
|
||||||
#define GPIO_SDA 19
|
|
||||||
#define GPIO_RS 20
|
|
||||||
#define GPIO_RST 21
|
|
||||||
#define GPIO_LED 22
|
|
||||||
|
|
||||||
#if ENABLE_SOUND
|
|
||||||
/**
|
|
||||||
* Global variables for audio task
|
|
||||||
* stream contains N=AUDIO_SAMPLES samples
|
|
||||||
* each sample is 32 bits
|
|
||||||
* 16 bits for the left channel + 16 bits for the right channel in stereo interleaved format)
|
|
||||||
* This is intended to be played at AUDIO_SAMPLE_RATE Hz
|
|
||||||
*/
|
|
||||||
uint16_t *stream;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/** Definition of ROM data
|
|
||||||
* We're going to erase and reprogram a region 1Mb from the start of the flash
|
|
||||||
* Once done, we can access this at XIP_BASE + 1Mb.
|
|
||||||
* Game Boy DMG ROM size ranges from 32768 bytes (e.g. Tetris) to 1,048,576 bytes (e.g. Pokemod Red)
|
|
||||||
*/
|
|
||||||
#define FLASH_TARGET_OFFSET (1024 * 1024)
|
|
||||||
const uint8_t *rom = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET);
|
|
||||||
static unsigned char rom_bank0[65536];
|
|
||||||
|
|
||||||
static uint8_t ram[32768];
|
|
||||||
static int lcd_line_busy = 0;
|
|
||||||
static palette_t palette; // Colour palette
|
|
||||||
static uint8_t manual_palette_selected=0;
|
|
||||||
|
|
||||||
static struct
|
|
||||||
{
|
|
||||||
unsigned a : 1;
|
|
||||||
unsigned b : 1;
|
|
||||||
unsigned select : 1;
|
|
||||||
unsigned start : 1;
|
|
||||||
unsigned right : 1;
|
|
||||||
unsigned left : 1;
|
|
||||||
unsigned up : 1;
|
|
||||||
unsigned down : 1;
|
|
||||||
} prev_joypad_bits;
|
|
||||||
|
|
||||||
/* Multicore command structure. */
|
|
||||||
union core_cmd {
|
|
||||||
struct {
|
|
||||||
/* Does nothing. */
|
|
||||||
#define CORE_CMD_NOP 0
|
|
||||||
/* Set line "data" on the LCD. Pixel data is in pixels_buffer. */
|
|
||||||
#define CORE_CMD_LCD_LINE 1
|
|
||||||
/* Control idle mode on the LCD. Limits colours to 2 bits. */
|
|
||||||
#define CORE_CMD_IDLE_SET 2
|
|
||||||
/* Set a specific pixel. For debugging. */
|
|
||||||
#define CORE_CMD_SET_PIXEL 3
|
|
||||||
uint8_t cmd;
|
|
||||||
uint8_t unused1;
|
|
||||||
uint8_t unused2;
|
|
||||||
uint8_t data;
|
|
||||||
};
|
|
||||||
uint32_t full;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Pixel data is stored in here. */
|
|
||||||
static uint8_t pixels_buffer[LCD_WIDTH];
|
|
||||||
|
|
||||||
#define putstdio(x) write(1, x, strlen(x))
|
|
||||||
|
|
||||||
/* Functions required for communication with the ILI9225. */
|
|
||||||
void mk_ili9225_set_rst(bool state)
|
|
||||||
{
|
|
||||||
gpio_put(GPIO_RST, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mk_ili9225_set_rs(bool state)
|
|
||||||
{
|
|
||||||
gpio_put(GPIO_RS, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mk_ili9225_set_cs(bool state)
|
|
||||||
{
|
|
||||||
gpio_put(GPIO_CS, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mk_ili9225_set_led(bool state)
|
|
||||||
{
|
|
||||||
gpio_put(GPIO_LED, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mk_ili9225_spi_write16(const uint16_t *halfwords, size_t len)
|
|
||||||
{
|
|
||||||
spi_write16_blocking(spi0, halfwords, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mk_ili9225_delay_ms(unsigned ms)
|
|
||||||
{
|
|
||||||
sleep_ms(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte from the ROM file at the given address.
|
|
||||||
*/
|
|
||||||
uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr)
|
|
||||||
{
|
|
||||||
(void) gb;
|
|
||||||
if(addr < sizeof(rom_bank0))
|
|
||||||
return rom_bank0[addr];
|
|
||||||
|
|
||||||
return rom[addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte from the cartridge RAM at the given address.
|
|
||||||
*/
|
|
||||||
uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr)
|
|
||||||
{
|
|
||||||
(void) gb;
|
|
||||||
return ram[addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a given byte to the cartridge RAM at the given address.
|
|
||||||
*/
|
|
||||||
void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr,
|
|
||||||
const uint8_t val)
|
|
||||||
{
|
|
||||||
ram[addr] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ignore all errors.
|
|
||||||
*/
|
|
||||||
void gb_error(struct gb_s *gb, const enum gb_error_e gb_err, const uint16_t addr)
|
|
||||||
{
|
|
||||||
#if 1
|
|
||||||
const char* gb_err_str[4] = {
|
|
||||||
"UNKNOWN",
|
|
||||||
"INVALID OPCODE",
|
|
||||||
"INVALID READ",
|
|
||||||
"INVALID WRITE"
|
|
||||||
};
|
|
||||||
printf("Error %d occurred: %s at %04X\n.\n", gb_err, gb_err_str[gb_err], addr);
|
|
||||||
// abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ENABLE_LCD
|
|
||||||
void core1_lcd_draw_line(const uint_fast8_t line)
|
|
||||||
{
|
|
||||||
static uint16_t fb[LCD_WIDTH];
|
|
||||||
|
|
||||||
for(unsigned int x = 0; x < LCD_WIDTH; x++)
|
|
||||||
{
|
|
||||||
fb[x] = palette[(pixels_buffer[x] & LCD_PALETTE_ALL) >> 4]
|
|
||||||
[pixels_buffer[x] & 3];
|
|
||||||
}
|
|
||||||
|
|
||||||
mk_ili9225_set_x(line + 16);
|
|
||||||
mk_ili9225_write_pixels(fb, LCD_WIDTH);
|
|
||||||
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
|
||||||
}
|
|
||||||
|
|
||||||
_Noreturn
|
|
||||||
void main_core1(void)
|
|
||||||
{
|
|
||||||
union core_cmd cmd;
|
|
||||||
|
|
||||||
/* Initialise and control LCD on core 1. */
|
|
||||||
mk_ili9225_init();
|
|
||||||
|
|
||||||
/* Clear LCD screen. */
|
|
||||||
mk_ili9225_fill(0x0000);
|
|
||||||
|
|
||||||
/* Set LCD window to DMG size. */
|
|
||||||
mk_ili9225_fill_rect(31,16,LCD_WIDTH,LCD_HEIGHT,0x0000);
|
|
||||||
|
|
||||||
// Sleep used for debugging LCD window.
|
|
||||||
//sleep_ms(1000);
|
|
||||||
|
|
||||||
/* Handle commands coming from core0. */
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
cmd.full = multicore_fifo_pop_blocking();
|
|
||||||
switch(cmd.cmd)
|
|
||||||
{
|
|
||||||
case CORE_CMD_LCD_LINE:
|
|
||||||
core1_lcd_draw_line(cmd.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CORE_CMD_IDLE_SET:
|
|
||||||
mk_ili9225_display_control(true, cmd.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CORE_CMD_NOP:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HEDLEY_UNREACHABLE();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ENABLE_LCD
|
|
||||||
void lcd_draw_line(struct gb_s *gb, const uint8_t pixels[LCD_WIDTH],
|
|
||||||
const uint_fast8_t line)
|
|
||||||
{
|
|
||||||
union core_cmd cmd;
|
|
||||||
|
|
||||||
/* Wait until previous line is sent. */
|
|
||||||
while(__atomic_load_n(&lcd_line_busy, __ATOMIC_SEQ_CST))
|
|
||||||
tight_loop_contents();
|
|
||||||
|
|
||||||
memcpy(pixels_buffer, pixels, LCD_WIDTH);
|
|
||||||
|
|
||||||
/* Populate command. */
|
|
||||||
cmd.cmd = CORE_CMD_LCD_LINE;
|
|
||||||
cmd.data = line;
|
|
||||||
|
|
||||||
__atomic_store_n(&lcd_line_busy, 1, __ATOMIC_SEQ_CST);
|
|
||||||
multicore_fifo_push_blocking(cmd.full);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ENABLE_SDCARD
|
|
||||||
/**
|
|
||||||
* Load a save file from the SD card
|
|
||||||
*/
|
|
||||||
void read_cart_ram_file(struct gb_s *gb) {
|
|
||||||
char filename[16];
|
|
||||||
uint_fast32_t save_size;
|
|
||||||
UINT br;
|
|
||||||
|
|
||||||
gb_get_rom_name(gb,filename);
|
|
||||||
save_size=gb_get_save_size(gb);
|
|
||||||
if(save_size>0) {
|
|
||||||
sd_card_t *pSD=sd_get_by_num(0);
|
|
||||||
FRESULT fr=f_mount(&pSD->fatfs,pSD->pcName,1);
|
|
||||||
if (FR_OK!=fr) {
|
|
||||||
printf("E f_mount error: %s (%d)\n",FRESULT_str(fr),fr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FIL fil;
|
|
||||||
fr=f_open(&fil,filename,FA_READ);
|
|
||||||
if (fr==FR_OK) {
|
|
||||||
f_read(&fil,ram,f_size(&fil),&br);
|
|
||||||
} else {
|
|
||||||
printf("E f_open(%s) error: %s (%d)\n",filename,FRESULT_str(fr),fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fr=f_close(&fil);
|
|
||||||
if(fr!=FR_OK) {
|
|
||||||
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
|
||||||
}
|
|
||||||
f_unmount(pSD->pcName);
|
|
||||||
}
|
|
||||||
printf("I read_cart_ram_file(%s) COMPLETE (%lu bytes)\n",filename,save_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a save file to the SD card
|
|
||||||
*/
|
|
||||||
void write_cart_ram_file(struct gb_s *gb) {
|
|
||||||
char filename[16];
|
|
||||||
uint_fast32_t save_size;
|
|
||||||
UINT bw;
|
|
||||||
|
|
||||||
gb_get_rom_name(gb,filename);
|
|
||||||
save_size=gb_get_save_size(gb);
|
|
||||||
if(save_size>0) {
|
|
||||||
sd_card_t *pSD=sd_get_by_num(0);
|
|
||||||
FRESULT fr=f_mount(&pSD->fatfs,pSD->pcName,1);
|
|
||||||
if (FR_OK!=fr) {
|
|
||||||
printf("E f_mount error: %s (%d)\n",FRESULT_str(fr),fr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FIL fil;
|
|
||||||
fr=f_open(&fil,filename,FA_CREATE_ALWAYS | FA_WRITE);
|
|
||||||
if (fr==FR_OK) {
|
|
||||||
f_write(&fil,ram,save_size,&bw);
|
|
||||||
} else {
|
|
||||||
printf("E f_open(%s) error: %s (%d)\n",filename,FRESULT_str(fr),fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fr=f_close(&fil);
|
|
||||||
if(fr!=FR_OK) {
|
|
||||||
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
|
||||||
}
|
|
||||||
f_unmount(pSD->pcName);
|
|
||||||
}
|
|
||||||
printf("I write_cart_ram_file(%s) COMPLETE (%lu bytes)\n",filename,save_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a .gb rom file in flash from the SD card
|
|
||||||
*/
|
|
||||||
void load_cart_rom_file(char *filename) {
|
|
||||||
UINT br;
|
|
||||||
uint8_t buffer[FLASH_SECTOR_SIZE];
|
|
||||||
bool mismatch=false;
|
|
||||||
sd_card_t *pSD=sd_get_by_num(0);
|
|
||||||
FRESULT fr=f_mount(&pSD->fatfs,pSD->pcName,1);
|
|
||||||
if (FR_OK!=fr) {
|
|
||||||
printf("E f_mount error: %s (%d)\n",FRESULT_str(fr),fr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FIL fil;
|
|
||||||
fr=f_open(&fil,filename,FA_READ);
|
|
||||||
if (fr==FR_OK) {
|
|
||||||
uint32_t flash_target_offset=FLASH_TARGET_OFFSET;
|
|
||||||
for(;;) {
|
|
||||||
f_read(&fil,buffer,sizeof buffer,&br);
|
|
||||||
if(br==0) break; /* end of file */
|
|
||||||
|
|
||||||
printf("I Erasing target region...\n");
|
|
||||||
flash_range_erase(flash_target_offset,FLASH_SECTOR_SIZE);
|
|
||||||
printf("I Programming target region...\n");
|
|
||||||
flash_range_program(flash_target_offset,buffer,FLASH_SECTOR_SIZE);
|
|
||||||
|
|
||||||
/* Read back target region and check programming */
|
|
||||||
printf("I Done. Reading back target region...\n");
|
|
||||||
for(uint32_t i=0;i<FLASH_SECTOR_SIZE;i++) {
|
|
||||||
if(rom[flash_target_offset+i]!=buffer[i]) {
|
|
||||||
mismatch=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Next sector */
|
|
||||||
flash_target_offset+=FLASH_SECTOR_SIZE;
|
|
||||||
}
|
|
||||||
if(mismatch) {
|
|
||||||
printf("I Programming successful!\n");
|
|
||||||
} else {
|
|
||||||
printf("E Programming failed!\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("E f_open(%s) error: %s (%d)\n",filename,FRESULT_str(fr),fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fr=f_close(&fil);
|
|
||||||
if(fr!=FR_OK) {
|
|
||||||
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
|
||||||
}
|
|
||||||
f_unmount(pSD->pcName);
|
|
||||||
|
|
||||||
printf("I load_cart_rom_file(%s) COMPLETE (%lu bytes)\n",filename,br);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function used by the rom file selector to display one page of .gb rom files
|
|
||||||
*/
|
|
||||||
uint16_t rom_file_selector_display_page(char filename[22][256],uint16_t num_page) {
|
|
||||||
sd_card_t *pSD=sd_get_by_num(0);
|
|
||||||
DIR dj;
|
|
||||||
FILINFO fno;
|
|
||||||
FRESULT fr;
|
|
||||||
|
|
||||||
fr=f_mount(&pSD->fatfs,pSD->pcName,1);
|
|
||||||
if (FR_OK!=fr) {
|
|
||||||
printf("E f_mount error: %s (%d)\n",FRESULT_str(fr),fr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear the filenames array */
|
|
||||||
for(uint8_t ifile=0;ifile<22;ifile++) {
|
|
||||||
strcpy(filename[ifile],"");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* search *.gb files */
|
|
||||||
uint16_t num_file=0;
|
|
||||||
fr=f_findfirst(&dj, &fno, "", "*.gb");
|
|
||||||
|
|
||||||
/* skip the first N pages */
|
|
||||||
if(num_page>0) {
|
|
||||||
while(num_file<num_page*22 && fr == FR_OK && fno.fname[0]) {
|
|
||||||
num_file++;
|
|
||||||
fr=f_findnext(&dj, &fno);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* store the filenames of this page */
|
|
||||||
num_file=0;
|
|
||||||
while(num_file<22 && fr == FR_OK && fno.fname[0]) {
|
|
||||||
strcpy(filename[num_file],fno.fname);
|
|
||||||
num_file++;
|
|
||||||
fr=f_findnext(&dj, &fno);
|
|
||||||
}
|
|
||||||
f_closedir(&dj);
|
|
||||||
f_unmount(pSD->pcName);
|
|
||||||
|
|
||||||
/* display *.gb rom files on screen */
|
|
||||||
mk_ili9225_fill(0x0000);
|
|
||||||
for(uint8_t ifile=0;ifile<num_file;ifile++) {
|
|
||||||
mk_ili9225_text(filename[ifile],0,ifile*8,0xFFFF,0x0000);
|
|
||||||
}
|
|
||||||
return num_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ROM selector displays pages of up to 22 rom files
|
|
||||||
* allowing the user to select which rom file to start
|
|
||||||
* Copy your *.gb rom files to the root directory of the SD card
|
|
||||||
*/
|
|
||||||
void rom_file_selector() {
|
|
||||||
uint16_t num_page;
|
|
||||||
char filename[22][256];
|
|
||||||
uint16_t num_file;
|
|
||||||
|
|
||||||
/* display the first page with up to 22 rom files */
|
|
||||||
num_file=rom_file_selector_display_page(filename,num_page);
|
|
||||||
|
|
||||||
/* select the first rom */
|
|
||||||
uint8_t selected=0;
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0xF800);
|
|
||||||
|
|
||||||
/* get user's input */
|
|
||||||
bool up,down,left,right,a,b,select,start;
|
|
||||||
while(true) {
|
|
||||||
up=gpio_get(GPIO_UP);
|
|
||||||
down=gpio_get(GPIO_DOWN);
|
|
||||||
left=gpio_get(GPIO_LEFT);
|
|
||||||
right=gpio_get(GPIO_RIGHT);
|
|
||||||
a=gpio_get(GPIO_A);
|
|
||||||
b=gpio_get(GPIO_B);
|
|
||||||
select=gpio_get(GPIO_SELECT);
|
|
||||||
start=gpio_get(GPIO_START);
|
|
||||||
if(!start) {
|
|
||||||
/* re-start the last game (no need to reprogram flash) */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(!a | !b) {
|
|
||||||
/* copy the rom from the SD card to flash and start the game */
|
|
||||||
load_cart_rom_file(filename[selected]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(!down) {
|
|
||||||
/* select the next rom */
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0x0000);
|
|
||||||
selected++;
|
|
||||||
if(selected>=num_file) selected=0;
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0xF800);
|
|
||||||
sleep_ms(150);
|
|
||||||
}
|
|
||||||
if(!up) {
|
|
||||||
/* select the previous rom */
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0x0000);
|
|
||||||
if(selected==0) {
|
|
||||||
selected=num_file-1;
|
|
||||||
} else {
|
|
||||||
selected--;
|
|
||||||
}
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0xF800);
|
|
||||||
sleep_ms(150);
|
|
||||||
}
|
|
||||||
if(!right) {
|
|
||||||
/* select the next page */
|
|
||||||
num_page++;
|
|
||||||
num_file=rom_file_selector_display_page(filename,num_page);
|
|
||||||
if(num_file==0) {
|
|
||||||
/* no files in this page, go to the previous page */
|
|
||||||
num_page--;
|
|
||||||
num_file=rom_file_selector_display_page(filename,num_page);
|
|
||||||
}
|
|
||||||
/* select the first file */
|
|
||||||
selected=0;
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0xF800);
|
|
||||||
sleep_ms(150);
|
|
||||||
}
|
|
||||||
if((!left) && num_page>0) {
|
|
||||||
/* select the previous page */
|
|
||||||
num_page--;
|
|
||||||
num_file=rom_file_selector_display_page(filename,num_page);
|
|
||||||
/* select the first file */
|
|
||||||
selected=0;
|
|
||||||
mk_ili9225_text(filename[selected],0,selected*8,0xFFFF,0xF800);
|
|
||||||
sleep_ms(150);
|
|
||||||
}
|
|
||||||
tight_loop_contents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
static struct gb_s gb;
|
|
||||||
enum gb_init_error_e ret;
|
|
||||||
|
|
||||||
/* Overclock. */
|
|
||||||
{
|
|
||||||
const unsigned vco = 1596*1000*1000; /* 266MHz */
|
|
||||||
const unsigned div1 = 6, div2 = 1;
|
|
||||||
|
|
||||||
vreg_set_voltage(VREG_VOLTAGE_1_15);
|
|
||||||
sleep_ms(2);
|
|
||||||
set_sys_clock_pll(vco, div1, div2);
|
|
||||||
sleep_ms(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialise USB serial connection for debugging. */
|
|
||||||
stdio_init_all();
|
|
||||||
time_init();
|
|
||||||
// sleep_ms(5000);
|
|
||||||
putstdio("INIT: ");
|
|
||||||
|
|
||||||
/* Initialise GPIO pins. */
|
|
||||||
gpio_set_function(GPIO_UP, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_DOWN, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_LEFT, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_RIGHT, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_A, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_B, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_SELECT, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_START, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_CS, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_CLK, GPIO_FUNC_SPI);
|
|
||||||
gpio_set_function(GPIO_SDA, GPIO_FUNC_SPI);
|
|
||||||
gpio_set_function(GPIO_RS, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_RST, GPIO_FUNC_SIO);
|
|
||||||
gpio_set_function(GPIO_LED, GPIO_FUNC_SIO);
|
|
||||||
|
|
||||||
gpio_set_dir(GPIO_UP, false);
|
|
||||||
gpio_set_dir(GPIO_DOWN, false);
|
|
||||||
gpio_set_dir(GPIO_LEFT, false);
|
|
||||||
gpio_set_dir(GPIO_RIGHT, false);
|
|
||||||
gpio_set_dir(GPIO_A, false);
|
|
||||||
gpio_set_dir(GPIO_B, false);
|
|
||||||
gpio_set_dir(GPIO_SELECT, false);
|
|
||||||
gpio_set_dir(GPIO_START, false);
|
|
||||||
gpio_set_dir(GPIO_CS, true);
|
|
||||||
gpio_set_dir(GPIO_RS, true);
|
|
||||||
gpio_set_dir(GPIO_RST, true);
|
|
||||||
gpio_set_dir(GPIO_LED, true);
|
|
||||||
gpio_set_slew_rate(GPIO_CLK, GPIO_SLEW_RATE_FAST);
|
|
||||||
gpio_set_slew_rate(GPIO_SDA, GPIO_SLEW_RATE_FAST);
|
|
||||||
|
|
||||||
gpio_pull_up(GPIO_UP);
|
|
||||||
gpio_pull_up(GPIO_DOWN);
|
|
||||||
gpio_pull_up(GPIO_LEFT);
|
|
||||||
gpio_pull_up(GPIO_RIGHT);
|
|
||||||
gpio_pull_up(GPIO_A);
|
|
||||||
gpio_pull_up(GPIO_B);
|
|
||||||
gpio_pull_up(GPIO_SELECT);
|
|
||||||
gpio_pull_up(GPIO_START);
|
|
||||||
|
|
||||||
/* Set SPI clock to use high frequency. */
|
|
||||||
clock_configure(clk_peri, 0,
|
|
||||||
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
|
||||||
125 * 1000 * 1000, 125 * 1000 * 1000);
|
|
||||||
spi_init(spi0, 30*1000*1000);
|
|
||||||
spi_set_format(spi0, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
|
||||||
|
|
||||||
#if ENABLE_SOUND
|
|
||||||
// Allocate memory for the stream buffer
|
|
||||||
stream=malloc(AUDIO_BUFFER_SIZE_BYTES);
|
|
||||||
assert(stream!=NULL);
|
|
||||||
memset(stream,0,AUDIO_BUFFER_SIZE_BYTES); // Zero out the stream buffer
|
|
||||||
|
|
||||||
// Initialize I2S sound driver
|
|
||||||
i2s_config_t i2s_config = i2s_get_default_config();
|
|
||||||
i2s_config.sample_freq=AUDIO_SAMPLE_RATE;
|
|
||||||
i2s_config.dma_trans_count =AUDIO_SAMPLES;
|
|
||||||
i2s_volume(&i2s_config,2);
|
|
||||||
i2s_init(&i2s_config);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
#if ENABLE_LCD
|
|
||||||
#if ENABLE_SDCARD
|
|
||||||
/* ROM File selector */
|
|
||||||
mk_ili9225_init();
|
|
||||||
mk_ili9225_fill(0x0000);
|
|
||||||
rom_file_selector();
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Initialise GB context. */
|
|
||||||
memcpy(rom_bank0, rom, sizeof(rom_bank0));
|
|
||||||
ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read,
|
|
||||||
&gb_cart_ram_write, &gb_error, NULL);
|
|
||||||
putstdio("GB ");
|
|
||||||
|
|
||||||
if(ret != GB_INIT_NO_ERROR)
|
|
||||||
{
|
|
||||||
printf("Error: %d\n", ret);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Automatically assign a colour palette to the game */
|
|
||||||
char rom_title[16];
|
|
||||||
auto_assign_palette(palette, gb_colour_hash(&gb),gb_get_rom_name(&gb,rom_title));
|
|
||||||
|
|
||||||
#if ENABLE_LCD
|
|
||||||
gb_init_lcd(&gb, &lcd_draw_line);
|
|
||||||
|
|
||||||
/* Start Core1, which processes requests to the LCD. */
|
|
||||||
putstdio("CORE1 ");
|
|
||||||
multicore_launch_core1(main_core1);
|
|
||||||
|
|
||||||
putstdio("LCD ");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ENABLE_SOUND
|
|
||||||
// Initialize audio emulation
|
|
||||||
audio_init();
|
|
||||||
|
|
||||||
putstdio("AUDIO ");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ENABLE_SDCARD
|
|
||||||
/* Load Save File. */
|
|
||||||
read_cart_ram_file(&gb);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
putstdio("\n> ");
|
|
||||||
uint_fast32_t frames = 0;
|
|
||||||
uint64_t start_time = time_us_64();
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
int input;
|
|
||||||
|
|
||||||
gb.gb_frame = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
__gb_step_cpu(&gb);
|
|
||||||
tight_loop_contents();
|
|
||||||
} while(HEDLEY_LIKELY(gb.gb_frame == 0));
|
|
||||||
|
|
||||||
frames++;
|
|
||||||
#if ENABLE_SOUND
|
|
||||||
if(!gb.direct.frame_skip) {
|
|
||||||
audio_callback(NULL, stream, AUDIO_BUFFER_SIZE_BYTES);
|
|
||||||
i2s_dma_write(&i2s_config, stream);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Update buttons state */
|
|
||||||
prev_joypad_bits.up=gb.direct.joypad_bits.up;
|
|
||||||
prev_joypad_bits.down=gb.direct.joypad_bits.down;
|
|
||||||
prev_joypad_bits.left=gb.direct.joypad_bits.left;
|
|
||||||
prev_joypad_bits.right=gb.direct.joypad_bits.right;
|
|
||||||
prev_joypad_bits.a=gb.direct.joypad_bits.a;
|
|
||||||
prev_joypad_bits.b=gb.direct.joypad_bits.b;
|
|
||||||
prev_joypad_bits.select=gb.direct.joypad_bits.select;
|
|
||||||
prev_joypad_bits.start=gb.direct.joypad_bits.start;
|
|
||||||
gb.direct.joypad_bits.up=gpio_get(GPIO_UP);
|
|
||||||
gb.direct.joypad_bits.down=gpio_get(GPIO_DOWN);
|
|
||||||
gb.direct.joypad_bits.left=gpio_get(GPIO_LEFT);
|
|
||||||
gb.direct.joypad_bits.right=gpio_get(GPIO_RIGHT);
|
|
||||||
gb.direct.joypad_bits.a=gpio_get(GPIO_A);
|
|
||||||
gb.direct.joypad_bits.b=gpio_get(GPIO_B);
|
|
||||||
gb.direct.joypad_bits.select=gpio_get(GPIO_SELECT);
|
|
||||||
gb.direct.joypad_bits.start=gpio_get(GPIO_START);
|
|
||||||
|
|
||||||
/* hotkeys (select + * combo)*/
|
|
||||||
if(!gb.direct.joypad_bits.select) {
|
|
||||||
#if ENABLE_SOUND
|
|
||||||
if(!gb.direct.joypad_bits.up && prev_joypad_bits.up) {
|
|
||||||
/* select + up: increase sound volume */
|
|
||||||
i2s_increase_volume(&i2s_config);
|
|
||||||
}
|
|
||||||
if(!gb.direct.joypad_bits.down && prev_joypad_bits.down) {
|
|
||||||
/* select + down: decrease sound volume */
|
|
||||||
i2s_decrease_volume(&i2s_config);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if(!gb.direct.joypad_bits.right && prev_joypad_bits.right) {
|
|
||||||
/* select + right: select the next manual color palette */
|
|
||||||
if(manual_palette_selected<12) {
|
|
||||||
manual_palette_selected++;
|
|
||||||
manual_assign_palette(palette,manual_palette_selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!gb.direct.joypad_bits.left && prev_joypad_bits.left) {
|
|
||||||
/* select + left: select the previous manual color palette */
|
|
||||||
if(manual_palette_selected>0) {
|
|
||||||
manual_palette_selected--;
|
|
||||||
manual_assign_palette(palette,manual_palette_selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!gb.direct.joypad_bits.start && prev_joypad_bits.start) {
|
|
||||||
/* select + start: save ram and resets to the game selection menu */
|
|
||||||
#if ENABLE_SDCARD
|
|
||||||
write_cart_ram_file(&gb);
|
|
||||||
#endif
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!gb.direct.joypad_bits.a && prev_joypad_bits.a) {
|
|
||||||
/* select + A: enable/disable frame-skip => fast-forward */
|
|
||||||
gb.direct.frame_skip=!gb.direct.frame_skip;
|
|
||||||
printf("I gb.direct.frame_skip = %d\n",gb.direct.frame_skip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Serial monitor commands */
|
|
||||||
input = getchar_timeout_us(0);
|
|
||||||
if(input == PICO_ERROR_TIMEOUT)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch(input)
|
|
||||||
{
|
|
||||||
#if 0
|
|
||||||
static bool invert = false;
|
|
||||||
static bool sleep = false;
|
|
||||||
static uint8_t freq = 1;
|
|
||||||
static ili9225_color_mode_e colour = ILI9225_COLOR_MODE_FULL;
|
|
||||||
|
|
||||||
case 'i':
|
|
||||||
invert = !invert;
|
|
||||||
mk_ili9225_display_control(invert, colour);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
freq++;
|
|
||||||
freq &= 0x0F;
|
|
||||||
mk_ili9225_set_drive_freq(freq);
|
|
||||||
printf("Freq %u\n", freq);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
case 'c':
|
|
||||||
{
|
|
||||||
static ili9225_color_mode_e mode = ILI9225_COLOR_MODE_FULL;
|
|
||||||
union core_cmd cmd;
|
|
||||||
|
|
||||||
mode = !mode;
|
|
||||||
cmd.cmd = CORE_CMD_IDLE_SET;
|
|
||||||
cmd.data = mode;
|
|
||||||
multicore_fifo_push_blocking(cmd.full);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'i':
|
|
||||||
gb.direct.interlace = !gb.direct.interlace;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
gb.direct.frame_skip = !gb.direct.frame_skip;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'b':
|
|
||||||
{
|
|
||||||
uint64_t end_time;
|
|
||||||
uint32_t diff;
|
|
||||||
uint32_t fps;
|
|
||||||
|
|
||||||
end_time = time_us_64();
|
|
||||||
diff = end_time-start_time;
|
|
||||||
fps = ((uint64_t)frames*1000*1000)/diff;
|
|
||||||
printf("Frames: %u\n"
|
|
||||||
"Time: %lu us\n"
|
|
||||||
"FPS: %lu\n",
|
|
||||||
frames, diff, fps);
|
|
||||||
stdio_flush();
|
|
||||||
frames = 0;
|
|
||||||
start_time = time_us_64();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.start = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '\b':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.select = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '8':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.up = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '2':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.down = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '4':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.left= 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '6':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.right = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'z':
|
|
||||||
case 'w':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.a = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
{
|
|
||||||
gb.direct.joypad_bits.b = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'q':
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
puts("\nEmulation Ended");
|
|
||||||
/* stop lcd task running on core 1 */
|
|
||||||
multicore_reset_core1();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+488
@@ -0,0 +1,488 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2022 by Mahyar Koshkouei <mk@deltabeard.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Peanut-GB emulator settings
|
||||||
|
#define ENABLE_LCD 1
|
||||||
|
#define ENABLE_SOUND 0 // 1
|
||||||
|
#define ENABLE_SDCARD 0 // 1
|
||||||
|
#define PEANUT_GB_HIGH_LCD_ACCURACY 1
|
||||||
|
#define PEANUT_GB_USE_BIOS 0
|
||||||
|
|
||||||
|
/* Use DMA for all drawing to LCD. Benefits aren't fully realised at the moment
|
||||||
|
* due to busy loops waiting for DMA completion. */
|
||||||
|
#define USE_DMA 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducing VSYNC calculation to lower multiple.
|
||||||
|
* When setting a clock IRQ to DMG_CLOCK_FREQ_REDUCED, count to
|
||||||
|
* SCREEN_REFRESH_CYCLES_REDUCED to obtain the time required each VSYNC.
|
||||||
|
* DMG_CLOCK_FREQ_REDUCED = 2^18, and SCREEN_REFRESH_CYCLES_REDUCED = 4389.
|
||||||
|
* Currently unused.
|
||||||
|
*/
|
||||||
|
#define VSYNC_REDUCTION_FACTOR 16u
|
||||||
|
#define SCREEN_REFRESH_CYCLES_REDUCED (SCREEN_REFRESH_CYCLES / VSYNC_REDUCTION_FACTOR)
|
||||||
|
#define DMG_CLOCK_FREQ_REDUCED (DMG_CLOCK_FREQ / VSYNC_REDUCTION_FACTOR)
|
||||||
|
|
||||||
|
/* C Headers */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* RP2040 Headers */
|
||||||
|
#include <hardware/vreg.h>
|
||||||
|
|
||||||
|
/* Project headers */
|
||||||
|
#include "hedley.h"
|
||||||
|
#include "minigb_apu.h"
|
||||||
|
#include "peanut_gb.h"
|
||||||
|
// #include "sdcard.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "game_bin.h"
|
||||||
|
#include "i2s.h"
|
||||||
|
|
||||||
|
/* GPIO Connections. */
|
||||||
|
#define GPIO_UP 16
|
||||||
|
#define GPIO_DOWN 16
|
||||||
|
#define GPIO_LEFT 16
|
||||||
|
#define GPIO_RIGHT 16
|
||||||
|
#define GPIO_A 16
|
||||||
|
#define GPIO_B 16
|
||||||
|
#define GPIO_SELECT 16
|
||||||
|
#define GPIO_START 16
|
||||||
|
|
||||||
|
#if ENABLE_SOUND
|
||||||
|
/**
|
||||||
|
* Global variables for audio task
|
||||||
|
* stream contains N=AUDIO_SAMPLES samples
|
||||||
|
* each sample is 32 bits
|
||||||
|
* 16 bits for the left channel + 16 bits for the right channel in stereo interleaved format)
|
||||||
|
* This is intended to be played at AUDIO_SAMPLE_RATE Hz
|
||||||
|
*/
|
||||||
|
uint16_t* stream;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Definition of ROM data
|
||||||
|
* We're going to erase and reprogram a region 1Mb from the start of the flash
|
||||||
|
* Once done, we can access this at XIP_BASE + 1Mb.
|
||||||
|
* Game Boy DMG ROM size ranges from 32768 bytes (e.g. Tetris) to 1,048,576 bytes (e.g. Pokemod Red)
|
||||||
|
*/
|
||||||
|
// #define FLASH_TARGET_OFFSET (1024 * 1024)
|
||||||
|
// const uint8_t *rom = (const uint8_t *)(XIP_BASE + FLASH_TARGET_OFFSET);
|
||||||
|
const uint8_t* rom = GAME_DATA;
|
||||||
|
static unsigned char rom_bank0[65536];
|
||||||
|
|
||||||
|
static uint8_t ram[32768];
|
||||||
|
static uint8_t manual_palette_selected = 0;
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
unsigned a : 1;
|
||||||
|
unsigned b : 1;
|
||||||
|
unsigned select : 1;
|
||||||
|
unsigned start : 1;
|
||||||
|
unsigned right : 1;
|
||||||
|
unsigned left : 1;
|
||||||
|
unsigned up : 1;
|
||||||
|
unsigned down : 1;
|
||||||
|
} prev_joypad_bits;
|
||||||
|
|
||||||
|
static struct gb_s gb;
|
||||||
|
|
||||||
|
#define putstdio(x) write(1, x, strlen(x))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a byte from the ROM file at the given address.
|
||||||
|
*/
|
||||||
|
uint8_t gb_rom_read(struct gb_s* gb, const uint_fast32_t addr) {
|
||||||
|
(void)gb;
|
||||||
|
if (addr < sizeof(rom_bank0))
|
||||||
|
return rom_bank0[addr];
|
||||||
|
|
||||||
|
return rom[addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a byte from the cartridge RAM at the given address.
|
||||||
|
*/
|
||||||
|
uint8_t gb_cart_ram_read(struct gb_s* gb, const uint_fast32_t addr) {
|
||||||
|
(void)gb;
|
||||||
|
return ram[addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a given byte to the cartridge RAM at the given address.
|
||||||
|
*/
|
||||||
|
void gb_cart_ram_write(struct gb_s* gb, const uint_fast32_t addr,
|
||||||
|
const uint8_t val) {
|
||||||
|
ram[addr] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore all errors.
|
||||||
|
*/
|
||||||
|
void gb_error(struct gb_s* gb, const enum gb_error_e gb_err, const uint16_t addr) {
|
||||||
|
#if 1
|
||||||
|
const char* gb_err_str[4] = {
|
||||||
|
"UNKNOWN",
|
||||||
|
"INVALID OPCODE",
|
||||||
|
"INVALID READ",
|
||||||
|
"INVALID WRITE"};
|
||||||
|
Serial.printf("Error %d occurred: %s at %04X\n.\n", gb_err, gb_err_str[gb_err], addr);
|
||||||
|
// abort();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
#if ENABLE_LCD
|
||||||
|
#if ENABLE_SDCARD
|
||||||
|
/* ROM File selector */
|
||||||
|
lcd_init();
|
||||||
|
lcd_fill(TFT_BLACK); // 0x0000);
|
||||||
|
rom_file_selector();
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Initialise GB context. */
|
||||||
|
memcpy(rom_bank0, rom, sizeof(rom_bank0));
|
||||||
|
auto ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read,
|
||||||
|
&gb_cart_ram_write, &gb_error, NULL);
|
||||||
|
Serial.println("GB ");
|
||||||
|
|
||||||
|
if (ret != GB_INIT_NO_ERROR) {
|
||||||
|
Serial.printf("Error: %d\n", ret);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Automatically assign a colour palette to the game */
|
||||||
|
char rom_title[16];
|
||||||
|
auto_assign_palette(palette, gb_colour_hash(&gb), gb_get_rom_name(&gb, rom_title));
|
||||||
|
|
||||||
|
#if ENABLE_LCD
|
||||||
|
gb_init_lcd(&gb, &lcd_draw_line);
|
||||||
|
|
||||||
|
/* Start Core1, which processes requests to the LCD. */
|
||||||
|
Serial.println("CORE1 ");
|
||||||
|
multicore_launch_core1(core1_init);
|
||||||
|
|
||||||
|
Serial.println("LCD ");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_SOUND
|
||||||
|
// Initialize audio emulation
|
||||||
|
audio_init();
|
||||||
|
|
||||||
|
Serial.println("AUDIO ");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_SDCARD
|
||||||
|
/* Load Save File. */
|
||||||
|
read_cart_ram_file(&gb);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Serial.print("\n> ");
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
Serial.println("\nEmulation Ended");
|
||||||
|
/* stop lcd task running on core 1 */
|
||||||
|
multicore_reset_core1();
|
||||||
|
while (true)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
/* Overclock. */
|
||||||
|
{
|
||||||
|
const unsigned vco = 1596 * 1000 * 1000; /* 266MHz */
|
||||||
|
const unsigned div1 = 6, div2 = 1;
|
||||||
|
|
||||||
|
vreg_set_voltage(VREG_VOLTAGE_1_15);
|
||||||
|
sleep_ms(2);
|
||||||
|
set_sys_clock_pll(vco, div1, div2);
|
||||||
|
sleep_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialise USB serial connection for debugging. */
|
||||||
|
Serial.begin(115200);
|
||||||
|
while (!Serial)
|
||||||
|
;
|
||||||
|
|
||||||
|
#if ENABLE_SDCARD
|
||||||
|
time_init();
|
||||||
|
#endif
|
||||||
|
// sleep_ms(5000);
|
||||||
|
Serial.println("INIT: ");
|
||||||
|
|
||||||
|
/* Initialise GPIO pins. */
|
||||||
|
gpio_set_function(GPIO_UP, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_DOWN, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_LEFT, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_RIGHT, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_A, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_B, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_SELECT, GPIO_FUNC_SIO);
|
||||||
|
gpio_set_function(GPIO_START, GPIO_FUNC_SIO);
|
||||||
|
|
||||||
|
gpio_set_dir(GPIO_UP, false);
|
||||||
|
gpio_set_dir(GPIO_DOWN, false);
|
||||||
|
gpio_set_dir(GPIO_LEFT, false);
|
||||||
|
gpio_set_dir(GPIO_RIGHT, false);
|
||||||
|
gpio_set_dir(GPIO_A, false);
|
||||||
|
gpio_set_dir(GPIO_B, false);
|
||||||
|
gpio_set_dir(GPIO_SELECT, false);
|
||||||
|
gpio_set_dir(GPIO_START, false);
|
||||||
|
|
||||||
|
gpio_pull_up(GPIO_UP);
|
||||||
|
gpio_pull_up(GPIO_DOWN);
|
||||||
|
gpio_pull_up(GPIO_LEFT);
|
||||||
|
gpio_pull_up(GPIO_RIGHT);
|
||||||
|
gpio_pull_up(GPIO_A);
|
||||||
|
gpio_pull_up(GPIO_B);
|
||||||
|
gpio_pull_up(GPIO_SELECT);
|
||||||
|
gpio_pull_up(GPIO_START);
|
||||||
|
|
||||||
|
/* Set SPI clock to use high frequency. */
|
||||||
|
#if 0
|
||||||
|
clock_configure(clk_peri, 0,
|
||||||
|
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
||||||
|
125 * 1000 * 1000, 125 * 1000 * 1000);
|
||||||
|
spi_init(spi0, 30*1000*1000);
|
||||||
|
spi_set_format(spi0, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_SOUND
|
||||||
|
// Allocate memory for the stream buffer
|
||||||
|
stream = malloc(AUDIO_BUFFER_SIZE_BYTES);
|
||||||
|
assert(stream != NULL);
|
||||||
|
memset(stream, 0, AUDIO_BUFFER_SIZE_BYTES); // Zero out the stream buffer
|
||||||
|
|
||||||
|
// Initialize I2S sound driver
|
||||||
|
i2s_config_t i2s_config = i2s_get_default_config();
|
||||||
|
i2s_config.sample_freq = AUDIO_SAMPLE_RATE;
|
||||||
|
i2s_config.dma_trans_count = AUDIO_SAMPLES;
|
||||||
|
i2s_volume(&i2s_config, 2);
|
||||||
|
i2s_init(&i2s_config);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextPalette() {
|
||||||
|
manual_palette_selected = manual_palette_selected < 12 ? manual_palette_selected + 1 : 0;
|
||||||
|
manual_assign_palette(palette, manual_palette_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void prevPalette() {
|
||||||
|
manual_palette_selected = manual_palette_selected > 0 ? manual_palette_selected - 1 : 12;
|
||||||
|
manual_assign_palette(palette, manual_palette_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleInput(uint_fast32_t& frames) {
|
||||||
|
static uint64_t start_time = time_us_64();
|
||||||
|
|
||||||
|
/* Serial monitor commands */
|
||||||
|
int input = Serial.read();
|
||||||
|
if (input <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (input) {
|
||||||
|
#if 0
|
||||||
|
static bool invert = false;
|
||||||
|
static bool sleep = false;
|
||||||
|
static uint8_t freq = 1;
|
||||||
|
static ili9225_color_mode_e colour = ILI9225_COLOR_MODE_FULL;
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
invert = !invert;
|
||||||
|
mk_ili9225_display_control(invert, colour);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
freq++;
|
||||||
|
freq &= 0x0F;
|
||||||
|
mk_ili9225_set_drive_freq(freq);
|
||||||
|
Serial.printf("Freq %u\n", freq);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case 'c': {
|
||||||
|
#if 0
|
||||||
|
static ili9225_color_mode_e mode = ILI9225_COLOR_MODE_FULL;
|
||||||
|
union core_cmd cmd;
|
||||||
|
|
||||||
|
mode = !mode;
|
||||||
|
cmd.cmd = CORE_CMD_IDLE_SET;
|
||||||
|
cmd.data = mode;
|
||||||
|
multicore_fifo_push_blocking(cmd.full);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
gb.direct.interlace = !gb.direct.interlace;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
gb.direct.frame_skip = !gb.direct.frame_skip;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': {
|
||||||
|
uint64_t end_time;
|
||||||
|
uint32_t diff;
|
||||||
|
uint32_t fps;
|
||||||
|
|
||||||
|
end_time = time_us_64();
|
||||||
|
diff = end_time - start_time;
|
||||||
|
fps = ((uint64_t)frames * 1000 * 1000) / diff;
|
||||||
|
Serial.printf("Frames: %u\n"
|
||||||
|
"Time: %lu us\n"
|
||||||
|
"FPS: %lu\n",
|
||||||
|
frames, diff, fps);
|
||||||
|
Serial.flush();
|
||||||
|
frames = 0;
|
||||||
|
start_time = time_us_64();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
case '\r': {
|
||||||
|
gb.direct.joypad_bits.start = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '\b': {
|
||||||
|
gb.direct.joypad_bits.select = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '8': {
|
||||||
|
gb.direct.joypad_bits.up = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '2': {
|
||||||
|
gb.direct.joypad_bits.down = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '4': {
|
||||||
|
gb.direct.joypad_bits.left = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '6': {
|
||||||
|
gb.direct.joypad_bits.right = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'z':
|
||||||
|
case 'w': {
|
||||||
|
gb.direct.joypad_bits.a = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'x': {
|
||||||
|
gb.direct.joypad_bits.b = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'q':
|
||||||
|
stop();
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
nextPalette();
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePad() {
|
||||||
|
/* Update buttons state */
|
||||||
|
prev_joypad_bits.up = gb.direct.joypad_bits.up;
|
||||||
|
prev_joypad_bits.down = gb.direct.joypad_bits.down;
|
||||||
|
prev_joypad_bits.left = gb.direct.joypad_bits.left;
|
||||||
|
prev_joypad_bits.right = gb.direct.joypad_bits.right;
|
||||||
|
prev_joypad_bits.a = gb.direct.joypad_bits.a;
|
||||||
|
prev_joypad_bits.b = gb.direct.joypad_bits.b;
|
||||||
|
prev_joypad_bits.select = gb.direct.joypad_bits.select;
|
||||||
|
prev_joypad_bits.start = gb.direct.joypad_bits.start;
|
||||||
|
gb.direct.joypad_bits.up = gpio_get(GPIO_UP);
|
||||||
|
gb.direct.joypad_bits.down = gpio_get(GPIO_DOWN);
|
||||||
|
gb.direct.joypad_bits.left = gpio_get(GPIO_LEFT);
|
||||||
|
gb.direct.joypad_bits.right = gpio_get(GPIO_RIGHT);
|
||||||
|
gb.direct.joypad_bits.a = gpio_get(GPIO_A);
|
||||||
|
gb.direct.joypad_bits.b = gpio_get(GPIO_B);
|
||||||
|
gb.direct.joypad_bits.select = gpio_get(GPIO_SELECT);
|
||||||
|
gb.direct.joypad_bits.start = gpio_get(GPIO_START);
|
||||||
|
|
||||||
|
/* hotkeys (select + * combo)*/
|
||||||
|
if (!gb.direct.joypad_bits.select) {
|
||||||
|
#if ENABLE_SOUND
|
||||||
|
if (!gb.direct.joypad_bits.up && prev_joypad_bits.up) {
|
||||||
|
/* select + up: increase sound volume */
|
||||||
|
i2s_increase_volume(&i2s_config);
|
||||||
|
}
|
||||||
|
if (!gb.direct.joypad_bits.down && prev_joypad_bits.down) {
|
||||||
|
/* select + down: decrease sound volume */
|
||||||
|
i2s_decrease_volume(&i2s_config);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!gb.direct.joypad_bits.right && prev_joypad_bits.right) {
|
||||||
|
/* select + right: select the next manual color palette */
|
||||||
|
nextPalette();
|
||||||
|
}
|
||||||
|
if (!gb.direct.joypad_bits.left && prev_joypad_bits.left) {
|
||||||
|
/* select + left: select the previous manual color palette */
|
||||||
|
prevPalette();
|
||||||
|
}
|
||||||
|
if (!gb.direct.joypad_bits.start && prev_joypad_bits.start) {
|
||||||
|
/* select + start: save ram and resets to the game selection menu */
|
||||||
|
#if ENABLE_SDCARD
|
||||||
|
write_cart_ram_file(&gb);
|
||||||
|
#endif
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
if (!gb.direct.joypad_bits.a && prev_joypad_bits.a) {
|
||||||
|
/* select + A: enable/disable frame-skip => fast-forward */
|
||||||
|
gb.direct.frame_skip = !gb.direct.frame_skip;
|
||||||
|
Serial.printf("I gb.direct.frame_skip = %d\n", gb.direct.frame_skip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
static uint_fast32_t frames = 0;
|
||||||
|
|
||||||
|
gb.gb_frame = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
__gb_step_cpu(&gb);
|
||||||
|
tight_loop_contents();
|
||||||
|
} while (HEDLEY_LIKELY(gb.gb_frame == 0));
|
||||||
|
|
||||||
|
frames++;
|
||||||
|
#if ENABLE_SOUND
|
||||||
|
if (!gb.direct.frame_skip) {
|
||||||
|
audio_callback(NULL, stream, AUDIO_BUFFER_SIZE_BYTES);
|
||||||
|
i2s_dma_write(&i2s_config, stream);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
handlePad();
|
||||||
|
handleInput(frames);
|
||||||
|
}
|
||||||
-1297
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
|||||||
|
// USER DEFINED SETTINGS
|
||||||
|
// Set driver type, fonts to be loaded, pins used and SPI control method etc.
|
||||||
|
//
|
||||||
|
// See the User_Setup_Select.h file if you wish to be able to define multiple
|
||||||
|
// setups and then easily select which setup file is used by the compiler.
|
||||||
|
//
|
||||||
|
// If this file is edited correctly then all the library example sketches should
|
||||||
|
// run without the need to make any more changes for a particular hardware setup!
|
||||||
|
// Note that some sketches are designed for a particular TFT pixel width/height
|
||||||
|
|
||||||
|
// User defined information reported by "Read_User_Setup" test & diagnostics example
|
||||||
|
#define USER_SETUP_INFO "User_Setup"
|
||||||
|
|
||||||
|
// Define to disable all #warnings in library (can be put in User_Setup_Select.h)
|
||||||
|
//#define DISABLE_ALL_LIBRARY_WARNINGS
|
||||||
|
|
||||||
|
// ##################################################################################
|
||||||
|
//
|
||||||
|
// Section 1. Call up the right driver file and any options for it
|
||||||
|
//
|
||||||
|
// ##################################################################################
|
||||||
|
|
||||||
|
// Tell the library to use parallel mode (otherwise SPI is assumed)
|
||||||
|
//#define TFT_PARALLEL_8_BIT
|
||||||
|
#define TFT_PARALLEL_16_BIT // **** 16-bit parallel ONLY for RP2040 processor ****
|
||||||
|
|
||||||
|
// Display type - only define if RPi display
|
||||||
|
//#define RPI_DISPLAY_TYPE // 20MHz maximum SPI
|
||||||
|
|
||||||
|
// Only define one driver, the other ones must be commented out
|
||||||
|
#define ILI9341_DRIVER // Generic driver for common displays
|
||||||
|
//#define ILI9341_2_DRIVER // Alternative ILI9341 driver, see https://github.com/Bodmer/TFT_eSPI/issues/1172
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define ST7789_2_DRIVER // Minimal configuration option, define additional parameters below for this display
|
||||||
|
#define TFT_INVERSION_OFF
|
||||||
|
#define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// For ST7789, ST7735, ILI9163 and GC9A01 ONLY, define the pixel width and height in portrait orientation
|
||||||
|
#define TFT_WIDTH 240 // ST7789 240 x 240 and 240 x 320
|
||||||
|
#define TFT_HEIGHT 320 // ST7789 240 x 320
|
||||||
|
|
||||||
|
// ##################################################################################
|
||||||
|
//
|
||||||
|
// Section 2. Define the pins that are used to interface with the display here
|
||||||
|
//
|
||||||
|
// ##################################################################################
|
||||||
|
|
||||||
|
// If a backlight control signal is available then define the TFT_BL pin in Section 2
|
||||||
|
// below. The backlight will be turned ON when tft.begin() is called, but the library
|
||||||
|
// needs to know if the LEDs are ON with the pin HIGH or LOW. If the LEDs are to be
|
||||||
|
// driven with a PWM signal or turned OFF/ON then this must be handled by the user
|
||||||
|
// sketch. e.g. with digitalWrite(TFT_BL, LOW);
|
||||||
|
|
||||||
|
// #define TFT_BL 32 // LED back-light control pin
|
||||||
|
// #define TFT_BACKLIGHT_ON HIGH // Level to turn ON back-light (HIGH or LOW)
|
||||||
|
|
||||||
|
#define TFT_D0 0
|
||||||
|
#define TFT_D1 1
|
||||||
|
#define TFT_D2 2
|
||||||
|
#define TFT_D3 3
|
||||||
|
#define TFT_D4 4
|
||||||
|
#define TFT_D5 5
|
||||||
|
#define TFT_D6 6
|
||||||
|
#define TFT_D7 7
|
||||||
|
#define TFT_WR 19 // Write strobe for modified Raspberry Pi TFT only
|
||||||
|
#define TFT_DC 20 // Data Command control pin
|
||||||
|
#define TFT_CS 21 // Chip select control pin D8
|
||||||
|
#define TFT_RST 22 // Reset pin (could connect to NodeMCU RST, see next line)
|
||||||
|
//#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
|
||||||
|
|
||||||
|
// ##################################################################################
|
||||||
|
//
|
||||||
|
// Section 3. Define the fonts that are to be used here
|
||||||
|
//
|
||||||
|
// ##################################################################################
|
||||||
|
|
||||||
|
// Comment out the #defines below with // to stop that font being loaded
|
||||||
|
// The ESP8366 and ESP32 have plenty of memory so commenting out fonts is not
|
||||||
|
// normally necessary. If all fonts are loaded the extra FLASH space required is
|
||||||
|
// about 17Kbytes. To save FLASH space only enable the fonts you need!
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
|
||||||
|
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
|
||||||
|
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
|
||||||
|
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
|
||||||
|
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
|
||||||
|
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
|
||||||
|
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
|
||||||
|
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
|
||||||
|
|
||||||
|
// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
|
||||||
|
// this will save ~20kbytes of FLASH
|
||||||
|
#define SMOOTH_FONT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ##################################################################################
|
||||||
|
//
|
||||||
|
// Section 4. Other options
|
||||||
|
//
|
||||||
|
// ##################################################################################
|
||||||
|
|
||||||
|
// For RP2040 processor and SPI displays, uncomment the following line to use the PIO interface.
|
||||||
|
//#define RP2040_PIO_SPI // Leave commented out to use standard RP2040 SPI port interface
|
||||||
|
|
||||||
|
// For RP2040 processor and 8 or 16-bit parallel displays:
|
||||||
|
// The parallel interface write cycle period is derived from a division of the CPU clock
|
||||||
|
// speed so scales with the processor clock. This means that the divider ratio may need
|
||||||
|
// to be increased when overclocking. It may also need to be adjusted dependant on the
|
||||||
|
// display controller type (ILI94341, HX8357C etc.). If RP2040_PIO_CLK_DIV is not defined
|
||||||
|
// the library will set default values which may not suit your display.
|
||||||
|
// The display controller data sheet will specify the minimum write cycle period. The
|
||||||
|
// controllers often work reliably for shorter periods, however if the period is too short
|
||||||
|
// the display may not initialise or graphics will become corrupted.
|
||||||
|
// PIO write cycle frequency = (CPU clock/(4 * RP2040_PIO_CLK_DIV))
|
||||||
|
//#define RP2040_PIO_CLK_DIV 1 // 32ns write cycle at 125MHz CPU clock
|
||||||
|
//#define RP2040_PIO_CLK_DIV 2 // 64ns write cycle at 125MHz CPU clock
|
||||||
|
//#define RP2040_PIO_CLK_DIV 3 // 96ns write cycle at 125MHz CPU clock
|
||||||
|
|
||||||
|
// For the RP2040 processor define the SPI port channel used (default 0 if undefined)
|
||||||
|
//#define TFT_SPI_PORT 1 // Set to 0 if SPI0 pins are used, or 1 if spi1 pins used
|
||||||
|
|
||||||
|
// For the STM32 processor define the SPI port channel used (default 1 if undefined)
|
||||||
|
//#define TFT_SPI_PORT 2 // Set to 1 for SPI port 1, or 2 for SPI port 2
|
||||||
|
|
||||||
|
// Define the SPI clock frequency, this affects the graphics rendering speed. Too
|
||||||
|
// fast and the TFT driver will not keep up and display corruption appears.
|
||||||
|
// With an ILI9341 display 40MHz works OK, 80MHz sometimes fails
|
||||||
|
// With a ST7735 display more than 27MHz may not work (spurious pixels and lines)
|
||||||
|
// With an ILI9163 display 27 MHz works OK.
|
||||||
|
|
||||||
|
// #define SPI_FREQUENCY 1000000
|
||||||
|
// #define SPI_FREQUENCY 5000000
|
||||||
|
// #define SPI_FREQUENCY 10000000
|
||||||
|
// #define SPI_FREQUENCY 20000000
|
||||||
|
#define SPI_FREQUENCY 27000000
|
||||||
|
// #define SPI_FREQUENCY 40000000
|
||||||
|
// #define SPI_FREQUENCY 55000000 // STM32 SPI1 only (SPI2 maximum is 27MHz)
|
||||||
|
// #define SPI_FREQUENCY 80000000
|
||||||
|
|
||||||
|
// Optional reduced SPI frequency for reading TFT
|
||||||
|
#define SPI_READ_FREQUENCY 20000000
|
||||||
|
|
||||||
|
// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
|
||||||
|
#define SPI_TOUCH_FREQUENCY 2500000
|
||||||
|
|
||||||
|
// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
|
||||||
|
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
|
||||||
|
// then uncomment the following line:
|
||||||
|
//#define USE_HSPI_PORT
|
||||||
|
|
||||||
|
// Comment out the following #define if "SPI Transactions" do not need to be
|
||||||
|
// supported. When commented out the code size will be smaller and sketches will
|
||||||
|
// run slightly faster, so leave it commented out unless you need it!
|
||||||
|
|
||||||
|
// Transaction support is needed to work with SD library but not needed with TFT_SdFat
|
||||||
|
// Transaction support is required if other SPI devices are connected.
|
||||||
|
|
||||||
|
// Transactions are automatically enabled by the library for an ESP32 (to use HAL mutex)
|
||||||
|
// so changing it here has no effect
|
||||||
|
|
||||||
|
// #define SUPPORT_TRANSACTIONS
|
||||||
Reference in New Issue
Block a user