This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

usblib uDMAUSBTransfer() modifications

Other Parts Discussed in Thread: TM4C1232C3PM

I'm programming the tm4c1232c3pm and have attached an I2C DAC and a small speaker.

I got the audio example to work (after porting from older example), but the example has 48kHz samplerate and is stereo. I changed the samplerate to 16kHz mono, since the 400kHz I2C device needs 18 clocks per sample and that gives a maximum samplerate of 22.2kHz. I don't want to waste cycles downconverting and anti-aliasing on the micro.

The device now recognizes correctly as a 16kHz device, and volume and mute controls work, but I don't get any data. I debugged this a bit and found the reason in uDMAUSBTransfer(), it returns from this code at the beginning of the function:

    if((ui32Size < 64) || ((uint32_t)pvBuffer & 0x3))
    {
        return(0);
    }


Since 16kHz mono at 16bits only requires 32 bytes per frame, the check "ui32Size < 64" is true. What is the reasoning behind this check?

Furthermore, I was browsing through this function and found this:

    if((psUSBDMAInst->pui32Config[ui32Channel - 1] & UDMA_SIZE_32) ==
       UDMA_SIZE_32)
    {
        ui32TransferCount >>= 2;
    }
    else if((psUSBDMAInst->pui32Config[ui32Channel - 1] & UDMA_SIZE_32) ==
            UDMA_SIZE_32)
    {
        ui32TransferCount >>= 1;
    }

