I've managed to duplicate the configuration descriptor from a working sound card, with sound out + in and MIDI out + in. I captured with Wireshark and copied the data in the comments below, and then fixed it up by hand and used lsusb -v for more info as well. Aparently wireshark doesn't know about the MIDI streaming descriptors, but lsusb does. It is possible to write a tool that generates the C code, probably using libusb, but this time I did it by hand.
Next up is to duplicate the rest of the USB packets that are sent back to the host, by using wireshark to capture the USB packets to and from the working sound card, and doing the same thing in my STM32 code.
I've also managed to get a decent workflow with a ST-LINK v2 programmer, make, openocd and gdb.
(https://i.imgur.com/poQnUJR.png)
Here is my array:
#define AUDIO_SAMPLE_FREQ(frq) \
(uint8_t)(frq), \
(uint8_t)((frq >> 8)), \
(uint8_t)((frq >> 16))
#define AUDIO_PACKET_SZE(frq) \
(uint8_t)(((frq * 2 * 2)/1000) & 0xFF), \
(uint8_t)((((frq * 2 * 2)/1000) >> 8) & 0xFF)
#define FIELD_UINT16(a) (uint8_t)a, (uint8_t)(a >> 8)
#define FIELD_BCD(a, b) (uint8_t)b, (uint8_t)a
/* USB AUDIO device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_AUDIO_CfgDesc[USB_AUDIO_CONFIG_DESC_SIZ] __ALIGN_END = {
// CONFIGURATION DESCRIPTOR
9, // bLength
0x02, // bDescriptorType (CONFIGURATION)
FIELD_UINT16(255), // wTotalLength
4, // bNumInterfaces
1, // bConfigurationValue
0, // iConfiguration
0x80, // Configuration bmAttributes:
// 1... .... = Must be 1: Must be 1 for USB 1.1 and higher
// .0.. .... = Self-Powered: This device is powered from the USB bus
// ..0. .... = Remote Wakeup: This device does NOT support remote wakeup
250, // bMaxPower (500mA)
// INTERFACE DESCRIPTOR (0.0): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
0, // bInterfaceNumber
0, // bAlternateSetting
0, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x01, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// Class-specific Audio Control Interface Descriptor: Header Descriptor
11, // bLength
0x24, // bDescriptorType (audio class interface)
0x01, // Subtype: Header Descriptor
FIELD_BCD(1, 00), // Version: 1,00
FIELD_UINT16(53), // Total length
3, // Total number of interfaces
1, // Interface number
2, // Interface number
3, // Interface number
// Class-specific Audio Control Interface Descriptor: Input terminal descriptor
12, // bLength
0x24, // bDescriptorType (audio class interface)
0x02, // Subtype: Input terminal descriptor
1, // bTerminalID
FIELD_UINT16(0x101),// wTerminalType USB Streaming
0, // bAssocTerminal
2, // bNrChannels
FIELD_UINT16(3), // wChannelConfig
// Left Front (L)
// Right Front (R)
0, // iChannelNames
0, // iTerminal
// Class-specific Audio Control Interface Descriptor: Output terminal descriptor
9, // bLength
0x24, // bDescriptorType (audio class interface)
0x03, // Subtype: Output terminal descriptor
2, // bTerminalID
FIELD_UINT16(0x301),// wTerminalType Speaker
0, // bAssocTerminal
1, // bSourceID
0, // iTerminal
// Class-specific Audio Control Interface Descriptor: Input terminal descriptor
12, // bLength
0x24, // bDescriptorType (audio class interface)
0x02, // Subtype: Input terminal descriptor
3, // bTerminalID
FIELD_UINT16(0x201),// wTerminalType Microphone
0, // bAssocTerminal
2, // bNrChannels
FIELD_UINT16(0x0003),// wChannelConfig
//Left Front (L)
//Right Front (R)
0, // iChannelNames
0, // iTerminal
// Class-specific Audio Control Interface Descriptor: Output terminal descriptor
9, // bLength
0x24, // bDescriptorType (audio class interface)
0x03, // Subtype: Output terminal descriptor
4, // bTerminalID
FIELD_UINT16(0x101),// wTerminalType USB Streaming
0, // bAssocTerminal
3, // bSourceID
0, // iTerminal
// INTERFACE DESCRIPTOR (1.0): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
1, // bInterfaceNumber
0, // bAlternateSetting
0, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x02, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// INTERFACE DESCRIPTOR (1.1): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
1, // bInterfaceNumber
1, // bAlternateSetting
1, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x02, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// Class-specific Audio Streaming Interface Descriptor: General AS Descriptor
7, // bLength
0x24, // bDescriptorType (audio class interface)
0x01, // Subtype: General AS Descriptor
1, // Terminal ID
0, // Interface delay in frames
FIELD_UINT16(1), // Format
// Class-specific Audio Streaming Interface Descriptor: Format type descriptor
14, // bLength
0x24, // bDescriptorType (audio class interface)
0x02, // Subtype: Format type descriptor
1, // bFormatType (FORMAT_TYPE_I)
2, // bNrChannels
3, // bSubframeSize
24, // bBitResolution
2, // bSamFreqType Discrete
AUDIO_SAMPLE_FREQ(44100),
AUDIO_SAMPLE_FREQ(48000),
// ENDPOINT DESCRIPTOR
9, // bLength
0x05, // bDescriptorType (ENDPOINT)
0x01, // bEndpointAddress OUT Endpoint:1
// 0... .... = Direction: OUT Endpoint (0x01)
// .... 0001 = Endpoint Number (0x01)
0x09, // bmAttributes
// .... ..01 = Transfertype: Isochronous-Transfer
// .... 10.. = Synchronisationtype: Adaptive (0x02)
// ..00 .... = Behaviourtype: Data-Endpoint (0x00)
FIELD_UINT16(288), // wMaxPacketSize
// ...0 0... .... .... = Transactions per microframe: 1
// .... ..01 0010 0000 = Maximum Packet Size (288)
1, // bInterval
0,
0,
// Class-specific Audio Streaming Endpoint Descriptor
7, // bLength
0x25, // bDescriptorType (audio class endpoint)
0x01, // Subtype
0x01, // bmAttributes
//Sampling Frequency
0, // bLockDelayUnits Undefined
FIELD_UINT16(0), // wLockDelay Undefined
// INTERFACE DESCRIPTOR (2.0): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
2, // bInterfaceNumber
0, // bAlternateSetting
0, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x02, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// INTERFACE DESCRIPTOR (2.1): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
2, // bInterfaceNumber
1, // bAlternateSetting
1, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x02, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// Class-specific Audio Streaming Interface Descriptor: General AS Descriptor
7, // bLength
0x24, // bDescriptorType (audio class interface)
0x01, // Subtype: General AS Descriptor
4, // Terminal ID
0, // Interface delay in frames
FIELD_UINT16(1), // Format
// Class-specific Audio Streaming Interface Descriptor: Format type descriptor
14, // bLength
0x24, // bDescriptorType (audio class interface)
0x02, // Subtype: Format type descriptor
1, // bFormatType (FORMAT_TYPE_I)
2, // bNrChannels
3, // bSubframeSize
24, // bBitResolution
0, // bSamFreqType Continuous
AUDIO_SAMPLE_FREQ(44100), // tLowerSamFreq
AUDIO_SAMPLE_FREQ(48000), // tUpperSamFreq
// ENDPOINT DESCRIPTOR
9, // bLength
0x05, // bDescriptorType (ENDPOINT)
0x82, // bEndpointAddress IN Endpoint:2
// 1... .... = Direction: IN Endpoint (0x01)
// .... 0010 = Endpoint Number (0x02)
0x05, // bmAttributes
// .... ..01 = Transfertype: Isochronous-Transfer
// .... 01.. = Synchronisationtype: Asynchronous (0x01)
// ..00 .... = Behaviourtype: Data-Endpoint (0x00)
FIELD_UINT16(291), // wMaxPacketSize
// ...0 0... .... .... = Transactions per microframe: 1 (0)
// .... ..01 0010 0011 = Maximum Packet Size (291)
1, // bInterval
0, // bRefresh
0, // bSynchAddress
// AudioControl Endpoint Descriptor
7, // bLength
0x25, // bDescriptorType (audio class endpoint)
0x01, // Subtype
0x00, // bmAttributes
0, // bLockDelayUnits Undefined
FIELD_UINT16(0), // wLockDelay Undefined
// INTERFACE DESCRIPTOR (3.0): class Audio
9, // bLength
0x04, // bDescriptorType (INTERFACE)
3, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x01, // bInterfaceClass: Audio
0x03, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// MIDIStreaming Interface Descriptor
7, // bLength
0x24, // bDescriptorType (unknown)
1, // bDescriptorSubtype (HEADER)
FIELD_BCD(1, 00), // bcdADC .00
FIELD_UINT16(65), // wTotalLength
// MIDIStreaming Interface Descriptor
6, // bLength
0x24, // bDescriptorType (unknown)
2, // bDescriptorSubtype (MIDI_IN_JACK)
1, // bJackType Embedded
1, // bJackID
0, // iJack
// MIDIStreaming Interface Descriptor
6, // bLength
0x24, // bDescriptorType (unknown)
2, // bDescriptorSubtype (MIDI_IN_JACK)
2, // bJackType External
2, // bJackID
0, // iJack
// MIDIStreaming Interface Descriptor
9, // bLength
0x24, // bDescriptorType (unknown)
3, // bDescriptorSubtype (MIDI_OUT_JACK)
1, // bJackType Embedded
3, // bJackID
1, // bNrInputPins
2, // baSourceID( 0) 2
1, // BaSourcePin( 0) 1
0, // iJack
// MIDIStreaming Interface Descriptor
9, // bLength
0x24, // bDescriptorType (unknown)
3, // bDescriptorSubtype (MIDI_OUT_JACK)
2, // bJackType External
4, // bJackID
1, // bNrInputPins
1, // baSourceID( 0) 1
1, // BaSourcePin( 0) 1
0, // iJack
// ENDPOINT DESCRIPTOR
9, // bLength
0x05, // bDescriptorType (ENDPOINT)
0x03, // bEndpointAddress OUT Endpoint:3
// 0... .... = Direction: OUT Endpoint (0x02)
// .... 0011 = Endpoint Number (0x03)
0x02, // bmAttributes
// .... ..10 = Transfertype: Bulk-Transfer
FIELD_UINT16(16), // wMaxPacketSize
// .... ..00 0001 0000 = Maximum Packet Size (16)
0, // bInterval
0, // bRefresh
0, // bSynchAddress
// MIDIStreaming Endpoint Descriptor
5, // bLength
0x25, // bDescriptorType (unknown)
1, // bDescriptorSubtype (GENERAL)
1, // bNumEmbMIDIJack
1, // baAssocJackID( 0) 1
// ENDPOINT DESCRIPTOR
9, // bLength
0x05, // bDescriptorType (ENDPOINT)
0x84, // bEndpointAddress IN Endpoint:4
// 1... .... = Direction: IN Endpoint (0x02)
// .... 0100 = Endpoint Number: 0x04
0x02, // bmAttributes
// .... ..10 = Transfertype: Bulk-Transfer (0x02)
FIELD_UINT16(16), // wMaxPacketSize
// .... ..00 0001 0000 = Maximum Packet Size
0, // bInterval
0, // bRefresh
0, // bSynchAddress
// MIDIStreaming Endpoint Descriptor
5, // bLength
0x25, // bDescriptorType (unknown)
1, // bDescriptorSubtype (GENERAL)
1, // bNumEmbMIDIJack
3 // baAssocJackID(0)
};
Hooray! After adding the USB Audio Streaming descriptors to my code, and setting up the isochronous endpoint, I struggled to figure out how to use the libopencm3 functions, but finally figured it out, and now I've managed to actually get data from the device into the computer, by recording a wav file with arecord -D hw:1,0,0 -c 2 -f S16_LE -r 96000 -d 1 foo.wav
The wav file looks like a series of consecutive incrementing values:
00000000 52 49 46 46 24 dc 05 00 57 41 56 45 66 6d 74 20 |RIFF$...WAVEfmt |
00000010 10 00 00 00 01 00 02 00 00 77 01 00 00 dc 05 00 |.........w......|
00000020 04 00 10 00 64 61 74 61 00 dc 05 00 34 00 35 00 |....data....4.5.|
00000030 36 00 37 00 38 00 39 00 3a 00 3b 00 3c 00 3d 00 |6.7.8.9.:.;.<.=.|
00000040 3e 00 3f 00 40 00 41 00 42 00 43 00 44 00 45 00 |>.?.@.A.B.C.D.E.|
00000050 46 00 47 00 48 00 49 00 4a 00 4b 00 4c 00 4d 00 |F.G.H.I.J.K.L.M.|
00000060 4e 00 4f 00 50 00 51 00 52 00 53 00 54 00 55 00 |N.O.P.Q.R.S.T.U.|
00000070 56 00 57 00 58 00 59 00 5a 00 5b 00 5c 00 5d 00 |V.W.X.Y.Z.[.\.].|
00000080 5e 00 5f 00 40 00 00 00 80 00 08 88 00 01 04 00 |^._.@...........|
00000090 c0 00 00 88 40 01 80 01 a8 bb b1 9f b2 da ac cb |....@...........|
And the code looks like this:
int sending_iso = 0;
uint16_t audio_buf[96 * 2];
static void send_audiobuf(usbd_device *usbd_dev) {
int i;
for(i = 0; i < 96 * 2; i++) audio_buf[i] = i;
usbd_ep_write_packet(usbd_dev, 0x2, audio_buf, sizeof(audio_buf));
}
static void usbaudio_data_tx_cb(usbd_device *usbd_dev, uint8_t ep) {
if(ep == 2 && sending_iso) {
printf("usbaudio ep %p, %d tx\n", usbd_dev, ep);
send_audiobuf(usbd_dev);
}
}
int usbaudio_control_callback(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, usbd_control_complete_callback *complete) {
(void)buf;
(void)len;
printf("control_callback %p %02x %02x %02x\n", usbd_dev, req->bmRequestType, req->bRequest, req->wValue);
if((req->bmRequestType & 0x01) && req->bRequest == 0x0b) {
if(req->wValue == 1) {
// Begin sending isochronous data
sending_iso = 1;
printf("start sending iso\n");
} else {
sending_iso = 0;
printf("stop sending iso\n");
}
}
return 1;
}
whereas the callbacks are initialized thusly:
usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_ISOCHRONOUS, 96 * 2 * 2, usbaudio_data_tx_cb);
usbd_register_control_callback(
usbd_dev,
USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE,
USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
usbaudio_control_callback);
My problem next was how does the host select the samplerate, because in my case I have many sample rates:
#define AUDIO_SAMPLE_FREQ(frq) \
{ (uint8_t)(frq), (uint8_t)(((frq) >> 8)), (uint8_t)(((frq) >> 16)) }
static const struct {
struct usb_audio_streaming_header_descriptor header;
struct usb_audio_streaming_format_type_descriptor format_type;
uint8_t freq[4][3];
} __attribute__((packed)) audio_streaming_functional_descriptors = {
.header = {
.bLength = 7,
.bDescriptorType = 0x24,
.bDescriptorSubtype = 0x01,
.bTerminalID = 4,
.bInterfaceDelay = 0,
.wFormat = 1
},
.format_type = {
.bLength = 20,
.bDescriptorType = 0x24,
.bDescriptorSubtype = 0x02,
.bFormatType = 1,
.bNrChannels = 2,
.bSubframeSize = 2,
.bBitResolution = 16,
.bSamFreqType = 4
},
.freq = {
AUDIO_SAMPLE_FREQ(32000),
AUDIO_SAMPLE_FREQ(48000),
AUDIO_SAMPLE_FREQ(64000),
AUDIO_SAMPLE_FREQ(96000)
}
};
I have tried recording with another, working sound card that can do 48000 and 44100, but besides the SET INTERFACE request, there is no other data sent to it to select the sample rate. I found that quite strange. However, another USB sound interface that I own (08bb:2902 Texas Instruments PCM2902 Audio Codec) cleared up what I need to do - I need to have an interface descriptor with a different bAlternateSetting for each of the sample rates, each with its own endpoint.
So that's up next, and then figuring out how to get the data from the DAC, because the signal coming into it seems similar to I2S, but it has a strange delay, so I think I'll just do it somehow from software, perhaps with interrupts. Besides, even if it was I2S, the STM32F103RBT6, which is what is on the Olimex board, does not have support for it. I have ordered a couple of STM32RCT6's, but I am still waiting for a hot air station to arrive, so I can exchange them. They're pin compatible, and the C one has I2S.