Монитор порта, отладка

Как мы с вами знаем из урока “Что умеет микроконтроллер“, у многих микроконтроллеров есть интерфейс UART, позволяющий передавать и принимать различные данные. У интерфейса есть два вывода на плате – пины TX и RX. На большинстве Arduino-плат к этим пинам подключен USB-UART преобразователь (расположен на плате), при помощи которого плата может определяться компьютером при подключении USB кабеля и обмениваться с ним информацией. На компьютере создаётся виртуальный COM порт (последовательный порт), к которому можно подключиться при помощи программ-терминалов и принимать-отправлять текстовые данные. Таким же образом кстати работают некоторые принтеры и большинство станков с ЧПУ.

В самой Arduino IDE есть встроенная “консоль” – монитор порта, кнопка с иконкой лупы в правом верхнем углу программы. Нажав на эту кнопку мы откроем сам монитор порта, в котором будут настройки:

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

  • Конец строки: тут есть несколько вариантов на выбор, чуть позже вы поймёте, на что они влияют. Лучше поставить нет конца строки, так как это позволит избежать непонятных ошибок на первых этапах знакомства с платформой.
    • Нет конца строки – никаких дополнительных символов в конце введённых символов после нажатия на кнопку отправка или клавишу Enter.
    • NL – символ переноса строки в конце отправленных данных.
    • CR – символ возврата каретки в конце отправленных данных.
    • NL+CR – и то и то.
  • Скорость – тут на выбор нам даётся целый список скоростей, т.к. общение по Serial может осуществляться на разных скоростях, измеряемых в бод (baud), и если скорости приёма и отправки не совпадают – данные будут получены некорректно. По умолчанию скорость стоит 9600, её и оставим.

Объект Serial


Начнём знакомство с одним из самых полезных инструментов Arduino-разработчика – Serial, который идёт в комплекте со стандартными библиотеками. Serial позволяет как просто принимать и отправлять данные через последовательный порт, так и наследует из класса Stream кучу интересных возможностей и фишек, давайте сразу их все рассмотрим, а потом перейдём к конкретным примерам.

Serial.begin(speed)

Запустить связь по Serial на скорости speed (измеряется в baud, бит в секунду). Скорость можно поставить любую, но есть несколько “стандартных” значений. Список скоростей для монитора порта Arduino IDE:

  • 300
  • 1200
  • 2400
  • 4800
  • 9600 чаще всего используется, можно назвать стандартной
  • 19200
  • 38400
  • 57600
  • 115200 тоже часто встречается
  • 230400
  • 250000
  • 500000
  • 1000000
  • 2000000 – максимальная скорость, не работает на некоторых китайских платах
Serial.end()
Прекратить связь по Serial. Также освобождает пины RX и TX.
Serial.available()
Возвращает количество байт, находящихся в буфере приёма и доступных для чтения.
Serial.availableForWrite()
Возвращает количество байт, которые можно записать в буфер последовательного порта, не блокируя при этом функцию записи.
Serial.write(val), Serial.write(buf, len)
Отправляет в порт val численное значение или строку, или отправляет количество len байт из буфера buf. Важно! Отправляет данные как байт (см. таблицу ASCII), то есть отправив 88 вы получите букву X: Serial.write(88);.
Serial.print(val), Serial.print(val, format)

Отправляет в порт значение val – число или строку, фактически “печатает”. В отличие от write выводит именно текст, т.е. отправив 88, вы получите 88: Serial.print(88);. Отправляет любые стандартные типы данных: численные, символьные, строковые. Также методы print()/println() имеют несколько настроек для разных данных, что делает их очень удобным инструментом отладки:

Serial.print(78);        // выведет 78
Serial.print(1.23456);   // 1.23 (по умолч. 2 знака)
Serial.print('N');       // выведет N
Serial.print("Hello world."); // Hello world.

// можно сделать форматированный вывод в стиле
Serial.print("i have " + String(50) + " apples");
// выведет строку i have 50 apples

// вместо чисел можно пихать переменные
byte appls = 50;
Serial.print("i have " + String(appls) + " apples");
// выведет то же самое

