Problems defining an USB Audio Class Device core for Teensy 3.0

Status
Not open for further replies.

MickMad

Well-known member
Hello there, I'm having a difficult time trying to setup an USB Audio core type for the Teensy. I'm currently working on Teensy 3.0, and I want it to enumerate as an USB Audio Device. I started from the MIDI core type, deleted all the MIDI stuff, and started adding the Audio Class Device descriptors.

First thing, was to add the core type in the Arduino IDE contextual menu: I edited hardware/boards.txt and added the following:

Code:
teensy3.menu.usb.audio.name=Audio
teensy3.menu.usb.audio.build.define0=-DUSB_AUDIO
teensy3.menu.usb.audio.fake_serial=teensy_gateway

then I edited hardware/cores/teensy/core_id.h, hardware/cores/teensy/usb.c , hardware/cores/teensy/usb_api.cpp, hardware/cores/teensy/usb_api.h, hardware/cores/teensy/usb_private.h, hardware/cores/teensy3/core_id.h and added:
Code:
#elif defined(USB_AUDIO)
#include "../usb_audio/xxx"
where xxx is the name of the file to include(core_id.h, usb.c, and so on)

next, I edited /hardware/cores/teensy3/usb_inst.cpp and added:
Code:
#ifdef USB_AUDIO
usb_seremu_class Serial;
#endif

I left usb_desc.c as it is, but I edited usb_desc.h and added:
Code:
#elif defined(USB_AUDIO)
  #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',' ','A','u','d','i','o'}
  #define PRODUCT_NAME_LEN	12
  #define EP0_SIZE		64
  #define NUM_ENDPOINTS     3
  #define NUM_USB_BUFFERS	16
  #define NUM_INTERFACE	3
  #define SEREMU_INTERFACE      2	// Serial emulation
  #define SEREMU_TX_ENDPOINT    1
  #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 AUDIO_INTERFACE        0	// Audio
  #define AUDIO_TX_ENDPOINT      3
  #define AUDIO_TX_SIZE          64
  #define SEREMU_DESC_OFFSET	(9 + 118 + 9)
  #define CONFIG_DESC_SIZE	(9 + 118 + 9+9+7+7)
  #define ENDPOINT1_CONFIG	ENDPOINT_TRANSIMIT_ONLY
  #define ENDPOINT2_CONFIG	ENDPOINT_RECEIVE_ONLY
  #define ENDPOINT3_CONFIG	ENDPOINT_TRANSIMIT_ONLY

I set NUM_INTERFACES to 3 because the Audio Class is defined to have one AudioControl interface, which is the main control interface put on endpoint 0, and an AudioStreaming interface, which handles the actual data transfer and it is connected to endpoint 3, which is an isochronous endpoint.

Then, I finally started to actually modify the usb core. In usb_api.cpp and usb_api.h I just block-commented the whole usb_midi_class class definitions; in usb_private.h the only modified part is the defines:
Code:
#define STR_PRODUCT             L"Teensy Audio"
#define ENDPOINT0_SIZE          64

#define DEBUG_INTERFACE		2
#define DEBUG_TX_ENDPOINT	1
#define DEBUG_TX_SIZE		64
#define DEBUG_TX_BUFFER		EP_DOUBLE_BUFFER
#define DEBUG_TX_INTERVAL	1
#define DEBUG_RX_ENDPOINT	2
#define DEBUG_RX_SIZE		32
#define DEBUG_RX_BUFFER		EP_DOUBLE_BUFFER
#define DEBUG_RX_INTERVAL	2

#define AUDIO_INTERFACE		0
#define AUDIO_TX_ENDPOINT	3
#define AUDIO_TX_SIZE		64
#define AUDIO_TX_BUFFER		EP_DOUBLE_BUFFER

#define NUM_ENDPOINTS		3
#define NUM_INTERFACE		3

#endif

The config descriptor in usb.c is the big part of everything: there's the Interface Association Descriptor, which tells that the device is a composite audio device, then the Audio Control interface, which contains the definitions for a Clock Source Unit, an Input Terminal (the virtual microphone), and an Output Terminal, which is the actual USB stream from Teensy to PC; then there's the AudioStreaming interface definition, which contains the audio Format defintion, and the Isochronous endpoint description.
Code:
#define CONFIG1_DESC_SIZE			( 9 + 122 + 32 )
#define DEBUG_HID_DESC_OFFSET		( 9 + 122 + 9 )

