Make SD-card work
This commit is contained in:
+244
-154
@@ -1,209 +1,299 @@
|
||||
#if ENABLE_SDCARD
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "SdFat.h"
|
||||
#include "hardware/flash.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "input.h"
|
||||
#include "gb.h"
|
||||
#include "card_loader.h"
|
||||
|
||||
#define RAM_SAVENAME_LENGTH 16
|
||||
|
||||
SdFs sd;
|
||||
|
||||
gpio_function_t UseSDPinFunctionScope::sd_sck_pin_func = GPIO_FUNC_NULL;
|
||||
gpio_function_t UseSDPinFunctionScope::sd_mosi_pin_func = GPIO_FUNC_NULL;
|
||||
|
||||
bool init_sdcard_hardware() {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
SPI.setMISO(SD_MISO_PIN);
|
||||
SPI.setMOSI(SD_MOSI_PIN);
|
||||
SPI.setSCK(SD_SCK_PIN);
|
||||
bool success = sd.begin(SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50), &SPI));
|
||||
if (success) {
|
||||
UseSDPinFunctionScope::init();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void init_sdcard() {
|
||||
Serial.println("Initialize SD-Card ...");
|
||||
|
||||
if (!init_sdcard_hardware()) {
|
||||
tft.setCursor(0, ERROR_TEXT_OFFSET, FONT_ID);
|
||||
tft.setTextColor(TFT_RED);
|
||||
sd.printSdError(&tft);
|
||||
sd.printSdError(&Serial);
|
||||
reset(5000);
|
||||
}
|
||||
if (sd.vol()->fatType() == 0) { // vol() and fatType() do not access the sd-card
|
||||
error("Can't find a valid FAT16/FAT32/exFAT partition");
|
||||
}
|
||||
|
||||
Serial.printf("SD-Card initialized: FAT-Type=%d\n", sd.vol()->fatType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a save file from the SD card
|
||||
*/
|
||||
void read_cart_ram_file(struct gb_s* gb) {
|
||||
char filename[16];
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
char filename[RAM_SAVENAME_LENGTH];
|
||||
uint_fast32_t save_size;
|
||||
UINT br;
|
||||
FsFile file;
|
||||
|
||||
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);
|
||||
if (!file.open(filename, O_RDONLY)) {
|
||||
Serial.printf("E f_open(%s) error\n", filename);
|
||||
} else {
|
||||
printf("E f_open(%s) error: %s (%d)\n", filename, FRESULT_str(fr), fr);
|
||||
file.read(ram, file.size());
|
||||
}
|
||||
|
||||
fr = f_close(&fil);
|
||||
if (fr != FR_OK) {
|
||||
printf("E f_close error: %s (%d)\n", FRESULT_str(fr), fr);
|
||||
if (!file.close()) {
|
||||
Serial.printf("E f_close error\n");
|
||||
}
|
||||
f_unmount(pSD->pcName);
|
||||
}
|
||||
printf("I read_cart_ram_file(%s) COMPLETE (%lu bytes)\n", filename, save_size);
|
||||
|
||||
Serial.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];
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
char filename[RAM_SAVENAME_LENGTH];
|
||||
uint_fast32_t save_size;
|
||||
UINT bw;
|
||||
FsFile file;
|
||||
|
||||
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);
|
||||
if (!file.open(filename, O_WRONLY | O_CREAT)) {
|
||||
Serial.printf("E f_open(%s) error\n", filename);
|
||||
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);
|
||||
file.write(ram, save_size);
|
||||
if (!file.close()) {
|
||||
Serial.printf("E f_close error\n");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Serial.printf("I write_cart_ram_file(%s) COMPLETE (%lu bytes)\n", filename, save_size);
|
||||
}
|
||||
|
||||
bool write_rom_sector_to_flash(FsFile& file, uint8_t* buffer, uint32_t offset) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
int nread = file.read(buffer, FLASH_SECTOR_SIZE);
|
||||
if (nread < 0) {
|
||||
scope.close();
|
||||
error("Failed to read file!");
|
||||
}
|
||||
|
||||
if (nread == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t flash_offset = ((uint32_t) &rom[offset]) - XIP_BASE;
|
||||
|
||||
uint32_t ints = save_and_disable_interrupts();
|
||||
flash_range_erase(flash_offset, FLASH_SECTOR_SIZE);
|
||||
flash_range_program(flash_offset, buffer, FLASH_SECTOR_SIZE);
|
||||
restore_interrupts(ints);
|
||||
|
||||
/* Read back target region and check programming */
|
||||
if (memcmp(&rom[offset], buffer, FLASH_SECTOR_SIZE) != 0) {
|
||||
scope.close();
|
||||
error("Programming failed - Flash mismatch");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void open_rom_file(FsFile& file, char* filename) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
if (!file.open(filename, O_RDONLY)) {
|
||||
scope.close();
|
||||
error("Failed to open ROM: " + String(filename));
|
||||
}
|
||||
}
|
||||
|
||||
static void close_rom_file(FsFile& file) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
if (!file.close()) {
|
||||
Serial.printf("E f_close error\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setCursor(0, 0, FONT_ID);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
tft.println("Loading ROM: ");
|
||||
|
||||
FsFile file;
|
||||
open_rom_file(file, filename);
|
||||
|
||||
Serial.printf("I Program target region...\n");
|
||||
|
||||
uint32_t offset = 0;
|
||||
while (write_rom_sector_to_flash(file, buffer, offset)) {
|
||||
tft.print("#");
|
||||
|
||||
/* Next sector */
|
||||
offset += FLASH_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
close_rom_file(file);
|
||||
|
||||
Serial.printf("I load_cart_rom_file(%s) COMPLETE\n", filename);
|
||||
}
|
||||
|
||||
static uint16_t read_file_page_from_card(char filename[FILES_PER_PAGE][MAX_PATH_LENGTH], uint16_t num_page) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
FsFile dir;
|
||||
FsFile file;
|
||||
|
||||
/* clear the filenames array */
|
||||
for (uint8_t ifile = 0; ifile < FILES_PER_PAGE; ifile++) {
|
||||
strcpy(filename[ifile], "");
|
||||
}
|
||||
|
||||
if (!dir.open("/")) {
|
||||
scope.close();
|
||||
error("Failed to open root dir");
|
||||
}
|
||||
|
||||
/* search *.gb files */
|
||||
uint16_t num_files = 0;
|
||||
uint16_t num_file_offset = num_page * FILES_PER_PAGE;
|
||||
char currentFilename[MAX_PATH_LENGTH];
|
||||
while (file.openNext(&dir, O_RDONLY)) {
|
||||
file.getName(currentFilename, sizeof(currentFilename));
|
||||
|
||||
auto currentFilenameStr = String(currentFilename);
|
||||
currentFilenameStr.toLowerCase();
|
||||
if (!currentFilenameStr.endsWith(".gb") && !currentFilenameStr.endsWith(".gbc")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (num_files < num_file_offset) {
|
||||
// skip the first N pages
|
||||
} else {
|
||||
// store the filenames of this page
|
||||
strcpy(filename[num_files], currentFilename);
|
||||
}
|
||||
|
||||
num_files++;
|
||||
file.close();
|
||||
|
||||
if (num_files >= num_file_offset + FILES_PER_PAGE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dir.close();
|
||||
|
||||
return num_files;
|
||||
}
|
||||
|
||||
|
||||
void print_file_entry(char* s, uint8_t index, uint8_t num_files, bool selected = false) {
|
||||
if (num_files == 0) {
|
||||
const char* no_files = "<No files on card>";
|
||||
tft.drawString(no_files, ERROR_TEXT_OFFSET, index * FONT_HEIGHT, FONT_ID);
|
||||
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);
|
||||
tft.setTextColor(TFT_WHITE, selected ? TFT_RED : TFT_BLACK);
|
||||
tft.drawString(s, 0, index * FONT_HEIGHT, FONT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
static uint16_t rom_file_selector_display_page(char filename[FILES_PER_PAGE][MAX_PATH_LENGTH], uint16_t num_page) {
|
||||
uint16_t num_files = read_file_page_from_card(filename, num_page);
|
||||
|
||||
/* 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);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
for (uint8_t ifile = 0; ifile < num_files; ifile++) {
|
||||
print_file_entry(filename[ifile], ifile, num_files);
|
||||
}
|
||||
return num_file;
|
||||
|
||||
return num_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ROM selector displays pages of up to 22 rom files
|
||||
* The ROM selector displays pages of up to FILES_PER_PAGE 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;
|
||||
uint16_t num_page = 0;
|
||||
char filename[FILES_PER_PAGE][MAX_PATH_LENGTH];
|
||||
uint16_t num_files;
|
||||
|
||||
/* display the first page with up to 22 rom files */
|
||||
num_file = rom_file_selector_display_page(filename, num_page);
|
||||
/* display the first page with up to FILES_PER_PAGE rom files */
|
||||
num_files = 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);
|
||||
print_file_entry(filename[selected], selected, num_files, true);
|
||||
|
||||
/* 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);
|
||||
up = readJoypad(PIN_UP);
|
||||
down = readJoypad(PIN_DOWN);
|
||||
left = readJoypad(PIN_LEFT);
|
||||
right = readJoypad(PIN_RIGHT);
|
||||
a = readJoypad(PIN_A);
|
||||
b = readJoypad(PIN_B);
|
||||
select = readJoypad(PIN_SELECT);
|
||||
start = readJoypad(PIN_START);
|
||||
if (!start) {
|
||||
/* re-start the last game (no need to reprogram flash) */
|
||||
break;
|
||||
@@ -215,45 +305,45 @@ void rom_file_selector() {
|
||||
}
|
||||
if (!down) {
|
||||
/* select the next rom */
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0x0000);
|
||||
print_file_entry(filename[selected], selected, num_files);
|
||||
selected++;
|
||||
if (selected >= num_file)
|
||||
if (selected >= num_files)
|
||||
selected = 0;
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||
print_file_entry(filename[selected], selected, num_files, true);
|
||||
sleep_ms(150);
|
||||
}
|
||||
if (!up) {
|
||||
/* select the previous rom */
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0x0000);
|
||||
print_file_entry(filename[selected], selected, num_files);
|
||||
if (selected == 0) {
|
||||
selected = num_file - 1;
|
||||
selected = num_files - 1;
|
||||
} else {
|
||||
selected--;
|
||||
}
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||
print_file_entry(filename[selected], selected, num_files, true);
|
||||
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) {
|
||||
num_files = rom_file_selector_display_page(filename, num_page);
|
||||
if (num_files == 0) {
|
||||
/* no files in this page, go to the previous page */
|
||||
num_page--;
|
||||
num_file = rom_file_selector_display_page(filename, num_page);
|
||||
num_files = rom_file_selector_display_page(filename, num_page);
|
||||
}
|
||||
/* select the first file */
|
||||
selected = 0;
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||
print_file_entry(filename[selected], selected, num_files, true);
|
||||
sleep_ms(150);
|
||||
}
|
||||
if ((!left) && num_page > 0) {
|
||||
/* select the previous page */
|
||||
num_page--;
|
||||
num_file = rom_file_selector_display_page(filename, num_page);
|
||||
num_files = rom_file_selector_display_page(filename, num_page);
|
||||
/* select the first file */
|
||||
selected = 0;
|
||||
lcd_text(filename[selected], 0, selected * 8, 0xFFFF, 0xF800);
|
||||
print_file_entry(filename[selected], selected, num_files, true);
|
||||
sleep_ms(150);
|
||||
}
|
||||
tight_loop_contents();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "SdFat.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define FILES_PER_PAGE 22
|
||||
#define MAX_PATH_LENGTH 256
|
||||
|
||||
extern SdFs sd;
|
||||
|
||||
void init_sdcard();
|
||||
|
||||
void read_cart_ram_file(struct gb_s* gb);
|
||||
|
||||
void write_cart_ram_file(struct gb_s* gb);
|
||||
|
||||
void rom_file_selector();
|
||||
|
||||
class UseSDPinFunctionScope {
|
||||
public:
|
||||
UseSDPinFunctionScope() :
|
||||
old_sck_pin_func(gpio_get_function(SD_SCK_PIN)),
|
||||
old_mosi_pin_func(gpio_get_function(SD_MOSI_PIN))
|
||||
{
|
||||
if (sd_sck_pin_func != GPIO_FUNC_NULL) {
|
||||
gpio_set_function(SD_SCK_PIN, sd_sck_pin_func);
|
||||
}
|
||||
if (old_mosi_pin_func != GPIO_FUNC_NULL) {
|
||||
gpio_set_function(SD_MOSI_PIN, sd_sck_pin_func);
|
||||
}
|
||||
}
|
||||
|
||||
~UseSDPinFunctionScope() {
|
||||
close();
|
||||
}
|
||||
|
||||
static void init() {
|
||||
sd_sck_pin_func = gpio_get_function(SD_SCK_PIN);
|
||||
sd_mosi_pin_func = gpio_get_function(SD_MOSI_PIN);
|
||||
}
|
||||
|
||||
void close() {
|
||||
gpio_set_function(SD_SCK_PIN, old_sck_pin_func);
|
||||
gpio_set_function(SD_MOSI_PIN, old_mosi_pin_func);
|
||||
}
|
||||
|
||||
private:
|
||||
static gpio_function_t sd_sck_pin_func;
|
||||
static gpio_function_t sd_mosi_pin_func;
|
||||
|
||||
gpio_function_t old_sck_pin_func = gpio_get_function(SD_SCK_PIN);
|
||||
gpio_function_t old_mosi_pin_func = gpio_get_function(SD_MOSI_PIN);
|
||||
};
|
||||
+59
-4
@@ -1,13 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if ENABLE_LCD
|
||||
#include <TFT_eSPI.h>
|
||||
#endif
|
||||
#include <SdFat.h>
|
||||
#include "gb.h"
|
||||
|
||||
/* Joypad Pins. */
|
||||
#define USE_JOYPAD_I2C_IO_EXPANDER
|
||||
#ifdef USE_JOYPAD_I2C_IO_EXPANDER
|
||||
// Use PCF8574 for Joypad. Only required if an LCD with 16-bit parallel bus is used,
|
||||
// as Pico does not have enough pins for all peripherals. With 8-bit parallel or SPI LCDs this should not be necessary.
|
||||
#define PCF8574_ADDR 0x20
|
||||
#define PCF8574_SDA 20
|
||||
#define PCF8574_SCL 21
|
||||
// pins below are on the IO expander
|
||||
#define PIN_UP 0
|
||||
#define PIN_DOWN 1
|
||||
#define PIN_LEFT 2
|
||||
#define PIN_RIGHT 3
|
||||
#define PIN_A 5
|
||||
#define PIN_B 4
|
||||
#define PIN_SELECT 6
|
||||
#define PIN_START 7
|
||||
#else
|
||||
// Use GPIOs directly on Pico for Joypad
|
||||
#define PIN_UP 2
|
||||
#define PIN_DOWN 3
|
||||
#define PIN_LEFT 4
|
||||
#define PIN_RIGHT 5
|
||||
#define PIN_A 6
|
||||
#define PIN_B 7
|
||||
#define PIN_SELECT 8
|
||||
#define PIN_START 9
|
||||
#endif
|
||||
|
||||
#if ENABLE_SOUND
|
||||
#define I2S_DIN_PIN 26
|
||||
#define I2S_BCLK_LRC_PIN_BASE 27 // BCLK + LRC (28)
|
||||
#endif
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
#define SD_CS_PIN 17
|
||||
#define SD_SCK_PIN 18
|
||||
#define SD_MOSI_PIN 19
|
||||
#define SD_MISO_PIN 16
|
||||
|
||||
extern uint8_t _FS_start;
|
||||
extern uint8_t _FS_end;
|
||||
#define MAX_ROM_SIZE (&_FS_end - &_FS_start)
|
||||
#endif
|
||||
|
||||
// display is rotated, so TFT_WIDTH/HEIGHT cannot be used
|
||||
#define DISPLAY_WIDTH TFT_HEIGHT
|
||||
#define DISPLAY_HEIGHT TFT_WIDTH
|
||||
|
||||
#define FONT_HEIGHT 8
|
||||
#define ERROR_TEXT_OFFSET FONT_HEIGHT
|
||||
#define FONT_ID 1
|
||||
|
||||
enum class ScalingMode {
|
||||
NORMAL = 0,
|
||||
STRETCH,
|
||||
@@ -17,9 +69,8 @@ enum class ScalingMode {
|
||||
|
||||
extern volatile ScalingMode scalingMode;
|
||||
|
||||
extern struct gb_s gb;
|
||||
extern palette_t palette; // Colour palette
|
||||
extern uint_fast32_t frames;
|
||||
extern TFT_eSPI tft;
|
||||
|
||||
/* Multicore command structure. */
|
||||
union core_cmd {
|
||||
@@ -45,8 +96,12 @@ void nextPalette();
|
||||
|
||||
void prevPalette();
|
||||
|
||||
void lcd_init(bool isCore1);
|
||||
void lcd_draw_line(struct gb_s* gb, const uint8_t* pixels, const uint_fast8_t line);
|
||||
void lcd_fill(uint16_t color);
|
||||
|
||||
void core1_init();
|
||||
|
||||
void reset();
|
||||
void reset(uint32_t sleepMs = 0);
|
||||
|
||||
void error(String message);
|
||||
|
||||
+64
@@ -4,3 +4,67 @@
|
||||
#include "peanut_gb.h"
|
||||
|
||||
#include "gbcolors.h"
|
||||
#include "gb.h"
|
||||
#include "common.h"
|
||||
|
||||
struct gb_s gb;
|
||||
palette_t palette; // Colour palette
|
||||
|
||||
uint8_t ram[RAM_SIZE];
|
||||
|
||||
// Definition of ROM data
|
||||
#if !ENABLE_SDCARD
|
||||
#include "game_bin.h"
|
||||
const uint8_t *rom = GAME_DATA;
|
||||
#else
|
||||
/** Definition of ROM data
|
||||
* We're going to erase and reprogram the region defines as "Filesystem" in platformio.ini (see board_build.filesystem_size).
|
||||
* This is available from _FS_start (i.e. XIP_BASE + program size) to _FS_end. Note that the last sector is reserved for EEPROM.
|
||||
* Game Boy DMG ROM size ranges from 32768 bytes (e.g. Tetris) to 1,048,576 bytes (e.g. Pokemod Red)
|
||||
*/
|
||||
const uint8_t *rom = (const uint8_t *)(&_FS_start);
|
||||
#endif
|
||||
|
||||
static unsigned char rom_bank0[65536];
|
||||
|
||||
/**
|
||||
* Returns a byte from the ROM file at the given address.
|
||||
*/
|
||||
static 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.
|
||||
*/
|
||||
static 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.
|
||||
*/
|
||||
static void gb_cart_ram_write(struct gb_s* gb, const uint_fast32_t addr,
|
||||
const uint8_t val) {
|
||||
ram[addr] = val;
|
||||
}
|
||||
|
||||
static void gb_error(struct gb_s* gb, const enum gb_error_e gb_err, const uint16_t addr) {
|
||||
const char* gb_err_str[4] = {
|
||||
"UNKNOWN",
|
||||
"INVALID OPCODE",
|
||||
"INVALID READ",
|
||||
"INVALID WRITE"};
|
||||
error(String("Error ") + gb_err + " occurred: " + gb_err_str[gb_err] + " at 0x" + String(addr, 16));
|
||||
}
|
||||
|
||||
gb_init_error_e initGbContext() {
|
||||
memcpy(rom_bank0, rom, sizeof(rom_bank0));
|
||||
return gb_init(&gb, &gb_rom_read, &gb_cart_ram_read,
|
||||
&gb_cart_ram_write, &gb_error, NULL);
|
||||
}
|
||||
|
||||
@@ -5,3 +5,12 @@
|
||||
|
||||
#define GBCOLOR_HEADER_ONLY
|
||||
#include "gbcolors.h"
|
||||
|
||||
extern struct gb_s gb;
|
||||
extern palette_t palette; // Colour palette
|
||||
extern const uint8_t* rom;
|
||||
|
||||
#define RAM_SIZE 32768
|
||||
extern uint8_t ram[RAM_SIZE];
|
||||
|
||||
gb_init_error_e initGbContext();
|
||||
|
||||
+85
-40
@@ -1,10 +1,32 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "input.h"
|
||||
#include "i2s-audio.h"
|
||||
|
||||
#include "gb.h"
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
#include "card_loader.h"
|
||||
#endif
|
||||
|
||||
#if ENABLE_SOUND
|
||||
#include "i2s-audio.h"
|
||||
extern i2s_config_t i2s_config;
|
||||
#endif
|
||||
|
||||
extern uint8_t gamma_int;
|
||||
|
||||
static struct
|
||||
{
|
||||
@@ -18,13 +40,30 @@ static struct
|
||||
unsigned down : 1;
|
||||
} prev_joypad_bits;
|
||||
|
||||
#ifndef USE_PAD_GPIO
|
||||
static PCF8574 pcf8574(0x20, 16, 17);
|
||||
#endif
|
||||
#ifdef USE_JOYPAD_I2C_IO_EXPANDER
|
||||
|
||||
static PCF8574 pcf8574(PCF8574_ADDR, PCF8574_SDA, PCF8574_SCL);
|
||||
|
||||
#ifdef USE_PAD_GPIO
|
||||
void initJoypad() {
|
||||
static void initJoypadI2CIoExpander() {
|
||||
for (int pin = 0; pin < 8; ++pin) {
|
||||
pcf8574.pinMode(pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
pcf8574.setLatency(5);
|
||||
|
||||
if (!pcf8574.begin()){
|
||||
Serial.println("PCF8574 initialization failed");
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool readJoypad(uint8_t pin) {
|
||||
return pcf8574.digitalRead(pin);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void initJoypadGpios() {
|
||||
gpio_set_function(GPIO_UP, GPIO_FUNC_SIO);
|
||||
gpio_set_function(GPIO_DOWN, GPIO_FUNC_SIO);
|
||||
gpio_set_function(GPIO_LEFT, GPIO_FUNC_SIO);
|
||||
@@ -50,25 +89,25 @@ void initJoypad() {
|
||||
gpio_pull_up(GPIO_A);
|
||||
gpio_pull_up(GPIO_B);
|
||||
gpio_pull_up(GPIO_SELECT);
|
||||
gpio_pull_up(GPIO_START);
|
||||
gpio_pull_up(GPIO_START);
|
||||
}
|
||||
#else
|
||||
void initJoypad() {
|
||||
for (int pin = 0; pin < 8; ++pin) {
|
||||
pcf8574.pinMode(pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
pcf8574.setLatency(5);
|
||||
|
||||
if (pcf8574.begin()){
|
||||
Serial.println("PCF8574 initialized");
|
||||
}else{
|
||||
Serial.println("PCF8574 initialization failed");
|
||||
while (true) ;
|
||||
}
|
||||
bool readJoypad(uint8_t pin) {
|
||||
return gpio_get(pin);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void initJoypad() {
|
||||
Serial.println("Init Joypad IOs ...");
|
||||
#ifdef USE_JOYPAD_I2C_IO_EXPANDER
|
||||
initJoypadI2CIoExpander();
|
||||
#else
|
||||
initJoypadGpios();
|
||||
#endif
|
||||
Serial.println("Joypad IOs initialized");
|
||||
}
|
||||
|
||||
void handleSerial() {
|
||||
static uint64_t start_time = time_us_64();
|
||||
|
||||
@@ -158,7 +197,7 @@ void handleSerial() {
|
||||
}
|
||||
}
|
||||
|
||||
void handlePad() {
|
||||
void handleJoypad() {
|
||||
/* Update buttons state */
|
||||
prev_joypad_bits.up = gb.direct.joypad_bits.up;
|
||||
prev_joypad_bits.down = gb.direct.joypad_bits.down;
|
||||
@@ -169,16 +208,6 @@ void handlePad() {
|
||||
prev_joypad_bits.select = gb.direct.joypad_bits.select;
|
||||
prev_joypad_bits.start = gb.direct.joypad_bits.start;
|
||||
|
||||
#ifdef USE_PAD_GPIO
|
||||
gb.direct.joypad_bits.up = gpio_get(PIN_UP);
|
||||
gb.direct.joypad_bits.down = gpio_get(PIN_DOWN);
|
||||
gb.direct.joypad_bits.left = gpio_get(PIN_LEFT);
|
||||
gb.direct.joypad_bits.right = gpio_get(PIN_RIGHT);
|
||||
gb.direct.joypad_bits.a = gpio_get(PIN_A);
|
||||
gb.direct.joypad_bits.b = gpio_get(PIN_B);
|
||||
gb.direct.joypad_bits.select = gpio_get(PIN_SELECT);
|
||||
gb.direct.joypad_bits.start = gpio_get(PIN_START);
|
||||
#else
|
||||
#if 0
|
||||
Serial.printf("pins:\n");
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
@@ -188,14 +217,30 @@ void handlePad() {
|
||||
Serial.printf("\n");
|
||||
#endif
|
||||
|
||||
gb.direct.joypad_bits.up = pcf8574.digitalRead(PIN_UP);
|
||||
gb.direct.joypad_bits.down = pcf8574.digitalRead(PIN_DOWN);
|
||||
gb.direct.joypad_bits.left = pcf8574.digitalRead(PIN_LEFT);
|
||||
gb.direct.joypad_bits.right = pcf8574.digitalRead(PIN_RIGHT);
|
||||
gb.direct.joypad_bits.a = pcf8574.digitalRead(PIN_A);
|
||||
gb.direct.joypad_bits.b = pcf8574.digitalRead(PIN_B);
|
||||
gb.direct.joypad_bits.select = pcf8574.digitalRead(PIN_SELECT);
|
||||
gb.direct.joypad_bits.start = pcf8574.digitalRead(PIN_START);
|
||||
gb.direct.joypad_bits.up = readJoypad(PIN_UP);
|
||||
gb.direct.joypad_bits.down = readJoypad(PIN_DOWN);
|
||||
gb.direct.joypad_bits.left = readJoypad(PIN_LEFT);
|
||||
gb.direct.joypad_bits.right = readJoypad(PIN_RIGHT);
|
||||
gb.direct.joypad_bits.a = readJoypad(PIN_A);
|
||||
gb.direct.joypad_bits.b = readJoypad(PIN_B);
|
||||
gb.direct.joypad_bits.select = readJoypad(PIN_SELECT);
|
||||
gb.direct.joypad_bits.start = readJoypad(PIN_START);
|
||||
|
||||
#if 0
|
||||
if (!gb.direct.joypad_bits.up) {
|
||||
if (!gb.direct.joypad_bits.select && prev_joypad_bits.select) {
|
||||
gamma_int++;
|
||||
Serial.printf("gamma: %d\n", gamma_int);
|
||||
get_colour_palette(palette, 0, 5);
|
||||
}
|
||||
}
|
||||
if (!gb.direct.joypad_bits.down) {
|
||||
if (!gb.direct.joypad_bits.select && prev_joypad_bits.select) {
|
||||
gamma_int--;
|
||||
get_colour_palette(palette, 0, 5);
|
||||
Serial.printf("gamma: %d\n", gamma_int);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* hotkeys (select + * combo)*/
|
||||
|
||||
+2
-22
@@ -2,28 +2,8 @@
|
||||
|
||||
#include <PCF8574.h>
|
||||
|
||||
/* GPIO Connections. */
|
||||
#ifdef USE_PAD_GPIO
|
||||
#define PIN_UP 2
|
||||
#define PIN_DOWN 3
|
||||
#define PIN_LEFT 4
|
||||
#define PIN_RIGHT 5
|
||||
#define PIN_A 6
|
||||
#define PIN_B 7
|
||||
#define PIN_SELECT 8
|
||||
#define PIN_START 9
|
||||
#else
|
||||
#define PIN_UP 0
|
||||
#define PIN_DOWN 1
|
||||
#define PIN_LEFT 2
|
||||
#define PIN_RIGHT 3
|
||||
#define PIN_A 5
|
||||
#define PIN_B 4
|
||||
#define PIN_SELECT 6
|
||||
#define PIN_START 7
|
||||
#endif
|
||||
|
||||
void initJoypad();
|
||||
bool readJoypad(uint8_t pin);
|
||||
|
||||
void handlePad();
|
||||
void handleJoypad();
|
||||
void handleSerial();
|
||||
|
||||
+62
-29
@@ -1,11 +1,24 @@
|
||||
#include <TFT_eSPI.h>
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
//#define USE_FRAMEBUFFER
|
||||
|
||||
static TFT_eSPI tft = TFT_eSPI();
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
static TFT_eSprite framebuffer = TFT_eSprite(&tft);
|
||||
#else
|
||||
static TFT_eSPI& framebuffer = tft;
|
||||
@@ -30,10 +43,13 @@ static void calcExtraLineTable() {
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_init(void) {
|
||||
void lcd_init(bool isCore1) {
|
||||
tft.init();
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
tft.initDMA();
|
||||
#if ENABLE_LCD_DMA
|
||||
// do not enable DMA on core0 as it fails on core1 if it is already enabled
|
||||
if (isCore1 && !tft.initDMA(/*ctrl_cs not supported in RP2040 implementation*/)) {
|
||||
error("Failed to initialize TFT DMA");
|
||||
}
|
||||
#endif
|
||||
tft.setRotation(1);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
@@ -56,7 +72,7 @@ void lcd_draw_line(struct gb_s* gb, const uint8_t pixels[LCD_WIDTH], const uint_
|
||||
multicore_fifo_push_blocking(cmd.full);
|
||||
}
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
void lcd_pushColors(size_t offset, const uint16_t* pixels, uint_fast16_t count) {
|
||||
uint16_t* image = (uint16_t*) framebuffer.getPointer();
|
||||
memcpy(&image[offset], pixels, count * sizeof(uint16_t));
|
||||
@@ -64,7 +80,18 @@ void lcd_pushColors(size_t offset, const uint16_t* pixels, uint_fast16_t count)
|
||||
#else
|
||||
void lcd_pushColors(uint16_t colOffset, uint16_t lineOffset, const uint16_t* pixels, uint_fast16_t count) {
|
||||
framebuffer.setAddrWindow(colOffset, lineOffset, count, 1);
|
||||
#if ENABLE_LCD_DMA
|
||||
// DMA mode does not have a measurable effect without a framebuffer
|
||||
static uint16_t dmaBuffer[DISPLAY_WIDTH];
|
||||
tft.dmaWait();
|
||||
memcpy(dmaBuffer, pixels, count * sizeof(uint16_t));
|
||||
tft.setSwapBytes(true);
|
||||
tft.startWrite(); // manual start required as DMA transfer is asynchronous
|
||||
tft.pushPixelsDMA((uint16_t*) dmaBuffer, count);
|
||||
//tft.endWrite(); // do not call endWrite(), as it will wait for the DMA transfer to finish, which results in no performance gain
|
||||
#else
|
||||
tft.pushColors((uint16_t*) pixels, count, true);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -72,7 +99,7 @@ void lcd_write_pixels_normal(const uint16_t* pixels, uint8_t line, uint_fast16_t
|
||||
const uint16_t colOffset = (DISPLAY_WIDTH - count) / 2;
|
||||
const uint16_t screenLineOffset = (DISPLAY_HEIGHT - LCD_HEIGHT) / 2;
|
||||
const uint16_t lineOffset = screenLineOffset + line;
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
lcd_pushColors(lineOffset * DISPLAY_WIDTH + colOffset, pixels, count);
|
||||
#else
|
||||
lcd_pushColors(colOffset, lineOffset, pixels, count);
|
||||
@@ -90,7 +117,7 @@ void lcd_write_pixels_stretched(const uint16_t* pixels, uint8_t line, uint_fast1
|
||||
|
||||
const uint8_t lineRepeated = IS_REPEATED(line);
|
||||
const uint16_t lineOffset = scaledLineOffsetTable[line];
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
size_t offset = lineOffset * DISPLAY_WIDTH;
|
||||
lcd_pushColors(offset, doubledPixels, stretchedWidth);
|
||||
if (lineRepeated) {
|
||||
@@ -119,7 +146,7 @@ void lcd_write_pixels_stretched_keep_aspect(const uint16_t* pixels, uint8_t line
|
||||
|
||||
uint8_t lineRepeated = IS_REPEATED(line);
|
||||
const uint16_t lineOffset = scaledLineOffsetTable[line];
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
size_t offset = lineOffset * DISPLAY_WIDTH + colOffset;
|
||||
lcd_pushColors(offset, doubledPixels, stretchedWidth);
|
||||
if (lineRepeated) {
|
||||
@@ -133,6 +160,7 @@ void lcd_write_pixels_stretched_keep_aspect(const uint16_t* pixels, uint8_t line
|
||||
#endif
|
||||
}
|
||||
|
||||
// Writes pixels to screen or framebuffer
|
||||
void lcd_write_pixels(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
|
||||
switch (scalingMode)
|
||||
{
|
||||
@@ -149,19 +177,28 @@ void lcd_write_pixels(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb)
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
// Writes framebuffer to screen
|
||||
void lcd_write_framebuffer_to_screen() {
|
||||
tft.setSwapBytes(true);
|
||||
#ifdef ENABLE_LCD_DMA
|
||||
tft.startWrite(); // manual start required as DMA transfer is asynchronous
|
||||
tft.pushImageDMA(0, 0, framebuffer.width(), framebuffer.height(), (uint16_t *) framebuffer.getPointer());
|
||||
//tft.endWrite(); // do not call endWrite(), as it will wait for the DMA transfer to finish, which results in no performance gain
|
||||
#else
|
||||
tft.pushImage(0, 0, framebuffer.width(), framebuffer.height(), (uint16_t *) framebuffer.getPointer());
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void lcd_fill(uint16_t color) {
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
framebuffer.fillSprite(color);
|
||||
#else
|
||||
tft.fillScreen(color);
|
||||
#endif
|
||||
}
|
||||
|
||||
void lcd_text(char* s, uint8_t x, uint8_t y, uint16_t color, uint16_t bgcolor) {
|
||||
framebuffer.setTextColor(TFT_WHITE, TFT_BLACK); // TODO
|
||||
framebuffer.drawString(s, x, y);
|
||||
}
|
||||
|
||||
void core1_lcd_draw_line(const uint_fast8_t line) {
|
||||
static uint16_t fb[LCD_WIDTH];
|
||||
|
||||
@@ -179,10 +216,9 @@ void core1_lcd_draw_line(const uint_fast8_t line) {
|
||||
lcd_write_pixels(fb, line, LCD_WIDTH);
|
||||
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
if (line == LCD_HEIGHT - 1) {
|
||||
tft.setSwapBytes(true);
|
||||
tft.pushImageDMA(0, 0, framebuffer.width(), framebuffer.height(), (uint16_t *) framebuffer.getPointer());
|
||||
lcd_write_framebuffer_to_screen();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -209,20 +245,17 @@ void core1DispatchLoop() {
|
||||
|
||||
void core1_init() {
|
||||
/* Initialise and control LCD on core 1. */
|
||||
lcd_init();
|
||||
lcd_init(true);
|
||||
|
||||
/* Clear LCD screen. */
|
||||
lcd_fill(TFT_BLACK);
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#if ENABLE_LCD_FRAMEBUFFER
|
||||
framebuffer.setColorDepth(16);
|
||||
framebuffer.createSprite(tft.width(), tft.height());
|
||||
#endif
|
||||
|
||||
calcExtraLineTable();
|
||||
/* Clear LCD screen. */
|
||||
lcd_fill(TFT_BLACK);
|
||||
|
||||
// Sleep used for debugging LCD window.
|
||||
// sleep_ms(1000);
|
||||
calcExtraLineTable();
|
||||
|
||||
while (true) {
|
||||
core1DispatchLoop();
|
||||
|
||||
+73
-116
@@ -15,29 +15,32 @@
|
||||
|
||||
#include "gb.h"
|
||||
|
||||
/* C Headers */
|
||||
// C Headers
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* RP2040 Headers */
|
||||
// RP2040 Headers
|
||||
#include <hardware/vreg.h>
|
||||
|
||||
/* Project headers */
|
||||
// Project headers
|
||||
#include "hedley.h"
|
||||
#include "minigb_apu.h"
|
||||
|
||||
// #include "sdcard.h"
|
||||
#include "common.h"
|
||||
#include "game_bin.h"
|
||||
#include "i2s-audio.h"
|
||||
|
||||
#define GBCOLOR_HEADER_ONLY
|
||||
#include "gbcolors.h"
|
||||
|
||||
#include "input.h"
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
#include "card_loader.h"
|
||||
#include "SdFat.h"
|
||||
#ifdef ENABLE_USB_STORAGE_DEVICE
|
||||
#include "msc.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ENABLE_SOUND
|
||||
#include "i2s-audio.h"
|
||||
#include "minigb_apu.h"
|
||||
|
||||
/**
|
||||
* Global variables for audio task
|
||||
* stream contains N=AUDIO_SAMPLES samples
|
||||
@@ -46,86 +49,24 @@
|
||||
* This is intended to be played at AUDIO_SAMPLE_RATE Hz
|
||||
*/
|
||||
uint16_t* stream;
|
||||
|
||||
i2s_config_t i2s_config;
|
||||
#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;
|
||||
|
||||
struct gb_s gb;
|
||||
palette_t palette; // Colour palette
|
||||
i2s_config_t i2s_config;
|
||||
uint_fast32_t frames = 0;
|
||||
|
||||
#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 startEmulator() {
|
||||
#if ENABLE_LCD
|
||||
#if ENABLE_SDCARD
|
||||
/* ROM File selector */
|
||||
lcd_init();
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
Serial.println("Starting ROM file selector ...");
|
||||
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 ");
|
||||
|
||||
Serial.println("Init GB context ...");
|
||||
auto ret = initGbContext();
|
||||
if (ret != GB_INIT_NO_ERROR) {
|
||||
Serial.printf("Error: %d\n", ret);
|
||||
reset();
|
||||
@@ -139,32 +80,36 @@ void startEmulator() {
|
||||
gb_init_lcd(&gb, &lcd_draw_line);
|
||||
|
||||
/* Start Core1, which processes requests to the LCD. */
|
||||
Serial.println("CORE1 ");
|
||||
Serial.println("Starting Core1 ...");
|
||||
multicore_launch_core1(core1_init);
|
||||
|
||||
Serial.println("LCD ");
|
||||
#endif
|
||||
|
||||
#if ENABLE_SOUND
|
||||
// Initialize audio emulation
|
||||
Serial.println("Starting audio ...");
|
||||
audio_init();
|
||||
|
||||
Serial.println("AUDIO ");
|
||||
#endif
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
/* Load Save File. */
|
||||
read_cart_ram_file(&gb);
|
||||
//Serial.println("Load save file ...");
|
||||
//read_cart_ram_file(&gb);
|
||||
#endif
|
||||
|
||||
Serial.print("\n> ");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
void reset(uint32_t sleepMs) {
|
||||
Serial.println("\nEmulation Ended");
|
||||
|
||||
sleep_ms(sleepMs);
|
||||
|
||||
/* stop lcd task running on core 1 */
|
||||
multicore_reset_core1();
|
||||
watchdog_reboot(0,0,0);
|
||||
watchdog_reboot(0, 0, 0);
|
||||
}
|
||||
|
||||
void halt() {
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
void overclock() {
|
||||
@@ -177,33 +122,10 @@ void overclock() {
|
||||
sleep_ms(2);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
overclock();
|
||||
|
||||
/* Initialise USB serial connection for debugging. */
|
||||
Serial.begin(115200);
|
||||
//while (!Serial) ;
|
||||
//delay(2000);
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
time_init();
|
||||
#endif
|
||||
// sleep_ms(5000);
|
||||
Serial.println("INIT: ");
|
||||
|
||||
/* Initialise joypad. */
|
||||
initJoypad();
|
||||
|
||||
/* 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
|
||||
|
||||
void initSound() {
|
||||
#if ENABLE_SOUND
|
||||
Serial.println("Initialize Sound ...");
|
||||
|
||||
// Allocate memory for the stream buffer
|
||||
stream = (uint16_t*) malloc(AUDIO_BUFFER_SIZE_BYTES);
|
||||
assert(stream != NULL);
|
||||
@@ -213,11 +135,37 @@ void setup() {
|
||||
i2s_config = i2s_get_default_config();
|
||||
i2s_config.sample_freq = AUDIO_SAMPLE_RATE;
|
||||
i2s_config.dma_trans_count = AUDIO_SAMPLES;
|
||||
i2s_config.data_pin = 26, // DIN
|
||||
i2s_config.clock_pin_base = 27, // BCLK + LRC
|
||||
i2s_config.data_pin = I2S_DIN_PIN,
|
||||
i2s_config.clock_pin_base = I2S_BCLK_LRC_PIN_BASE,
|
||||
i2s_volume(&i2s_config, 2);
|
||||
i2s_init(&i2s_config);
|
||||
|
||||
Serial.println("Sound initialized");
|
||||
#endif
|
||||
}
|
||||
|
||||
void setup() {
|
||||
#if ENABLE_LCD
|
||||
lcd_init(false);
|
||||
#endif
|
||||
|
||||
overclock();
|
||||
|
||||
// Initialise USB serial connection for debugging.
|
||||
Serial.begin(115200);
|
||||
//while (!Serial) ;
|
||||
//delay(2000);
|
||||
|
||||
#if ENABLE_SDCARD
|
||||
init_sdcard();
|
||||
#if ENABLE_USB_STORAGE_DEVICE
|
||||
initUsbStorageDevice();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
initJoypad();
|
||||
|
||||
initSound();
|
||||
|
||||
startEmulator();
|
||||
}
|
||||
@@ -248,6 +196,15 @@ void loop() {
|
||||
}
|
||||
#endif
|
||||
|
||||
handlePad();
|
||||
handleJoypad();
|
||||
handleSerial();
|
||||
}
|
||||
|
||||
void error(String message) {
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(TFT_RED);
|
||||
tft.drawString(message, 0, ERROR_TEXT_OFFSET, FONT_ID);
|
||||
Serial.printf("E %s\n", message.c_str());
|
||||
Serial.flush();
|
||||
reset(5000);
|
||||
}
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#if ENABLE_USB_STORAGE_DEVICE
|
||||
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
|
||||
#include "card_loader.h"
|
||||
|
||||
static Adafruit_USBD_MSC usb_msc;
|
||||
|
||||
// Callback invoked when received READ10 command.
|
||||
static int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
bool rc = sd.card()->readSectors(lba, (uint8_t*) buffer, bufsize/512);
|
||||
return rc ? bufsize : -1;
|
||||
}
|
||||
|
||||
// Callback invoked when received WRITE10 command.
|
||||
static int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
bool rc = sd.card()->writeSectors(lba, buffer, bufsize/512);
|
||||
return rc ? bufsize : -1;
|
||||
}
|
||||
|
||||
// Callback invoked when WRITE10 command is completed (status received and accepted by host).
|
||||
static void msc_flush_cb (void) {
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
|
||||
sd.card()->syncDevice();
|
||||
}
|
||||
|
||||
void initUsbStorageDevice() {
|
||||
usb_msc.setID("GB", "SD Card", "1.0");
|
||||
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
|
||||
usb_msc.setUnitReady(false);
|
||||
usb_msc.begin();
|
||||
|
||||
if (TinyUSBDevice.mounted()) {
|
||||
TinyUSBDevice.detach();
|
||||
delay(10);
|
||||
TinyUSBDevice.attach();
|
||||
}
|
||||
|
||||
auto scope = UseSDPinFunctionScope();
|
||||
uint32_t block_count = sd.card()->sectorCount();
|
||||
usb_msc.setCapacity(block_count, 512);
|
||||
usb_msc.setUnitReady(true);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -62,11 +62,11 @@
|
||||
#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
|
||||
#define TFT_WR 18 // Write strobe for modified Raspberry Pi TFT only
|
||||
#define TFT_DC 19 // Data Command control pin
|
||||
#define TFT_CS 22 // 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
|
||||
|
||||
// ##################################################################################
|
||||
//
|
||||
@@ -79,8 +79,8 @@
|
||||
// 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
|
||||
#if 0
|
||||
#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
|
||||
|
||||
Reference in New Issue
Block a user