format позволяет настраивать вывод данных: BIN, OCT, DEC, HEX выведут число в соответствующей системе счисления: двоичная, восьмеричная, десятичная (по умолчанию) и 16-ричная. Цифра после вывода float позволяет настраивать выводимое количество знаков после точки:

Serial.print(78, BIN);    // вывод "1001110"
Serial.print(78, OCT);    // вывод "116"
Serial.print(78, DEC);    // вывод "78"
Serial.print(78, HEX);    // вывод "4E"
Serial.print(1.23456, 0); // вывод "1"
Serial.print(1.23456, 2); // вывод "1.23"
Serial.print(1.23456, 4); // вывод "1.2345"
Serial.println(), Serial.println(val), Serial.println(val, format)
Полный аналог print(), но автоматически переводит строку после вывода. Позволяет также вызываться без аргументов (с пустыми скобками) просто для перевода курсора на новую строку.
Serial.flush()
Ожидает окончания передачи данных.
Serial.peek()
Возвращает текущий байт с края буфера, не убирая его из буфера. При вызове Serial.read() будет считан тот же байт, но из буфера уже уберётся.
Serial.read()
Читает и возвращает крайний символ из буфера.
Serial.setTimeout(time)
Устанавливает time (миллисекунды) таймаут ожидания приёма данных для следующих ниже функций. По умолчанию равен 1000 мс (1 секунда).
Serial.find(target), Serial.find(target, length)

Читает данные из буфера и ищет набор символов target (тип char), опционально можно указать длину length. Возвращает true, если находит указанные символы. Ожидает передачу по таймауту.

// будем искать слово hello
char target[] = "hello";

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

void loop() {
  if (Serial.available() > 0) {
    if (Serial.find(target))
      Serial.println("found");
    // вывести found, если было послано
  }
}
Serial.findUntil(target, terminal)
Читает данные из буфера и ищет набор символов target (тип char) либо терминальную строку terminal. Ожидает окончания передачи по таймауту, либо завершает приём после чтения terminal.
Serial.readBytes(buffer, length)
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[]). Также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер).
Serial.readBytesUntil(character, buffer, length)
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[]), также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер) и терминальный символ character. Окончание приёма в buffer происходит при достижении заданного количества length, при приёме терминального символа character (он в буфер не идёт) или по таймауту
Serial.readString()
Читает порт, формирует из данных строку String, и возвращает её (урок про стринги). Заканчивает работу по таймауту.
Serial.readStringUntil(terminator)
Читает порт, формирует из данных строку String, и возвращает её (урок про стринги). Заканчивает работу по таймауту или после приёма символа terminator (символ char).
Serial.parseInt(), Serial.parseInt(skipChar)
Читает целочисленное значение из порта и возвращает его (тип long). Заканчивает работу по таймауту. Прерывает чтение на всех знаках, кроме знака – (минус). Можно также отдельно указать символ skipChar, который нужно пропустить, например кавычку-разделитель тысяч (10’325’685), чтобы принять такое число.
Serial.parseFloat()
Читает значение с плавающей точкой из порта и возвращает его. Заканчивает работу по таймауту.

Плоттер


Помимо монитора последовательного порта, в Arduino IDE есть плоттер – построитель графиков в реальном времени по данным из последовательного порта. Достаточно отправлять значение при помощи команды Serial.println(значение) и открыть плоттер по последовательному соединению, например построим график значения с аналогового пина A0:

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

void loop() {
  Serial.println(analogRead(0));
  delay(10);
}

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

значение1 пробел_или_запятая значение2 пробел_или_запятая значение3 пробел_или_запятая перенос_строки

Давайте выведем значения с аналоговых пинов A0, A1 и A2:

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

void loop() {
  Serial.print(analogRead(0));
  Serial.print(',');
  Serial.print(analogRead(1));
  Serial.print(',');
  Serial.print(analogRead(2));
  Serial.println();
  delay(5);
}

Получим вот такие графики:

В Arduino IDE с версии 1.8.10 добавили возможность подписать графики, для этого перед выводом нужно отправить названия в виде название 1, название 2, название n с переносом строки, и дальше просто выводить данные: blank

Отправка в порт


Рассмотрим самый классический пример для всех языков программирования: Hello World!
blank
Отправка в порт позволяет узнать значение переменной в нужном месте программы, этот процесс называется отладка. Когда код работает не так, как нужно, начинаем смотреть, где какие переменные какие значения принимают. Или выводим текст из разных мест программы, чтобы наблюдать за порядком её работы. Во взрослых  средах разработки и более серьёзных микроконтроллерах есть аппаратная отладка, которая позволяет наблюдать за ходом выполнения программы и значениями любых переменных без вывода в порт.

Давайте вспомним урок циклы и массивы и выведем в порт элементы массива:

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

  byte arr[] = {0, 50, 68, 85, 15, 214, 63, 254};
  for (byte i = 0; i < 8; i++) {
    Serial.print(arr[i]);
    Serial.print(' ');
  }
}

void loop() {
}

Вывод: 0 50 68 85 15 214 63 254 – элементы массива, разделённые пробелами.

Чтение из порта


Проблемы возникают при попытке принять данные в порт. Дело в том, что метод read() читает один символ, а если вы отправите длинное число или строку – программа получит его по одному символу. Чтение сложных данных называется парсинг. Его можно делать вручную, об этом мы поговорим в отдельном уроке из блока “Алгоритмы”. В рамках этого урока рассмотрим встроенные инструменты для парсинга Serial.

Чтобы не нагружать программу чтением пустого буфера, нужно использовать конструкцию

if (Serial.available()) {
  // тут читаем
}

Таким образом чтение будет осуществляться только в том случае, если в буфере есть какие-то данные.

Парсинг цифр


Для чтения целых цифр используем Serial.parseInt(), для дробных – Serial.parseFloat(). Пример, который читает целое число и отправляет его обратно:
void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    int val = Serial.parseInt();
    Serial.println(val);
  }
}
Если при парсинге у вас появляются лишние цифры – поставьте “Нет конца строки” в настройках монитора порта

Вы заметите, что после отправки проходит секунда, прежде чем плата ответит в порт. Эта секунда является таймаутом, о котором мы говорили чуть выше. Программа ждёт секунду после принятия последнего символа, чтобы все данные успели прийти. Секунда это очень много, ожидать, скажем, 50 миллисекунд. Это можно сделать при помощи метода setTimeout().

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

void loop() {
  if (Serial.available()) {
    int val = Serial.parseInt();
    Serial.println(val);
  }
}

Теперь после отправки цифры программа будет ждать всего 50 мс и ответит гораздо быстрее!

Парсинг текста


Проще всего прочитать текст в String-строку (урок про них). Это максимально не оптимально, но зато довольно просто для восприятия:

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

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

Данный пример выводит любой текст, который был отправлен в монитор порта.

Управляющие символы


Существуют так называемые управляющие символы, позволяющие форматировать вывод. Их около десятка, но вот самые полезные из них

  • \n – новая строка
  • \r – возврат каретки
  • \v – вертикальная табуляция
  • \t – горизонтальная табуляция

Также если в тексте вы захотите использовать одинарные кавычки ', двойные кавычки ", обратный слэш \ и некоторые другие символы – их нужно экранировать при помощи обратного слэша, он просто ставится перед символом:

  • \" – двойные кавычки
  • \' – апостроф
  • \\ – обратный слэш
  • \0 – нулевой символ
  • \? – знак вопроса

Выведем строку с кавычками:

Serial.println("\"Hello, World!\"");  // выведет "Hello, World!"

Комбинация \r\n переведёт строку и вернёт курсор в левое положение:

Serial.print("Hello, World!\r\nArduino Forever");
// выведет
// Hello, World!
// Arduino Forever

Символы табуляции позволят удобно отправлять данные для последующей вставки в таблицу. Например выведем несколько степеней двойки в виде таблицы, используя символ табуляции \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   Удобно!

Видео


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


4.9/5 - (24 голоса)
Подписаться
Уведомить о
guest

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