static const uint8_t PROGMEM config1_descriptor[CONFIG1_DESC_SIZE] = {
	// configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10
	9, 					// bLength;
	2,					// bDescriptorType;
	LSB(CONFIG1_DESC_SIZE),	MSB(CONFIG1_DESC_SIZE),	// wTotalLength
	3,					// bNumInterfaces (one AC interface, one AS interface, one HID interface)
	1,					// bConfigurationValue
	0,					// iConfiguration
	0xC0,				// bmAttributes
	50,					// bMaxPower

	// Standard Interface Assoctiation Descriptor (IAD) (Table 4.3, p.46, USB DCD for Audio Devices 2.0)
	8,					// bLength
	0x0B,				// bDescriptorType
	0,					// bFirstInterface
	2,					// bInterfaceCount
	1,					// FunctionClass = AUDIO
	0,					// bFuncionSubClass
	0x20,				// bFunctionProtocol = AF_VERSION_02_00
	0,					// iFunction
	
	// Standard AudioControl (AC) Interface Descriptor	(Table 4.4, p.47, USB DCD for Audio Devices 2.0)
	9,					// bLength
	4,					// bDescriptorType = INTERFACE
	0,					// bInterfaceNumber
	0,					// bAlternateSetting
	0,					// bNumEndpoints
	1,					// bInterfaceClass = AUDIO
	1,					// bInterfaceSubclass = AUDIO_CONTROL
	0x20,				// bInterfaceProtocol = IP_VERSION_02_00
	0,					// iInterface

	// Class-specific AC Interface Descriptor (Table 4.5, p.48, USB DCD for Audio Devices 2.0)
	9,					// bLength
	0x24,				// bDescriptorType = CS_INTERFACE
	1,					// bDescriptorSubtype = HEADER
	0x00, 0x02,			// bcdADC
	3,					// bCategory = MICROPHONE
	0x2E, 0x00,			// wTotalLength (9 + 8 + 17 + 12)
	0,					// bmControls

		// Clock Source Descriptor (Table 4.6, p. 49, USB Device Class Definition for Audio Devices 2.0)
		8,										//bLength
		0x24,									//bDescriptorType = CS_INTERFACE
		0x0A,									//bDescriptorSubType = CLOCK_SOURCE
		0x10,									//bClockID = CLOCK_SOURCE_ID
		1,										//bmAttributes = internal fixed clock
		7,										//bmControls
		0,										//bAssocTerminal
		0,										//iClockSource
		
		// Input Terminal Descriptor (Table 4.9, p.53, USB Device Class Definition for Audio Devices 2.0)
		17,										// bLength
		0x24,									// bDescriptorType = CS_INTERFACE
		0x02,									// bDescriptorSubtype = INPUT_TERMINAL
		0x20,									// bTerminalID
		0x01, 0x02,								// wTerminalType = MICROPHONE
		0x00,									// bAssocTerminal
		0x10,									// bCSourceID = CLOCK_SOURCE_ID
		1,										// bNrChannels
		0,										// bmChannelConfig = MONO
		0,										// iChannelNames
		0x00, 0x00,								// bmControls
		0x00,									// iTerminal
		
		// Output Terminal Descriptor (Table 4.10, p. 54, USB Device Class Definition for Audio Devices 2.0)
		12,										// bLength
		0x24,									// bDescriptorType = CS_INTERFACE
		0x03,									// bDescriptorSubtype = INPUT_TERMINAL
		0x30,									// bTerminalID
		0x01, 0x01,								// wTerminalType = USB STREAMING
		0x00,									// bAssocTerminal
		0x20,									// bSourceID = INPUT_TERMINAL_ID
		0x10,									// bCSourceID = CLOCK_SOURCE_ID
		0x00, 0x00,								// bmControls
		0x00,									// iTerminal
		
	// Standard AudioStreaming (AS) Interface Descriptor (Table 4.26, p. 75, USB Device Class Definition for Audio Devices 2.0)
	// Alternate 0
	// default setting with zero bandwidth
	9,										// bLenght
	4										// bDescriptorType = INTERFACE
	1,										// bInterfaceNumber
	0,										// bAlternateSetting
	0,										// bNumEndpoints
	1,										// bInterfaceClass = AUDIO
	2,										// bInterfaceSubclass = AUDIO_STREAMING
	0x20,									// bInterfaceProtocol = IP 2.0
	0,										// iInterface
	// Alternate 1
	// alternate interface for data streaming
	9,										// bLenght
	4										// bDescriptorType = INTERFACE
	1,										// bInterfaceNumber
	1,										// bAlternateSetting
	1,										// bNumEndpoints
	1,										// bInterfaceClass = AUDIO
	2,										// bInterfaceSubclass = AUDIO_STREAMING
	0x20,									// bInterfaceProtocol = IP 2.0
	0,										// iInterface
	
		// Class-Specific AS Interface Descriptor (Table 4.27, p. 76, USB Device Class Definition for Audio Devices 2.0)
		16, 									// bLength
		0x24,									// bDescriptorType = CS_INTERFACE
		1,										// bDescriptorSubtype = AS_GENERAL
		0x30,									// bTerminalLink = OUTPUT_TERMINAL_ID
		0,										// bmControls
		1,										// bFormatType
		0x01, 0x00, 0x00, 0x00,					// bmFormats
		1,										// bNrChannels
		0,0,0,0,								// bmChannelConfig
		0,										// iChannelNames
	
		// Type I Format Descriptor (Table 2.2, p. 15,  USB Device Class Definition for Audio Data Formats 2.0)
		6,										// bLength
		0x24,									// bDescriptorType = CS_INTERFACE
		2,										// bDescriptorSubtype = FORMAT_TYPE
		1,										// bFormatType = FORMAT_TYPE_I
		2,										// bSubslotSize = 16 bits
		16,										// bBitResolution

		//not documented in USB DCD for Audio Data Formats 2.0
		0x01,									// bSamFreqType
		0x40,0x1F,0x00,							// tSamFreq
		
		// Standard AS Isochronous Audio Data Endpoint Descriptor(4.33, p. 85, USB Device Class Definition for Audio Devices 2.0)
		7, 										// bLength 
		0x05, 									// bDescriptorType = ENDPOINT_DESCRIPTOR
		AUDIO_TX_ENDPOINT | 0x80, 				// bEndpointAddress = 3 - IN
		0x05, 									// bmAttributes = iso+asynch+data
		AUDIO_TX_SIZE, 0x00, 					// wMaxPacketSize = 8(8 samples * 1 bytes * 1 channel) 
		AUDIO_TX_INTERVAL, 						// bInterval =  2^x ms
		
		// Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) 
		0x08,  									// bLength
		0x25,  									// bDescriptorType = CS_ENDPOINT 
		0x01,  									// bDescriptorSubtype = EP_GENERAL
		0x00,  									// bmAttributes = MaxPacketsOnly = FALSE
		0x00,  									// bmControls
		0x00,  									// bLockDelayUnits
		0x00, 0x00,  							// wLockDelay

	// interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
	9,                                      // bLength
	4,                                      // bDescriptorType
	DEBUG_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
        sizeof(debug_hid_report_desc),          // wDescriptorLength
        0,
		
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        DEBUG_TX_ENDPOINT | 0x80,               // bEndpointAddress
        0x03,                                   // bmAttributes (0x03=intr)
        DEBUG_TX_SIZE, 0,                       // wMaxPacketSize
        DEBUG_TX_INTERVAL,                      // bInterval
		
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        DEBUG_RX_ENDPOINT,                      // bEndpointAddress
        0x03,                                   // bmAttributes (0x03=intr)
        DEBUG_RX_SIZE, 0,                       // wMaxPacketSize
        DEBUG_RX_INTERVAL,                      // bInterval

};


Unfortunately, the only thing that I can get to work is the "Teensy Audio" to appear in the device informations; I tried the whole bcdDevice thing, but it doesn't seem to update; if I look at the device informations, it states, under Hardware ID: "USB\VID_16C0&PID_0485&REV_0100"; I suppose that the REV number should be the actual bcdDevice field, but it always reads as 0100. There's also one thing that bugs me so hard, which is the supported sampling frequencies definition in the Type I Format Descriptor: according to USB DCD for Audio Data Formats 2.0 there's no supported sampling frequency field at all, while the USB DCD for Audio Data Formats 1.0 does define it, and you can see I added those lines in the descriptor, as documented in the Freescale Application Note AN4665. I really don't know where to look for the error. In the meanwhile, I'll try to remove everything except for the AC interface, and see if that enumerates the Teensy as a no-function audio device (USB 2.0 allows that). If anybody finds anything that can help , please let me know!


EDIT: in regard to the bcdDevice not updating, I've found the problem: I had to edit usb_desc.c and usb_desc.h a bit; I've added the Audio Configuration Descriptor as defined in my usb.c into usb_desc.c, just like for the other usb types I've added an "#ifdef AUDIO_INTERFACE [.....] #endif" block containing all the descriptor stuff, then I added a #define BCD_DEVICE into usb_desc.h and modified the definition of the device descriptor like this:
Code:
static uint8_t device_descriptor[] = {
        18,                                     // bLength
        1,                                      // bDescriptorType
        0x00, 0x02,                             // bcdUSB
#ifdef DEVICE_CLASS
        DEVICE_CLASS,                           // bDeviceClass
#else
	0,
#endif
#ifdef DEVICE_SUBCLASS
        DEVICE_SUBCLASS,                        // bDeviceSubClass
#else
	0,
#endif
#ifdef DEVICE_PROTOCOL
        DEVICE_PROTOCOL,                        // bDeviceProtocol
#else
	0,
#endif
        EP0_SIZE,                               // bMaxPacketSize0
        LSB(VENDOR_ID), MSB(VENDOR_ID),         // idVendor
        LSB(PRODUCT_ID), MSB(PRODUCT_ID),       // idProduct
#ifdef BCD_DEVICE
        LSB(BCD_DEVICE), MSB(BCD_DEVICE),       // bcdDevice
#else
		0x00, 0x01,					// bcdDevice
#endif
        1,                                      // iManufacturer
        2,                                      // iProduct
        3,                                      // iSerialNumber
        1                                       // bNumConfigurations
};


Now, I can easily update the bcdDevice field.

I've tried to remove everything from the config descriptor, leaving only the Interface Association Descriptor, the Standard AC Interface Descriptor and the class-specific AC Interface Descriptor, and it does something; the PC finds a peripheral called "Teensy Audio", which is composed of a not working audio interface, and some HID stuff related to the Serial emulation. Well, at least, the Serial emulation is working AND the PC finds an audio peripheral. Hope I get the Teensy to actually spit some audio on an USB endpoint soon!
 
