Adding HID/PID Force Feedback Endpoint to Teensy 4

Status
Not open for further replies.

Fletcher

Member
I'm interested in building a force feedback joystick controller around Teensy 4.0. I've purchased some salvaged old control loading servos that communicate via CANOpen, and have established communications using a python canopen library on my desktop. The servos report position as a 16-bit signed integer and accept drive torque as a 16-bit signed integer. I tried implementing a desktop application with the latest vjoy, which supports force feedback, but I want tighter control over cycle times than a multi-threaded desktop app can give me, thanks to the limits of sleep precision on Windows. I'm looking at Teensy 4.0 to do the soft real-time force calculation loop and CAN IO. I want to extend Teensy's HID joystick endpoint to add Physical Input Device support, and also maintain a virtual serial port for debugging/configuration.

Firmware for such a device that's close to what I want already exists (ref: jmriego's Fino project, among others), but in the Arduino ecosystem, using their PluggableUSB code. While both are USB HID endpoints, the PluggableUSB API is quite different from Teensy's usb.c / usb_desc.h. I'm ignorant enough about the implementation details of USB HID that porting this feels pretty daunting. I've already read the canonical "Adding a new USB type" PJRC forum thread, but that discusses creating an endpoint that includes a different mix of the builtin Teensyduino device types, not something entirely new. Also relevant: the Fino project includes a PDF of a USB Implementers' Forum document describing the Physical Input Device USB spec.

As best as I can tell, the force feedback device type is implemented as an extension of the HID standard, and involves implementing an extra interrupt out endpoint on top of the regular USB HID joystick profile. The Fino project includes an implementation of the descriptor. So I think I need to create a new USB device type that defines that descriptor, in addition to the joystick descriptor, and write an extension of usb_joystick.cpp that parses those datastructures out of the report described by the descriptor, populates the relevant structs, and makes them available so that I can employ the effect code from Fino to generate the output torques (and then use CANOpen to send the forces to the actuators, and update the position/velocity/acceleration).

[edit]I just had a look in the comments in usb_desc.h and usb_desc.c, which are much nicer since I was last in there several years ago. Thank you for the improved comments, Paul! That also led me to the canonical description of the stack.

So it looks like I need to add the FFB descriptor to usb_desc.c, and wire up usb_desc.h such that I have a device with a Serial, and a FFBJoystick, which is just a Joystick except I'm re-using the endpoint to receive as well. I feel like I should be able to use the Serial receive code to figure out getting data from the usb_packet_t into the structs I plan to borrow from Fino...[/edit]

Any pointers on how to put together the various disconnected bits here into an approach to modifying Teensyduino to get where I need to be?
 
Last edited:
Replying to myself here to add some more details, since I figure I'll get better help with more details, and improve my own understanding! I've been reversing a lot of undocumented hardware lately, and I've got the full code here so this is actually a better situation than I've been in otherwise! I'm looking at a fresh install of Teensyduino 1.55, for the record.

Okay, so, let's start with the joystick descriptor, which is declared as `joystick_report_desc[]` in `usb_desc.c`, guarded by an ifdef on `JOYSTICK_INTERFACE`.

Where else does JOYSTICK_INTERFACE show up? There's a block on line 598 that defines JOYSTICK_INTERFACE_DESC_POS as SEREMU_INTERFACE_DESC_POS + SEREMU_INTERFACE_DESC_SIZE. I think this is saying that the joystick descriptor is stored in memory immediately after the serial emulator descriptor. I can see above that if SEREMU_INTERFACE is not defined, then SEREMU_INTERFACE_DESC_SIZE is defined as 0, so, we're chaining together all these descriptors as a linked list using preprocessor directives. JOYSTICK_INTERFACE_DESC_SIZE is defined as 9+9+7. Those are magic numbers to me. It looks like it has something to do with the size of the descriptor, but then it's unclear why we don't just use a macro. The "Adding a new USB type" thread suggests that it's the length of the descriptor (in bytes?) but that doesn't seem right, since the joystick descriptor is more like 83 bytes.

There's also JOYSTICK_HID_DESC_OFFSET which appears to be pos + 9, which is less than DESC_SIZE. (this gets used to build usb_descriptor_list later)..

`
Back to JOYSTICK_INTERFACE: This also gets used around line 1294 to add to the giant `usb_config_descriptor_480` structure, which looks to be an array of usb descriptors jammed together in memory. bLength is 9 (I guess that's where those 9s come from, but I'm still not quite clear what exactly is 9 bytes long). It also gets used to set the interface number (so, okay, later we'll define JOYSTICK INTERFACE to be a valid byte value)! It also shows up on line 2308, in what I think is a separate USB descriptor for higher-speed USB (`usb_config_descriptor_12`)? References to the HID 1.11 spec, so that's somewhere to look later..

JOYSTICK_INTERFACE also shows up in `usb_descriptor_list`, which is a table providing access to all that data? I'll have to come back to this and find-all-occurrences..

Now we get into more documented stuff. In `usb_desc.h` there are the flags for eg: USB_HID. This is already well-documented in other posts. Looks like the most important bits are the interface number (must be unique, unsure if must be monotonically increasing). There's a single endpoint defined there (JOYSTICK_ENDPOINT), which also maps back to the giant usb_config_descriptor structs. (This is also where JOYSTICK_SIZE shows up, now that I look at it. So 12 and 64 are the sizes of the joystick packets in bytes, then!) The ENDPOINT*_CONFIG stuff appears to be defined in usb.c, and it looks like I'll just need to make sure that whatever endpoint I select for my FFB type has both ENDPOINT_RECEIVE_INTERRUPT and ENDPOINT_TRANSMIT_INTERRUPT. It looks like max 7 endpoints are supported, but that's fine, since it looks like we can re-use the same endpoint for TX and RX.

JOYSTICK_INTERFACE also shows up in usb_inst.cpp where it appears to define an instance of the usb joystick class (which can later be used in Teensyduino sketches, and in usb_joystick .c/.h, which defines the actual Joystick class.

So, okay, what are the implications for implementing the PID spec, which is "joystick HID + a receive endpoint?" I guess first thing is to copy all the JOYSTICK stuff, but with a new set of defines (JOYSTICK_FFB_* or something), and add the ffb descriptor to usb_desc.c..
 
Status
Not open for further replies.
Back
Top