The conditions on the if and else if branches are exactly the same, so the else if branch will never be executed.

  • Esa-Pekka Pyokkimies said:
    the check "ui32Size < 64" is true. What is the reasoning behind this check?



    I believe the stack code strictly follows this sentence in the TM4C123x data sheet,

    18.3.4 DMA Operation
    If the number of bytes to transfer is less than 64, then a programmed I/O method must be used to copy the data to or from the FIFO.

    Esa-Pekka Pyokkimies said:
    Furthermore, I was browsing through this function and found this:

    Are you playing with the latest TivaWare version, TivaWare_C_Series-2.1.0.12573?
    The second "if" clause should apply UDMA_SIZE_16, instead of UDMA_SIZE_32

    else if((psUSBDMAInst->pui32Config[ui32Channel - 1] & UDMA_SIZE_16) ==
                UDMA_SIZE_16)

    OR, additional UDMA_SIZE_MASK definition gives better code.

    #define UDMA_SIZE_8             0x00000000
    #define UDMA_SIZE_16            0x11000000
    #define UDMA_SIZE_32            0x22000000
    #define UDMA_SIZE_MASK          0x33000000

    else if((psUSBDMAInst->pui32Config[ui32Channel - 1] & UDMA_SIZE_MASK) ==
                UDMA_SIZE_16)

    Tsuneo

  • Tsuneo Chinzei said:

    18.3.4 DMA Operation
    If the number of bytes to transfer is less than 64, then a programmed I/O method must be used to copy the data to or from the FIFO.

    OK thanks. However, I tried doing DMA transfers with the 32 bytes and it seemed to work fine. Maybe I should try transferring 2 packets at once so I would get 64 bytes of data, but this requires more modifications.

    Tsuneo Chinzei said:

    Are you playing with the latest TivaWare version, TivaWare_C_Series-2.1.0.12573?
    The second "if" clause should apply UDMA_SIZE_16, instead of UDMA_SIZE_32

    else if((psUSBDMAInst->pui32Config[ui32Channel - 1] & UDMA_SIZE_16) ==
                UDMA_SIZE_16)

    Actually I was using the previous version, but the newest version has the same issue.

    Esa-Pekka

  • Tsuneo Chinzei said:

    I believe the stack code strictly follows this sentence in the TM4C123x data sheet,

    18.3.4 DMA Operation
    If the number of bytes to transfer is less than 64, then a programmed I/O method must be used to copy the data to or from the FIFO.

    Now that I have read the chapter more carefully, I think the 64 byte limit refers to an example, so using 32 bytes should be fine if the FIFO size is also 32:

    For example, if the USB endpoint is configured with a FIFO size of 64 bytes, the μDMA channel can be used to transfer 64 bytes to or from the endpoint FIFO. If the number of bytes to transfer is less than 64, then a programmed I/O method must be used to copy the data to or from the FIFO.

  • Anyway, I don’t recommend DMA for the audio isoc EPs, either IN or OUT,
    if you would write commercial-quality coding, in which you take care of synchronization and missing packet handling.

    The reasons are,

    1) DMA may delay SOF interrupt timing.
    As the USB SIE on TM4C123/129 doesn’t have hardware SOF trigger, the firmware should detect exact SOF timing by SOF interrupt, to synchronize to host. DMA may delay this SOF interrupt process.

    2) DMA makes process of missing packet difficult.
    DMA assumes that isoc packets are regularly transferred from/to host without any drop.
    When a packet would drop by line noise, the firmware fills the buffer with dummy packet instead of scheduled incoming packet. The buffer pointer on the DMA controller has to be moved. DMA controller should be touched just after SOF timing, in which no isoc transaction comes. But this timing overlaps above requirement 1).

    Tsuneo

  • Thanks for the heads-up. I was planning to use a ring-buffer, which would empty itself at 16kHz rate using a timer, and filled via DMA completition interrupt (volume needs to be applied to the data before inserting into the ring buffer). The emptying rate would first be initialized from the main oscillator, but then modified slightly so that the buffer stays roughly half-full all the time.

    What do you mean that there is no SOF interrupt? Do you mean something different from this:

    17.3.1.7 Start-of-Frame
    When the SOF packet is received, the 11-bit frame number contained in the packet is written into the USB Frame Value (USBFRAME) register, and an SOF interrupt is also signaled and can be handled by the application.

    I was under the impression that DMA happens as a background process, and if a SOF interrupt comes, it can be handled immediately. So I don't understand why DMA delays the SOF interrupt process, or any interrupt for that matter.

    I would like to configure the DMA to start only if we new get data, so the audio would only have skips in case of missing data and not garbage.

    Esa-Pekka

  • The emptying rate would first be initialized from the main oscillator, but then modified slightly so that the buffer stays roughly half-full all the time.


    Your plan sounds like an orthodox one.
    In this plan, the problem lies in the timing when your firmware detects delay/advance of the sampling clock. Many USB examples, including Tiva-/Stellaris-Ware, detect it at transaction completion interrupt (or DMA completion interrupt). This method introduces jitter to the sampling clock, because the timing of transaction completion is not regular. It moves around in each USB frame by host bus scheduling.

    For example, USB speaker often has an adiitional HID interface for volume/mute control. To this HID interface, host feeds interrupt IN transactions regularly, but not on each frame (according to bInterval field of the endpoint descriptor). In this scenario, the transaction timing on the bus look like,

    Frame1: SOF -- Isoc transaction --------------------------- SOF
    Frame2: SOF -- Interrupt transaction -- Isoc transaction -- SOF
    Frame3: SOF -- Isoc transaction --------------------------- SOF
    ...

    PC host controller places interrupt transactions before isoc transactions, just when interrupt transaction occurs. In this way, the completion timing of Isoc transactions moves around in each frame.

    Regular timing is given by SOF interrupt. Therefore, you have to tune the sampling clock in the SOF ISR.


    What do you mean that there is no SOF interrupt?

    I wrote no hardware SOF trigger, instead of no SOF interrupt.
    That is "SOF output" pin.
    If the USB engine would have a "SOF output" pin, we could feed this signal to a timer/capture trigger. And then, we could make up a digital PLL, which generates the sampling clock, synchronized to SOF, with a little firmware code.

    As the Tiva/Stellaris USB engine doesn't have "SOF output" pin, we have to make a soft-PLL using SOF interrupt, with less stability than above hardware apporach.

    I was under the impression that DMA happens as a background process,

    While DMA occupies the SRAM access, the SOF ISR code, which also accesses to the SRAM, should be defferred.

    I would like to configure the DMA to start only if we new get data, so the audio would only have skips in case of missing data and not garbage.

    When do you stop the audio output, and when do you start it again, if missing packet occurs?
    In your scenario, you have to give the exact timing of stop/start audio output.
    Dummy (silent) data without stopping audio output, is much easier than your scenario.

    Tsuneo

  • Your plan sounds like an orthodox one.
    In this plan, the problem lies in the timing when your firmware detects delay/advance of the sampling clock. Many USB examples, including Tiva-/Stellaris-Ware, detect it at transaction completion interrupt (or DMA completion interrupt). This method introduces jitter to the sampling clock, because the timing of transaction completion is not regular. It moves around in each USB frame by host bus scheduling.

    I agree that in general, using DMA completion interrupt for timing purposes can introduce jitter. However I plan to modify the ring buffers emptying rate only slowly to match the incoming data rate. In this way small variations in the DMA transaction latency will not be noticeable.


    For example, USB speaker often has an adiitional HID interface for volume/mute control. To this HID interface, host feeds interrupt IN transactions regularly, but not on each frame (according to bInterval field of the endpoint descriptor).

    In the speaker case, we have an interrupt OUT pipe, where the host only sends packets when the volume has changed. Therefore it would introduce jitter only when changing the volume. With an interrupt IN pipe it would indeed be called regularly and would introduce jitter if bInterval is not one.


    I wrote no hardware SOF trigger, instead of no SOF interrupt.
    That is "SOF output" pin.
    If the USB engine would have a "SOF output" pin, we could feed this signal to a timer/capture trigger. And then, we could make up a digital PLL, which generates the sampling clock, synchronized to SOF, with a little firmware code.

    As the Tiva/Stellaris USB engine doesn't have "SOF output" pin, we have to make a soft-PLL using SOF interrupt, with less stability than above hardware apporach.

    Thanks for clarifying.


    While DMA occupies the SRAM access, the SOF ISR code, which also accesses to the SRAM, should be defferred.

    8.2 Functional Description

    The μDMA controller's usage of the bus is always subordinate to the processor core, so it never holds up a bus transaction by the processor.

    Looking at the Figure 1-1. "Tiva TM4C1232C3PM Microcontroller High-Level Block Diagram" , I can see that SRAM is on the bus matrix, so I assume that processor SRAM accesses should take priority over uDMA. This would mean that using SOF ISR while DMA transfer is going on would not cause jitter.


    When do you stop the audio output, and when do you start it again, if missing packet occurs?
    In your scenario, you have to give the exact timing of stop/start audio output.
    Dummy (silent) data without stopping audio output, is much easier than your scenario.

    I will make the buffer big enough so that a single missing packet would not cause the audio output to stop, just skip a bit. The silent data route may also be reasonable.

    Esa-Pekka

  • However I plan to modify the ring buffers emptying rate only slowly to match the incoming data rate. In this way small variations in the DMA transaction latency will not be noticeable.


    I said that the isoc transaction timing and following DMA timing (triggered by transaction completion) moves around in each USB frame. I don't touch to "DMA transaction latency" here.

    Also, slow tracking to bus timing (ie. SOF) results in requirement of large buffer and long delay.

    In the speaker case, we have an interrupt OUT pipe, where the host only sends packets when the volume has changed.

    Huh?
    Have you written a custom PC driver just for your device?

    USB audio spec suggests that volume/mute change should be tuned by Feature Unit on the speaker device. Set_Cur request over the default endpoint carries volume/mute change. This feature is supported by standard USB audio driver in all of major OS. Tiva-/Sterallis-Ware examples have already implemented this feature. Also, the OS's volume control GUI directly connects to this feature. I recommend you to get back to the standard, to make your way easy ;-)

    Therefore it would introduce jitter only when changing the volume. With an interrupt IN pipe it would indeed be called regularly and would introduce jitter if bInterval is not one.

    USB mouse / keyboard on the bus also affect to the isoc transaction timing.

    8.2 Functional Description

    Ah, I see. I was wrong.

    By the way, this uDMA "feature" and monolithic SRAM suggest single-layer AHB.
    It makes Tiva-C/Stellaris less attractive, whereas competitors have already introduced multi-layer AHB with separate SRAM banks for high-speed peripherals.

    Tsuneo

  • Tsuneo Chinzei said:

    Also, slow tracking to bus timing (ie. SOF) results in requirement of large buffer and long delay.

    I am now using 256 byte ring buffer for the audio, and using proportional control (P from PID) to modify the
     timer frequency. The buffer stays about half full when streaming from host. The stable frequency seems to be +2Hz from the nominal.


    Have you written a custom PC driver just for your device?

    No, I'm using the standard audio interface, but because the information flow of volume is from host to device, doesn't that mean that it must be an OUT pipe. And pipes are unidirectional, therefore the host needs to only generate packets when the volume has changed. Am I wrong?

    Esa-Pekka

  • You may do just with two-packets buffer, if you would take the synch-to-SOF method.
    Also, the buffering delay reduces into just one frame.

    Synch to transaction (DMA) completion doesn’t start until the first isoc transaction comes from host. It takes a couple of frames to be stabilized, while audio output goes on. The sound is distorted at the start of play.

    On the other hand, Synch-to-SOF method is applied before isoc transaction starts, because SOF is continuously put by host after the bus reset of device enumeration. The sound plays on the stabilized sampling clock at the first note.

    Synch-to-SOF method counts SOF interval using a timer, driven by system clock. The count is divided by sample numbers at a frame (or frames), and divided values are fed to another timer to generate sampling clock.

    For example, 10 frames of SOF intervals counts up to 500,021 counts over 50 MHz SysClock.
    500,021 counts / (32 samples/frame * 10 frames) = 1562 counts / sample .. 181 remainder

    To the the sampling clock counter (generator), 1563 counts (base+1) are provided for the first 181 intervals, and 1562 base counts for the rest intervals.

    50MHz / 1562 = 32,010.2 Hz
    50MHz / 1563 = 31,989.7 Hz
    The frequency error is, +/-10Hz
    This amount of error is unavoidable, unless the SysClock is greater enough.

    In this way, the sampling clock is generated without any tuning factor.

    The stable frequency seems to be +2Hz from the nominal.

    How did you measure it?
    250 MHz SysClock, at least, is required to achieve this number.

    In the speaker case, we have an interrupt OUT pipe..
    No, I'm using the standard audio interface

    Your discussion often goes out of track.
    You said that an interrupt OUT pipe turns the volume of your device.
    Where is any interrupt OUT pipe on the standard audio interfaces?

    Tsuneo


  • Tsuneo Chinzei said:

    Synch-to-SOF method counts SOF interval using a timer, driven by system clock. The count is divided by sample numbers at a frame (or frames), and divided values are fed to another timer to generate sampling

    clock.

    For example, 10 frames of SOF intervals counts up to 500,021 counts over 50 MHz SysClock.
    500,021 counts / (32 samples/frame * 10 frames) = 1562 counts / sample .. 181 remainder

    To the the sampling clock counter (generator), 1563 counts (base+1) are provided for the first 181 intervals, and 1562 base counts for the rest intervals.

    50MHz / 1562 = 32,010.2 Hz
    50MHz / 1563 = 31,989.7 Hz
    The frequency error is, +/-10Hz
    This amount of error is unavoidable, unless the SysClock is greater enough.

    In this way, the sampling clock is generated without any tuning factor.


    Thanks for the idea. It sounds like a good one. I will try that at some point. Now I'm just happy that I have a working implementation at all.

    The stable frequency seems to be +2Hz from the nominal.

    How did you measure it?
    250 MHz SysClock, at least, is required to achieve this number.

    [/quote]

    I'm now using 8kHz sample rate to further relax timing and also the speaker is really small.
    50MHz/6250 = 8000Hz, 50MHz/6249 = 8001.3Hz. The counter value settles at about 6248.


    Tsuneo Chinzei said:

    You said that an interrupt OUT pipe turns the volume of your device.
    Where is any interrupt OUT pipe on the standard audio interfaces?


    You were talking about interrupt IN before:

    Tsuneo Chinzei said:

    For example, USB speaker often has an adiitional HID interface for volume/mute control. To this HID interface, host feeds interrupt IN transactions regularly


    To get data from host to device, we need an OUT pipe, do we not? IN is device to host. OUT is host to device.

    Esa-Pekka

  • As I wrote in above post, host passes volume/mute setting to the speaker device using Set_Cur class request over the default endpoint.

    On the TivaWare stack, this is the supposed way.

    1) Feature Unit Descriptor declares the device capability of volume / mute setup

    \TivaWare_C_Series-2.1.0.12573\usblib\device\usbdaudio.c

    const uint8_t g_pui8AudioControlInterface[CONTROLINTERFACE_SIZE] =
    {
        ...
        //
        // Audio Feature Unit Descriptor
        //
        13,                         // The size of this descriptor.
        USB_DTYPE_CS_INTERFACE,     // Interface descriptor is class specific.
        USB_ACDSTYPE_FEATURE_UNIT,  // Descriptor sub-type is FEATURE_UNIT.
        AUDIO_CONTROL_ID,           // Unit ID for this interface.
        AUDIO_IN_TERMINAL_ID,       // ID of the Unit or Terminal to which this
                                    // Feature Unit is connected.
        2,                          // Size in bytes of an element of the
                                    // bmaControls() array that follows.
                                    // Master Mute control.
        USBShort(USB_ACONTROL_MUTE),                                  // <-----
                                    // Left channel volume control.
        USBShort(USB_ACONTROL_VOLUME),                                // <-----
                                    // Right channel volume control.
        USBShort(USB_ACONTROL_VOLUME),                                // <-----
        0,                          // Feature unit string index.

    2) When the device receives Set_Cur request, the stack saves the value into tAudioInstance.i16Volume or .ui8Mute, and set flag on tAudioInstance.ui16Update (VOLUME_CONTROL / MUTE_CONTROL)

    \TivaWare_C_Series-2.1.0.12573\usblib\device\usbdaudio.c

    static void
    HandleRequests(void *pvAudioDevice, tUSBRequest *psUSBRequest)
    {
        ...
        else if(ui32Recipient == USB_RTYPE_INTERFACE)
        {
            ...
            //
            // Handle an audio control request to the feature control unit.
            //
            if((AUDIO_CONTROL_ID << 8) ==
               (psUSBRequest->wIndex & USB_CS_CONTROL_M))
            {
                //
                // Determine the type of request.
                //
                switch(psInst->ui8Request)
                {
                    ...
                    case USB_AC_SET_CUR:
                    {
                        if(ui32Control == VOLUME_CONTROL)
                        {
                            //
                            // Read the new volume level.
                            //
                            USBDCDRequestDataEP0(0,
                                                 (uint8_t *)&psInst->i16Volume,
                                                 2);

                            //
                            // Save what we are updating.
                            //
                            psInst->ui16Update = VOLUME_CONTROL;
                        }
                        else if(ui32Control == MUTE_CONTROL)
                        {
                            //
                            // Read the new mute setting.
                            //
                            USBDCDRequestDataEP0(0,
                                                 (uint8_t *)&psInst->ui8Mute,
                                                 1);

                            //
                            // Save what we are updating.
                            //
                            psInst->ui16Update = MUTE_CONTROL;
                        }
                        else
                        {
                            //
                            // Stall on unknown commands.
                            //
                            ui32Stall = 1;
                        }
                        break;
                    }

    3) Your code polls tAudioInstance.ui16Update for the change (VOLUME_CONTROL / MUTE_CONTROL), and applies these settings to the audio output.

    Tsuneo