Last edited:
Hey guys, I've found the problem: an USB Audio Device receives requests from the host via endpoint 0, and the requests types are class-specific, so I have to handle every request by adding stuff to usb_dev.c, very low-level, and I think I might use some help to understand better how control requests are handled by the Teensy library. Right now, the only thing that comes into my mind is line 120 of usb_dev.c, where a huge switch is made on the wRequestAndType value, which is stored into a usb packet called setup, and that packet totally looks like a request packet. I've added an #ifdef at line 256, right after the other #ifdef blocks, that looks like this:
Code:
#if defined(AUDIO_CONTROL_INTERFACE)
	 case 0x0021: 	// AUDIO_CONTROL_SET_UNDEFINED
		serial_print("AUDIO_CONTROL_SET_UNDEFINED\n");
		endpoint0_stall();
		return;
	 case 0x0121:	// AUDIO_CONTROL_SET_CUR
		serial_print("AUDIO_CONTROL_SET_CUR\n");
		endpoint0_stall();
		return;
	 case 0x0221:	// AUDIO_CONTROL_SET_RANGE
		serial_print("AUDIO_CONTROL_SET_RANGE \n");
		endpoint0_stall();
		return;
	 case 0x0321:	// AUDIO_CONTROL_SET_MEM
	 	serial_print("AUDIO_CONTROL_SET_MEM \n");
		endpoint0_stall();
		return;
	 case 0x00A1: 	// AUDIO_CONTROL_GET_UNDEFINED
	 	serial_print("AUDIO_CONTROL_GET_UNDEFINED \n");
		endpoint0_stall();
		return;
	 case 0x01A1:	// AUDIO_CONTROL_GET_CUR
	 	serial_print("AUDIO_CONTROL_GET_CUR \n");
		endpoint0_stall();
		return;
	 case 0x02A1:	// AUDIO_CONTROL_GET_RANGE
	 	serial_print("AUDIO_CONTROL_GET_RANGE \n");
		endpoint0_stall();
		return;
	 case 0x03A1:	// AUDIO_CONTROL_GET_MEM
	 	serial_print("AUDIO_CONTROL_GET_MEM \n");
		endpoint0_stall();
		return;
		
	 case 0x0022: 	// AUDIO_ENDPOINT_SET_UNDEFINED
	 	serial_print("AUDIO_ENDPOINT_SET_UNDEFINED \n");
	 	 endpoint0_stall();
		return;
	 case 0x0122:	// AUDIO_ENDPOINT_SET_CUR
	 	serial_print("AUDIO_ENDPOINT_SET_CUR \n");
	 	 endpoint0_stall();
		return;
	 case 0x0222:	// AUDIO_ENDPOINT_SET_RANGE
	 	serial_print("AUDIO_ENDPOINT_SET_RANGE \n");
	 	 endpoint0_stall();
		return;
	 case 0x0322:	// AUDIO_ENDPOINT_SET_MEM
	 	serial_print("AUDIO_ENDPOINT_SET_MEM \n");
	 	 endpoint0_stall();
		return;
	 case 0x00A2: 	// AUDIO_ENDPOINT_GET_UNDEFINED
	 	serial_print("AUDIO_ENDPOINT_GET_UNDEFINED \n");
	 	 endpoint0_stall();
		return;
	 case 0x01A2:	// AUDIO_ENDPOINT_GET_CUR
	 	serial_print("AUDIO_ENDPOINT_GET_CUR \n");
		//if ControlSelector = AS_ACT_ALT_SETTING_CONTROL
		endpoint0_stall();
		return;
	 case 0x02A2:	// AUDIO_ENDPOINT_GET_RANGE
	 	serial_print("AUDIO_ENDPOINT_GET_RANGE \n");
	 	 endpoint0_stall();
		return;
	 case 0x03A2:	// AUDIO_ENDPOINT_GET_MEM
	 	serial_print("AUDIO_ENDPOINT_GET_MEM \n");
	 	 endpoint0_stall();
		return;
#endif

I'll try to read some stuff from the setup packet fields and put it into some appropriate function, which will handle a request and respond on a dedicated endpoint, in order to avoid using endpoint 0 for both transmission and reception of requests, so to keep away from further modifying usb_dev.c.
 
Last edited:
Yes, unfortunately this is one place where my USB stack isn't very "stack" like. The current design requires code to be added in the low-level usb_dev.c file for endpoint 0 stuff.

A more traditional design would probably use callback functions, maybe fixed at compile time, maybe dynamically assignable. That may be necessary someday, but it does add overhead. So far only a small number of interface specific requests have been needed, so I just put them right inside that function.

There's no need to add code for requests you don't support. The default will stall for anything that is unknown.
 
I need some help about the use of endpoint 0 on the Teensy; I can't quite understand the usb_dev.c file, at least some part of it; I'd like to know how can I handle a class-specific request; I mean, you said that I could use some callback function for every class-specific request I had and put them in that big switch in usb_setup(), but how do I handle a possible data stage after that? I mean, can I just copy and modifiy one of those usb_xxx_send / usb_xxx_read methods from any usb device library you made (like usb_midi_send_raw in usb_midi/usb_api.cpp) to let it point to endpoint0 and read/send data for that request, and then give back the control of endpoint0 to the usb_dev.c functions, like for handling the status request and whatsoever? Here's a rough code example of what I'm saying:

Code:
// USB_DEV.C
static void usb_setup(void)
{  
     // ...
     switch (setup.wRequestAndType) {
     // ...
          case SOME_AUDIO_SPECIFIC_REQUEST:
               usb_audio_handle_request();
               return;
          }
}

Code:
// USB_AUDIO.h in same folder as usb_dev.c
void usb_audio_handle_request(){
        // let's suppose that this method handles a request that has a data stage to read
    	uint8_t c, intr_state;
	uint8_t b0, b1, b2, b3;

	intr_state = SREG;
	cli();
	if (!usb_configuration) {
		SREG = intr_state;
		return false;
	}
	UENUM = ENDPOINT_0;
	retry:
	c = UEINTX;
	if (!(c & (1<<RWAL))) {
		if (c & (1<<RXOUTI)) {
			UEINTX = 0x6B;
			goto retry;
		}
		SREG = intr_state;
		return false;
	}
	b0 = UEDATX;
	b1 = UEDATX;
	b2 = UEDATX;
	b3 = UEDATX;
	if (!(UEINTX & (1<<RWAL))) UEINTX = 0x6B;
	SREG = intr_state;
}

If this is not possible, how can I do something that looks like this to work with endpoint 0? This part of the library is not self-descriptive at all, in fact it is very complex and, unfortunately, not commented enough. I hope you can help me figure this out :)
 
Are we talking about Teensy 3.0, which is in this thread's subject and has the file usb_dev.c?

Or are we talking about Teensy 2.0, which has registers SREG, UEINTX, UEDATX from the code sample you posted?

You can't mix the two code bases! Each chip was completely different USB hardware.
 
Paul, I'm talking about the Teensy 3.0; I'm sorry but from your examples it is almost impossible to understand what's going on, and I really had no idea about the fact that the API you present in /hardware/teensy/usb_xxx contains Teensy 2.0-only stuff. So, I think I have to write my own library to be put in /teensy3 directory, and bind it with defines to the other usb-relevant parts of the teensy library. But the problem is still the same: how do I write/read data in a data stage after a control request has arrived? I've seen a function called endpoint0_transmit() but I haven't seen any endpoint0_receive()... Do I have to manually handle the status stage too, or is it already taken care of? In other words: what code and where should I add it to successfully handle a class specific request control with a data stage?

I don't want you to write any actual code for me, of course :D I just want to understand what parts of usb_dev.c I should edit, what its methods do, what methods I can use, and what methods I should not touch for any reason.

EDIT: I had another closer look at usb_dev.c and actually I've found an usb_tx() method; I'm trying to lay down some functions to read/write from an endpoint as in usb_midi.c ... I'll keep you up
 
Last edited:
This thing is becoming more and more painful each day... I found out that Microsoft does not give driver support for USB Audio 2.0, unless you specifically write a WDM driver. I reconfigured the descriptor to match USB Audio 1.0 specs:
Code:
#ifdef AUDIO_CONTROL_INTERFACE
			// Standard AudioControl (AC) Interface Descriptor
			0x09,				// bLength
			0x04,				// bDescriptorType = INTERFACE
			AUDIO_CONTROL_INTERFACE, // bInterfaceNumber (must be 0)
			0x00,				// bAlternateSetting
			0x00,				// bNumEndpoints
			0x01,				// bInterfaceClass = AUDIO
			0x01,				// bInterfaceSubclass = AUDIO_CONTROL
			0x00,				// bInterfaceProtocol = IP_VERSION_02_00
			0x00,				// iInterface
			
			// Class-specific AC Interface Descriptor 
			0x09,				// bLength
			0x24,				// bDescriptorType = CS_INTERFACE
			0x01,				// bDescriptorSubtype = HEADER
			0x00, 0x01,		// bcdADC
			0x14, 0x00,		// wTotalLength (interface + clock + in + out)
			0x01,				// bInCollection
			0x01,				// baInterfaceNr
				
				// Input Terminal Descriptor
				0x0B,									//bLength
				0x24,									//bDescriptorType = CS_INTERFACE
				0x01,									//bDescriptorSubType = INPUT_TERMINAL
				0x10,									//bTerminalID = INPUT_TERMINAL_ID
				0x01,0x02,								//wTerminalType = MICROPHONE
				0x00,									//bAssocTerminal
				0x01,									//bNrChannels = 1 channel
				0x00,0x00,								//wChannelConfig = mono sets no position bits
				0x00,									//iChannelNames
				0x00, 								//iTerminal
				// Output Terminal Descriptor
				0x09,									// bLength
				0x24,									// bDescriptorType = CS_INTERFACE
				0x03,									// bDescriptorSubtype = OUTPUT_TERMINAL
				0x20,									// bTerminalID = OUTPUT_TERMINAL_ID
				0x01, 0x01,							// wTerminalType = USB_STREAMING
				0x00,									// bAssocTerminal
				0x10,									// bCSourceID = INPUT_TERMINAL_ID
				0x00,									// iTerminal
				
				/*// Standard AC Interrupt Endpoint Descriptor
				0x09,									// bLength
				0x05,									// bDescriptorType = ENDPOINT
				AUDIO_CONTROL_TX_ENDPOINT | 0x80,		        // bEndpointAddress = 3 - IN
				0x03,									// bmAttributes = 11 : interrupt
				AUDIO_CONTROL_TX_SIZE,0x00,				// wMaxPacketSize
				AUDIO_CONTROL_TX_INTERVAL,				// bInterval
				0x00,									// bRefresh
				0x00,									// bSynchAddress*/
#ifdef AUDIO_STREAMING_INTERFACE
	// Standard AudioStreaming (AS) Interface Descriptor 
	// Alternate 0
	// default setting with zero bandwidth
			0x09,										// bLenght
			0x04,										// bDescriptorType = INTERFACE
			AUDIO_STREAMING_INTERFACE,					// bInterfaceNumber
			0x00,										// bAlternateSetting
			0x00,										// bNumEndpoints
			0x01,										// bInterfaceClass = AUDIO
			0x02,										// bInterfaceSubclass = AUDIO_STREAMING
			0x00,										// bInterfaceProtocol
			0x00,										// iInterface	

			// Alternate 1
			// alternate interface for data streaming
			0x09,										// bLenght
			0x04,										// bDescriptorType = INTERFACE
			AUDIO_STREAMING_INTERFACE,					// bInterfaceNumber
			0x01,										// bAlternateSetting
			0x01,										// bNumEndpoints
			0x01,										// bInterfaceClass = AUDIO
			0x02,										// bInterfaceSubclass = AUDIO_STREAMING
			0x00,										// bInterfaceProtocol
			0x00,										// iInterface
			
				// Class-Specific AS Interface Descriptor 
				0x07, 								// bLength
				0x24,									// bDescriptorType = CS_INTERFACE
				0x01,									// bDescriptorSubtype = EP_GENERAL
				0x20,									// bTerminalLink = OUTPUT_TERMINAL_ID
				0x01,									// bDelay
				0x01,0x00,								// wFormatTag
	
				// Type I Format Descriptor 
				0x0B,									// bLength
				0x24,									// bDescriptorType = CS_INTERFACE
				0x02,									// bDescriptorSubtype = FORMAT_TYPE
				0x01,									// bFormatType = FORMAT_TYPE_I
				0x01, 								// bNrChannels = 1
				0x01,									// bSubFrameSize = 1 byte
				0x08,									// bBitResolution = 8 bits
				0x01,									// bSamFreqType = 1 frequency
				0x40,0x1F,0x00,							// tSamFreq = 8KHz
				
				// Standard AS Isochronous Audio Data Endpoint Descriptor
				0x09, 								// bLength 
				0x05, 								// bDescriptorType = ENDPOINT_DESCRIPTOR
				AUDIO_STREAMING_TX_ENDPOINT | 0x80,		// bEndpointAddress = 3 - IN
				0x05, 								// bmAttributes = iso
				AUDIO_STREAMING_TX_SIZE, 0x00, 			// wMaxPacketSize
				AUDIO_STREAMING_TX_INTERVAL, 			// bInterval =  2^x ms
				0x00,									// bRefresh
				0x00,									// bSynchAddress
				
				// Class-Specific AS Isochronous Audio Data Endpoint Descriptor
				0x07,  									// bLength
				0x25,  									// bDescriptorType = CS_ENDPOINT 
				0x01,  									// bDescriptorSubtype = EP_GENERAL
				0x01,  									// bmAttributes = MaxPacketsOnly = FALSE
				0x00,  									// bLockDelayUnits
				0x00, 0x00,  							        // wLockDelay 

