2

This question probably needs a better title but I'm having trouble thinking of one! Please feel free to edit or suggest one.

My target system is a pair of Arduino Leonardos linked by XBee radios. One is the coordinator and is expected to communicate with a host PC over the USB connection (virtual serial port). The coordinator parses commands and potentially forwards them on to the endpoint by radio. The endpoint replies to commands and can also generate output spontaneously. Both devices can also send diagnostic output to the USB/Serial port. The endpoint sends "heartbeat" messages every 8 seconds. If the coordinator misses 2 consecutive heartbeats then it'll reinitialize. If the endpoint sends a heartbeat and doesn't get a response within a couple of seconds, then it will also reinitialize.

This all seems to work pretty well, but I've noticed something odd. If I connect to both devices with a terminal emulator and just watch the output, everything is good and the system is stable for at least 14 hours (the longest I've tested it). I can see the heartbeat messages happening and the diagnostic output coming out. However, as soon as I disconnect one of the serial ports, things begin to fail. The XBee comms state machine appears to time out at both sides and goes into an endless re-initialization loop.

I'm struggling to think why this might be, but disconnecting one of the serial ports reliably causes the issue within 10 to 20 seconds (I think the "no heartbeat" timeout is being hit).

It seems like if nothing is listening to the serial port, something is getting jammed up. My understanding is that its OK to read from and write to a port that nothing is connected to, and that data will just fall into the bitbucket. Is this not the case? Why should disconnecting the client from the serial port jam everything up?

UPDATE: I've tested with this modified "blink" sketch:

#include <Arduino.h>

auto& host = Serial;

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  host.begin(115200);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
  host.print("Time ");
  host.println(millis());
}

With something (PuTTY) connected to the USB/serial port, I get a nice even mark-space ratio on the LED. As soon as I disconnect PuTTY, the 'off' period of the LED becomes about twice as long as the 'on' period, i.e. there is upwards of a second delay each time I write to the serial port. Is that normal?

8
  • It depends what the mystery devices you have that communicate over USB are and how they are programmed.
    – Majenko
    Jan 30, 2020 at 14:43
  • @Majenko, [arduino-leonardo]
    – Juraj
    Jan 30, 2020 at 15:14
  • @Juraj I never trust tags.
    – Majenko
    Jan 30, 2020 at 15:15
  • 1
    add some LED on/off code to aid debugging ...for example, LED on before serial comm and LED off after serial comm
    – jsotola
    Jan 30, 2020 at 16:19
  • 1
    Do you also read from serial? Eventually (un)plugging the serial port causes an edge on the receiving line which results in an arbitrary value. Depending on how you manage serial inputs on the arduino anything could happen
    – Sim Son
    Jan 30, 2020 at 17:34

2 Answers 2

2

Theoretically, nothing should happen to serial data if nothing is listening.

  • For a traditional UART the data is just sent regardless. It doesn't need someone listening for that to happen. Like a light doesn't need someone to see it for it to be on.
  • For USB CDC/ACM the data should be discarded even before it's attempted to be sent.

The latter, though, does rely on the host computer "playing ball" and cleanly closing the port properly. On most operating systems that is handled by the OS and happens automatically. However on Windows it is handed off to the driver, and I guess the driver may even hand it off to the terminal software.

So in Windows there is a chance that when you disconnect your serial port it's not cleanly closing the connection and the USB thinks it's still connected, thus blocking waiting for the serial data to be collected.

6
  • I've updated my question with additional info and a simple test sketch. When I disconnect the serial port, I'm seeing something like a second delay every time around my loop.
    – Tim Long
    Jan 30, 2020 at 17:42
  • OK let's try a different angle. It looks like I have a problem with serial output blocking even though there's nothing connected to the serial stream. If I were to write a new stream class that wraps the serial stream, maybe I could make manual checks to see if I should write the data to the underlying stream, or simply discard it. In that situation, what do you think I should check for?
    – Tim Long
    Jan 31, 2020 at 12:48
  • 1
    @TimLong Well, the only thing you really can check is the CDC/ACM "Line State" - and the existing code already does that.
    – Majenko
    Jan 31, 2020 at 12:48
  • 1
    You could try looking at the returned value of Serial.availableForWrite() to see if there is enough buffer space...
    – Majenko
    Jan 31, 2020 at 12:49
  • 1
1

OK so here is my solution.

I made a wrapper class called SafeSerial. In SafeSerial.h:

#pragma once
#include <Arduino.h>

class SafeSerial : public Serial_
    {
    size_t write(const uint8_t* buffer, size_t size) override;
    };

In SafeSerial.cpp:

/*
 * SafeSerial implementation for Arduino Leonardo.
 * Overrides the Serial_::write() method and checks that there is enough room
 * in the output buffer. If not, the output is discarded.
 * The intent is to prevent serial output from blocking when no host is listening.
 * All other operations are handed off to the underlying Serial_ instance.
 */

#include "SafeSerial.h"

size_t SafeSerial::write(const uint8_t* buffer, size_t size)
    {
    /*
     * Check if we can write without blocking. If we need to block,
     * then we assume that the host has disconnected.
     */

    if (availableForWrite() < size)
        return 0;

    return Serial_::write(buffer, size);
    }

In my sketch, in global scope:

SafeSerial host;

Now, because I'm using <ArduinoSTL> and all my output goes via std::cout, I need to take some actions to correctly configure that library. This requires modifying the library code per the instructions in the README.md file to make it not default to the built-in Serial instance and to not create default instances of cin and cout. This now needs to be done in the sketch, so (again in global scope):

// cin and cout for ArduinoSTL
namespace std
    {
    ohserialstream cout(host);
    ihserialstream cin(host);
    }

and in setup():

    host.begin(115200);
    // Connect cin and cout to our SafeSerial instance
    ArduinoSTL_Serial.connect(host);

...and finally, I have fully non-blocking Serial I/O even when the host has disconnected, at the risk of some possible data loss. However so far, in my application, data loss doesn't seem to be an issue as I only produce minimal terse output and generally only 1 line every few seconds.

Thank you all for your valuable input.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.