Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track if a USB ZLP is needed on flush #4138

Closed
wants to merge 3 commits into from

Conversation

sandeepmistry
Copy link
Contributor

Resolves #3946. This patch tracks if a USB ZLP (zero length packet) is needed to flush data.

A ZLP is needed to indicate end of transfer, if the size of data sent is a multiple of USB_EP_SIZE (64 bytes on AVR).

If a ZLP is needed, USB_Flush waits for the current endpoint's FIFO bank to be available before doing a release.

@sandeepmistry sandeepmistry added Architecture: AVR Applies only to the AVR microcontrollers (Uno, etc.) USB: CDC serial Serial interface used by MCUs with native USB (e.g. Leonardo) to communicate with the computer labels Nov 12, 2015
@embmicro
Copy link
Contributor

This looks good but I propose changing the line where you assign _sendZlp to the following so that if the TRANSFER_RELEASE flag is set you don't set _sendZlp.

        _sendZlp[ep & USB_ENDPOINTS_MASK] = !ReadWriteAllowed() && (len == 0) && !(ep & TRANSFER_RELEASE);

        if (!ReadWriteAllowed())    // Release full buffer
            ReleaseTX();

        if ((len == 0) && (ep & TRANSFER_RELEASE))
            ReleaseTX();

That is unless I have a misunderstanding on what TRANSFER_RELEASE is supposed to do.

@sandeepmistry
Copy link
Contributor Author

@embmicro thanks for reviewing!

As for your suggestion, if ReleaseTX is called twice in row, the second call must wait for a ReadWriteAllowed()to be true again, otherwise it won't send a ZLP. Something like this:

        bool sendZlp = !ReadWriteAllowed() && (len == 0);

        if (!ReadWriteAllowed())    // Release full buffer
            ReleaseTX();

        if (sendZlp && (ep & TRANSFER_RELEASE)) {
            sendZlp = false;
           while(!ReadWriteAllowed());
        }

        if ((len == 0) && (ep & TRANSFER_RELEASE))
            ReleaseTX();

        _sendZlp[ep & USB_ENDPOINTS_MASK] = _sendZlp;

@NicoHood
Copy link
Contributor

Can you build this? I want to try.

I am just not sure if ZLPs are okay for other devices, such as HID or MIDI. Have you checked that first?

@sandeepmistry
Copy link
Contributor Author

@ArduinoBot build this please

@embmicro
Copy link
Contributor

Can't you just add the while then like the following?

            _sendZlp[ep & USB_ENDPOINTS_MASK] = !ReadWriteAllowed() && (len == 0) && !(ep & TRANSFER_RELEASE);

            if (!ReadWriteAllowed())    // Release full buffer
                ReleaseTX();

            if ((len == 0) && (ep & TRANSFER_RELEASE)) {
                while(!ReadWriteAllowed());
                ReleaseTX();
            }

@sandeepmistry
Copy link
Contributor Author

@embmicro agreed, your suggestion is much cleaner. Initially I thought the r/w check was only needed if a ZLP needs to be sent when the TRANSFER_RELEASE flag is set, but it doesn't hurt to check it regardless.

I've pushed your suggestion in sandeepmistry@bc80824

@sandeepmistry
Copy link
Contributor Author

@ArduinoBot build this please

@embmicro
Copy link
Contributor

embmicro commented Dec 8, 2015

I've run into a problem with this patch. Take the following example.

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  if (Serial) {
    if (Serial.read() == 'h'){
      Serial.write("Hello ");
      Serial.flush();
      Serial.write("World!\r\n");
      Serial.flush();
    }
  }
}

With this sketch I expect "Hello World!" to be printed every time I send an 'h' to the board. However, on my Windows 10 machine, I typically only get "Hello ". Sending another 'h' will then show "Hello World!\r\nHello "

Removing the calls to flush makes it work as expected, but that isn't much of a solution.

On my Linux machine it works perfectly fine with and without flush.

@NicoHood
Copy link
Contributor

NicoHood commented Dec 8, 2015

Sounds like an useful option if it works on linux. +1 from me ;)

For the windows users a better fix might be to not send USB_EP_SIZE bytes. Also as described above, I dont think every EP should use the ZLP mechanism, since other devices work different.
I suggest to count the bytes in buffer and if there are any, flush them. For the write() function we should send those bytes if they exceed USB_EP_SIZE - 1, so we dont have to deal with a ZLP at all. That is also what LUFA does in its usb-serial.

@embmicro
Copy link
Contributor

I don't know if this is ideal, but I modified USB_Send to never send a full packet and it seems to be working with all my tests.

int USB_Send(u8 ep, const void* d, int len)
{
    if (!_usbConfiguration)
        return -1;

    int r = len;
    const u8* data = (const u8*)d;
    u8 timeout = 250;       // 250ms timeout on send? TODO
    while (len)
    {
        u8 n = USB_SendSpace(ep);
        if (n == 0)
        {
            if (!(--timeout))
                return -1;
            delay(1);
            continue;
        }

        n--; // leave one byte free

        if (n > len)
            n = len;
        {
            LockEP lock(ep);
            // Frame may have been released by the SOF interrupt handler
            if (!ReadWriteAllowed())
                continue;
            len -= n;
            if (ep & TRANSFER_ZERO)
            {
                while (n--)
                    Send8(0);
            }
            else if (ep & TRANSFER_PGM)
            {
                while (n--)
                    Send8(pgm_read_byte(data++));
            }
            else
            {
                while (n--)
                    Send8(*data++);
            }

            // Release full buffer
            if (!ReadWriteAllowed() || USB_SendSpace(ep) == 1 || ((len == 0) && (ep & TRANSFER_RELEASE)) )
                ReleaseTX();
        }
    }

I also reverted flush back to it's original form. Still calling flush a lot like in my previous example causes trouble. However, flush doesn't seem to be needed at all so maybe it should be an empty function.

@BlackBrix
Copy link

does someone knows how this is going on ?
will this last suggestion of @embmicro be implemented somehow ?

I think I maybe have a related problem with 32u4 (?)
--> http://forum.arduino.cc/index.php?topic=360286.msg2686772#msg2686772
--> http://forum.arduino.cc/index.php?topic=360286.msg2689095#msg2689095

can I just try to exchange the original USB_Send() with this last one from @embmicro without changing any other code segments in other places/files ?
(using IDE 1.6.8)

maybe Paul Stoffregen's approach would be a better Solution ?
--> https://github.com/PaulStoffregen/cores/tree/master/usb_serial

@sandeepmistry
Copy link
Contributor Author

I'm closing this in favour of #4864. Which is a similar approach to @embmicro's suggestion in #4138 (comment).

However, it will spin with a delay and timeout if buffer space is EP size - 1.

cmaglie added a commit to facchinm/Arduino that referenced this pull request Jan 4, 2017
cmaglie added a commit that referenced this pull request Mar 16, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Architecture: AVR Applies only to the AVR microcontrollers (Uno, etc.) USB: CDC serial Serial interface used by MCUs with native USB (e.g. Leonardo) to communicate with the computer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants