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

Arduino Micro USB Serial cannot receive exactly 64 bytes at once #112

Open
kervinck opened this issue Jul 28, 2018 · 10 comments
Open

Arduino Micro USB Serial cannot receive exactly 64 bytes at once #112

kervinck opened this issue Jul 28, 2018 · 10 comments
Labels

Comments

@kervinck
Copy link

This issue was first reported in my project here kervinck/gigatron-rom#36 but I now believe the root cause might be in USBCore.cpp

Tested in Ardiuno IDE 1.8.1 and 1.8.6 (arduino-PR-6886-BUILD-734-macosx.zip)
BN: Arduino/Genuino Micro
VID: 2341
PID: 8037
SN: Upload any sketch to obtain it

The example sketch just receives lines of text and reports their length:

void setup() {
  Serial.begin(115200);
}

void loop() {
  static int lineLength = 0;
  if (Serial.available()) {
    char next = Serial.read();
    lineLength++;
    if (next == '\n' || next == '\r') {
      Serial.println(lineLength);
      lineLength = 0;
    }
  }
}

Test Python program to demonstrate that the issue is with data messages of exactly 64 bytes:

import serial
from time import sleep
tty = '/dev/tty.usbmodem1411' # Adapt for your own computer
ser = serial.Serial(tty, baudrate=115200, timeout=1)
sleep(1) # For Arduinos that reboot

for length in [10, 70, 65, 64, 10, 10]:
  line = (length-1)*'A' + '\n'
  print('> %s (%d bytes)' % (repr(line), len(line)))
  ser.write(line)
  reply = ser.readline()
  print('< %s %s' % (repr(reply), 'OK' if reply else 'TIMEOUT'))

Output shows that the Arduino Micro stops receiving data after having received the 64-byte "killer" line. It does see the 64-byte message itself and it is still able to send. But it doesn't see new data anymore.

> 'AAAAAAAAA\n' (10 bytes)
< '10\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (70 bytes)
< '70\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (65 bytes)
< '65\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (64 bytes)
< '64\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< '' TIMEOUT
> 'AAAAAAAAA\n' (10 bytes)
< '' TIMEOUT

Expected output is what the Arduino Uno does:

> 'AAAAAAAAA\n' (10 bytes)
< '10\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (70 bytes)
< '70\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (65 bytes)
< '65\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (64 bytes)
< '64\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< '10\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< '10\r\n' OK

Somewhat related to https://github.com/arduino/Arduino/issues/6669 ("Arduino Micro USB Serial cannot receive more than 255 data once") and arduino/Arduino#6886 ("Handle receiving a ZLP in USB_Available"), however the proposed solution of that issue doesn't solve this.

I suspect the problem is caused by not following the datasheet's sequence, specifically:

RXOUTI shall always be cleared before clearing FIFOCON

The way USB_Recv is written I don't think ReleaseRX will ever be called twice in a row. And that is exactly what gets me out of the hangup situation. In my own project I have integrated a workaround kervinck/gigatron-rom@94d167d that seems to work by clearing FICOCON after Serial.read() and observing that UEBCLX has become 0. But I haven't considered every possible scenario (race conditions?).


I have some general remarks about USBCore.cpp, and this is very subjective, so please take no offense. But having studied the code with an actual problem, the Arduino Micro hooked up and the datasheets at hand, I have the impression some more issues might be lurking here. It is not obvious (to me) that the code and the datasheet match.

  • Why not use RWAL in USB_Available instead of UEBCLX. Tracing its value in different scenarios, UEBCLX doesn't always seem quite the right indicator. It can sometimes momentarily read 0 in the middle of a transfer sequence. (Besides the question what to to with UEBCHX in case that ever becomes relevant)
  • What is the magic constant 0x6B in ReleaseRX? Why not address the desired bits explicitly with masking operations.
  • USB_Recv() does some interesting things with its n and len, and the condition before ReleaseRX seems a bit suspect to me.
@randomricky2018
Copy link

Correct me if I'm wrong, I'm new to Arduino / USB related stuff, so it's very likely I might misunderstood something.

