View Categories

Монитор порта

Консоль #

Самый простой способ получить обратную связь от программы - вывод данных в консоль - окно ввода-вывода текстовых данных для взаимодействия с программой. Это самый простой способ отладки кода - отправлять из программы данные на разных участках её выполнения, выводить ошибки, показания датчиков и прочее. Если речь идёт о компьютере, то программа выполняется на компьютере и консоль расположена там же, выполнением программы и выводом в консоль занимается один и тот же процессор.

Для примера можно открыть онлайн-компилятор OneCompiler, в нём будет минимальный пример, который выводит в консоль строку "Hello, World!". Если запустить программу - можно увидеть эту строку в консоли:

UART #

С Arduino ситуация немного иная - программа выполняется на отдельном устройстве со своим процессором, к которому нет такого прямого доступа. Arduino подключается к компьютеру по USB, на самой плате USB гнездо подключено не напрямую к микроконтроллеру - он не умеет с ним работать - а к USB-UART преобразователю. UART - это универсальный приёмник-передатчик, почти все микроконтроллеры имеют такой интерфейс для связи с внешними устройствами. UART представлен двумя пинами RX (Receive, приём) и TX (Transmit, передача) - именно к ним и подключен преобразователь. Взглянем на плату Arduino Nano:

С его помощью Arduino может принимать и отправлять данные по USB проводу.

COM порт #

Компьютер взаимодействует именно с USB-UART преобразователем, он не знает, что это плата Arduino. При подключении Arduino на стороне компьютера создаётся виртуальный COM порт (последовательный порт) - именно так компьютер видит нашу плату:

То есть на стороне микроконтроллера для связи используется UART, а на стороне компьютера - COM порт.

Монитор порта #

Чтобы работать с портом на стороне компьютера, понадобится монитор порта - программа, которая умеет отправлять и читать данные с COM порта. Таких программ много и подойдёт любая, но в Arduino IDE есть свой встроенный монитор порта, что очень удобно. Чтобы начать работу, нужно выбрать порт (тот же, что был выбран для загрузки прошивки) и открыть монитор:

В окне монитора порта есть несколько важных настроек:

  • Скорость - скорость связи. Должна совпадать со скоростью UART, установленной в программе на микроконтроллере. Обычно 9600 или 115200
  • Конечный символ или терминирующий символ, конец строки - будет добавляться в конце текста при отправке из монитора порта

Serial #

Для работы с портом на стороне Arduino используется системный объект Serial, он позволяет читать и отправлять данные.

Все возможности Serial можно посмотреть в документации на класс Stream - Serial его наследует

Перед началом работы нужно запустить связь с указанием скорости - метод begin:

void setup() {
    Serial.begin(115200);   // запустить связь на скорости 115200 бод
}

Скорость передачи данных по интерфейсу задаётся в бодах (baud rate) - бит в секунду. У UART имеется 2 "лишних" бита на каждый отправляемый байт данных, поэтому реальная скорость передачи информации - битрейт (bit rate) - составляет 80% от baud rate. Время передачи одного байта в миллисекундах равна 10000 / baud

Также можно остановить общение по Serial:

Serial.end();

Отправка #

Для отправки используются методы print(любые данные) и println(любые данные) - первый просто печатает данные, второй печатает и переносит строку. Давайте напишем классический пример, который выводит строку "Hello, World!" в консоль:

void setup() {
    Serial.begin(115200);
    Serial.println("Hello, World!");
}

void loop() {
}

Загрузите программу и откройте монитор порта на скорости 115200:

Данный код выполнится один раз при запуске МК. Нажмите на плате кнопку Reset (перезагрузка) несколько раз - после каждого нажатия в порт снова выведется строка

