PS3 controller on the Switch (or Xbox, etc.?)

Last year, I posted a couple of articles on using a USB-HID controller on an MSX and on a Nintendo Switch. I also posted an article on a PS3 controller repair.

A happy Pico enjoying a round of Mario Kart. (This particular Pico is doing something else, but it’s content watching its brother play.)

Well, it’s time to put 2 and 2 together and get that silly PS3 controller to work on the Switch! (Or on other devices supported by GP2040-CE, which as I understand includes the Xbox and PS4/PS5?) To do this, we just have to realize that the PS3 controller works on Linux. So we just need to figure out how Linux does it. And a quick Google query (though I can’t remember the words I chose) surfaced up an old patch from 2007 introducing PS3 support to the Linux kernel:

Add the USB HID quirk HID_QUIRK_SONY_PS3_CONTROLLER. This sends an
HID_REQ_GET_REPORT to the the PS3 controller to put the device into
‘operational mode’.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4a1a4d8b87389e35c3af04c0d0a95f6a0391b964

Looking into the patch, we see that there isn’t much to it, it’s just, as the description says, sending a single request to the controller:

+	result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+				 HID_REQ_GET_REPORT,
+				 USB_DIR_IN | USB_TYPE_CLASS |
+				 USB_RECIP_INTERFACE,
+				 (3 << 8) | 0xf2, ifnum, buf, 17,
+				 USB_CTRL_GET_TIMEOUT);

So, how could we do this in 1) our code that we used for the MSX, and 2) in GP2040-CE?

Modifying Pico-PIO-USB to work with the PS3 controller

Pico-PIO-USB isn’t very over-engineered yet(?!), and we can just modify the enumerate_device function in pio_usb_host.c to check if we’re seeing a PS3 controller, and send the request. Near the bottom of this function, we send the “get_hid_report_descrpitor_request” to the USB device. Immediately after we add our own code, as below:

diff --git a/src/pio_usb_host.c b/src/pio_usb_host.c
index 864d048..0efc567 100644
--- a/src/pio_usb_host.c
+++ b/src/pio_usb_host.c
@@ -1018,6 +1018,21 @@ static int enumerate_device(usb_device_t *device, uint8_t address) {
         printf("\n");
         stdio_flush();
 
+        // We need to send a special HID request otherwise the contoller won't do anything
+        if ((device->vid == 0x054c) && (device->pid == 0x0268)) {
+          usb_setup_packet_t get_hid_report_ps3_controller_request =
+              GET_HID_REPORT_PS3_CONTROLLER;
+          control_in_protocol(
+              device, (uint8_t *)&get_hid_report_ps3_controller_request,
+              sizeof(get_hid_report_ps3_controller_request), rx_buffer, LINUX__SIXAXIS_REPORT_0xF2_SIZE);
+          printf("\t\tPS3 controller response:");
+          for (int i = 0; i < LINUX__SIXAXIS_REPORT_0xF2_SIZE; i++) {
+            printf("%02x ", device->control_pipe.rx_buffer[i]);
+          }
+          printf("\n");
+          stdio_flush();
+        }
+
       } break;
       default:
         break;

This requires the following definitions to be added in usb_definitions.h. I just used the same names used in Linux (not the ancient 2007 version, but something recent):

diff --git a/src/usb_definitions.h b/src/usb_definitions.h
index c345bdd..845fbbc 100644
--- a/src/usb_definitions.h
+++ b/src/usb_definitions.h
@@ -302,10 +302,16 @@ enum {
   USB_REQ_REC_OTHER = 0x03,
 };
 
+// some constants taken from Linux source code (.../ch9.h, .../hid.h)
+#define LINUX__USB_REQ_GET_DESCRIPTOR 0x06
+#define LINUX__HID_REQ_GET_REPORT 0x01
+#define LINUX__HID_FEATURE_REPORT 0x02
+#define LINUX__SIXAXIS_REPORT_0xF2_SIZE 17
+
 #define GET_DEVICE_DESCRIPTOR_REQ_DEFAULT                                      \
-  { USB_REQ_DIR_IN, 0x06, 0, 0x01, 0, 0, 0x12, 0 }
+  { USB_REQ_DIR_IN, LINUX__USB_REQ_GET_DESCRIPTOR, 0, 0x01, 0, 0, 0x12, 0 }
 #define GET_CONFIGURATION_DESCRIPTOR_REQ_DEFAULT                               \
-  { USB_REQ_DIR_IN, 0x06, 0, 0x02, 0, 0, 0x09, 0 }
+  { USB_REQ_DIR_IN, LINUX__USB_REQ_GET_DESCRIPTOR, 0, 0x02, 0, 0, 0x09, 0 }
 #define SET_CONFIGURATION_REQ_DEFAULT                                          \
   { USB_REQ_DIR_OUT, 0x09, 0, 0, 0, 0, 0, 0 }
 #define SET_ADDRESS_REQ_DEFAULT                                                \
