Что происходит с последовательным выводом, когда никто не слушает?

Этот вопрос, вероятно, нуждается в лучшем названии, но я не могу его придумать! Пожалуйста, не стесняйтесь редактировать или предлагать свой вариант.

Моей целевой системой является пара Arduino Leonardo, соединенных радиомодулями XBee. Один из них является координатором и должен обмениваться данными с хост-компьютером через соединение USB (виртуальный последовательный порт). Координатор анализирует команды и потенциально пересылает их на конечную точку по радио. Конечная точка отвечает на команды, а также может спонтанно генерировать выходные данные. Оба устройства также могут отправлять диагностические данные на USB/последовательный порт. Конечная точка отправляет сообщения пульса каждые 8 секунд. Если координатор пропускает 2 последовательных пульса, он повторно инициализируется. Если конечная точка отправляет пульс и не получает ответа в течение нескольких секунд, она также будет повторно инициализирована.

Кажется, все это работает довольно хорошо, но я заметил кое-что странное. Если я подключаюсь к обоим устройствам с помощью эмулятора терминала и просто смотрю вывод, то все хорошо и система работает стабильно не менее 14 часов (самое долгое время, которое я тестировал). Я вижу, как происходят сообщения о сердцебиении и выводятся диагностические данные. Однако, как только я отключаю один из последовательных портов, все начинает глючить. Автомат связи XBee истекает по тайм-ауту с обеих сторон и переходит в бесконечный цикл повторной инициализации.

Я изо всех сил пытаюсь понять, почему это может быть, но отключение одного из последовательных портов надежно вызывает проблему в течение 10–20 секунд (я думаю, что истекло время ожидания «нет пульса»).

Похоже, что если ничего не прослушивает последовательный порт, значит, что-то заклинило. Насколько я понимаю, можно читать и писать в порт, к которому ничего не подключено, и эти данные просто попадут в битбакет. Разве это не так? Почему при отключении клиента от последовательного порта все должно зависать?

ОБНОВЛЕНИЕ: я протестировал этот модифицированный скетч "blink":

#include <Arduino.h>

auto& host = Serial;

void setup() {
  // инициализируем цифровой вывод LED_BUILTIN как выход.
  pinMode(LED_BUILTIN, OUTPUT);
  host.begin(115200);
}

// функция цикла запускается снова и снова навсегда
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // включаем светодиод (HIGH - уровень напряжения)
  delay(1000);                       // ждем секунду
  digitalWrite(LED_BUILTIN, LOW);    // выключаем светодиод, понижая напряжение
  delay(1000);                       // ждем секунду
  host.print("Time ");
  host.println(millis());
}

Когда что-то (PuTTY) подключено к USB/последовательному порту, я получаю хорошее равномерное соотношение меток и пробелов на светодиоде. Как только я отключаю PuTTY, период «выключения» светодиода становится примерно в два раза длиннее периода «включения», т.е. каждый раз, когда я пишу в последовательный порт, возникает задержка более секунды. Это нормально?

, 👍2

Обсуждение

Это зависит от того, какие у вас загадочные устройства, которые обмениваются данными через USB, и как они запрограммированы., @Majenko

@Majenko, [ардуино-леонардо], @Juraj

@Juraj Я никогда не доверяю тегам., @Majenko

добавьте некоторый код включения / выключения светодиода, чтобы облегчить отладку ... например, светодиод горит перед последовательной связью и светодиод выключается после последовательной связи, @jsotola

Вы тоже по сериалу читаете? В конце концов (отключение) последовательного порта вызывает перепад на приемной линии, что приводит к произвольному значению. В зависимости от того, как вы управляете последовательными входами на Arduino, может случиться что угодно., @Sim Son

@jsotola это отличная идея, думаю, я так и сделаю., @Tim Long

@SimSon да, знаю (читай из сериала). Мой синтаксический анализатор команд довольно защищен от бомб, поэтому это не должно быть проблемой., @Tim Long

Arduino Leonardo использует встроенный USB для создания «последовательного порта». Вероятно, поэтому его print ведет себя не так, как Arduino UNO. UNO имеет отдельную микросхему для виртуального ком., @Fahad


2 ответа


