summaryrefslogtreecommitdiff
path: root/_posts/en/2025-04-04-picoshock_devlog_1.md
blob: 967d75d7f378caa6aee057c372db9dc79e96caee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
---
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 "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:

![Minicom screenshot](/assets/images/2025/minicom.jpg)

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.