@@ -313,10 +319,13 @@ enum {
 #define SET_HID_IDLE_REQ_DEFAULT                                               \
   { USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE, 0x0A, 0, 0, 0, 0, 0, 0 }
 #define GET_HID_REPORT_DESCRIPTOR_DEFAULT                                      \
-  { USB_REQ_DIR_IN | USB_REQ_REC_IFACE, 0x06, 0, 0x22, 0, 0, 0xff, 0 }
+  { USB_REQ_DIR_IN | USB_REQ_REC_IFACE, LINUX__USB_REQ_GET_DESCRIPTOR, 0, 0x22, 0, 0, 0xff, 0 }
+#define GET_HID_REPORT_PS3_CONTROLLER                               \
+  { USB_REQ_DIR_IN | USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE, LINUX__HID_REQ_GET_REPORT, 0xf2, LINUX__HID_FEATURE_REPORT+1, 0, 0, LINUX__SIXAXIS_REPORT_0xF2_SIZE, 0 }
+
 #define GET_HUB_DESCRPTOR_REQUEST                                              \
   {                                                                            \
-    USB_REQ_DIR_IN | USB_REQ_TYP_CLASS | USB_REQ_REC_DEVICE, 0x06, 0, 0x29, 0, \
+    USB_REQ_DIR_IN | USB_REQ_TYP_CLASS | USB_REQ_REC_DEVICE, LINUX__USB_REQ_GET_DESCRIPTOR, 0, 0x29, 0, \
         0, 8, 0                                                                \
   }
 #define GET_HUB_PORT_STATUS_REQUEST

And that’s all!

So, will the same modifications work in GP2040-CE too? No, while GP2040-CE uses Pico-PIO-USB to get USB host functionality to work at all while the Pico is busy being a USB device, protocol-y stuff is handled by TinyUSB, which by the way is included in the Pico SDK. Looking at pspassthrough.cpp for inspiration, I found that if we just add the following code to our previously modified version of src/addons/keyboard_host.cpp, our PS3 controller wakes up from its daze and starts sending our inputs!

diff --git a/src/addons/keyboard_host.cpp b/src/addons/keyboard_host.cpp
index 59bc85e..88d82da 100644
--- a/src/addons/keyboard_host.cpp
+++ b/src/addons/keyboard_host.cpp
@@ -2,6 +2,8 @@
 #include "storagemanager.h"
 #include "usbhostmanager.h"
 
+uint8_t report_buffer[64];
+
 bool KeyboardHostAddon::available() {
     const KeyboardHostOptions& keyboardHostOptions = Storage::getInstance().getAddonOptions().keyboardHostOptions;
     return keyboardHostOptions.enabled &&
@@ -77,6 +79,10 @@ void KeyboardHostAddon::preprocess() {
 }
 
 void KeyboardHostAddon::mount(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) {
+  uint8_t* buf = report_buffer;
+  tuh_hid_get_report(dev_addr, instance, 0xf2, HID_REPORT_TYPE_FEATURE, buf, 17);
+
   _keyboard_host_enabled = true;
 }
 
Works for me!

Commodore SR-37 calculator “repair” and review

Before Commodore made computers, they made typewriters, and later calculators. I scored one such calculator, one that was listed as “non-functional”. The repair itself was very quick, as I had expected.

Repair

Honestly, it was just the power adapter. And some gunk in the keys. I’ll spare you the details on the gunk for today. Let’s see what’s wrong with the plug. Here’s the picture from the listing:

昭和の頃 赤色表示電卓 コモドール Commodore SR-37 難有品(^00WH10A_画像1
The pic from the listing.

Why would you bother to take a picture with the AC adapter cord connected to the calculator but the AC adapter not plugged in? Well, while I didn’t really think too much of it when I saw the listing, I quickly found the answer after it arrived here: it’s damn impossible to get out of there!

Until I got out some pliers and turned it left and right while pulling a little bit for a while. The cable was really sticky, and I believe (though this is half a year ago already) pretty much glued the connector to the device.

So, did they use a bit of an unusual plug shape? Yes, they did! It looks a bit like a mono headphone connector, maybe a bit thicker? (That reminds me, the ZX81’s power connector probably uses something similar.) But anyway, my trusty 28 in 1 “28 in 3” plug set (https://www.amazon.co.jp/gp/product/B01NCN3P3B/) contained something that fit beautifully, and applying power at about 9V, the calculator sprang to life.

Original connector (top) and replacement connector
Glorious LED display
Device’s innards. I didn’t take it apart any further than this, but cleaned up the gunk. Apparently didn’t take an “after” picture though, so you’ll just have to trust me that it looked as clean as a whistle afterwards. Maybe.

Review

So you are thinking of buying a calculator, and hey, you’ve always wanted to show off how cool retro tech can be. Someone nearby is selling a retro calculator with an LED display. It looks fantastic! But will it be useful?

The Commodore SR-37 does pack a lot of functions, maybe not quite as many as a modern scientific calculator, but it’s not far away. (For example, you can’t easily convert degrees to radians.)

So let’s see how useful this thing is. I’ve thought of some expectations the modern calculator user may have that aren’t quite fulfilled by this device:

ExpectationTrue:
False:
Doesn’t use power when turned off using power switch on device.
Doesn’t use a lot of power. So for example not 1.4 W even when you make it display 8888888888888888888888.8.
Doesn’t turn off the display to conserve power after a short while.
Doesn’t take a long time to compute, e.g., exponentials or roots. Definitely not like 2 seconds!
Comes with a boring LCD rather than a beautiful LED display.
Table 1.1: table of broken expectations

The main problem in my opinion is that it uses a lot of power. Even if you turn it off, it’ll use some power (100 mA or so). At least my model didn’t come with a place to put in batteries so it’s external power only. The fact that this device is basically slightly on all the time (unless you unplug the cord or have a switch nearby), I’m a bit concerned for its longevity. So I don’t think I’ll use it much. :(

Besides, what I really need is a calculator that is really good at converting between bases, like kcalc. I’m not sure such a device even exists!