Консоль #
Самый простой способ получить обратную связь от программы - вывод данных в консоль - окно ввода-вывода текстовых данных для взаимодействия с программой. Это самый простой способ отладки кода - отправлять из программы данные на разных участках её выполнения, выводить ошибки, показания датчиков и прочее. Если речь идёт о компьютере, то программа выполняется на компьютере и консоль расположена там же, выполнением программы и выводом в консоль занимается один и тот же процессор. Для примера можно открыть онлайн-компилятор 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
Также можно остановить общение по 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
для визуализации того, что происходит в программе.
Чтение #
В монитор порта также можно отправлять данные и читать их из программы. Эта глава немного забегает вперёд, но должна быть здесь. Возвращайтесь к ней позже, если что-то будет непонятно.
Для проверки наличия входящих данных используется метод 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());
}
}
Отправьте что-нибудь в монитор порта - этот текст придёт обратно.
Строки #
Текст в монитор порта отправляется и принимается посимвольно - в примере выше мы его посимвольно принимаем и выводим. Можно прочитать весь текст из порта как 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
(New Line) или CR
(Carriage Return) или оба. Поставьте в мониторе NL
, а в качестве символа в программе - '\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 i = Serial.parseInt();
Serial.println(i);
}
}
Для отправки и парсинга чисел отключите "конец строки" в мониторе порта, иначе лишние символы будут прочитаны как дополнительное отправленное число 0