Skip to content

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.

6 participants