I'm working on a similar project, but it might sometimes tries to receive 64, 128 (64+64+64), 192 (64x3), 256(64x4) bytes.

Like what Marcel said, I think there are a few problems in the code so it cannot work correctly. (And it seems working applying these fixes).

In USB_Recv:
1). I think maybe we should check ReadWriteAllowed() at the beginning?

2). Before reading FifoByteCount / FIFOCON, we should check if RXOUTI is flipped? If it's flipped then we should clear RXOUTI bit (so Fifo queue will be freezed, otherwise if it's not fulled FifoByteCount may not be accurate?). If if RXOUTI = 0 then it means it's not ready and we should return -1?

3). We should always clear FIFOCON when FifoByteCount() ==0, as even len==0 we should still clear FIFOCON bit when RXOUTI is cleared.

Similarly, I think USB_Send() has similar issue, it now always send a Zlp, which is not correct, as if I want to send 128bytes out, but due to out of memory issue I break it down into 2 USB_Send() calls, my first USB_Send() will send a Zlp which terminate the transfer already.
We should allow dev to 64bytes out without Zlp.

@tozz88
Copy link

tozz88 commented Mar 11, 2019

Has there been any new info on this issue? I am seeing similar behavior on the MKRZERO board. Using the above method I see failure in the following way. Results when attempting to send bytes from host
to MKRZERO over its USB COM port:

size results
64 fails
128 fails
192 fails
256 success
320 fails (first 256 bytes delivered)
384 fails (first 256 bytes delivered)
448 fails (first 256 bytes delivered)
512 success
576 fails (first 512 bytes delivered)

As you can see the failure occurs if size%64==0 and size%256!=0
Also all size - size%256 bytes are delivered. its just the residual size%256 bytes that are not.

@sandeepmistry sandeepmistry transferred this issue from arduino/Arduino Sep 16, 2019
@Unreal-Dan
Copy link

Unreal-Dan commented Jan 30, 2023

the problem is in the available() function it modulates by the buffer sizes to return the number of bytes available, so if you send exactly the buffer size it returns 64 % 64 as the amount available.

I suspect the data is being delivered fine and is in the receive buffer, you're just not being notified about it.

Serial::available needs to be fixed.

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/HardwareSerial.cpp#L166

you can solve this by just sending one byte at a time but that shouldn't be the solution, we should be able to send any size frame of data and it should be reported as delivered.

This is my literal working solution:

bool writeData(const uint8_t *buffer, uint32_t amt)
{
  if (amt > 0 && (amt % 64) == 0) {
    // recurse with 1 byte then send the rest after
    if (!writeData(buffer, 1)) {
      return false;
    }
    buffer++;
    amt--;
  }
  DWORD bytesSent = 0;
  if (!WriteFile(m_hFile, buffer, amt, &bytesSent, 0)) {
    return false;
  }
  return (bytesSent == amt) {
}

It's really stupid but it solves all of my problems.

@matthijskooijman
Copy link
Collaborator

@Unreal-Dan I'm a bit confused. The report is about Arduino Micro USB serial, which is native CDC serial handled by https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/CDC.cpp, while you link to the HardwareSerial implementation that is used for UARTs only.

As for the fix, you suggest a writeData function that fixes things, but where should this function be applied? I cannot find a function by that name anywhere in this repository?

@tozz88 also wrote (before this issue was moved into the AVR core repo, I see):

Has there been any new info on this issue? I am seeing similar behavior on the MKRZERO board. Using the above method I see failure in the following way. Results when attempting to send bytes from host
to MKRZERO over its USB COM port:

For reference, the code that handles MKRZERO lives at https://github.com/arduino/ArduinoCore-samd, so that is a different issue resulting from different code that should ideally be reported in that repository (if there is no open issue about it yet - I have not checked). It could be that the same mistake was made in that code too, but maybe it's a different problem with the same symptom, so better to track them separately.

@Unreal-Dan
Copy link

Unreal-Dan commented Jan 31, 2023

I'm not super familiar with the arduino source code and I and just peeked quickly inside and assumed I knew the answer, I realize now that there are different implementations for available()

Whatever the issue is, sending exactly 64 bytes is problematic when I send it to a trinket m0, the receiver (trinket) never indicates any data is available and by simply sending one byte ahead of time and sending the rest of the 63 afterward everything works well.

Perhaps this information can help to solve the problem, or it can help others in similar situations as I was.

Also apologies, the writeData function I provided is a routine I am using to communicate with my arduino from windows. I am manually connecting the COM port and sending serial data, when I send a block of exactly 64bytes the arduino never sees anything, and it just always returns 0 from available(). As soon as I send one more byte then available() returns non-zero and I am able to read all 65 bytes.

When I say it is a solution, I mean it is a solution for people looking to communicate with their arduino without the 64 byte issue getting in their way.

@matthijskooijman
Copy link
Collaborator

Whatever the issue is, sending exactly 64 bytes is problematic when I send it to a trinket m0, the receiver (trinket) never indicates any data is available and by simply sending one byte ahead of time and sending the rest of the 63 afterward everything works well.

Oh, that's even yet another board. The trinket M0 is runs on the Adafruit's version of the samd core, which I guess is derived from Arduino's samd core. Good to know that all three of these cores (AVR, SAMD and Adafruit SAMD) are affected, though this issue is strictly about the AVR version only (but I suspect that there might be some shared code that is the underlying cause).

When I say it is a solution, I mean it is a solution for people looking to communicate with their arduino without the 64 byte issue getting in their way.

Ah, thanks for clarifying.

@PaulStoffregen
Copy link
Sponsor Contributor

I ran this on Arduino Leonardo using AVR boards 1.8.6. Could not reproduce the problem. Prints this:

> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (70 bytes)
< b'70\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (65 bytes)
< b'65\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (64 bytes)
< b'64\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK

I did have to modify the python script slightly to work with the python3 install on Ubuntu 22.04.

import serial
from time import sleep
tty = '/dev/ttyACM0' # Adapt for your own computer
ser = serial.Serial(tty, baudrate=115200, timeout=1)
sleep(1) # For Arduinos that reboot

for length in [10, 70, 65, 64, 10, 10]:
  line = (length-1)*'A' + '\n'
  print('> %s (%d bytes)' % (repr(line), len(line)))
  ser.write(line.encode())
  reply = ser.readline()
  print('< %s %s' % (repr(reply), 'OK' if reply else 'TIMEOUT'))

@PaulStoffregen
Copy link
Sponsor Contributor

Also tried on Arduino Zero using SAMD boards 1.8.14. To use the native port I had to replace "Serial" with "SerialUSB", as "Serial" talks to the debug port. This is the code I ran on Arduino Zero:

void setup() {
  SerialUSB.begin(115200);
}

void loop() {
  static int lineLength = 0;
  if (SerialUSB.available()) {
    char next = SerialUSB.read();
    lineLength++;
    if (next == '\n' || next == '\r') {
      SerialUSB.println(lineLength);
      lineLength = 0;
    }
  }
}

Also could not reproduce the problem, running the python script on Ubuntu 22.04. Got this again:

> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (70 bytes)
< b'70\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (65 bytes)
< b'65\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (64 bytes)
< b'64\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK

And FWIW, also tested on Teensy 4.1, Teensy 3.2, Teensy LC, Teensy++ 2.0 and Teensy 2.0. Could not reproduce the problem on any of those either.

@PaulStoffregen
Copy link
Sponsor Contributor

One last test, MKR1000 with the original code (not "SerialUSB"). Also works.

> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (70 bytes)
< b'70\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (65 bytes)
< b'65\r\n' OK
> 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' (64 bytes)
< b'64\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK
> 'AAAAAAAAA\n' (10 bytes)
< b'10\r\n' OK

@PaulStoffregen
Copy link
Sponsor Contributor

Unless someone can confirm the problem happen with Windows or MacOS, seems likely this issue is no longer an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants