Hi
in my project I use the lm3s9b92 eval kit to connect a digital microphone via I2S. Now I want to send the whole raw data from the microphone to a pc. The sample rate of the microphone is 48 kHz and every sample has a resolution of 24 bit and will be stored in a 32 bit integer variable, so I will get a total amount of 1.536 MBit/s = 192 kByte/s. This is to much to use UART for the transfer, even if I would lower the sample rate. Therefor I want to use USB but I never developed an usb application before. I thought it would make sense to use isochronous transfer mode to have a continious and correctly ordered data stream send to the host. Unfortunately I don't know where to start. Where do I have to take care and how do I read out the send data on the host?Thank you for any kind of help.,
1.) Is it right that the whole enumartion would be handled by the USBDAudioInit() function which calls the USBDCDInit() function? Because in the "ubs_dev_audio" example there seems to be no additional code that deals with any kind of usb configuration processes. Also the usb peripheral won't be enabled explicitely.
2.) In the theory of usb, the host would send the device an IN token to signal the device that it can send new data from the IN endpoint fifo of the usb controller. Will this token be handled automaticly by the usb lib or do I have to add this functionality in my firmware?
3.) What interrupts can occur that I need to handle in my firmware?
Thank you for your fast reply.
Tsuneo ChinzeiYour questions are out of focus.With these Q & A, you don't get closer to real implementation so much.Ask right questions, if you don't like guided tour ;-)
Tell me please, what are the right questions? The whole library is very confusing to me.
The whole library is very confusing to me.
2) Enumeration by hostWhen host detects voltage change at D+ line (full-speed device), it starts enumeration sequence (requests). The outline of enumeration request sequence is,- bus reset- Get_Descriptor( DEVICE ) - to know bMaxPacket0- Set_Address- more Get_Descriptors for Device, Configuration set, and String descriptors- Set_Configuration- Class-specific requestsDepending on OS, number of bus resets and Get_Descriptor requests differ.In this enumeration course, these requests relates to our target source.- Set_Configuration request - ConfigChangeHandler() callback is called by the stackThis routine just passes "connection" event (USB_EVENT_CONNECTED) to user application AudioMessageHandler(). I don't like this macro name, because it's misleading with device plug in. USB_EVENT_CONFIGURED is better.- Class-specific requests - HandleRequests(), DataReceived()As this USB speaker offers mute and volume control on its Feature Unit descriptor, host puts a couple of class-specific request to ask current setting of these options, and to set them up.But, usually, USB microphone doesn't offer such Feature Unit options. Instead, if you would declare a couple of sampling rate options, 32k, 44.1k or 48kHz, you'll see these calls for Sampling Frequency Control.3) Audio streamingAt the start and end of audio streaming, host puts Set_Interface requests.USB spec has a rule that isoc endpoint should start with zero-bandwidth at the default interface. To make the isoc endpoint work, host switches interface of the endpoint to one of alternate interfaces. When streaming finishes, host recover the default interface. Set_Interface request switches target interface from default to alternate, vice-versa.At Set_Interface request, the stack calls InterfaceChange().This routine passes USBD_AUDIO_EVENT_IDLE event (to default interface), or USBD_AUDIO_EVENT_ACTIVE event (to alternate interface), to user application AudioMessageHandler().Also, this routine enables DMA (MAP_USBEndpointDMAEnable) for isoc OUT endpoint at alternate interface.Now that audio streaming starts at last :-)When a packet is received at the isoc OUT endpoint, and after the packet is copied to audio data buffer by DMA, HandleEndpoints() is called by the USB ISR on the stack - actually, this routine is always called at every USB event. Therefore, the code confirms that the call is caused by DMA, seeing MAP_uDMAChannelModeGet() as UDMA_MODE_STOP. And then, this routine reports USBD_AUDIO_EVENT_DATAOUT to application USBBufferCallback(). Also, it releases the endpoint buffer by MAP_USBDevEndpointDataAck() for the next packet.USBBufferCallback() (in usb_dev_audio.c) maintains audio data buffer, so that sound play speed matches to data coming speed over USB. And then, it calls USBAudioBufferOut() (in usbdaudio.c). USBAudioBufferOut() sets up DMA for next transaction.4) Disconnection of deviceHandleDisconnect() is called from the stack, when device disconnects from bus.This routine just passes disconnection event (USB_EVENT_DISCONNECTED) to user application AudioMessageHandler().5) Routines not used on your implementationHandleDevice() - For composite device configurationUSBDAudioTerm() - not called from anywhereOK, any question?Tsuneo
So, I really must say thank you for your great effort :-)
Your first answer, I immediately put into action, the second I'll work through tomorrow and then I'll ask you my questions ;-)
Tsuneo Chinzei2) Enumeration by host ...
Do I understand it right that, in fact, I don't need to take care about the enumeration because the stack will handle the biggest part of it?
Tsuneo Chinzei- Class-specific requests - HandleRequests(), DataReceived()As this USB speaker offers mute and volume control on its Feature Unit descriptor, host puts a couple of class-specific request to ask current setting of these options, and to set them up.But, usually, USB microphone doesn't offer such Feature Unit options. Instead, if you would declare a couple of sampling rate options, 32k, 44.1k or 48kHz, you'll see these calls for Sampling Frequency Control.
What would be the most useful way to deal with this functions? I only use 48kHz so there is need for an option to change the sampling frequency.
Tsuneo ChinzeiAlso, this routine enables DMA (MAP_USBEndpointDMAEnable) for isoc OUT endpoint at alternate interface.
In this line I changed USB_EP_DEV_OUT to USB_EP_DEV_IN. Should be the necessary change at this point.
Tsuneo ChinzeiNow that audio streaming starts at last :-)When a packet is received at the isoc OUT endpoint, and after the packet is copied to audio data buffer by DMA, HandleEndpoints() is called by the USB ISR on the stack - actually, this routine is always called at every USB event. Therefore, the code confirms that the call is caused by DMA, seeing MAP_uDMAChannelModeGet() as UDMA_MODE_STOP. And then, this routine reports USBD_AUDIO_EVENT_DATAOUT to application USBBufferCallback(). Also, it releases the endpoint buffer by MAP_USBDevEndpointDataAck() for the next packet.USBBufferCallback() (in usb_dev_audio.c) maintains audio data buffer, so that sound play speed matches to data coming speed over USB. And then, it calls USBAudioBufferOut() (in usbdaudio.c). USBAudioBufferOut() sets up DMA for next transaction.
In fact I "only" need to change the direction, right? So in every function I have to change the order of source and sink of the DMA transfers, I think. But what is the next task?
Beside this, I read out the I2S buffer by DMA in ping pong mode. The DMA will cause an I2S interrupt if one of the two ping pong buffers is full. I think filling the usb fifo in this ISR might be a good, right?
Once again, thank you for your very good help :-)
Tsuneo Chinzei2) Enumeration by host ...Do I understand it right that, in fact, I don't need to take care about the enumeration because the stack will handle the biggest part of it?
- Class-specific requests - HandleRequests(), DataReceived()What would be the most useful way to deal with this functions? I only use 48kHz so there is need for an option to change the sampling frequency.
- Class-specific requests - HandleRequests(), DataReceived()
Also, this routine enables DMA (MAP_USBEndpointDMAEnable) for isoc OUT endpoint at alternate interface.In this line I changed USB_EP_DEV_OUT to USB_EP_DEV_IN. Should be the necessary change at this point.
Also, this routine enables DMA (MAP_USBEndpointDMAEnable) for isoc OUT endpoint at alternate interface.
In fact I "only" need to change the direction, right?
Umm..Above "Asynchronous" synchronization may be too complicated for the first-time project, though audio maniacs prefer this one because of fixed I2S clock frequency."Synchronous" synchronization method may be easier. The original speaker example also works on this synchronization type. In this method, I2S clock frequency is tuned so that the data production rate of I2S matches to the rate of USB transfer. You may apply ping-pong buffering of I2S with a little modification. Also, USB-uDMA is applied on the USB side, because the packet size is always fixed to 48 samples.Host puts Set_Interface request at the start of streaming, which is caught by InterfaceChange(). In this routine,- I2S starts.The Stellaris I2S runs as a master. Instead of two-buffers ping-pong, the I2S fills four (or more) buffers in turn. We assign greater number of buffers to make room, so that overflow/underflow doesn't immediately occur. The buffers are "pre-charged" by I2S, until isoc transactions start by host, regardless of overwrite on old data.- The isoc IN endpoint is loaded with "silent" (all zero) packet of 48 samples.With this "dummy" packet, we know the timing when the real isoc streaming starts.Host starts isoc transactions after around 20 frames delay from Set_Interface.We know this timing with HandleEndpoints(). In this routine,- We start DMA so that it fills the isoc IN endpoint with 48 samples packet from one of the "pre-charged" buffers. At the first time call of this routine, a buffer of two delay from the current I2S target is chosen. At the second and later call, the next buffer from the last call is transferred.- This routine raises a flag, to start tuning process at next SOF timing.On the SOF interrupt,- The I2S target buffer and transferred buffer is compared. To keep the distance of these buffers, I2S clock is tuned, like original USBBufferCallback() does.Tsuneo
Tsuneo ChinzeiHost puts Set_Interface request at the start of streaming, which is caught by InterfaceChange(). In this routine,- I2S starts.
So I have to add the I2S start code in the else branche, where the USBD_AUDIO_EVENT_ACTIVE event occurs?
Tsuneo ChinzeiThe Stellaris I2S runs as a master. Instead of two-buffers ping-pong, the I2S fills four (or more) buffers in turn. We assign greater number of buffers to make room, so that overflow/underflow doesn't immediately occur. The buffers are "pre-charged" by I2S, until isoc transactions start by host, regardless of overwrite on old data.
What should be a usefull buffer size? Four (or more) arrays containing 48 samples (long buffer[4][48])?
Tsuneo Chinzei- The isoc IN endpoint is loaded with "silent" (all zero) packet of 48 samples.With this "dummy" packet, we know the timing when the real isoc streaming starts.
Should this be done by using USBEndpointDataPut() in the InterfaceChange()?
Tsuneo ChinzeiThis routine raises a flag, to start tuning process at next SOF timing
You mean the line "psInst->sBuffer.pfnCallback(pucData, psInst->sBuffer.ulSize, USBD_AUDIO_EVENT_DATAOUT);" which calls the USBBufferCallback() ?
Tsuneo ChinzeiOn the SOF interrupt,- The I2S target buffer and transferred buffer is compared. To keep the distance of these buffers, I2S clock is tuned, like original USBBufferCallback() does.
So the distance between I2S target buffer and the one that should be transfered by usb should be one, right?
I hope you have enough energy to endure my beginner questions :-)
Host puts Set_Interface request at the start of streaming, which is caught by InterfaceChange(). In this routine,- I2S starts.So I have to add the I2S start code in the else branche, where the USBD_AUDIO_EVENT_ACTIVE event occurs?
Host puts Set_Interface request at the start of streaming, which is caught by InterfaceChange(). In this routine,- I2S starts.
The Stellaris I2S runs as a master. Instead of two-buffers ping-pong, the I2S fills four (or more) buffers in turn. We assign greater number of buffers to make room, so that overflow/underflow doesn't immediately occur. The buffers are "pre-charged" by I2S, until isoc transactions start by host, regardless of overwrite on old data.What should be a usefull buffer size? Four (or more) arrays containing 48 samples (long buffer[4][48])?
The Stellaris I2S runs as a master. Instead of two-buffers ping-pong, the I2S fills four (or more) buffers in turn. We assign greater number of buffers to make room, so that overflow/underflow doesn't immediately occur. The buffers are "pre-charged" by I2S, until isoc transactions start by host, regardless of overwrite on old data.
- The isoc IN endpoint is loaded with "silent" (all zero) packet of 48 samples.With this "dummy" packet, we know the timing when the real isoc streaming starts.Should this be done by using USBEndpointDataPut() in the InterfaceChange()?
- The isoc IN endpoint is loaded with "silent" (all zero) packet of 48 samples.With this "dummy" packet, we know the timing when the real isoc streaming starts.
This routine raises a flag, to start tuning process at next SOF timingYou mean the line "psInst->sBuffer.pfnCallback(pucData, psInst->sBuffer.ulSize, USBD_AUDIO_EVENT_DATAOUT);" which calls the USBBufferCallback() ?
This routine raises a flag, to start tuning process at next SOF timing
On the SOF interrupt,- The I2S target buffer and transferred buffer is compared. To keep the distance of these buffers, I2S clock is tuned, like original USBBufferCallback() does.So the distance between I2S target buffer and the one that should be transfered by usb should be one, right?
On the SOF interrupt,- The I2S target buffer and transferred buffer is compared. To keep the distance of these buffers, I2S clock is tuned, like original USBBufferCallback() does.
Tsuneo ChinzeiIs the microphone monaural, 24 bits?Then "long buffer[20][48];" is fine, when you declare TypeI format descriptor, as follows.The 24 bits should be "left-justified" in 4-bytes "long", ie. the LSB one byte is 0.With this setting, the buffer is passed to endpoint without any conversion.
Thats right, 24 Bit mono left-justified.
Tsuneo ChinzeiExactly. USBEndpointDataPut() copies data to the endpoint buffer.Set up USB_EP_AUTO_SET to the isoc IN endpoint. And then, when the entire packet is written, the buffer is automatically validated without calling USBEndpointDataSend() - In this "Synchronous" method, we apply fixed size packet, which matches to wMaxPacketSize of the endpoint.usbdaudio.cconst tFIFOConfig g_sUSBAudioFIFOConfig ={ // // IN endpoints. // { { false, USB_EP_DEV_IN | USB_EP_AUTO_SET }, // <---- { false, USB_EP_DEV_IN }, ...If you would feel DMA setting troublesome, you may apply USBEndpointDataPut() for every packet copy. The packet size is not so large.
Is #define AUDIO_PACKET_SIZE (((48000*4)/1000) * 2) and wMaxPacketSize the same?
The usb controller offers one fifo for all endpoints that I can configure and split, as I understand right. Where/how do I have to configure the fifo size or would the whole fifo space automaticly be used if I only use one endpoint?
I think it might be reasonable to directly use DMA so I set
// // IN endpoints. // { { false, USB_EP_DEV_IN | USB_EP_DMA_MODE_1 | USB_EP_AUTO_SET}, { false, USB_EP_DEV_IN },
Tsuneo ChinzeiNo, we don't call USBBufferCallback() here. The tuning function of USBBufferCallback() moves to the SOF ISR. Instead, we make a new flag, which activates tuning function. This flag is enabled here.
So the HandleEndpoints() would set the flag? And the body of the USBBufferCallback() routine moves to the SOF ISR? Which one is the SOF ISR?
Tsuneo ChinzeiNow, your questions are well focused.Good job.
That's all your earnings :-)
Which one is the SOF ISR?
Tsuneo ChinzeiSo the HandleEndpoints() would set the flag? And the body of the USBBufferCallback() routine moves to the SOF ISR? Which one is the SOF ISR?Exactly.Which one is the SOF ISR?The stack enables SOF interrupt, but it doesn't expose direct callback to users.The USB ISR is modified to call our SOF ISR, as follows.usbdhandler.cvoidUSB0DeviceIntHandler(void){ unsigned long ulStatus; // // Get the controller interrupt status. // ulStatus = MAP_USBIntStatusControl(USB0_BASE); // <------- insert from here // // Start of Frame was received. // if(ulStatus & USB_INTCTRL_SOF) { // call our SOF ISR here } // <------- to here // // Call the internal handler. // USBDeviceIntHandlerInternal(0, ulStatus);}
So as a result, I can replace "// call our SOF ISR here" by the body of the USBBufferCallback() routine? And how would setting the flag in the HandleEndpoints() look like?
EDIT: what's about HandleEndpoints() routine in general? I think this is the right place to start new DMA transfers for filling the fifo with data buffers from the I2S, isn't it?
So as a result, I can replace "// call our SOF ISR here" by the body of the USBBufferCallback() routine?
And how would setting the flag in the HandleEndpoints() look like?
what's about HandleEndpoints() routine in general? I think this is the right place to start new DMA transfers for filling the fifo with data buffers from the I2S, isn't it?
Tsuneo Chinzeiusbdhandler.cvoidUSB0DeviceIntHandler(void){ unsigned long ulStatus; // // Get the controller interrupt status. // ulStatus = MAP_USBIntStatusControl(USB0_BASE); // <------- insert from here // // Start of Frame was received. // if(ulStatus & USB_INTCTRL_SOF) { // call our SOF ISR here } // <------- to here // // Call the internal handler. // USBDeviceIntHandlerInternal(0, ulStatus);}
One thing I don't understand yet. If the USB0DeviceIntHandler would call the SOF ISR everytime the ulStatus & USB_INTCTRL_SOF condition is true, why should the HandleEndpoints() routine explicitely raise a flag to start the tuning process?
This is my current HandleEndpoints() routine. Beside the thing about the SOF ISR flag, this should work, shouldn't it?
static voidHandleEndpoints(void *pvInstance, unsigned long ulStatus){ unsigned long ulEPStatus; tAudioInstance *psInst; unsigned char *pucData; const tUSBDAudioDevice *psDevice; ASSERT(pvInstance != 0); // // Create the instance pointer. // psDevice = (const tUSBDAudioDevice *)pvInstance; // // Make a copy of this pointer for ease of use later in this function. // psInst = psDevice->psPrivateData; // // Make sure this was for the isochronous IN endpoint. // if(ulStatus & (0x10000 << USB_EP_TO_INDEX(psInst->ucINEndpoint))) { // fill endpoint with next packet // // Configure and enable DMA for the OUT transfer. // MAP_uDMAChannelTransferSet(psInst->ucINDMA, UDMA_MODE_BASIC, MyPufferPlaceholder, (void *)USBFIFOAddrGet(USB0_BASE,psInst->ucINEndpoint), ulSize >> 2); // // Start the DMA transfer. // MAP_uDMAChannelEnable(psInst->ucINDMA); }
}
Of course I renamed all "...OUT..." to "...IN...". Maybe I should ask for UDMA_MODE_STOP? And what about the endpoint status request in the original HandlEnpoints()?