Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Bitfunx memory card and Game ID functionality #82

Open
wants to merge 8 commits into
base: pmc+/release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ add_definitions(-DNO_PICO_LED) # prevent SD SPI library from using LED
add_subdirectory(no-OS-FatFS-SD-SPI-RPi-Pico/FatFs_SPI PicoMemcard)
add_subdirectory(poc_examples)

#optimization flag
add_compile_options(-O2)

# Example source
target_sources(PicoMemcard PUBLIC
${CMAKE_SOURCE_DIR}/src/led.c
Expand Down Expand Up @@ -52,4 +55,4 @@ target_link_libraries(
hardware_adc
)

pico_add_extra_outputs(PicoMemcard)
pico_add_extra_outputs(PicoMemcard)
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ PicoMemcard allows you to build your own supercharged PSX Memory Card that can b
* Allows to play burned CDs (thanks to [FreePSXBoot])
* Cheaper than an original memory card
* Can store hudreds of memory card images
* Switching memory cards using MemcardPro commands with L2/R2

## Bill of materials
* **Raspberry Pi Pico** (around $5)
Expand Down Expand Up @@ -111,14 +112,19 @@ Inside `docs/images` you can find two memory card images. One has a couple of sa
## Switching/Creating Images
On **PicoMemcard+** you can switch the active memory card image with the following inputs:
* `START + SELECT + DPAD UP` will switch to the next image (e.g from `1.MCR` to `2.MCR`).
* `START + SELECT + DPAD RIGHT` will switch to the next image by 5 (e.g from `1.MCR` to `6.MCR`).
* `START + SELECT + DPAD DOWN` will switch to the previous image (e.g from `1.MCR` to `0.MCR`).
* `START + SELECT + DPAD LEFT` will switch to the previous image by 5 (e.g from `6.MCR` to `1.MCR`).

Additionally you can create a new empty memory card image (and automatically switch to it) by pressing `START + SELECT + TRIANGLE`.

**Attention**: this method only works on PSX if the controller used to provide the input is plugged in the same slot as PicoMemcard (exactly under it). Using a controller from a different slot will have no effect.

Additionally this method does not work on PS2 Memory Cards and Controllers are wired on a different bus.

## Game ID
On **PicoMemcard+** you can switch automatically to a virtual memory card based on the game you are playing, without any input, if you are using an Xstation, Unirom or a patched BIOS.

## Syncing Changes
Generally speaking, new data written to PicoMemcard (e.g. when you save) is permanently stored only after a short period of time (due to hardware limitation). The on board LED indicates whether all changes have been stored or not, in particular:
* On Rapsbery Pi Pico the LED will be on when all changes have been saved, off otherwise.
Expand Down Expand Up @@ -157,16 +163,20 @@ In particular, the RP2040-Zero has RGB LED that provides a more clear output. Th
| Data synced | Led on | Green solid led |

### PicoMemcard+ Status
| Status | Pico | RP2040-Zero
| --- | --- | --- |
| Failed to read SD card | Blinking led | Red blinking led
| Data not fully synced (do not turn off PSX)| Led off | Red led (or flashing red and green) |
| Data synced | Led on | Green solid led |
| Memory Card image changed | Three fast blinks | Single blue blink |
| Memory Card image not changed (end of list) | Nine fast blinks | Single orange blink |
| New Memory Card image created | Multiple very fast blinks | Single light blue blink |
| Status | Pico | RP2040-Zero | Bitfunx Memory card
| --- | --- | --- | --- |
| Failed to read SD card | Blinking led | Red blinking led | Red blinking led |
| Data not fully synced (do not turn off PSX)| Led off | Yellow led | Yellow led |
| Data synced | Led on | Green led | Green led |
| Memory Card image changed | Three fast blinks | Single blue blink | Single blue blink |
| Memory Card image not changed (end of list) | Nine fast blinks | Single orange blink | Single purple blink |
| New Memory Card image created | Multiple very fast blinks | Single light blue blink | Single light blue blink |

## General Warnings
This fork has not been tested with a PicoMemcard+ (because i don't have one) but with a Bitfunx Memory card. This version of the firmware may or may not work with your board.

Due to Hardware limitations the Game ID functionality can't detect a disc swap in multidisc game, if you boot the second cd a new memory card will be created without transfering your progress.

I would recommend to never plug PicoMemcard both into the PC (via USB) and the PSX at the same time! Otherwise the 5V provided by USB would end up on the 3.3V rail of the PSX. I'm not really sure if this could cause actual damage but I would avoid risking it.

If you really need to have the Pico plugged into both the USB and PSX (e.g. for debugging purposes), disconnect the 3.3V line from the VBUS pin. In this way you can power on the Pico using a simple USB phone charger or by plugging it into your PC.
Expand All @@ -184,13 +194,21 @@ Special thanks to everybody that supported it so far! You are all amazing.
For people interested in understanding how PicoMemcard works I provide a more extensive explanation in [this post] (although now somewhat outdated).

## Thanks To
* [Schermaiolo] - Writing the Game ID write function
* [SantX27] - Debugging the 1.0.3 firmware to work on the Bitfunx Memory card
* [whitezombie2000] - Extensive debugging and report of all bugs related to Picomemcard, without him the releases would be way more buggy.
* [wired-filipino-owl] - Documentation and implementation of the led functionality on the Bitfunx Memory card.
* [psx-spx] and Martin "NO$PSX" Korth - PlayStation Specifications and documented Memory Card protocol and filesystem.
* [Andrew J. McCubbin] - Additional information about Memory Card and Controller communication with PSX.
* [littlefs] - Filesystem designed to work on NOR flash used by many microcontrollers, including the Raspberry Pi Pico.
* [ChaN FatFS] - FAT filesystem implementation for embedded devices.
* [Scoppy] - Use your Raspberry Pi Pico as an oscilloscope, very cheap and accurate. Without this I would have not been able to debug many issues.
* [PulseView] - Used to import, visualize and manipulate the data from Scoppy on a PC.

[Schermaiolo]: https://github.com/schermaiolo
[SantX27]: https://github.com/SantX27
[whitezombie2000]: https://github.com/whitezombie2000
[wired-filipino-owl]: https://github.com/wired-filipino-owl
[FreePSXBoot]: https://github.com/brad-lin/FreePSXBoot
[psx-spx]: https://psx-spx.consoledev.net/pinouts/#controller-ports-and-memory-card-ports
[Andrew J. McCubbin]: http://www.emudocs.org/PlayStation/psxcont/
Expand All @@ -202,4 +220,4 @@ For people interested in understanding how PicoMemcard works I provide a more ex
[this post]: https://dangiu.github.io/2022/05/13/picomemcard.html
[from here]: https://forms.gle/f6XHtz6W5fn5qDZV7
[This guy]: https://github.com/MrSVCD/PicoPSX_3D
[MemcardRex]: https://github.com/ShendoXT/memcardrex
[MemcardRex]: https://github.com/ShendoXT/memcardrex
21 changes: 19 additions & 2 deletions inc/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
#define IDLE_AUTOSYNC_TIMEOUT 5 * 1000 // time (in ms) the memory card must be inactive before automatic sync from RAM to LFS
#define MAX_MC_FILENAME_LEN 32 // max length of memory card file name (including extension)
#define MAX_MC_IMAGES 255 // maximum number of different mc images
#define MC_RECONNECT_TIME 1000 // time (in ms) the memory card stays disconnected when simulating reconnection
#define MC_RECONNECT_TIME 250 // time (in ms) the memory card stays disconnected when simulating reconnection

/* Board targeted by build */
#define PICO
//#define PICO
//#define RP2040ZERO
#define BITFUNX
#define GAMEID

/* Invert red and green. Uncomment this if the LED colours for your RP2040 Zero are incorrect. */
#define INVERT_RED_GREEN
Expand All @@ -33,6 +35,14 @@
#define PIN_ACK 13
#endif

#ifdef BITFUNX
//#define PIN_DAT 5
//#define PIN_CMD PIN_DAT + 1 // must be immediately after PIN_DAT
//#define PIN_SEL PIN_CMD + 1 // must be immediately after PIN_CMD
//#define PIN_CLK PIN_SEL + 1 // must be immediately after PIN_SEL
//#define PIN_ACK 9
#endif

/* SD Card Configuration */
#define BLOCK_SIZE 512 // SD card communicate using only 512 block size for consistency
#define BAUD_RATE 5000 * 1000
Expand All @@ -50,4 +60,11 @@
#define PIN_SS 1
#endif

#ifdef BITFUNX
#define PIN_MISO 16
#define PIN_MOSI 19
#define PIN_SCK 18
#define PIN_SS 17
#endif

#endif
10 changes: 7 additions & 3 deletions inc/memcard_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>


/* Error codes */
#define MM_OK 0
Expand All @@ -18,9 +21,10 @@ bool memcard_manager_exist(uint8_t* filename);
uint32_t memcard_manager_count();
uint32_t memcard_manager_get(uint32_t index, uint8_t* out_filename);
#define memcard_manager_get_initial(out_filename) memcard_manager_get(memcard_manager_get_prev_loaded_memcard_index(), (out_filename))
uint32_t update_prev_loaded_memcard_index(uint32_t index);
uint32_t memcard_manager_get_prev_loaded_memcard_index();
uint32_t memcard_manager_get_next(uint8_t* filename, uint8_t* out_nextfile);
uint32_t memcard_manager_get_prev(uint8_t* filename, uint8_t* out_prevfile);
uint32_t memcard_manager_get_next(uint8_t* filename, uint8_t* out_nextfile, bool skip);
uint32_t memcard_manager_get_prev(uint8_t* filename, uint8_t* out_prevfile, bool skip);
uint32_t memcard_manager_create(uint8_t* out_filename);

uint32_t create_index(uint8_t *vec, uint8_t size, uint8_t *out_filename);
#endif
105 changes: 91 additions & 14 deletions src/led.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
#define PICO_LED_PIN 25
#endif

#ifdef BITFUNX
#define PICO_RED_PIN 25
#define PICO_GRN_PIN 24
#define PICO_BLU_PIN 23
#endif

static uint smWs2813;
static uint offsetWs2813;
static int32_t picoW = -1;
Expand All @@ -34,19 +40,27 @@ void ws2812_put_rgb(uint8_t red, uint8_t green, uint8_t blue) {
#endif

void led_init() {
#ifdef PICO
if (is_pico_w()) {
if (cyw43_arch_init()) {
picoW = 0;
}
}
init_led(PICO_LED_PIN);
#endif
#ifdef RP2040ZERO
offsetWs2813 = pio_add_program(pio1, &ws2812_program);
smWs2813 = pio_claim_unused_sm(pio1, true);
ws2812_program_init(pio1, smWs2813, offsetWs2813, 16, 800000, true);
#ifdef PICO
if (is_pico_w()) {
if (cyw43_arch_init()) {
picoW = 0;
}
}
init_led(PICO_LED_PIN);
#endif
#ifdef RP2040ZERO
offsetWs2813 = pio_add_program(pio1, &ws2812_program);
smWs2813 = pio_claim_unused_sm(pio1, true);
ws2812_program_init(pio1, smWs2813, offsetWs2813, 16, 800000, true);
#endif
#ifdef BITFUNX
gpio_init(PICO_RED_PIN);
gpio_init(PICO_GRN_PIN);
gpio_init(PICO_BLU_PIN);
gpio_set_dir(PICO_RED_PIN, GPIO_OUT);
gpio_set_dir(PICO_GRN_PIN, GPIO_OUT);
gpio_set_dir(PICO_BLU_PIN, GPIO_OUT);
#endif
}

void led_output_sync_status(bool out_of_sync) {
Expand All @@ -61,6 +75,20 @@ void led_output_sync_status(bool out_of_sync) {
ws2812_put_rgb(0, 255, 0);
}
#endif
#ifdef BITFUNX
if(out_of_sync) {
//yellow
gpio_put(PICO_RED_PIN, 1);
gpio_put(PICO_GRN_PIN, 1);
gpio_put(PICO_BLU_PIN, 0);
}
else {
//green
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 1);
gpio_put(PICO_BLU_PIN, 0);
}
#endif
}

void led_blink_error(int amount) {
Expand All @@ -71,6 +99,11 @@ void led_blink_error(int amount) {
#ifdef RP2040ZERO
ws2812_put_rgb(0, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
sleep_ms(500);
/* start blinking */
for(int i = 0; i < amount; ++i) {
Expand All @@ -80,13 +113,23 @@ void led_blink_error(int amount) {
#ifdef RP2040ZERO
ws2812_put_rgb(255, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 1);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
sleep_ms(500);
#ifdef PICO
set_led(PICO_LED_PIN, false);
#endif
#ifdef RP2040ZERO
ws2812_put_rgb(0, 0, 0);
ws2812_put_rgb(0, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
sleep_ms(500);
}
}
Expand All @@ -105,6 +148,15 @@ void led_output_mc_change() {
sleep_ms(100);
ws2812_put_rgb(0, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 0); // blue
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 1);
sleep_ms(100);
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
}

void led_output_end_mc_list() {
Expand All @@ -123,6 +175,15 @@ void led_output_end_mc_list() {
sleep_ms(500);
ws2812_put_rgb(0, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 1); // purple
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 1);
sleep_ms(500);
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
}

void led_output_new_mc() {
Expand All @@ -141,11 +202,21 @@ void led_output_new_mc() {
sleep_ms(1000);
ws2812_put_rgb(0, 0, 0);
#endif
#ifdef BITFUNX
gpio_put(PICO_RED_PIN, 0); // teal
gpio_put(PICO_GRN_PIN, 1);
gpio_put(PICO_BLU_PIN, 1);
sleep_ms(1000);
gpio_put(PICO_RED_PIN, 0);
gpio_put(PICO_GRN_PIN, 0);
gpio_put(PICO_BLU_PIN, 0);
#endif
}

// Attempt to check if device is a Raspberry Pi Pico W
// Based on https://forums.raspberrypi.com/viewtopic.php?t=336884
int32_t is_pico_w() {
#ifdef PICO
if (picoW == -1) {
// When the VSYS_ADC is read on a Pico it will always be > 1V
// On a Pico-W it will be < 1V when GPIO25 is low
Expand Down Expand Up @@ -173,16 +244,22 @@ int32_t is_pico_w() {
}
}
return picoW;
#endif

#ifdef BITFUNX
return 0;
#endif
}

#ifdef PICO
void init_led(uint32_t pin) {
if (!is_pico_w() || (pin != 25)) {
gpio_init(pin);
gpio_set_dir(pin, GPIO_OUT);
}
}

#ifdef PICO

void set_led(uint32_t pin, uint32_t level) {
if ((pin == 25) && is_pico_w()) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, level);
Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ int main(void) {
if(to_ms_since_boot(get_absolute_time()) > TUD_MOUNT_TIMEOUT && !tud_mount_status)
break;
}

/* Pico powered by PSX, initialize memory card simulation */
simulate_memory_card();

Expand Down
Loading