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.