[MIDI, 3.6] Using USBHost_t36 functionality without setting USB Type

Status
Not open for further replies.

curiousjp

Member
Hi,

I apologise if this is a bit premature - my 3.6 is currently in the mail, and I am looking at the documentation in preparation for its arrival. I have also had a look in other forum posts, but I wasn't able to find one with exactly the same problem I am anticipating. I am planning to port some software I wrote on another platform to the 3.6. The device reads MIDI data through the USB host, but appears to its own host as a joypad.

All of the MIDI examples I have found bundled with Teensyduino - including USBHost_t36 > MIDI > InputFunctions - request that they be run under one of the MIDI types from the "Tools > USB Type" menu, which also adds MIDI endpoints. I will probably have to roll my own set of definitions for usb_desc.h (as I have a feeling, based on previous experience, that the host will freak out if presented with e.g. USB_ALL, and I will need to set the vendor and product ID), but I'm curious about exactly how much I have to include, i.e. does MIDI_INTERFACE / MIDI_NUM_CABLES / etc really have to be defined to use the USBHost_t36 MIDIDevice class (and what would e.g. MIDI_TX_ENDPOINT be on a device that has no MIDI endpoint?) Neither USBHost_t36.h or libraries\USBHost_t36\midi.cpp seem to make use of it, which is why I'm curious.

As a second question, one reason I was moving to Teensy was to make it easier for end users to make their own modifications to the code. If I do end up having to provide a new USB Type, has anyone come up with a way to bundle it with the rest of the sketch, in the way that MIDI_name's name.c exports a handful of symbols? Thank you to anyone that has any insight on the above.
 
AFAIK,
setting Tools->USB Type sets the type for USB device, which is connecting the teensy to a PC USBHost
if you attach to a teensy another USB device, for which you use the 2nd USB port as a USB Host, then you have to use USBHost_t36 which should be flexible enough to accept the USB devices of your interest.
 
Hi WMXZ,

Yes, you are right in that it is the USBHost_t36 library that has to be used for reading from the USB host. My question was about the USBHost_t36 example asking that I switch my Teensy to MIDI mode - I can confirm that this is not required.

Unfortunately I am now dealing with an unrelated issue - trying to get the Teensy to behave as a USB joystick that is not also a composite serial device. Fortunately some other posters here are dealing with the same problem, so I will keep on with it.
 
trying to get the Teensy to behave as a USB joystick that is not also a composite serial device.

For that, you will need to edit usb_desc.h and usb_desc.c.

If you create a USB type without serial and without seremu, you will not be able to print to the Arduino Serial Monitor and Teensyduino will not be able to send an auto-reboot request. Without auto-reboot, the Upload button in Arduino will not work automatically. Instead, you'll need to click Verify and then physically press the button on your Teensy to get into bootloader mode and do the upload.

Highly recommend getting everything else done and well tested first, since troubleshooting and uploading is not as easy when you delete the serial & seremu stuff.
 
Hi Paul -

Yes, I am fairly far down that road now - and your advice about losing serial is good, thank you. I am currently debugging with distinctive flashing patterns from the onboard LED. Before I get any further, thank you for engineering this wonderful platform.

I have another problem, but I am not sure I'll be able to debug it here as I don't think it's on the Teensy side. I will type it out though in case anyone has experienced the same.

I have now created a new USB Type corresponding to the out-of-production controller I am trying to replace. It has a distinctive Vendor and Product ID, distinctive reports, and so on. I have written functions to pack the reports and send them. The device (with one exception, which I will get to) is indistinguishable from the original controller when inspected with USBView from the Windows 10 SDK, and dumps of sniffed USB traffic via USBPCap show that the packets are internally the same. A simple test program written under this USB Type (to toggle button 2 on and off every half second) works perfectly under Windows. The joystick control panel shows the same controls, in the same activation states, as you would see on the original controller.

When I plug it into the Playstation, it is recognised (and acknowledged by the software) as a controller of the relevant type. Unfortunately, usb_tx_packet_count() almost instantly exceeds TX_PACKET_LIMIT (which I have at 3) - it seems the Playstation is refusing to consume packets from the endpoint. As a result, attempts to send time out. I don't think this is e.g. some kind of Playstation authentication / magic bytes issue because:
  1. I have had a similar setup working previously with a Raspberry Pi running in USB Gadget mode.
  2. A test program on the Teensy running with the stock Keyboard+Mouse+Joystick USB type is recognised by the PS3 as a standard controller, and can advance through menus by pressing button 2.

