Arduino wrap или подкласс print() для работы с несколькими Serial

Я пишу программу Arduino, которая использует Bluetooth на Serial1 для печати текста на тернале Bluetooth на телефоне Android, а также обычный последовательный для печати текста на последовательный монитор на ноутбуке. Я хотел бы обернуть функции Serial.print() и Serial.println() так, чтобы они работали как с последовательным, так и с Сериалом1. Например, приведенный ниже код отлично работает в зависимости от значений глобальных переменных. Но это работает только для отдельных символов, но print() и println() могут принимать самые разные типы данных. Если я также определяю функции перегрузки для типов int и String, это работает нормально, но это очень подробное и, возможно, хрупкое решение, оно также игнорирует необязательные входные данные для базовых функций. Как правильно это сделать ?

void print(char x) {
  if (g_use_Serial)
    Serial.print(x);
  if (g_use_Serial1)
    Serial1.print(x);
}

void println(char x) {
  if (g_use_Serial)
    Serial.println(x);
  if (g_use_Serial1)
    Serial1.println(x);
}

Я реализовал предложенное решение, и оно работает почти идеально. Похоже, возникла проблема с новыми символами строки в соединении Bluetooth (Serial1).

Код таков:

class DualPrint : public Print
{
public:
    DualPrint() : use_Serial(false), use_Serial1(false) {}
    virtual size_t write(uint8_t c) {
        if (use_Serial) Serial.write(c);
        if (use_Serial1) Serial1.write(c);
        return 1;
    }
    bool use_Serial, use_Serial1;
} out;

void setup()  {
  Serial.begin(9600);
  Serial1.begin(9600);
  delay(1000); 
}

void loop() {

  out.use_Serial = true;
  out.print("Печатается только на USB.\n");
  out.use_Serial1 = true;
  out.print("Печатается как на USB, так и на BT.\n");
  out.use_Serial = false;
  out.print("Печатается только на BT.\n");
  out.use_Serial1 = false;

  delay(3000);
}

Вывод на последовательный монитор USB выглядит следующим образом:

Printed to USB only.
Printed to both USB and BT.
Printed to USB only.
Printed to both USB and BT.

Вывод на терминале Bluetooth не такой, как ожидалось, новые линии находятся не в правильном положении:

Printed to both USB and BT.Pri
nted to BT only.
Printed to both USB and BT.Pr
inted to BT only.
Printed to both USB and BT.Pr
inted to BT only.
Printed to both USB and BT.Pr
inted to BT only.

Любые предложения о том, как это исправить. Существуют ли другие функциональные элементы, которые необходимо повторно реализовать, чтобы это работало должным образом?

Большое спасибо за высококачественные ответы до сих пор.

, 👍2

Обсуждение

Нет. Общая техника должна работать нормально. Возможно, Bluetooth не может обрабатывать более 30 байт, прежде чем вводить собственную новую строку. В любом случае, почему бы не использовать "println" вместо "print", а затем указать свою собственную новую строку?, @Nick Gammon

Я попробовал использовать как" println ("...")", так и " print ("...\n")", одинаковое поведение с обоими. Я жестко закодировал одни и те же выходы с помощью "Serial" и "Serial1", и это работает нормально, так что ничего общего с Bluetooth. Это как-то связано с новым классом., @Hubert B

Соединение Bluetooth должно видеть точно такой же поток байтов, независимо от того, печатаете ли вы через "Serial1" или этот класс. Только время будет немного отличаться, поэтому может быть так, что все, что получает данные, чувствительно к времени входящих байтов. Это можно было бы смягчить, переопределив " запись виртуального size_t(const uint8_t *буфер, размер size_t)`., @Edgar Bonet

Должно ли это выглядеть так: запись виртуального размера(const uint8_t *буфер, размер size_t) { если (use_Serial) Последовательный.запись(буфер, размер); если (use_Serial1) Сериал1.запись(буфер, размер); возврат 1; }, @Hubert B

Я сомневаюсь, что время будет иметь значение. Эти байты будут буферизованы и отправлены с помощью процедуры обслуживания прерываний., @Nick Gammon