Лучший ответ:

1

Хорошо, вот мое решение.

Я создал класс-оболочку с именем SafeSerial. В SafeSerial.h:

#pragma once
#include <Arduino.h>

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

В SafeSerial.cpp:

/*
* Реализация SafeSerial для Arduino Leonardo.
* Переопределяет метод Serial_::write() и проверяет, достаточно ли места
* в выходной буфер. Если нет, вывод отбрасывается.
* Цель состоит в том, чтобы предотвратить блокировку последовательного вывода, когда хост не прослушивается.
* Все остальные операции передаются базовому экземпляру Serial_.
*/

#include "SafeSerial.h"

size_t SafeSerial::write(const uint8_t* buffer, size_t size)
    {
/*
* Проверьте, можем ли мы писать без блокировки. Если нам нужно заблокировать,
* то мы предполагаем, что хост отключился.
*/

    if (availableForWrite() < size)
        return 0;

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

В моем скетче в глобальном масштабе:

SafeSerial host;

Теперь, поскольку я использую <ArduinoSTL>, и все мои выходные данные проходят через std::cout, мне нужно предпринять некоторые действия, чтобы правильно настроить эту библиотеку. . Для этого необходимо изменить код библиотеки в соответствии с инструкциями в файле README.md, чтобы он не использовался по умолчанию для встроенного экземпляра Serial и не создавал экземпляры по умолчанию для cin и cout. Теперь это нужно сделать в скетче, поэтому (опять же в глобальном масштабе):

// cin и cout для ArduinoSTL
namespace std
    {
    ohserialstream cout(host);
    ihserialstream cin(host);
    }

и в setup():

    host.begin(115200);
    // Подключаем cin и cout к нашему экземпляру SafeSerial
    ArduinoSTL_Serial.connect(host);

... и, наконец, у меня есть полностью неблокирующий последовательный ввод-вывод, даже когда хост отключен, с риском некоторой возможной потери данных. Однако до сих пор в моем приложении потеря данных не казалась проблемой, поскольку я вывожу только минимальный краткий вывод и, как правило, только 1 строку каждые несколько секунд.

Спасибо всем за ценный вклад.

,

2

Теоретически с последовательными данными ничего не должно происходить, если ничего не прослушивается.

  • Для традиционного UART данные просто отправляются в любом случае. Для этого не нужно, чтобы кто-то слушал. Как свету не нужно, чтобы кто-то его видел, чтобы он зажегся.
  • Для USB CDC/ACM данные должны быть удалены еще до попытки отправки.

Последнее, однако, полагается на то, что хост-компьютер «играет в мяч» и корректно закрывает порт. В большинстве операционных систем это обрабатывается ОС и происходит автоматически. Однако в Windows он передается драйверу, и я думаю, что драйвер может даже передать его терминальному программному обеспечению.

Таким образом, в Windows существует вероятность того, что при отключении последовательного порта соединение не закрывается полностью, а USB считает, что он все еще подключен, что блокирует ожидание сбора последовательных данных.

,

Я обновил свой вопрос, добавив дополнительную информацию и простой тестовый набросок. Когда я отключаю последовательный порт, я вижу что-то вроде секундной задержки каждый раз вокруг моего цикла., @Tim Long

Хорошо, попробуем под другим углом. Похоже, у меня проблема с блокировкой последовательного вывода, хотя к последовательному потоку ничего не подключено. Если бы мне пришлось написать новый класс потока, который обертывает последовательный поток, возможно, я мог бы выполнить ручную проверку, чтобы увидеть, следует ли мне записывать данные в базовый поток или просто отбрасывать их. В этой ситуации, что, по-вашему, я должен проверить?, @Tim Long

@TimLong Ну, единственное, что вы действительно можете проверить, - это «состояние линии» CDC / ACM - и существующий код уже делает это., @Majenko

Вы можете попробовать посмотреть возвращаемое значение Serial.availableForWrite(), чтобы увидеть, достаточно ли места в буфере..., @Majenko

https://www.arduino.cc/reference/tr/language/functions/communication/serial/availableforwrite/, @Majenko

Похоже, стоит попробовать. Спасибо за отзыв., @Tim Long