Close

PlayStation & PlayStation 2 SPI interface

A project log for BlueRetro

Multiplayer Bluetooth controllers adapter for retro video game consoles

jacques-gagnonJacques Gagnon 11/27/2020 at 10:130 Comments

I took as base the information provided by Micah's notes on the DualShock 2 controller. My first step before writing the PS driver in BlueRetro was to write a packet sniffer to be able to make easily very long trace compared to what you can buffer in a logic analyzer. This usually helps a lot understanding how each packet relate to each other. See multiple traces here.

Basis

The original PlayStation had a single SPI port share across the 2 controller ports and the 2 memory card slots. Only two Chip Select lines (DTR) were available and so the first controller & first memory card share the first DTR line and the 2nd controller and the 2nd memory card share the second in one. Much like an I2C bus the two devices sharing the same DTR line use an address within the data stream (the first byte) to determine which device is accessed. Controller port devices response to request starting with 0x01 only while memory cards respond to request starting with 0x81.
Typical PS controller poll

An addition compared to more common SPI interface is the use of the DSR line as an ACK. Every bytes sent by the console needs to be acknowledged by the device for the transmission of the next byte to occur. This line is shared by the four devices just like the SCK, TXD & RXD as well. If a command is not supported by a device, the command byte will not be ACKed.

The two controller outputs (RXD & DSR) are open drain and require a pull-up resistor.

Each transmission is full duplex. Typically clock rate is 250 KHz for controller and memory card but 500 KHz and 1 MHz is used by the multitap. Bits are transmit LSB first.  CPOL = 1, CPHA = 1.

    ┌Address
    │ ┌Command
    │ │     ┌Ctrl input (ex: set rumble)
    ├┐├┐  ┌─┴────────┐
TX: 014200000000000000
RX: FF735AFFFF80808080
      ││  └─┬────────┘
      ││    └Ctrl output (ex: get buttons)
      │└Payload length in DWORD.
      └Peripheral ID

 Packets always start with a 3 bytes header. First TX bytes is the address to select either the controller (0x01) or memory card (0x81). 2nd TX byte is the command sent by the console. The 2nd  RX byte upper nibble is the ID of the peripheral. The lower nibble is the output size in DWORD (uint16_t). The 3rd RX byte is always 0x5A and signals the end of the header. The header is followed by the payload which always match the size reported in the 2nd RX byte even when not all the data is used. Unused TX bytes are set to 0x00 by PSX and 0x5A by PS2.

PS2

250 KHz DualShock 2 vs 25 MHz Memory card bus
The main difference with PSX is that the PS2 has 4 dedicated SPI interfaces for both controllers & memory card. The memory card slot now run at 25MHz while the controller port use same speeds as before.
PS2 Multitap control packet on memory card bus

Input devices

Input devices will only answer/ack to frame starting with 0x01.

0x42 Polling cmd

This commands is used by all devices for getting the buttons & joystick status. It is also used to set rumble for Analog & DualShock controllers.

0x43 Config mode cmd

Supported by DualShock 1/2 only

Enter or exit configuration mode. This is also used to detect DualShock 1/2 vs legacy controllers (Digital, Flightstick, Dual Analog) as the latter will not ACK the command byte.

TX: 0142000000
RX: FF735AFFFF

          ┌Enter config mode.
          ├┐
TX: 014300010000000000
RX: FF735AFFFF957D7388
      ├┘  └┬─────────┘
      ID   └Ctrl provide poll status via 0x43 if config mode not active.

TX: 0145005A5A5A5A5A5A
RX: FFF35A030201020100
      ├┘
      └ID is 0xF3 until config mode is exit.

          ┌Clear config mode.
          ├┐
TX: 014300005A5A5A5A5A
RX: FFF35A000000000000
      ├┘  └┬─────────┘
      ID   └Ctrl do not provide poll data while in config mode via 0x43.

TX: 0142000000
RX: FF735AFFFF
      ├┘
      └ID revert on config mode exit.

0x44 Enable analog cmd

Supported by DualShock 1/2 only

Require to be in config mode

This allows software to enable/disable the analog mode without requiring users to use analog button.

          ┌Enable analog mode. (0x00 Disable)
          ├┐
TX: 014400010300000000
RX: FFF35A000000000000 

0x45 Identity / Status cmd

Supported by DualShock 1/2 only

Require to be in config mode

This command is used to tell between a DS1 and DS2 controller. It also shows the state of the analog mode.

(DualShock 1)
TX: 014500000000000000
RX: FFF35A010200020100
          ├┘  ├┘
          │   └Analog mode state.
          └0x01: ID DualShock 1.

(DualShock 2)
TX: 0145005A5A5A5A5A5A
RX: FFF35A030201020100 
          ├┘  ├┘
          │   └Analog mode state.
          └0x03: ID DualShock 2.

