summaryrefslogtreecommitdiff
path: root/_posts/en/2025-07-26-picoshock_devlog_3.md
diff options
context:
space:
mode:
Diffstat (limited to '_posts/en/2025-07-26-picoshock_devlog_3.md')
-rw-r--r--_posts/en/2025-07-26-picoshock_devlog_3.md164
1 files changed, 164 insertions, 0 deletions
diff --git a/_posts/en/2025-07-26-picoshock_devlog_3.md b/_posts/en/2025-07-26-picoshock_devlog_3.md
new file mode 100644
index 0000000..205bca4
--- /dev/null
+++ b/_posts/en/2025-07-26-picoshock_devlog_3.md
@@ -0,0 +1,164 @@
+---
+layout: post
+title: "PicoShock Devlog #3"
+lang: en
+date: 2025-07-26
+tag: ["Programming", "PicoShock"]
+---
+
+Well, it's been a while. Since [last time](https://silosneeded.com/2025/04/picoshock_devlog_2) I had success reading
+the data the Playstation 2 was sending me. After that, I took the logical next step: sending data back. That was my first
+block.
+
+After a couple of days of banging my head against the wall I took a rest that prolonged itself more than it should have.
+Just today I got back at it. And I think I've made a respectable advancement, but with a twist. Because nothing can
+be as simple as I'd like things to be.
+
+# First off: debugging
+
+So the problem was knowing what in God's name I was actually putting on the line. That's when I got into the rabbit hole of logic analyzers.
+I came across a couple of pretty interesting projects: [sigrok](https://sigrok.org), its GUI, [pulseview](http://sigrok.org/wiki/PulseView) and
+a driver for using a raspberry pi pico as the analyzer: [sigrok-pico](http://sigrok.org/wiki/PulseView).
+
+Installing these tools is outside of the scope of this article, but the only thing worth mentioning here is that compiling sigrok and pulseview
+oneself is necessary if you pretend to use a raspberry pi pico as your logic analyzer. I remember struggling with that but in the end it was
+possible.
+
+![Pulseview screenshot](/assets/images/2025/pulseview.jpg)
+
+# Taking a peek at the output line
+
+With sigrok I could actually see the output I was sending. And it was nonsense, for the most part. But something functional reared its head from time
+to time. Let's remember the table of bytes I was receiving, and the ones I'd like to send in response:
+
+|Byte Number|Received|Sent|
+|-----------|--------|----|
+|1 |0x01 |0xff|
+|2 |0x42 |0x41|
+|3 |0x00 |0x5a|
+|4 |0x00 |0xff|
+|5 |0x00 |0x40|
+
+Bytes 4 and 5 are entirely mine, so to speak: the playstation 2 drives its line low and expects two bytes from my end. In these bytes is where the magic happens:
+mode setting, button presses... In this example I want to send a button press, the cross button if I remember correctly. This button corresponds to the seventh
+bit of the second byte: in binary, 0100 0000. In hex, 0x40. If there's nothing pressed then I must send all 1s (0xff).
+
+What was strange about the data I was sending is, well, first, that it was wrong, but not every time. Sometimes, a correct byte was sent, but as the third or
+fourth one for example, instead of its intended order. This also meant that the preceding and following bytes were gibberish, well, not TOTAL gibberish, they
+sometimes differed from the actual byte I wanted to send for just a bit. For example, I wanted to send 0x5a (0101 1010 in binary) but the byte that was being sent
+was 0xda (1101 1010). Pretty mystical isn't it?
+
+# First steps towards actually fixing this: the C portion
+
+After some time suffering with this and posting a help thread on the forums I decided to revisit my bible on this project, yes, the
+[curious inventor's guide](https://store.curiousinventor.com/guides/PS2).
+
+The paragraph that caught my eye was this one:
+
+> The play station sends a byte at the same time as it receives one (full duplex) via serial communication.
+
+I already knew this, but I'm such a newbie on this that I actually pondered on this. This means that the TX FIFO must have the byte I want to send BEFORE
+I consume the RX FIFO byte, i.e. I must send my 0xff byte before I consume the 0x01 the console sends me. The same for every byte. I do have to pattern match
+for the second byte, as that is the command (poll, configure...) but MY second byte is independent from the one from the console. The next bytes will be the ones
+that will act as the response to the console's command.
+
+So I decided to change my main loop, from pattern matching the bytes I was receiving to jamming the output FIFO with my data. When I start writing the library
+proper I will have to add logic checking for the data I'm receiving, but for now I want to emulate the base case exposed in the guide.
+
+```c
+while (true) {
+ // The TX FIFO will have data ready to simultaneously send while
+ // the RX FIFO is being emptied.
+ pio_sm_put_blocking(data_pio, data_sm, (0xff ^ 0xff));
+ data = pio_sm_get_blocking(pio, sm) >> 24;
+ pio_sm_put_blocking(data_pio, data_sm, (0xff ^ 0x41));
+ data = pio_sm_get_blocking(pio, sm) >> 24;
+
+ // Receiving all zeros. Send button presses.
+ pio_sm_put_blocking(data_pio, data_sm, (0xff ^ 0x5a));
+ uint32_t buf = pio_sm_get_blocking(pio, sm) >> 24;
+
+ if (buf != 0x00)
+ continue;
+
+ pio_sm_put_blocking(data_pio, data_sm, (0xff ^ 0xff));
+ buf = pio_sm_get_blocking(pio, sm) >> 24;
+
+ if (buf != 0x00)
+ continue;
+
+ pio_sm_put_blocking(data_pio, data_sm, (0xff ^ 0x40));
+
+ printf("%x\n", data);
+}
+```
+
+# A small change in the PIO program
+
+In my previous version of my PIO program, after sending my byte, the program looped to the `set pindirs 0` instruction:
+
+```asm
+.program dualshock_data
+
+ .wrap_target
+ set pindirs 0
+wait_for_output:
+ jmp !osre wait_for_att ;; Stall while we don't have data to output
+ jmp wait_for_output
+
+wait_for_att:
+ set x, 7 ;; Set bit counter: we want to send a byte.
+ wait 0 pin 0 ;; Wait for ATT.
+
+out_loop:
+ wait 0 pin 1 ;; When clock goes from low to high,
+ wait 1 pin 1 ;; send our data.
+ out pindirs, 1 ;; Send a bit to the PS2.
+ jmp x-- out_loop ;; Decrement bit counter.
+.wrap
+```
+
+I told myself, well, isn't it more appropriate to jump to the label that waits for more data to be present in the TX FIFO than to let the program wrap?
+I wasn't thinking about the importance of the `set pindirs 0` (for those not in the loop, this instruction drives the data pin high, as it is an open collector
+output... now that I'm reading my program's config... does having the data pin as a SET pin makes sense? Will check in the future) so I decided to jump to
+`wait_for_output` instead after every byte:
+
+```asm
+out_loop:
+ wait 0 pin 1 ;; When clock goes from low to high,
+ wait 1 pin 1 ;; send our data.
+ out pindirs, 1 ;; Send a bit to the PS2.
+ jmp x-- out_loop ;; Decrement bit counter.
+ jmp wait_for_output ;; We sent the byte; let's check if there's another one to send.
+```
+
+What came after was a pretty big surprise
+
+# Now (it seems) I'm sending what I actually want to send
+
+After these changes, it seems I've made some progress. I say that it seems because I have no way of actually verifying it as for now I'm not capable of seeing what's
+going on the Playstation 2, I gave the monitor I was using to test this to a friend and started using another one I had on a corner. But it doesn't like
+to receive a 1080p resolution image. The other one wasn't compatible either but at least showed something, so I'm pretty much blind: I can only peek at my logic
+analyzer.
+
+And this is what the logic analyzer shows me:
+
+![Success! Maybe](/assets/images/2025/successful_output.jpg)
+
+If we see the data line, and count the bits (on the rising edge of the clock, that's the moment when the Playstation 2 reads our bit), those are the actual bytes
+I want to send. But... there's a caveat. The data line is an open collector output, this means that it's normally held high as it is the console that actually puts
+the voltage on the line, not us. I'm not entirely sure of the consequences of the line being by default low, as 0 is the last bit of our last byte. And I can't
+see the playstation's screen so I'm not sure if it's actually listening to me and registering the button press I want it to register.
+
+So, for the next time around...
+
+# What comes next
+
+The reason for the data pin to be a set pin (and also an output pin) is that that was the recommendation I was given on the forums. When it is a set pin, I can
+drive it high or low at convenience.
+
+So, what I must do is look for a compatible monitor or small CRT TV to do my testing and know if, once and for all, I'm doing something correctly for a change,
+and the console is registering my button press. If it's not doing that, maybe the fact that the line is pulled low and stays low after every packet has
+something to do with it.
+
+Well, see you next time!