#endif
#endif

Total lenght of config descriptor is 9+38+52 (38 for AC interface, 52 for AS interface); The device, as configured by the descriptor, should present an Input Terminal, which is configured as a Microphone, and an Output Terminal, which is configured as an USB OUT streaming. Data flow should be:

Audio data ----> Input Terminal ----> Output Terminal ----> USB HOST

In theory, there should be a function that gets some audio data (be it from a wavetable, or input from an actual ADC connected to the Teensy, or whatever), puts it in a properly formed USB packet, and transmits it on the isochronous AUDIO_STREAMING_TX_ENDPOINT.

I also wrote a very small library which will contain functions to properly enumerate the device, and handle the isocrhonous endpoint:
this is the usb_audio.h header file, which contains variables, library methods, and class definition:
Code:
#ifndef USBaudio_h_
#define USBaudio_h_

#if defined (USB_AUDIO)

#include <inttypes.h>
#include "usb_dev.h"
#ifdef __cplusplus
extern "C"{
#endif
void usb_audio_send_audio();
void usb_audio_class_begin();
extern uint8_t audio_streaming_alt_setting;
#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
class usb_audio_class{
	public:
		void begin(){ usb_audio_class_begin(); };
		void sendAudio(){ usb_audio_send_audio(); };
		uint8_t getAltSetting(){ return audio_streaming_alt_setting;};
};

extern usb_audio_class Audio;

#endif // __cplusplus 

#endif // USB_AUDIO
#endif // USBaudio_h_

this is the usb_audio.c file, which contains actual implementation of methods and class:
Code:
#include "usb_dev.h"
#include "usb_audio.h"
#include "core_pins.h" // for yield(()
#ifdef AUDIO_CONTROL_INTERFACE

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

#if F_CPU == 96000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 596)
#elif F_CPU == 48000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 428)
#elif F_CPU == 24000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 262)
#endif

static usb_packet_t *streaming_packet=NULL;
static uint8_t streaming_transmit_previous_timeout=0;
static uint8_t streaming_noautoflush=0;
uint8_t audio_streaming_alt_setting = 0;

void usb_audio_send_audio(){
	uint16_t index, wait_count=0;
	streaming_noautoflush = 1;
	if (!streaming_packet) {
        	while (1) {
                	if (!usb_configuration) {
				//serial_print("error1\n");
                        	return;
                	}
                	if (usb_tx_packet_count(AUDIO_STREAMING_TX_ENDPOINT) < TX_PACKET_LIMIT) {
                        	streaming_packet = usb_malloc();
                        	if (streaming_packet) break;
                	}
                	if (++wait_count > TX_TIMEOUT || streaming_transmit_previous_timeout) {
                        	streaming_transmit_previous_timeout = 1;
				//serial_print("error2\n");
                        	return;
                	}
                	yield();
        	}
	}
	streaming_transmit_previous_timeout = 0;
	index = streaming_packet->index;
	uint8_t i = 0;
	for (i=0;i<8;i++){
		streaming_packet->buf[i] = i;
	}
	streaming_packet->len = AUDIO_STREAMING_TX_SIZE;
	usb_tx(AUDIO_STREAMING_TX_ENDPOINT, streaming_packet);
	streaming_packet = usb_malloc();
	streaming_noautoflush = 0;
}

void usb_audio_class_begin(){
	usb_init();
}

#endif

The usb_audio_send_audio() is copied directly from usb_seremu.c, and it's almost surely wrong, as I still have to figure out how to properly write on an isochronous endpoint; but that's not the problem. An Audio device, as stated in USB Audio 1.0, should support all standard Device Requests, and currently, the Teensy USB "stack" does not support all standard Device Requests; most of them are kinda useless to the actual audio function, but some look strictly necessary, like Set-Get Interface: these requests are used on interfaces which have alternate settings, to know the actual active alternate setting and to let the host select which alternate setting to activate. As you can see in the config descriptor, the AudioStreaming (AS) interface MUST have at least 2 alternate settings, the first one is alt.set. 0, and it's called a Zero Bandwidth setting, because it has no endpoints, and is mandatory for any Audio device; the second setting, which is alt.set. 1, is the real interface setting used to actually stream audio data on its endpoints (one endpoint in this case).

Like I said, this should be a very basic device to implement, but still, I get loads of errors. The device doesn't enumerate properly, sometimes it doesn't even get a valid usb_configuration. I checked like a hundred times, the USB config descriptor is well formed, and it closely follows USB specs. But I can't get the device even to start properly. I think some already defined request should need some modifications to suit the Audio class needs. Gotta investigate further.

By the way, I'm starting to think that I should try to put a totally vendor-specific interface, with one endpoint, and really try to write a WDM driver for this thing, altough the idea of getting mangled in Microsoft's arabic-looking APIs, and the fact that I should also write OS-specific drivers for every OS... oh my...
 
Little extra @PaulStoffregen: there is a mispelled define in usb_desc.h at line 43, it says ENDPOINT_TRANSIMIT , instead of TRANSMIT :)
 