0x46, 0x47, 0x4C Get constant cmds

Supported by DualShock 1/2 only

Require to be in config mode

These read some constant  on the controller. Response is same for DS1 & DS2.

          ┌Offset 0x00
          ├┐
TX: 014600005A5A5A5A5A
RX: FFF35A00000102000A

          ┌Offset 0x01
          ├┐
TX: 014600015A5A5A5A5A
RX: FFF35A000001010114

          ┌Offset 0x00 (No offset 0x01 for 0x47)
          ├┐
TX: 014700005A5A5A5A5A
RX: FFF35A000002000100

          ┌Offset 0x00
          ├┐
TX: 014C00005A5A5A5A5A
RX: FFF35A000000040000

          ┌Offset 0x01
          ├┐
TX: 014C00015A5A5A5A5A
RX: FFF35A000000070000 

0x4D Enable Rumble cmd

Supported by DualShock 1/2 only

Require to be in config mode

This configure which byte offset in the 0x42 poll cmd are used for each rumble motor.

0x00 configure the right small motor, 0x01 configure the left big motor, 0xFF disable this offset.

Typically the 1st byte is the right small motor and the 2nd byte: is the left big motor.

                ┌New rumble mapping
          ┌─────┴────┐
TX: 014D000001FFFFFFFF
RX: FFF35AFFFFFFFFFFFF
          └─────┬────┘
                └Previous rumble mapping

0x4F Polling config cmd

Supported by DualShock 2 only

Require to be in config mode

This enables digital buttons, axes and pressure buttons base on a mask.

            ┌Polling enable mask
          ┌─┴──┐
TX: 014F00FFFF03000000
RX: FFF35A00000000005A 

0x40 Pressure config cmd

Supported by DualShock 2 only

Require to be in config mode

This configure in someway the pressure buttons one at a time.

          ┌Button ID
          ├┐
TX: 014000000200000000
RX: FFF35A00000200005A

          ┌Button ID
          ├┐
TX: 014000010200000000
RX: FFF35A00000200005A

          ┌Button ID
          ├┐
TX: 014000020200000000
RX: FFF35A00000200005A

0x41 Polling config status cmd

Supported by DualShock 2 only

Require to be in config mode

This return a mask base on the polling config.

TX: 0141005A5A5A5A5A5A
RX: FFF35AFFFF0300005A 
          └─┬──┘
            └Polling enable mask

Multitap

PSX

Typical multitap exchange showing 250KHz bytes mixed with 1MHz ones

The PSX multitap aggregate data from 4 input devices and make it available to the system via a single packet. It alsos allow using 4 memory card but I haven't looked at how that part work yet. In any case input devices and memory card part are handled separately. Base on my limited testing with a few games, they look to be happy to see the multitap presence for controllers while only a regular memory card is on the bus.

Close-up of the packet beginning

The multitap is enabled by setting the 3rd TX to 0x01. If this byte is 0x00 then the controller on port A is passthrough to the console as if no multitap was present enabling software without support to be used without removing the multitap. When multitap mode is enabled, a one frame delay is introduced between the console request and the controller response.

 
         ┌Enable Multitap
         │ ┌Port A CMD     ┌Port B CMD     ┌Port C CMD     ┌Port D CMD
         ├┐├┐              ├┐              ├┐              ├┐
TX0: 0142014200000000000000420000000000000042000000000000004200000000000000
RX0: FF805A415AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

         ┌Disable Multitap
         ├┐
TX1: 01430001
RX1: FF805A41

         ┌Enable Multitap
         ├┐
TX2: 0142010000
RX2: FF415AFFFF

           ┌Enable config mode on Port A
           ├┐
TX3: 0142014300010000000000420000000000000042000000000000004200000000000000
RX3: FF805A415AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

TX4: 0142014200000000000000420000000000000042000000000000004200000000000000
RX4: FF805A415AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
               └─┬────────┘
                 └Response from TX3 0x43 from Port A

           ┌Get identity from Port A
           ├┐
TX5: 0142014500000000000000420000000000000042000000000000004200000000000000
RX5: FF805AF35AFFFF85756F89FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
           ├┘  └─┬────────┘
           │     └Response from TX4 0x42 from Port A
           └ID updated to 0xF3

TX6: 0142014200000000000000420000000000000042000000000000004200000000000000
RX6: FF805AF35A010200020100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
               └─┬────────┘
                 └Response from TX5 0x45 from Port A
Packet end @ 1MHz

PS2

I didn't look much into the PS2 multitap but basically it work by making one of the 4 controller active on the controller port by controlling the multitap via the memory card port.

I think the 3rd TX byte here select which controller to make active on the controller port.

Memory Card

TBD

Discussions