How to achieve a mixed USB type on Teensy 4.0

Status
Not open for further replies.

DaveBoulden

Member
I am attempting to create a MIDI controller project that also requires use of the Keyboard class to send keystrokes to the USB host alongside MIDI messages.

I have already read many posts on here and elsewhere across the internet on how to edit usb_desc.h in order to change the endpoints declared within a project. Everything I have read so far seems to apply to Teensy 3.x though, and so I don't know if the correct procedure is still the same on a Teensy 4.x board. I am currently building projects in VS Code using Platform.IO to target a Teensy 4.0 and using the MIDI+Serial USB type as there is currently no MIDI/Keyboard/Serial USB type. To avoid editing core module code, I have added a names.c code file to my project with the intention of overriding the USB product name, MIDI port name and to include KEYBOARD_INTERFACE and KEYMEDIA_INTERFACE endpoints to achieve the mixed USB type I require by way of supplementing the USB_MIDI_SERIAL USB type. The code I currently have in my names.c file is:

Code:
#include "usb_names.h"

#define MANUFACTURER_NAME	{'B','O','U','L','D','E','N','d','i','g','i','t','a','l'}
#define MANUFACTURER_NAME_LEN	14

#define PRODUCT_NAME		{'B','d','i','g','i','t','a','l','C','t','r','l','-','1'}
#define PRODUCT_NAME_LEN	14

#define SERIAL_NUMBER {	'B', 'D', '-', 'B', 'D', '1', '-', '0', '0', '1' }
#define SERIAL_NUMBER_LEN 10

// override endpoints config of USB_MIDI_SERIAL to add KEYBOARD and KEYMEDIA

#define NUM_ENDPOINTS         7
#define NUM_USB_BUFFERS	40
#define NUM_INTERFACE		5

#define KEYBOARD_INTERFACE    4	// Keyboard
#define KEYBOARD_ENDPOINT     6
#define KEYBOARD_SIZE         8
#define KEYBOARD_INTERVAL     1
#define KEYMEDIA_INTERFACE    5	// Keyboard Media Keys
#define KEYMEDIA_ENDPOINT     7
#define KEYMEDIA_SIZE         8
#define KEYMEDIA_INTERVAL     4
#define ENDPOINT6_CONFIG	ENDPOINT_TRANSIMIT_ONLY
#define ENDPOINT7_CONFIG	ENDPOINT_TRANSIMIT_ONLY

struct usb_string_descriptor_struct usb_string_manufacturer_name = {
	2 + MANUFACTURER_NAME_LEN * 2,
	3,
	MANUFACTURER_NAME};

struct usb_string_descriptor_struct usb_string_product_name = {
	2 + PRODUCT_NAME_LEN * 2,
	3,
	PRODUCT_NAME};

struct usb_string_descriptor_struct usb_string_serial_number = {
	2 + SERIAL_NUMBER_LEN * 2,
	3,
	SERIAL_NUMBER};

This is successfully changing the MIDI port name and the USB product name, but does not seem to allow me to use the Keyboard object in my man .cpp code. Including something like Keyboard.print("A") in my main loop produces a compile error:

Code:
src\button-matrix.cpp: In function 'void loop()':
src\button-matrix.cpp:546:17: error: 'Keyboard' was not declared in this scope
                 Keyboard.print("G");
                 ^
*** [.pio\build\teensy40\src\button-matrix.cpp.o] Error 1

Should I be directly including usb_keyboard.h or instantiating usb_keyboard_class in some other way, or is it that my endpoint config is not overriding the declarations in core and so not invoking the Keyboard object as it would when choosing a Keyboard oriented USB type? I did try the USB_EVERYTHING USB type, but it does not seem to compile cleanly on my Teensy 4.0

Does anyone have any pointers that apply specifically to achieving this on a Teensy 4.x?
 
declared within a project. Everything I have read so far seems to apply to Teensy 3.x though, and so I don't know if the correct procedure is still the same on a Teensy 4.x board.

It's basically the same, but the syntax for some things is a little different, so discard that file you have for Teensy 3 and start fresh with the usb_desc.h from the "teensy4" folder.

In particular, the endpoint config constants name the type of transmit and receive, and for bulk endpoints you'll see a pair of sizes since different numbers are used at 480 vs 12 Mbit speeds.

Teensy 4 doesn't use NUM_USB_BUFFERS. Memory is allocated differently on Teensy 4 and not configured from this file.

Use only endpoint number 2 to 7.


Should I be directly including usb_keyboard.h or instantiating usb_keyboard_class in some other way,

No.


or is it that my endpoint config is not overriding the declarations in core and so not invoking the Keyboard object as it would when choosing a Keyboard oriented USB type?

Something is not right. What exactly, I can't say from only this message.

If you get really stuck, bring those files over to Arduino 1.8.13 & Teensyduino 1.53 or 1.54-beta6... and if you still get the compile error, post the complete edited file and a screenshot of the Tools menu so we can see exactly what you've selected.


I did try the USB_EVERYTHING USB type, but it does not seem to compile cleanly on my Teensy 4.0

No, and it never will, because Teensy 4's USB device hardware supports fewer endpoints. 480 Mbit speed is 20X faster and the latency specs at the token level are much tighter and the host controller can send IN tokens with any arbitrary order. That's a triple-whammy of tough hardware timing requirements which all add up to needing much more very fast FIFO memory inside the USB controller. This is why NXP doesn't give us as many endpoint numbers as with the old controller supporting only 12 Mbit speed.


Does anyone have any pointers that apply specifically to achieving this on a Teensy 4.x?

Follow the examples in the correct usb_desc.h file and make sure you're not looking at the stuff from the other version. Best to start by just adding a syntax error, to make sure you're really editing part of the file which is being used by your compile settings.

If you really can't get it, bring it all over to the Arduino IDE, and then show us the complete usb_desc.h, so anyone can just copy it into their Arduino hardware folder to reproduce your problem. If using "Quick Reply" click "Go Advanced" to get to the full editor which lets you attach files.
 
Thank you for the detailed reply, Paul.

The changes to usb_desc.h are picked up in the Arduino IDE - it compiles OK and allows use of the Keyboard object. From what I've now read about PlatformIO, it seems it uses precompiled binaries for each board. Does that pretty much scupper this approach when using PlatformIO unless I go down the route of recompiling the board binary with my desired usb_desc.h changes, or can they still be overridden within my own code?

I'm trying to avoid needing to use the Arduino IDE at any point as I find it really irksome to use.
 
I have finally found where PlatformIO stores the source for Teensyduino. I have altered the USB_MIDI_SERIAL USB type declaration in: <Windows User Folder>/.platformio/packages/framework-arduinoteensy/cores/teensy4/usb_desc.h.

Is there a better solution to editing the core files as I will obviously have to reapply this patch each time I update PlatformIO with new releases of Teensyduino. I would much prefer to be able to have this patch programmatically applied from within my project. Is this possible?

For completeness, the entries I've added to USB_MIDI_SERIAL look like this:

Code:
  #define VENDOR_ID		0x16C0
  #define PRODUCT_ID		0x0489
  #define MANUFACTURER_NAME	{'T','e','e','n','s','y','d','u','i','n','o'} 
  #define MANUFACTURER_NAME_LEN	11
  #define PRODUCT_NAME		{'T','e','e','n','s','y',' ','M','I','D','I'}
  #define PRODUCT_NAME_LEN	11
  #define EP0_SIZE		64
  #define NUM_ENDPOINTS         6
  #define NUM_INTERFACE		5
  #define CDC_IAD_DESCRIPTOR	1
  #define CDC_STATUS_INTERFACE	0
  #define CDC_DATA_INTERFACE	1	// Serial
  #define CDC_ACM_ENDPOINT	2
  #define CDC_RX_ENDPOINT       3
  #define CDC_TX_ENDPOINT       3
  #define CDC_ACM_SIZE          16
  #define CDC_RX_SIZE_480       512
  #define CDC_TX_SIZE_480       512
  #define CDC_RX_SIZE_12        64
  #define CDC_TX_SIZE_12        64
  #define MIDI_INTERFACE        2	// MIDI
  #define MIDI_NUM_CABLES       1
  #define MIDI_TX_ENDPOINT      4
  #define MIDI_TX_SIZE_12       64
  #define MIDI_TX_SIZE_480      512
  #define MIDI_RX_ENDPOINT      4
  #define MIDI_RX_SIZE_12       64
  #define MIDI_RX_SIZE_480      512
  #define KEYBOARD_INTERFACE    3	// Keyboard
  #define KEYBOARD_ENDPOINT     5
  #define KEYBOARD_SIZE         8
  #define KEYBOARD_INTERVAL     1	
  #define KEYMEDIA_INTERFACE    4	// Keyboard Media Keys
  #define KEYMEDIA_ENDPOINT     6
  #define KEYMEDIA_SIZE         8
  #define KEYMEDIA_INTERVAL     4	
  #define ENDPOINT2_CONFIG	ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
  #define ENDPOINT3_CONFIG	ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK
  #define ENDPOINT4_CONFIG	ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK
  #define ENDPOINT5_CONFIG	ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
  #define ENDPOINT6_CONFIG	ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
 
This didn't work for me, so I added a whole new block like this to usb_desc.h, and a new entry to boards.txt.


#elif defined(USB_MIDI_KBD)
#define VENDOR_ID 0x16C0
#define PRODUCT_ID 0x0485
#define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'}
#define MANUFACTURER_NAME_LEN 11
#define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I'}
#define PRODUCT_NAME_LEN 11
#define EP0_SIZE 64
#define NUM_ENDPOINTS 6
#define NUM_INTERFACE 4
#define SEREMU_INTERFACE 1 // Serial emulation
#define SEREMU_TX_ENDPOINT 2
#define SEREMU_TX_SIZE 64
#define SEREMU_TX_INTERVAL 1
#define SEREMU_RX_ENDPOINT 2
#define SEREMU_RX_SIZE 32
#define SEREMU_RX_INTERVAL 2
#define MIDI_INTERFACE 2 // MIDI
#define MIDI_NUM_CABLES 1
#define MIDI_TX_ENDPOINT 3
#define MIDI_TX_SIZE_12 64
#define MIDI_TX_SIZE_480 512
#define MIDI_RX_ENDPOINT 3
#define MIDI_RX_SIZE_12 64
#define MIDI_RX_SIZE_480 512

#define KEYBOARD_INTERFACE 3 // Keyboard
#define KEYBOARD_ENDPOINT 4
#define KEYBOARD_SIZE 8
#define KEYBOARD_INTERVAL 1 // TODO: is this ok for 480 Mbit speed
#define KEYMEDIA_INTERFACE 4 // Keyboard Media Keys
#define KEYMEDIA_ENDPOINT 5
#define KEYMEDIA_SIZE 8
#define KEYMEDIA_INTERVAL 4 // TODO: is this ok for 480 Mbit speed

#define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT
#define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK

#define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT
#define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
#define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT


=== boards.txt ===
teensy41.menu.usb.midikbd=MIDI + Keyboard
teensy41.menu.usb.midikbd.build.usbtype=USB_MIDI_KBD
teensy41.menu.usb.midikbd.fake_serial=teensy_gateway
 
Status
Not open for further replies.
Back
Top