Ещё несколько примеров:

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

    Serial.print("Hello");      // строка
    Serial.print(',');          // символ
    Serial.println(" World!");  // строка с переносом

    Serial.println(12345);      // число по основанию 10 (по умолчанию)
    Serial.println(12345, BIN); // число по основанию 2

    Serial.println(3.1415);     // десятичная дробь, точность 2 знака по умолчанию
    Serial.println(3.1415, 4);  // десятичная дробь, точность 4 знака

    Serial.println();           // просто перевод строки
}

void loop() {
}

В дальнейших уроках мы будем пользоваться выводом в Serial для визуализации того, что происходит в программе.

Табуляция #

Символ табуляции '\t' позволяет удобно отправлять данные для последующей вставки в таблицу. Например выведем несколько степеней двойки в виде таблицы:

for (byte i = 0; i < 16; i++) { // i от 0 до 16
    Serial.print(i);            // степень
    Serial.print("\t");         // табуляция
    Serial.println(round(pow(2, i)));   // 2 в степени i
}

Результат скопируем и вставим в Excel:

Чтение #

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

Для проверки наличия входящих данных используется метод available() - он вернёт количество входящих байт. В таком условии можно проверить, что есть хотя бы один байт для чтения:

void loop() {
    if (Serial.available()) {
        // есть входящие данные
    }
}

Пока данные не прочитаны - они так и будут висеть на приёме - в приёмном буфере на стороне МК

Символы #

Давайте отправим в порт обратно принятые данные, для этого их сначала нужно прочитать - метод read(). Он возвращает тип int, но сами данные подразумевают байт, а если данные отправлены из монитора порта - то это всегда символ - char. Для конвертации принятого значения в символ нужно просто привести его к символьному типу - (char)Serial.read(), например для отправки обратно в порт.

Если прочитать данные, когда их никто не отправил (приёмный буфер пуст, available() равен 0) - read() вернёт -1. Именно для этого здесь используется тип int - проверить корректность данных

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

void loop() {
    if (Serial.available()) {
        Serial.print((char)Serial.read());

        // или
        // char c = Serial.read();
        // Serial.print(c)
    }
}

Отправьте что-нибудь в монитор порта - этот текст придёт обратно.

Здесь важна настройка конца строки в окне монитора порта - если включить NL (New Line), то каждая отправка будет выводиться на новой строке, т.к. символ переноса будет отправляться обратно в монитор и переносить строку. Если отключить - всё отправленное будет выводиться в одну строку

Коды символов #

Если сделать вывод без конвертации к char, то в порт будут печататься коды символов из таблицы ASCII:

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

void loop() {
    if (Serial.available()) {
        Serial.println(Serial.read());
    }
}

Отправьте текст abc: в порт будут выведены цифры 97, 98, 99 - они соответствуют кодам символов из таблицы.

Здесь важна настройка конца строки в окне монитора порта - если он включен, то в конце данных будет выведен его код. Например у переноса строки NL это 10, т.е. после отправки abc получим в мониторе 97, 98, 99, 10

Строки #

Текст в монитор порта отправляется и принимается посимвольно - в примере выше мы его посимвольно принимаем и выводим. Можно прочитать весь текст из порта как String-строку:

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

void loop() {
    if (Serial.available()) {
        String s = Serial.readString();
        Serial.println(s);
    }
}

Отправьте текст в монитор - он придёт обратно, но с ощутимой задержкой. Метод readString() является блокирующим - он собирает строку посимвольно, ожидая поступления новых символов. Если после получения символа проходит тайм-аут - передача считается завершённой и возвращается строка. Тайм-аут можно настроить - Serial.setTimeout(миллисекунды), по умолчанию установлен 1000 мс. Поставьте например 50:

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

void loop() {
    if (Serial.available()) {
        String s = Serial.readString();
        Serial.println(s);
    }
}

Теперь между отправкой в монитор и получением текста обратно не будет такой заметной паузы.

Ещё один вариант - терминирующий символ. Он отправляется в конце текста и будет сигналом конца строки, чтобы МК не ждал тайм-аут. Чтение строки такого формата выполняется методом readStringUntil(символ), пусть таким символом будет точка с запятой - ';':

