Max Sánchez
Published © GPL3+

Mini PCB console

A mini PCB console that fits in your palm— with custom games, SD support, and retro fun anywhere you go!

AdvancedShowcase (no instructions)Over 1 day412
Mini PCB console

Things used in this project

Hardware components

Buzzer
Buzzer
×1
LED (generic)
LED (generic)
×6
3.6V 0.5W Zener Diode
3.6V 0.5W Zener Diode
×1
Audio Jack(NJ2FD-V)
×1
SD Card Reader (DM3AT-SF-PEJM5)
×1
USB-C receptacle (USB4215-03-A)
×1
Through Hole Resistor, 294 ohm
Through Hole Resistor, 294 ohm
×6
Omron Electronic Components LLC Momentary poush button (B3FS-1010P)
×10
On/Off Switch (CAS-120A)
×2
Elegoo TFT Screen (1.8 TFT ST7735)
×1
RP2040
Raspberry Pi RP2040
×1
3.3V regulator (AP2112K-3.3TRG1)
×1

Software apps and online services

KiCad
KiCad
PCBWay

Hand tools and fabrication machines

PCBWay: PCB Fabrication + Assembly

Story

Read more

Custom parts and enclosures

BOM

The Bill of Materials used for PCBWay

PCB Datasheet

Schematics

Design image

An image of how mi design looks like

Network list

A The ki-cad file for my console

BOM

The bill of materials

Code

Project structure

C/C++
This is the structure of the project folder. Just a text file
/*
pico_nes_snes_emulator/         
├── CMakeLists.txt             
├── pico_sdk_import.cmake       
├── main.cpp                    

├── sdcard/                     
│   ├── sdcard.cpp
│   └── sdcard.hpp

├── menu/                       
│   ├── menu.cpp
│   └── menu.hpp

├── video/                      
│   ├── video.cpp
│   └── video.hpp

├── input/                      
│   ├── input.cpp
│   └── input.hpp

├── tinynes/                    
│   ├── include/                
│   ├── nes_cpu.c
│   ├── ppu.c
│   └── …                        

└── snes9x/                     
    ├── src/                    
    ├── snes9x.h
    └── …                        

Console code

C/C++
This is the first version for the console code
/*
CMakeLists.txt
*/
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(pico_nes_snes_emulator)

pico_sdk_init()

add_executable(${PROJECT_NAME}
    main.cpp
    sdcard.cpp
    menu.cpp
    video.cpp
    input.cpp
)

# Include emulation cores (paths relative to project)
add_subdirectory(tinynes)
add_subdirectory(snes9x)

target_include_directories(${PROJECT_NAME}
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/tinynes/include
        ${CMAKE_CURRENT_LIST_DIR}/snes9x/src
        ${CMAKE_CURRENT_LIST_DIR}/
)

target_link_libraries(${PROJECT_NAME}
    hardware_spi
    hardware_dma
    hardware_gpio
    pico_stdlib
    tinynes_core
    snes9x_core
)

pico_enable_stdio_usb(${PROJECT_NAME} 1)

# main.cpp starts here

#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "ff.h"            // FatFS header
#include "hardware/spi.h"
#include "nes_emulator.h"  // Your NES emulator core API
#include "snes_emulator.h" // Your SNES emulator core API

// SD card SPI pins
#define PIN_MISO 16
#define PIN_CS   17
#define PIN_SCK  18
#define PIN_MOSI 19

// ROM buffer size limits
#define MAX_NES_ROM_SIZE  0x80000  // 512 KB
#define MAX_SNES_ROM_SIZE 0x400000 // 4 MB

FATFS fs;
FIL file;
char rom_path[256];
uint8_t *rom_buffer;
uint32_t rom_size;

void mount_sd() {
    spi_init(spi0, 4 * 1000 * 1000);
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_init(PIN_CS);
    gpio_set_dir(PIN_CS, GPIO_OUT);
    gpio_put(PIN_CS, 1);

    FRESULT res = f_mount(&fs, "", 1);
    if (res != FR_OK) {
        printf("Failed to mount SD card: %d\n", res);
        while (1);
    }
    printf("SD card mounted\n");
}

bool load_rom(const char *path) {
    FRESULT res = f_open(&file, path, FA_READ);
    if (res != FR_OK) {
        printf("Failed to open %s: %d\n", path, res);
        return false;
    }
    rom_size = f_size(&file);
    if (strstr(path, ".nes") && rom_size <= MAX_NES_ROM_SIZE) {
        rom_buffer = (uint8_t*)malloc(rom_size);
    } else if ((strstr(path, ".smc") || strstr(path, ".sfc")) && rom_size <= MAX_SNES_ROM_SIZE) {
        rom_buffer = (uint8_t*)malloc(rom_size);
    } else {
        printf("Unsupported ROM or too large: %s\n", path);
        f_close(&file);
        return false;
    }
    UINT br;
    f_read(&file, rom_buffer, rom_size, &br);
    f_close(&file);
    if (br != rom_size) {
        printf("Read error: %u/%u\n", br, rom_size);
        free(rom_buffer);
        return false;
    }
    printf("Loaded %s (size: %u bytes)\n", path, rom_size);
    return true;
}

void list_and_select_rom() {
    DIR dir;
    FILINFO fno;
    f_opendir(&dir, "/");
    printf("Available ROMs:\n");
    int idx = 0;
    while (f_readdir(&dir, &fno) == FR_OK && fno.fname[0]) {
        if (strstr(fno.fname, ".nes") || strstr(fno.fname, ".smc") || strstr(fno.fname, ".sfc")) {
            printf("%d: %s\n", idx, fno.fname);
            idx++;
        }
    }
    f_closedir(&dir);
    printf("Select ROM index: ");
    int choice;
    scanf("%d", &choice);
    // For simplicity, assume choice maps to correct filename in array
    // In practice, store filenames in an array for indexing
    sprintf(rom_path, "%s", fno.fname); // placeholder
}

int main() {
    stdio_init_all();
    mount_sd();
    list_and_select_rom();
    if (!load_rom(rom_path)) return 1;

    if (strstr(rom_path, ".nes")) {
        nes_init(rom_buffer, rom_size);
        while (true) {
            nes_run_frame();
        }
    } else {
        snes_init(rom_buffer, rom_size);
        while (true) {
            snes_run_frame();
        }
    }

    free(rom_buffer);
    return 0;
}

Credits

Max Sánchez
7 projects • 5 followers
I am a student that is passionate about technology, hardware and software. I like making video games and experimenting with new technology
Thanks to loglow.

Comments