diff options
author | HombreLaser <buran@silosneeded.com> | 2025-04-04 22:48:40 -0600 |
---|---|---|
committer | HombreLaser <buran@silosneeded.com> | 2025-04-04 22:48:40 -0600 |
commit | b187ed942ffc6cf19189ce7833fc7da5ed4d39ff (patch) | |
tree | 91ba88b0ceb1fc8328040571f18817c3f1150007 | |
parent | 7a91ce0d8f4d7b7f003a27cf0e10e9151079f840 (diff) |
Add picoshock devlog #1
-rw-r--r-- | _posts/en/2025-04-04-picoshock_devlog_1.md | 193 | ||||
-rw-r--r-- | assets/images/2025/minicom.jpg | bin | 0 -> 31352 bytes |
2 files changed, 193 insertions, 0 deletions
diff --git a/_posts/en/2025-04-04-picoshock_devlog_1.md b/_posts/en/2025-04-04-picoshock_devlog_1.md new file mode 100644 index 0000000..e9e336a --- /dev/null +++ b/_posts/en/2025-04-04-picoshock_devlog_1.md @@ -0,0 +1,193 @@ +--- +layout: post +title: "PicoShock Devlog #1" +lang: en +date: 2025-04-04 +tags: ["Programming", "PicoShock"] +--- + +Let's just say that I like [fightsticks](https://silosneeded.com/en/2023/12/hori_fighting_stick_restoration). I also like +the PlayStation 2, if the amount of articles tangentially related to it aren't revealing enough. Besides fightsticks and the +Playstation 2, I also like Playstation 2 fighting games, specially SoulCalibur 2. + +So, creating a custom fightstick, powered by the extremely powerful RP2040 microcontroller in the spirit of other projects like +[GP-2040CE](https://github.com/OpenStickCommunity/GP2040-CE?ref=silosneeded.com), the [PhobGCC](https://github.com/PhobGCC?ref=silosneeded.com) and its distant +cousin, the [pico rectangle](https://github.com/JulienBernard3383279/pico-rectangle?ref=silosneeded.com) seemed so enticing to me. + +This idea came about more or less 2 years ago, in the same time frame I began building my own fight sticks. I searched for +similar projects or, at least, a library that could allow me to interface with the Playstation 2 as a controller. Basically, +I wanted to know if someone more intelligent than me had already taken the mantle of building the ultimate Playstation 2 controller. + +Let's just say, I couldn't find such projects. But there are some similar ones, like +[pico memcard](https://github.com/dangiu/PicoMemcard?ref=silosneeded.com), +which is a PSX memory card powered by an RP2040 microcontroller (both memory cards and controllers in the first Playstation consoles +spoke the same protocol, and the Playstation 2 protocol is, let's say, a superset of the Playstation 1's) and the +[ps1 mouse](https://github.com/Franticware/usb-to-ps1-mouse-pro?ref=silosneeded.com), a raspberry pi pico powered USB to PSX adapter that allows one to use a USB mouse as a Playstation mouse, which, yes, +[it did exist](https://en.wikipedia.org/wiki/PlayStation_Mouse?ref=silosneeded.com). + +My main objective, and the project I'll be making (and hope to finish) is a dualshock protocol implementation library: reasonably +high level and in C, allowing for projects both in C and C++. MicroPython compatibility could be in the roadmap, too. + +It will be under the MIT license because I ultimately want to make the GP2040-CE firmware Playstation 2 (and maybe 1) compatible, +and GP2040-CE is licensed under these terms. + +This license also, sadly, doesn't let me derive my work off of pico memcard, as it is licensed under the GPL, so I've been studying +instead the ps1 mouse's PIO (more on that later) source code. But... it's esoteric. + +Let this series of posts bare witness to my success (or failure) on making this project. If I don't achieve this, well, let them +be a mark of shame forever sitting on my blog. But hell, I really would like to have a universal fightstick which I could use +on my favorite console, with zero latency. Because the RP2040 is just that powerful (refer to the GP2040-CE's latency tests) + +# What I've done + +Yesterday I formally began my journey. I've been researching the Pico's Programmable Input/Output (PIO for short) feature +for a while now, and made an (unfinished) mock project to familiarize myself with the debugging and build process of pico projects. + +I started with a simple program, that reads the command pin (the Playstation -> controller communication bus) and outputs +to the screen what it has read: + +```c +#include <stdint.h> +#include <stdio.h> +#include "pico/stdlib.h" + +#include <stdio.h> +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "dualshock.pio.h" + +#define COMMAND_PIN 27 + +int main() { + stdio_init_all(); + PIO pio = pio0; + uint offset = pio_add_program(pio, &dualshock_program); + uint sm = pio_claim_unused_sm(pio, true); + + dualshock_program_init(pio, sm, offset, COMMAND_PIN); + + while (true) { + uint32_t data = pio_sm_get(pio, sm); + printf("%x\n", data); + sleep_ms(1000); + } +} +``` + +<br> +As you can see, I reference some mystical functions like `pio_add_program` and `pio_claim_unused_sm`. These are SDK functions that +work in conjunction with the PIO feature I talked about. + +## PIO Primer + +In the world of microcontrollers there are usually two ways of talking to a device: bitbang whatever data it transmits to us or +use specific hardware usually programmed by a third party vendor that allows us to communicate in a specific protocol, +like I2C or SPI. + +The first one has been the most used in the world of hobbyist electronics using arduino. Bitbanging is, basically, using the CPU to +try and time reads and writes to the device. If the device we want to communicate with sends us bytes on a, let's say, 1 MHz clock +cycle, then we do our best to make our CPU receive that byte in the time the device expects us to, same for writes. + +This is inefficient and hard, but, in the world of the PS2, has been done: Sukko Pera's +[PSX Newlib](https://github.com/SukkoPera/PsxNewLib?ref=silosneeded.com) and Bill Porter's +[arduino library](https://github.com/madsci1016/Arduino-PS2X?ref=silosneeded.com) come to mind. + +If we read the source code of this projects, we can see the use of `sleep` directives and the like, to try and time every read +and write. + +PIO allows us to not do that. Instead, it provides us a hardware specialized for input and output, consisting of state machines, +two FIFOs for reading and writing data and several shift registers to, well, shift data around. We program this +hardware under a pretty basic, but powerful, assembly language. + +For what I've learned about PIO (remember, I'm not an expert) it handles timing by running on a fraction of the frequency of +the RP2040 main CPU clock, which, if I remember correctly, runs at 125 MHz. This fraction can be 1, by the way, meaning it +can run at the full speed of the CPU. But in our case, and, quoting the [source](https://store.curiousinventor.com/guides/PS2?ref=silosneeded.com) +I'll probably base all of my work on, the dualshock 2 runs at a frequency of 500 KHz. + +# My (pretty basic) PIO program + +So now that we know PIO, what have I written in it? Well, let's remember that I want to read the data the playstation has sent +me and output it on the screen. In PIO terms, I want to read a byte off of my input pin, shift this byte to the ISR, and then push +whatever is in the ISR to my TX (read) FIFO, for it to be consumed by the pico's main CPU. + +```asm +.program dualshock + +.wrap_target + in pins, 8 ; Read 8 bits from the input pin, and + ; shift them to the Input Shift Register + push ; Push the data present in the Input Shift Register + ; to the RX FIFO. +.wrap + +% c-sdk { + void dualshock_program_init(PIO pio, uint sm, uint offset, uint command_pin) { + pio_sm_config cfg = dualshock_program_get_default_config(offset); + sm_config_set_in_pins(&cfg, command_pin); + sm_config_set_in_shift(&cfg, false, false, 8); + sm_config_set_clkdiv_int_frac(&cfg, 250, 0x00); + } +%} +``` + +Here it is, my masterpiece. Pretty basic, right? But I think it was a pretty good exercise in getting to know the PIO feature +and actually using it (later we'll see the Cmake file, to show you how you can build your project taking a PIO program in mind). + +Everything below the c-sdk is code that will be injected to the header file the assembler will generate with this file. +In it, we configure our PIO state machine: the pin we'll use as the command pin (I.E., the one we'll be reading), +the direction we'll shift bits on (in this case, the playstation 2 speaks a Least significant bit protocol, in layman terms, +we'll read what it has to say from left to right) and its clock frequency: 125/250 = 0.5 MHz, or, 500 KHz. I think. I broke +my head trying to come up with this, let's just say the documentation on clock dividers was hard to parse, for me, at least. + +## Building this abomination + +The cmake file is as follows: + +```cmake +cmake_minimum_required(VERSION 4.0) + +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +project(pico_shock C CXX ASM) + +pico_sdk_init() + +add_executable(main main.c) + +pico_generate_pio_header(main ${CMAKE_SOURCE_DIR}/dualshock.pio) + +target_link_libraries(main pico_stdlib hardware_pio) + +pico_enable_stdio_uart(main 1) + +pico_add_extra_outputs(main) +``` + +<br> +We only care about the `pico_generate_pio_header(main ${CMAKE_SOURCE_DIR}/dualshock.pio)` line. This tells make to +use the pio assembler to, well, assemble our pio program and generate the `dualshock.pio.h` file we include in our C program. + +# Results + +Well, the PS2 is speaking to us in chinese: + + + +None of this means anything per the Curious Inventor guide, and, it's expected. I was hoping to at least get a 0x00 byte, which +it seems to be the first byte the console sends in every packet. But no. + +It's expected because the PS2 speaks in a (albeit heavily modified) duplex SPI protocol: we receive and read data at the same +time, regulated by a clock pulse, and also using a chip select (attention, in DualShock terms) and acknowledge signals. + +# What comes after + +For now, I want to receive actual, useful data. I hope I can achieve this by only implementing the clock signal, but of course, +I'll have to implement the other ones (data, attention and acknowledge) sooner or later. + +The pico-examples repository provides a really relevant example: an +[SPI implementation in PIO](https://github.com/raspberrypi/pico-examples/tree/master/pio/spi?ref=silosneeded.com). +I've been basing most of my current work on it, and the C code seems specially interesting: particularly the way it reads +(and writes) data passed by the PIO state machine. + +It seems that using `pio_sm_get` and friends is the sucker's way of doing it. Well, I only hope the debugger plays nice, because +I'm gonna need it to poke at what I'll be actually receiving from the PIO state machine. diff --git a/assets/images/2025/minicom.jpg b/assets/images/2025/minicom.jpg Binary files differnew file mode 100644 index 0000000..207c9d5 --- /dev/null +++ b/assets/images/2025/minicom.jpg |