--- 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!