Терминал Bluetooth может не распознавать новые строки. Можете ли вы доказать, что это так?, @Nick Gammon

Например, некоторые ЖК-экраны не распознают новые строки. Вы должны явно указать им перейти к новой строке и/или столбцу., @Nick Gammon

@NickGammon. Обновить. Если я вставлю задержку в 100 мс перед использованием Serial1, проблема исчезнет. Но это неприемлемо для окончательной программы. Проблема, по-видимому, та же самая с прямым использованием "Serial1", что и с "DualPrint", поэтому на исходный вопрос дан ответ. Новый вопрос заключается в том, как управлять положением символов новой строки в терминале Bluetooth., @Hubert B


1 ответ


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

6

Вы можете создать класс, производный от Print, который перенаправляет свои выходные данные либо в Serial, либо в Serial1. Единственный метод, который вам нужно реализовать, чтобы это сработало, - это написать(uint8_t):

class DualPrint : public Print
{
public:
    DualPrint() : use_Serial(false), use_Serial1(false) {}
    virtual size_t write(uint8_t c) {
        if (use_Serial) Serial.write(c);
        if (use_Serial1) Serial1.write(c);
        return 1;
    }
    bool use_Serial, use_Serial1;
} out;

Вы бы использовали его так:

out.use_Serial = true;
out.println("Printed to Serial only");
out.use_Serial1 = true;
out.println("Printed to both Serial and Serial1");
out.use_Serial = false;
out.println("Printed to Serial1 only");

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


Изменить: Чтобы ответить на вопрос в комментарии OP, конструкция

class ClassName
{
    ...определение...
} classInstace;

является ли кадр для

class ClassName
{
    ...определение...
};
ClassName classInstace;

Очень похожая конструкция существует в простом C:

struct struct_name {
    ...поля структуры...
} struct_instance;
,

для продвинутых пользователей я бы тоже переопределил "availableForWrite ()" и " flush ()", @Juraj

@Edgar Большое спасибо за это, очень полезно. Один запрос, я более опытен в C, чем в C++, поэтому определение класса мне незнакомо. В примере использования " out "является экземпляром "DualPrint", верно? Как это декларируется? `Двойной отпечаток";??, @Hubert B

@HubertB: Да, " out " - это экземпляр DualPrint. См.Расширенный ответ., @Edgar Bonet

Эдгар и @Юрай Я обновил вопрос отчетом о ходе реализации предложенного решения. Он работает почти идеально, за исключением новых строк в неправильной последовательности в терминале BT. Есть какие-нибудь предложения? Связано ли это с функцией flush() или другими функциями, которые также необходимо повторно реализовать?, @Hubert B

Я реализовал это, спасибо (в данном случае для записи в файл ошибок SD) и заметил, что Serial.write() на последовательном мониторе работает намного медленнее, чем Serial.print() - какие-либо причины для этого и является ли это проблемой?, @CestLaGalere

@CestLaGalere: Понятия не имею, почему. Было бы трудно ответить на этот вопрос без более подробной информации о том, что вы делаете, и минимального проверяемого примера., @Edgar Bonet

Создав образец, который просто регистрируется в последовательном режиме, замедления нет, можно сделать вывод, что запись на SD-карту для входа в файл все еще продолжается при попытке записи последовательных данных, замедляя последовательную запись. Я не уверен, что сработавший пример, следовательно, поможет, так как для воссоздания требуется SD-карта, подключенная к ESP. Спасибо за обратную связь, @CestLaGalere

@CestLaGalere: Вполне возможно, что запись на SD-карту происходит медленно, если выполняется по одному символу за раз, но быстрее, если сразу записывается целое “сообщение”. Чтобы проверить эту идею, вы, возможно, захотите реализовать "виртуальную запись size_t(const uint8_t *буфер, размер size_t)" в "DualPrint", которая просто выполняет "запись(буфер, размер)" в базовые потоки. Посмотрим, ускорит ли это процесс., @Edgar Bonet

Спасибо @edgar-bonet, похоже, что так оно и есть, реализовав это намного быстрее., @CestLaGalere