Anyway, I am rapidly running out of ideas - at least for tonight. One side issue I haven't been able to sort out is the single difference in the device descriptors between the original controller and my clone - the original controller has a bMaxPacketSize0 of 8 - surprisingly small, but apparently spec legal. However, whenever I have tried to program the Teensy using this size, via the EP0_SIZE constant, the Playstation won't recognise it as a controller at all, and Windows often will say the device failed to initialise properly. I am not sure if this is significant, but the divergence in the descriptors concerns me.

I know rule one is to include code, in full, but my fingerprints are now all through cores/teensy3 - particularly usb_desc.c. I can perhaps provide diffs if they would be helpful, but they are likely to be extensive. In the meantime, usb_mpa.h / .c are:

Code:
/*
 * code is derived from PJRC.com's usb_joystick.h
 * this is mostly renamed variables, but I also reformatted
 * it to make it easier for me to read.
 *
 * as a trivial modification of someone else's work, no
 * distinctive intellectual property inheres  -- curious.jp@gmail.com
 *
 * mpas are similar to joysticks, but -
 *		no autosending
 *		they have a lot of fixed bytes in their reports
 */

#ifndef USBmpa_h_
#define USBmpa_h_

#include "usb_desc.h"

#if defined(MPA_INTERFACE)

    #define MPA_HAT_NEUTRAL 0x08
    #define MPA_HAT_UP 0x00
    #define MPA_HAT_DOWN 0x04
    #define MPA_HAT_LEFT 0x06
    #define MPA_HAT_RIGHT 0x02
	
	#include <inttypes.h>
	
	// C language implementation
#ifdef __cplusplus
extern "C" {
#endif

	void usb_mpa_set_hat( uint8_t hat_position );
	void usb_mpa_set_button( int button );
	void usb_mpa_reset_packet( void );
	int  usb_mpa_send( void );
	extern uint8_t usb_mpa_data[ MPA_PACK_SIZE ];

#ifdef __cplusplus
}
#endif

#endif // MPA_INTERFACE
#endif // USBmpa_h_

Code:
#include "usb_dev.h"
#include "usb_mpa.h"
#include "core_pins.h" // for yield()
#include "HardwareSerial.h"
#include <string.h> // for memcpy()

/*
 * code is derived from PJRC.com's usb_joystick.c
 * this is mostly renamed variables, but I also reformatted
 * it to make it easier for me to read.
 *
 * as a trivial modification of someone else's work, no
 * distinctive intellectual property inheres  -- curious.jp@gmail.com
 */

#ifdef MPA_INTERFACE
  #if F_CPU >= 20000000
    
    uint8_t usb_mpa_data[ MPA_PACK_SIZE ] = { 0x00, 0x00, // button states
                                              0x08, // hat state
                                              0x80, 0x80, 0x80, 0x80,
                                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                              0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02 };


    // Maximum number of transmit packets to queue so we don't starve other endpoints for memory
    #define TX_PACKET_LIMIT 3
    static uint8_t transmit_previous_timeout=0;

    // When the PC isn't listening, how long do we wait before discarding data?
    #define TX_TIMEOUT_MSEC 30

    #if F_CPU == 256000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1706)
    #elif F_CPU == 240000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1600)
    #elif F_CPU == 216000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1440)
    #elif F_CPU == 192000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1280)
    #elif F_CPU == 180000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1200)
    #elif F_CPU == 168000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1100)
    #elif F_CPU == 144000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 932)
    #elif F_CPU == 120000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 764)
    #elif F_CPU == 96000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 596)
    #elif F_CPU == 72000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 512)
    #elif F_CPU == 48000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 428)
    #elif F_CPU == 24000000
      #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 262)
    #endif

    void usb_mpa_set_hat( uint8_t hat_position ) {
      usb_mpa_data[ 2 ] = hat_position;
    }

    void usb_mpa_set_button( int button ) {
      if( button < 8 ) {
        usb_mpa_data[ 0 ] |= 1 << button;
      }
      else {
        usb_mpa_data[ 1 ] |= 1 << ( button - 8 );
      }
    }

    void usb_mpa_reset_packet( void ) {
      usb_mpa_data[ 0 ] = 0x00;
      usb_mpa_data[ 1 ] = 0x00;
      usb_mpa_data[ 2 ] = MPA_HAT_NEUTRAL;
    }

    int usb_mpa_send(void) {
      uint32_t wait_count=0;
      usb_packet_t *tx_packet;
      while (1) {
        if (!usb_configuration) {
          return -1;
        }
        if (usb_tx_packet_count(MPA_ENDPOINT) < TX_PACKET_LIMIT) {
          tx_packet = usb_malloc();
          if (tx_packet) break;
        }
        if (++wait_count > TX_TIMEOUT || transmit_previous_timeout) {
          transmit_previous_timeout = 1;
          return -2;
        }
      yield();
      }
      transmit_previous_timeout = 0;
      memcpy( tx_packet->buf, usb_mpa_data, MPA_PACK_SIZE);
      tx_packet->len = MPA_PACK_SIZE;
      usb_tx( MPA_ENDPOINT, tx_packet );
	    return 0;
    }

  #endif // F_CPU
#endif // MPA_INTERFACE
 
Actually, the diff wasn't as bad as I had thought it would be:

Code:
diff -r teensy.original/avr/boards.txt teensy/avr/boards.txt
429a430,432
> teensy36.menu.usb.mpa=MPA
> teensy36.menu.usb.mpa.build.usbtype=USB_MPA
> teensy36.menu.usb.mpa.fake_serial=teensy_gateway
diff -r teensy.original/avr/cores/teensy3/WProgram.h teensy/avr/cores/teensy3/WProgram.h
66a67,68
> #include "usb_mpa.h"
> 
diff -r teensy.original/avr/cores/teensy3/usb_desc.c teensy/avr/cores/teensy3/usb_desc.c
70a71,73
> #ifdef BCD_USB
>         LSB(BCD_USB), MSB(BCD_USB),
> #else
71a75
> #endif
112c116
<         3,                                      // iSerialNumber
---
>         0,                                      // iSerialNumber
352a357,426
> #ifdef MPA_INTERFACE
> static uint8_t mpa_report_desc[] = {
>     0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
>     0x09, 0x05,        // Usage (Game Pad)
>     0xA1, 0x01,        // Collection (Application)
>     0x15, 0x00,        //   Logical Minimum (0)
>     0x25, 0x01,        //   Logical Maximum (1)
>     0x35, 0x00,        //   Physical Minimum (0)
>     0x45, 0x01,        //   Physical Maximum (1)
>     0x75, 0x01,        //   Report Size (1)
>     0x95, 0x0D,        //   Report Count (13)
>     0x05, 0x09,        //   Usage Page (Button)
>     0x19, 0x01,        //   Usage Minimum (0x01)
>     0x29, 0x0D,        //   Usage Maximum (0x0D)
>     0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0x95, 0x03,        //   Report Count (3)
>     0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
>     0x25, 0x07,        //   Logical Maximum (7)
>     0x46, 0x3B, 0x01,  //   Physical Maximum (315)
>     0x75, 0x04,        //   Report Size (4)
>     0x95, 0x01,        //   Report Count (1)
>     0x65, 0x14,        //   Unit (System: English Rotation, Length: Centimeter)
>     0x09, 0x39,        //   Usage (Hat switch)
>     0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
>     0x65, 0x00,        //   Unit (None)
>     0x95, 0x01,        //   Report Count (1)
>     0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0x26, 0xFF, 0x00,  //   Logical Maximum (255)
>     0x46, 0xFF, 0x00,  //   Physical Maximum (255)
>     0x09, 0x30,        //   Usage (X)
>     0x09, 0x31,        //   Usage (Y)
>     0x09, 0x32,        //   Usage (Z)
>     0x09, 0x35,        //   Usage (Rz)
>     0x75, 0x08,        //   Report Size (8)
>     0x95, 0x04,        //   Report Count (4)
>     0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
>     0x09, 0x20,        //   Usage (0x20)
>     0x09, 0x21,        //   Usage (0x21)
>     0x09, 0x22,        //   Usage (0x22)
>     0x09, 0x23,        //   Usage (0x23)
>     0x09, 0x24,        //   Usage (0x24)
>     0x09, 0x25,        //   Usage (0x25)
>     0x09, 0x26,        //   Usage (0x26)
>     0x09, 0x27,        //   Usage (0x27)
>     0x09, 0x28,        //   Usage (0x28)
>     0x09, 0x29,        //   Usage (0x29)
>     0x09, 0x2A,        //   Usage (0x2A)
>     0x09, 0x2B,        //   Usage (0x2B)
>     0x95, 0x0C,        //   Report Count (12)
>     0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0x0A, 0x21, 0x26,  //   Usage (0x2621)
>     0x95, 0x08,        //   Report Count (8)
>     0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
>     0x0A, 0x21, 0x26,  //   Usage (0x2621)
>     0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
>     0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
>     0x46, 0xFF, 0x03,  //   Physical Maximum (1023)
>     0x09, 0x2C,        //   Usage (0x2C)
>     0x09, 0x2D,        //   Usage (0x2D)
>     0x09, 0x2E,        //   Usage (0x2E)
>     0x09, 0x2F,        //   Usage (0x2F)
>     0x75, 0x10,        //   Report Size (16)
>     0x95, 0x04,        //   Report Count (4)
>     0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
>     0xC0               // End Collection
> };
> #endif // MPA_INTERFACE
> 
574a649
> #ifndef MPA_INTERFACE
576c651
< 
---
> #endif
593a669
>         /* bryn
595a672,674
>         */
>         0x80,
>         0x20,
1093a1173,1207
> #ifdef MPA_INTERFACE
>         // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
>         9,                                    // bLength
>         4,                                    // bDescriptorType
>         MPA_INTERFACE,                        // bInterfaceNumber
>         0,                                    // bAlternateSetting
>         2,                                    // bNumEndpoints
>         0x03,                                 // bInterfaceClass (0x03 = HID)
>         0x00,                                 // bInterfaceSubClass
>         0x00,                                 // bInterfaceProtocol
>         0,                                    // iInterface
>         // HID interface descriptor, HID 1.11 spec, section 6.2.1
>         9,                                    // bLength
>         0x21,                                 // bDescriptorType
>         0x11, 0x01,                           // bcdHID
>         0,                                    // bCountryCode
>         1,                                    // bNumDescriptors
>         0x22,                                 // bDescriptorType
>         LSB(sizeof(mpa_report_desc)),         // wDescriptorLength
>         MSB(sizeof(mpa_report_desc)),
>         // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
>         7,                                    // bLength
>         5,                                    // bDescriptorType
>         MPA_ENDPOINT | 0x80,                  // bEndpointAddress
>         0x03,                                 // bmAttributes (0x03=intr)
>         MPA_MAX_SIZE, 0,                      // wMaxPacketSize
>         MPA_INTERVAL,                         // bInterval
>         7,                                    // bLength
>         5,                                    // bDescriptorType
>         0x02,                                 // bEndpointAddress
>         0x03,                                 // bmAttributes (0x03=intr)
>         MPA_MAX_SIZE, 0,                      // wMaxPacketSize
>         MPA_INTERVAL,                         // bInterval
> #endif // MPA_INTERFACE
> 
1518a1633,1636
> #endif
> #ifdef MPA_INTERFACE
>         {0x2200, MPA_INTERFACE, mpa_report_desc, sizeof(mpa_report_desc)},
>         {0x2100, MPA_INTERFACE, config_descriptor + MPA_DESC_OFFSET, 9},        
diff -r teensy.original/avr/cores/teensy3/usb_desc.h teensy/avr/cores/teensy3/usb_desc.h
175a176,210
> #elif defined(USB_MPA)
>   #define VENDOR_ID             0x12BA
>   #define PRODUCT_ID            0x0218
> 
>   #define MANUFACTURER_NAME     {'L', 'i', 'c', 'e', 'n', 's', 'e', 'd', ' ', 'b', 'y', ' ', 'S', 'o', 'n', 'y', ' ', 'C', 'o', 'm', 'p', 'u', 't', 'e', 'r', ' ', 'E', 'n', 't', 'e', 'r', 't', 'a', 'i', 'n', 'm', 'e', 'n', 't', ' ', 'A', 'm', 'e', 'r', 'i', 'c', 'a'}
>   #define MANUFACTURER_NAME_LEN 47
>   #define PRODUCT_NAME          {'H', 'a', 'r', 'm', 'o', 'n', 'i', 'x', ' ', 'D', 'r', 'u', 'm', ' ', 'k', 'i', 't', ' ', 'f', 'o', 'r', ' ', 'P', 'l', 'a', 'y', 'S', 't', 'a', 't', 'i', 'o', 'n', 0xae, '3'}
>   #define PRODUCT_NAME_LEN      35
> 
> /*
>   #define MANUFACTURER_NAME     {'c', 'u', 'r', 'i', 'o', 'u', 's', '.', 'j', 'p', '@', 'g', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm'}
>   #define MANUFACTURER_NAME_LEN 20
>   #define PRODUCT_NAME          {'T', 'e', 'e', 'n', 's', 'y', 'M', 'P', 'A'}
>   #define PRODUCT_NAME_LEN      9
> */
> 
>   #define DEVICE_CLASS          0x00
>   #define DEVICE_SUBCLASS       0x00
>   #define DEVICE_PROTOCOL       0x00
>   #define BCD_DEVICE            0x0200
>   #define BCD_USB               0x0200
>   #define EP0_SIZE              16
>   #define NUM_ENDPOINTS         2
>   #define NUM_USB_BUFFERS       30
>   #define NUM_INTERFACE         1
>   #define MPA_INTERFACE         0 // Midi Pro Adapter
>   #define MPA_ENDPOINT          1
>   #define MPA_PACK_SIZE         0x1B
>   #define MPA_MAX_SIZE          0x40
>   #define MPA_INTERVAL          0xA
>   #define MPA_DESC_OFFSET       (9)
>   #define CONFIG_DESC_SIZE      (9+9+9+7+7)
>   #define ENDPOINT1_CONFIG      ENDPOINT_TRANSMIT_ONLY
>   #define ENDPOINT2_CONFIG      ENDPOINT_RECEIVE_ONLY
> 
177c212
<   #define VENDOR_ID		0x16C0
---
>   #define VENDOR_ID		0x16C0 
diff -r teensy.original/avr/cores/teensy3/usb_dev.c teensy/avr/cores/teensy3/usb_dev.c
390c390,391
< #if defined(SEREMU_INTERFACE) || defined(KEYBOARD_INTERFACE)
---
> //#if defined(SEREMU_INTERFACE) || defined(KEYBOARD_INTERFACE)
> #if defined(SEREMU_INTERFACE) || defined(KEYBOARD_INTERFACE) || defined(MPA_INTERFACE)
485a487,503
> 
> // 'magic bytes' per https://github.com/johnburkert/YeOldeJoystick/tree/master/ps3_gamepad
> #if defined(MPA_INTERFACE)
> 	if( setup.bmRequestType == 0xA1 && setup.bRequest == 0x01 ) {
> 		reply_buffer[0] = 0x21;
> 		reply_buffer[1] = 0x26;
> 		reply_buffer[2] = 0x01;
> 		reply_buffer[3] = 0x07;
> 		reply_buffer[4] = 0x00;
> 		reply_buffer[5] = 0x00;
> 		reply_buffer[6] = 0x00;
> 		reply_buffer[7] = 0x00;
> 		data = reply_buffer;
> 		datalen = 8;
> 	}
> #endif
> 
Only in teensy/avr/cores/teensy3: usb_mpa.c
Only in teensy/avr/cores/teensy3: usb_mpa.h
diff -r teensy.original/avr/cores/teensy3/yield.cpp teensy/avr/cores/teensy3/yield.cpp
40a41
> #if !defined(USB_MPA)	
41a43
> #endif
diff -r teensy.original/avr/libraries/USBHost_t36/antplus.cpp teensy/avr/libraries/USBHost_t36/antplus.cpp
45c45
< #define printf(...) Serial.printf(__VA_ARGS__); Serial.write("\r\n")
---
> #define printf(...) /* Serial.printf(__VA_ARGS__); Serial.write("\r\n") */
diff -r teensy.original/avr/libraries/USBHost_t36/joystick.cpp teensy/avr/libraries/USBHost_t36/joystick.cpp
216c216
<     Serial.printf("Joystick update Rumble/LEDs");
---
>     //Serial.printf("Joystick update Rumble/LEDs");
287c287
< 	Serial.printf("Claim Additional axis: %x %x %d\n", additional_axis_usage_page_, additional_axis_usage_start_, additional_axis_usage_count_);
---
> 	//Serial.printf("Claim Additional axis: %x %x %d\n", additional_axis_usage_page_, additional_axis_usage_start_, additional_axis_usage_count_);
355c355
< 		Serial.printf("UP: usage_page=%x usage=%x add: %x %x %d\n", usage_page, usage, additional_axis_usage_page_, additional_axis_usage_start_, additional_axis_usage_count_);
---
> 		//Serial.printf("UP: usage_page=%x usage=%x add: %x %x %d\n", usage_page, usage, additional_axis_usage_page_, additional_axis_usage_start_, additional_axis_usage_count_);

edit: To annotate the above, everything from yield.cpp down is to remove references to the Serial class.

Modifications to usb_desc.c are to align the descriptor with the observed descriptor - the cryptic edit at 593a669 is to change the max power consumption / capabilities bytes to state the Teensy is bus powered at 64mA. The edit at 574a649 allows me to substitute my own value for CONFIG_DESC_SIZE as defined in the header.

The rest is, I think, reasonably self explanatory.
 
Last edited:
I am going to post a new thread for this, as it has drifted away from the original topic. I am not sure what the protocol is here, but please feel free to lock this one.
 
Status
Not open for further replies.
Back
Top