I started debugging using an Arduino as a Serial to USB converter, to better understand what's going on. I noticed that using a preconfigured USB, like Serial or Midi or whatever, the Teensy receives one address request, and then fixes its address at 0001, while using my descriptor the Teensy receives two address requests, one for 0001 and one for 0003; then it receives another request for address 0001 and stalls. As soon as I have my pc near hand I'll post some detailed debugging info.
 
while using my descriptor the Teensy receives two address requests, one for 0001 and one for 0003; then it receives another request for address 0001 and stalls.

That's very strange. Normally the host is supposed to set the address only once.

I can't do much on this right now. But if you're still having trouble by the end of this week, please post the complete code and I'll try it here on a board with a USB protocol analyzer connected.
 
Paul, I got it to "work"; I really don't know why or how, but the Microsoft calculator always gets my calculations off by one when using the hex calculator, so I had a bad descriptor size, my fault :)

I also had a wrong SET_INTERFACE request case in usb_dev.c; as soon as I fixed that, it enumerated correctly. For now, it enumerates and sets the streaming interface alternate setting to the default one, the zero bandwitdh one. When I open the Audio Control Panel, and open the Input Devices panel, the Teensy gets a SET_INTERFACE to switch to the actual streaming interface; as soon as I do that, the Teensy keeps writing "err" to the debug serial, which by the way gets printed when status == USB_ISTAT_ERROR ; that's probably due to the fact that I still have to understand how to use an isocrhonous endpoint correctly; I assume that I should use a callback function when status == USB_INTEN_SOFTOKEN to signal a Start Of Frame to my library, and I should write some data proportional to the size of the samples (16 bits) and the sampling freq (8000 Hz) for every SOF I receive (every 1 ms). Actually, I should be sending 8 samples per SOF, or 16 bytes per SOF, for this particular case. I might need some help to understand how to fill a usb packet with data, and how to write on an endpoint; I'm sure that I'm one little step away from getting this to really work, but I don't know if I can do it without your help :(
 
Just a little update: I added a startup function that allocates a usb packet, and a callback function that whenever there's a SOF writes 16 bytes of nothing in the packet and sends it. I can record data coming through, but the debug serial keeps reading "err" like before. I'm posting the code here:

Code:
void usb_audio_flush_callback(void){
	if (!audio_streaming_alternate_setting) return;
	uint8_t i = 0;
	for (i=0;i<16;i++){
		if (i%2==0) *(streaming_packet->buf + i) = 0;
		else *(streaming_packet->buf + i) = 128;
	}
	streaming_packet->len = AUDIO_STREAMING_TX_SIZE;
	usb_tx(AUDIO_STREAMING_TX_ENDPOINT, streaming_packet);
}

void usb_audio_class_begin(){
	while (!usb_configuration){
		delay(1);
	}
	while (!streaming_packet){
		streaming_packet = usb_malloc();
	}
}
The variable usb_streaming_alternate_setting is set to 1 when the host asks the device to stream data through its isochronous endpoint.

The sketch does nothing more than calling usb_audio_class_begin() in the setup() function.

I don't know if I'm allocating/filling the packet correctly, please let me know.
 
Okay guys, I decided to put everyhing I've done until now on my GitHub account. The only step I've made since the last update is that I've modified usb_dev.c so that it doesn't toggle between Data0 and Data1 PIDs when dealing with the isochronous endpoint associated with the audio streaming interface, and I modified usb_desc.h adding two defines for TX and RX endpoints with NO handshake. This way, the isocrhonous stream of data flows almost as it should.

https://github.com/MickMad/Teensy3-USBAudio/

Just click the link and read the whole README file for more informations. You'll also find some hardware stuff, which is complementary to this whole usb audio stuff :) I'll keep you up.
 
Thanks MickMad. Eventually I'll work on USB audio, but not until at least an alpha release of the audio library is done. When I do, I'll start by taking a careful look at everything you've already started.
 
I'm thinking of a restyling of the current USB memory management system, to support 1023 bytes packets. I've found two lines of code, lines 675 and 684 of usb_dev.c that look like this:

Code:
table[index(i, RX, EVEN)].desc = BDT_DESC(64, 0);

I investigated chapter 40 of the Kinetis K20 specs, looked at the BDT_DESC() macro and found that modifying the 64 inside this macro call will set the Buffer Descriptor Table Byte Count field to another value. Maybe, adding a #DEFINE in usb_mem.h like MAX_PACKET_BUFFER_SIZE or whatever, that can be used as a value for the uint8_t[] buffer array found in the typedef of usb_packet_t in usb_mem.h, could help to customize the usb packet very easily. We could even add another #DEFINE to specify the datatype of a single buffer slot; you know, something like:

Code:
#DEFINE MAX_PACKET_BUFFER_SIZE 128

#DEFINE PACKET_BUFFER_SLOT_TYPE 1

typedef struct usb_packet_struct {
	uint16_t len;
	uint16_t index;
	struct usb_packet_struct *next;
#IF PACKET_BUFFER_SLOT_TYPE == 1
        uint8_t buf[MAX_PACKET_BUFFER_SIZE];
#elif PACKET_BUFFER_SLOT_TYPE == 2
       uint16_t buf[MAX_PACKET_BUFFER_SIZE];
#elif PACKET_BUFFER_SLOT_TYPE == 3
      uint32_t buf[MAX_PACKET_BUFFER_SIZE];
#elif ...
...
#endif

} usb_packet_t;

What do you think about that?? I will surely try this out tomorrow morning and post results there. Meanwhile, I'd like to call the attention to another topic I've opened, under Project Guidance, which is called USB Audio for Teensy 3.0. I'd like to keep USB-only stuff, like this stuff that may be needed regardless of the Audio functionality, in this topic, while keeping USB Audio relevant stuff, from abstract idea to actual implementation, in the other topic.

edit: I just found out that you can easily do #DEFINE myType uint8_t and then declare a variable with myType, the compiler will automatically replace every occurrence of "myType" with wathever I write in the define! That's sweet! I'll try to mod usb_mem with this method :)
 
Last edited:
Like I said, this should be a very basic device to implement, but still, I get loads of errors. The device doesn't enumerate properly, sometimes it doesn't even get a valid usb_configuration. I checked like a hundred times, the USB config descriptor is well formed, and it closely follows USB specs. But I can't get the device even to start properly. I think some already defined request should need some modifications to suit the Audio class needs. Gotta investigate further.usb io
I don't know about your business type but your can follow this leaning. http://forum.pjrc.com/images/smilies/confused.png
 
Last edited:
Like I said, this should be a very basic device to implement, but still, I get loads of errors. The device doesn't enumerate properly, sometimes it doesn't even get a valid usb_configuration. I checked like a hundred times, the USB config descriptor is well formed, and it closely follows USB specs. But I can't get the device even to start properly. I think some already defined request should need some modifications to suit the Audio class needs. Gotta investigate further.usb io
I don't know about your business type but your can follow this leaning. http://forum.pjrc.com/images/smilies/confused.png


That message was way early in development stage, I really had no idea of what I was talking about! :D
USB device implementation may not be basic at all, especially when dealing with audio, and with a development enviroment which gives no support to isochronous endpoint communication, which is no support for 33% of usb communication stuff. I'm doing what I can, I've started dealing with the descriptor, then with requests, got the basic functionality, then I also got the device to actually stream data to the host. Now I'm facing data issues, probably due to the usb memory management system, and surely for some other stuff that I'm currently missing.

I'm working by error and trial, and I have no business type: I'm a computer engineering student trying to graduate with a sweet final project. Check the other topic (as said in my previous post) to get updates on the general project; this one is for USB-only problems that interfere with the functionality of the USB Audio class implementation.
 
Hey guys, I got the Teensy to transmit packets greather than 64 bytes, in fact I got it to stream 192 bytes per packet; I also got it to use more than 8 bytes for each word in the buffer. Paul, I think that you could close this thread because the problem has actually been solved; I also think that this stuff should be integrated with the Teensy native library, maybe I should open a new topic in the Suggestion forum??
 
Hello,
I m planning to deep dive on this usb audio code and to try implement a 24bits/96khz . The audio is bulk transferered every milissecond which needs a buffer of 576 bytes.
can we configure the end point buffer to this size and let the dMA engine to queue each of these 576bytes packet into some circular buffer in order to have an asynchronous flow between the USB host and control the output over I2S with another clock ?
I guess yes and I ve tried to read the datasheet about buffers but thats very complex...

apreciate guidance or feedback , thank you!
 
Thanks MickMad. Eventually I'll work on USB audio, but not until at least an alpha release of the audio library is done. When I do, I'll start by taking a careful look at everything you've already started.

had you chance to touch this side of the project, i waiting for this over a year

only thing i want to do is to connect mic to audio board, maybe apply some effect (eg: compressor), connect teensy to usb, and record voice in live

connecting mic directly to computer produce noise, and especially in games, when gpu start works, internal recording stuff produce a lot electric static noise

so i predict, if voice would be recorded outside of PC and converted to digital data, i can get rid of this static noise

thx
 
Hi people, I took the project back on track and I'm working on integrating it with the Audio Library (FINALLY!). It's gonna take some time due to the tremendous amount of work to do:

- USB stuff on Teensy has changed quite a bit since my last update on the project, I need to move the descriptors in the correct place.

- I need to add a Synchronization endpoint to do 44.1 KHz audio (since USB audio handles 1 ms of data per transaction, I have to send 44 samples x 10 transactions, and 45 on the 11th)

- USB audio stream has to be running independently of the Audio library streams; because the Audio library works at 128 samples (2.90 ms) per transaction and USB Audio works at 44/45 samples (1 ms) per transaction, we need to implement a buffer mechanism that allows a seamless stream of USB data.

I thought for a lot of time on this and I decided that the USB Audio API will be basic, it will only provide two inputs and two outputs (still gotta decide whether the inputs and outputs will be separate like the I2S blocks or integrated, the former makes more sense but it makes also thinks more complex), no configuration will be available for the time being.

The repo is divided in two, I forked the Teensy cores as well as the Audio library because work on both is needed:

https://github.com/MickMad/cores

https://github.com/MickMad/Audio

My Audio fork also brings some new material to control the AK4558 CODEC for another project I'm currently working on (https://hackaday.io/project/8567-hifi-audio-codec-module)
 
MickMad: This is a superimpressive project. USB audio is way cool, and the amount of work is phenomenal, but to have a crack at the AK4558 CODEC ...that is another league ... i've already promised my first-born child to another forum member, so you will have to settle for my second-born ...any help I can offer other than children is a bit minimal (i'm a noob, basically) but I can admire your work if nothing else.
 
Quick answer: nope. I'm working on an Arduino Zero smaller clone and on an interactive badge based on it. It is going to take some time but I didn't forget about this, that's for sure.
 
Status
Not open for further replies.
Back
Top