void setup() {
    Serial.begin(115200);
    // стандартный тайм-аут
}

void loop() {
    if (Serial.available()) {
        String s = Serial.readStringUntil(';');
        Serial.println(s);
    }
}

Отправьте какой-нибудь текст, например test - он вернётся с задержкой. А если отправить test; - без задержки.

Не забываем, что в мониторе порта можно настроить "конец строки" - поставьте NL, а в качестве терминирующего символа в программе - '\n' (Serial.readStringUntil('\n');). Теперь любой отправленный текст будет приниматься без задержки.

Числа #

Когда мы отправляем в монитор порта число 1234, то МК получит по очереди символы '1', '2', '3', '4'. Как преобразовать их обратно в численный тип?

  • Можно принять символы и собрать их в число вручную
  • Можно прочитать строку через readString() как выше, затем вывести из строки через toInt() или toFloat():
void setup() {
    Serial.begin(115200);
    Serial.setTimeout(50);
}

void loop() {
    if (Serial.available()) {
        String s = Serial.readString();
        int i = s.toInt();
        Serial.println(i);
    }
}
  • Можно использовать встроенный парсер Serial - parseInt() или parseFloat():
void setup() {
    Serial.begin(115200);
    Serial.setTimeout(50);
}

void loop() {
    if (Serial.available()) {
        // int числа
        int i = Serial.parseInt();
        Serial.println(i);

        // float числа
        // float f = Serial.parseFloat();
        // Serial.println(f);
    }
}

Для отправки и парсинга чисел отключите "конец строки" в мониторе порта, иначе лишние символы будут прочитаны как дополнительно отправленное число 0

  • Какой из способов оптимальнее? Если числа отправляются человеком из монитора порта, то эффективнее будет настроить в мониторе конец строки NL, в программе читать в строку через readStringUntil('\n'), а затем выводить в число через toInt()/toFloat(). Таким образом мы не будем ждать таймаута, как в случае с parseInt()/parseFloat().

Парсинг пакетов* #

При помощи Stream-инструментов можно очень просто разбирать несложные текстовые протоколы связи, например пакеты вида "ключ:значение<перенос строки>": сначала читаем до двоеточия, затем до символа переноса строки:

void loop() {
  if (Serial.available()) {
    String key = Serial.readStringUntil(':');
    String val = Serial.readStringUntil('\n');

    Serial.println(key);
    Serial.println(val);
    Serial.println();
  }
}

Нужно включить отправку NL в настройках монитора порта

Такой протокол можно использовать для управления устройством через монитор порта по проводу с компьютера или по Bluetooth со смартфона, например для включения/выключения светодиода на пине 13 (бортовой светодиод на Arduino Nano) и настройки яркости светодиода на пине 3:

void setup() {
  Serial.begin(115200);
  pinMode(3, OUTPUT);
  pinMode(13, OUTPUT);
}

void loop() {
  if (Serial.available()) {
    String key = Serial.readStringUntil(':');
    String val = Serial.readStringUntil('\n');

    if (key == "led") {
      digitalWrite(13, val.toInt());

    } else if (key == "pwm") {
      analogWrite(3, val.toInt());

    } else if (key == "echo") {
      Serial.println(val);
    }

  }
}
  • led:1 - светодиод включится, led:0 - выключится
  • pwm:100 - запустит ШИМ на пине 3 со значением 100
  • echo:hello! - отправит обратно в порт текст hello!

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

Продолжение темы с этим протоколом читай в уроке про хэш-строки, там рассказано об оптимизации сравнения строк

Для эффективного и асинхронного обмена данными между устройствами можно использовать мою библиотеку StreamPacket - она позволяет отправлять и принимать бинарные данные (не строки) в виде пакетов, а также контролирует целостность передачи

Дополнительно #

Полезные страницы #

5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх