add scaling modes

This commit is contained in:
Tobias Gunkel
2025-08-11 21:27:36 +02:00
parent 5302f017f2
commit 0c0bc03e48
5 changed files with 210 additions and 121 deletions
+1
View File
@@ -2,3 +2,4 @@ build
uf2 uf2
.pio .pio
.vscode .vscode
*_bin.h
+6 -5
View File
@@ -6,23 +6,24 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git#651837d09a1a5
framework = arduino framework = arduino
board_build.core = earlephilhower board_build.core = earlephilhower
board_build.filesystem_size = 512k #board_build.filesystem_size = 512k
#upload_protocol = mbed upload_protocol = mbed
upload_protocol = cmsis-dap #upload_protocol = cmsis-dap
debug_tool = cmsis-dap debug_tool = cmsis-dap
build_src_filter = build_src_filter =
+<*.cpp> +<*.cpp>
#lib_ldf_mode = chain+ lib_ldf_mode = chain+
lib_deps = lib_deps =
bodmer/TFT_eSPI@^2.5.43 bodmer/TFT_eSPI@^2.5.43
xreef/PCF8574 library@^2.3.7
build_flags = build_flags =
-I. -I.
-Isrc/tft-espi-config/ -Isrc/tft-espi-config/
-Iinc -Iext
-Iext/minigb_apu -Iext/minigb_apu
[env:pico] [env:pico]
+27 -20
View File
@@ -5,30 +5,37 @@
#define GBCOLOR_HEADER_ONLY #define GBCOLOR_HEADER_ONLY
#include "gbcolors.h" #include "gbcolors.h"
/* Multicore command structure. */ enum class ScalingMode {
union core_cmd NORMAL = 0,
{ STRETCH,
struct STRETCH_KEEP_ASPECT,
{ COUNT
/* 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 volatile ScalingMode scalingMode;
/* 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 extern palette_t palette; // Colour palette
void lcd_draw_line(struct gb_s *gb, const uint8_t *pixels, const uint_fast8_t line); void lcd_draw_line(struct gb_s* gb, const uint8_t* pixels, const uint_fast8_t line);
void core1_init(); void core1_init();
+60 -14
View File
@@ -16,16 +16,17 @@ static uint8_t pixels_buffer[LCD_WIDTH];
palette_t palette; // Colour palette palette_t palette; // Colour palette
volatile ScalingMode scalingMode = ScalingMode::NORMAL;
static int lcd_line_busy = 0; static int lcd_line_busy = 0;
#define IS_LINE_REPEATED(line) ((line % 2) || (line % 6 == 0)) #define IS_REPEATED(pos) ((pos % 2) || (pos % 6 == 0))
// #define IS_LINE_REPEATED(line) 0
static void calcExtraLineTable() { static void calcExtraLineTable() {
uint8_t offset = 0; uint8_t offset = 0;
for (uint8_t line = 0; line < LCD_HEIGHT; ++line) { for (uint8_t line = 0; line < LCD_HEIGHT; ++line) {
scaledLineOffsetTable[line] = offset; scaledLineOffsetTable[line] = offset;
offset += 1 + IS_LINE_REPEATED(line); offset += 1 + IS_REPEATED(line);
} }
} }
@@ -54,21 +55,66 @@ void lcd_draw_line(struct gb_s* gb, const uint8_t pixels[LCD_WIDTH],
multicore_fifo_push_blocking(cmd.full); multicore_fifo_push_blocking(cmd.full);
} }
void lcd_write_pixels(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) { void lcd_write_pixels_normal(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
const uint16_t colOffset = (tft.width() - nmemb) / 2;
const uint16_t lineOffset = (tft.height() - LCD_HEIGHT) / 2;
tft.setAddrWindow(colOffset, lineOffset + line, nmemb, 1);
tft.pushColors((uint16_t*) pixels, nmemb, true);
}
void lcd_write_pixels_stretched(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
static uint16_t doubledPixels[320]; static uint16_t doubledPixels[320];
uint16_t pos = 0; uint16_t pos = 0;
for (int i = 0; i < nmemb; ++i) { for (int col = 0; col < nmemb; ++col) {
doubledPixels[pos++] = pixels[i]; doubledPixels[pos++] = pixels[col];
doubledPixels[pos++] = pixels[i]; doubledPixels[pos++] = pixels[col];
} }
const uint16_t stretchedWidth = pos;
uint8_t repeatedLines = IS_LINE_REPEATED(line); uint8_t repeatedLines = IS_REPEATED(line);
// tft.setAddrWindow(0, scaledLineOffsetTable[line], nmemb * 2, 1 + repeatedLines); tft.setAddrWindow(0, scaledLineOffsetTable[line], stretchedWidth, 1);
tft.setAddrWindow(0, scaledLineOffsetTable[line], nmemb * 2, 1); tft.pushColors((uint16_t*)doubledPixels, stretchedWidth, true);
tft.pushColors((uint16_t*)doubledPixels, nmemb * 2, true);
if (repeatedLines) { if (repeatedLines) {
tft.setAddrWindow(0, scaledLineOffsetTable[line] + 1, nmemb * 2, 1); tft.setAddrWindow(0, scaledLineOffsetTable[line] + 1, stretchedWidth, 1);
tft.pushColors((uint16_t*)doubledPixels, nmemb * 2, true); tft.pushColors((uint16_t*)doubledPixels, stretchedWidth, true);
}
}
void lcd_write_pixels_stretched_keep_aspect(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
static uint16_t doubledPixels[320];
uint16_t pos = 0;
for (int col = 0; col < nmemb; ++col) {
doubledPixels[pos++] = pixels[col];
if (IS_REPEATED(col)) {
doubledPixels[pos++] = pixels[col];
}
}
const uint16_t stretchedWidth = pos;
const uint16_t colOffset = (tft.width() - stretchedWidth) / 2;
uint8_t repeatedLines = IS_REPEATED(line);
tft.setAddrWindow(colOffset, scaledLineOffsetTable[line], stretchedWidth, 1);
tft.pushColors((uint16_t*)doubledPixels, stretchedWidth, true);
if (repeatedLines) {
tft.setAddrWindow(colOffset, scaledLineOffsetTable[line] + 1, stretchedWidth, 1);
tft.pushColors((uint16_t*)doubledPixels, stretchedWidth, true);
}
}
void lcd_write_pixels(const uint16_t* pixels, uint8_t line, uint_fast16_t nmemb) {
switch (scalingMode)
{
case ScalingMode::STRETCH:
lcd_write_pixels_stretched(pixels, line, nmemb);
break;
case ScalingMode::STRETCH_KEEP_ASPECT:
lcd_write_pixels_stretched_keep_aspect(pixels, line, nmemb);
break;
case ScalingMode::NORMAL:
default:
lcd_write_pixels_normal(pixels, line, nmemb);
break;
} }
} }
@@ -112,7 +158,7 @@ void core1DispatchLoop() {
break; break;
case CORE_CMD_IDLE_SET: case CORE_CMD_IDLE_SET:
lcd_display_control(true, cmd.data); lcd_fill(TFT_BLACK);
break; break;
case CORE_CMD_NOP: case CORE_CMD_NOP:
+116 -82
View File
@@ -29,6 +29,8 @@
/* RP2040 Headers */ /* RP2040 Headers */
#include <hardware/vreg.h> #include <hardware/vreg.h>
#include <PCF8574.h>
/* Project headers */ /* Project headers */
#include "hedley.h" #include "hedley.h"
#include "minigb_apu.h" #include "minigb_apu.h"
@@ -39,14 +41,25 @@
#include "i2s.h" #include "i2s.h"
/* GPIO Connections. */ /* GPIO Connections. */
#define GPIO_UP 16 #ifdef USE_PAD_GPIO
#define GPIO_DOWN 16 #define PIN_UP 2
#define GPIO_LEFT 16 #define PIN_DOWN 3
#define GPIO_RIGHT 16 #define PIN_LEFT 4
#define GPIO_A 16 #define PIN_RIGHT 5
#define GPIO_B 16 #define PIN_A 6
#define GPIO_SELECT 16 #define PIN_B 7
#define GPIO_START 16 #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
#if ENABLE_SOUND #if ENABLE_SOUND
/** /**
@@ -72,6 +85,9 @@ static unsigned char rom_bank0[65536];
static uint8_t ram[32768]; static uint8_t ram[32768];
static uint8_t manual_palette_selected = 0; static uint8_t manual_palette_selected = 0;
static PCF8574 pcf8574(0x20, 16, 17);
static struct static struct
{ {
unsigned a : 1; unsigned a : 1;
@@ -130,9 +146,9 @@ void gb_error(struct gb_s* gb, const enum gb_error_e gb_err, const uint16_t addr
#endif #endif
} }
void stop(); void reset();
void start() { void startEmulator() {
#if ENABLE_LCD #if ENABLE_LCD
#if ENABLE_SDCARD #if ENABLE_SDCARD
/* ROM File selector */ /* ROM File selector */
@@ -150,7 +166,7 @@ void start() {
if (ret != GB_INIT_NO_ERROR) { if (ret != GB_INIT_NO_ERROR) {
Serial.printf("Error: %d\n", ret); Serial.printf("Error: %d\n", ret);
stop(); reset();
} }
/* Automatically assign a colour palette to the game */ /* Automatically assign a colour palette to the game */
@@ -182,38 +198,15 @@ void start() {
Serial.print("\n> "); Serial.print("\n> ");
} }
void stop() { void reset() {
Serial.println("\nEmulation Ended"); Serial.println("\nEmulation Ended");
/* stop lcd task running on core 1 */ /* stop lcd task running on core 1 */
multicore_reset_core1(); multicore_reset_core1();
while (true) watchdog_reboot(0,0,0);
;
} }
void setup(void) { #ifdef USE_PAD_GPIO
/* Overclock. */ void initJoypad() {
{
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_UP, GPIO_FUNC_SIO);
gpio_set_function(GPIO_DOWN, GPIO_FUNC_SIO); gpio_set_function(GPIO_DOWN, GPIO_FUNC_SIO);
gpio_set_function(GPIO_LEFT, GPIO_FUNC_SIO); gpio_set_function(GPIO_LEFT, GPIO_FUNC_SIO);
@@ -240,6 +233,49 @@ void setup(void) {
gpio_pull_up(GPIO_B); gpio_pull_up(GPIO_B);
gpio_pull_up(GPIO_SELECT); 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) ;
}
}
#endif
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) ;
//delay(2000);
#if ENABLE_SDCARD
time_init();
#endif
// sleep_ms(5000);
Serial.println("INIT: ");
/* Initialise joypad. */
initJoypad();
/* Set SPI clock to use high frequency. */ /* Set SPI clock to use high frequency. */
#if 0 #if 0
@@ -264,7 +300,7 @@ void setup(void) {
i2s_init(&i2s_config); i2s_init(&i2s_config);
#endif #endif
start(); startEmulator();
} }
void nextPalette() { void nextPalette() {
@@ -277,7 +313,7 @@ void prevPalette() {
manual_assign_palette(palette, manual_palette_selected); manual_assign_palette(palette, manual_palette_selected);
} }
void handleInput(uint_fast32_t& frames) { void handleSerial(uint_fast32_t& frames) {
static uint64_t start_time = time_us_64(); static uint64_t start_time = time_us_64();
/* Serial monitor commands */ /* Serial monitor commands */
@@ -287,37 +323,6 @@ void handleInput(uint_fast32_t& frames) {
} }
switch (input) { 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': case 'i':
gb.direct.interlace = !gb.direct.interlace; gb.direct.interlace = !gb.direct.interlace;
break; break;
@@ -387,7 +392,7 @@ void handleInput(uint_fast32_t& frames) {
} }
case 'q': case 'q':
stop(); reset();
case 'p': case 'p':
nextPalette(); nextPalette();
@@ -407,14 +412,35 @@ void handlePad() {
prev_joypad_bits.b = gb.direct.joypad_bits.b; prev_joypad_bits.b = gb.direct.joypad_bits.b;
prev_joypad_bits.select = gb.direct.joypad_bits.select; prev_joypad_bits.select = gb.direct.joypad_bits.select;
prev_joypad_bits.start = gb.direct.joypad_bits.start; 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); #ifdef USE_PAD_GPIO
gb.direct.joypad_bits.left = gpio_get(GPIO_LEFT); gb.direct.joypad_bits.up = gpio_get(PIN_UP);
gb.direct.joypad_bits.right = gpio_get(GPIO_RIGHT); gb.direct.joypad_bits.down = gpio_get(PIN_DOWN);
gb.direct.joypad_bits.a = gpio_get(GPIO_A); gb.direct.joypad_bits.left = gpio_get(PIN_LEFT);
gb.direct.joypad_bits.b = gpio_get(GPIO_B); gb.direct.joypad_bits.right = gpio_get(PIN_RIGHT);
gb.direct.joypad_bits.select = gpio_get(GPIO_SELECT); gb.direct.joypad_bits.a = gpio_get(PIN_A);
gb.direct.joypad_bits.start = gpio_get(GPIO_START); 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) {
int in = pcf8574.digitalRead(i);
Serial.printf(" %d", in);
}
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);
#endif
/* hotkeys (select + * combo)*/ /* hotkeys (select + * combo)*/
if (!gb.direct.joypad_bits.select) { if (!gb.direct.joypad_bits.select) {
@@ -441,13 +467,21 @@ void handlePad() {
#if ENABLE_SDCARD #if ENABLE_SDCARD
write_cart_ram_file(&gb); write_cart_ram_file(&gb);
#endif #endif
stop(); reset();
} }
if (!gb.direct.joypad_bits.a && prev_joypad_bits.a) { if (!gb.direct.joypad_bits.a && prev_joypad_bits.a) {
/* select + A: enable/disable frame-skip => fast-forward */ /* select + A: enable/disable frame-skip => fast-forward */
gb.direct.frame_skip = !gb.direct.frame_skip; gb.direct.frame_skip = !gb.direct.frame_skip;
Serial.printf("I gb.direct.frame_skip = %d\n", gb.direct.frame_skip); Serial.printf("I gb.direct.frame_skip = %d\n", gb.direct.frame_skip);
} }
if (!gb.direct.joypad_bits.b && prev_joypad_bits.b) {
/* select + B: change scaling mode */
scalingMode = (ScalingMode)(((int) scalingMode + 1) % ((int) ScalingMode::COUNT));
union core_cmd cmd;
cmd.cmd = CORE_CMD_IDLE_SET;
multicore_fifo_push_blocking(cmd.full);
Serial.printf("I Scaling mode: = %d\n", scalingMode);
}
} }
} }
@@ -470,5 +504,5 @@ void loop() {
#endif #endif
handlePad(); handlePad();
handleInput(frames); handleSerial(frames);
} }