Освой Arduino играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Serial Monitor. Общаемся с компьютером

Для общения между платой Arduino и компьютером или другим устройством используется последовательный порт, который иногда называют UART or USART. У некоторых моделей Arduino может быть несколько портов. Порт соединяется через цифровой пин 0 (RX) и 1 (TX) при подключении к компьютеру через USB, поэтому не используйте пины 0 и 1 для ввода/вывода.

Раньше на старых компьютерах были COM-порты, сейчас они создаются виртуально при помощи микросхемы FTDI, когда мы подключаем плату к компьютеру через USB.

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

В Arduino IDE есть специальный значок с изображением лупы, который запускает Serial Monitor.

Для общения используется класс Serial. В методе setup() мы открываем порт для общения функцией Serial.begin() с указанием скорости в бодах (baud). Бод - это количество изменений сигнала в секунду. В нашем случае сигналы могут быть только двоичными, так что скорость в бодах соответствует скорости в битах в секунду. Можно использовать любую скорость, главное чтобы на приёмной и передающей сторонах они были одинаковыми. Доступные скорости можно посмотреть в настройках порта. Значение 9600 является стандартным и его можно не менять. Если установить неправильную скорость, то вместо данных получим "мусор" - данные, которые нельзя обработать.

На платах Arduino Mega и Arduino Due доступны также Serial1, Serial2, Serial3.

Чтобы отправить сообщение в порт, используются методы print() (символы идут подряд) или println() (с переводом на новую строку).

Serial Port

Давайте выведем какое-нибудь сообщение. Это можно сделать в методе setup(), так как нам не нужно повторять одну и ту же фразу бесконечно. Метод loop() оставляем пустым.


void setup() {
  Serial.begin(9600);
  Serial.println("Hello Kitty!");
  Serial.print("Мяу!");
}

void loop(){
  
}

Если посылаем строку, то обрамляем её кавычками. Если число, то кавычки не используем. Изменим функцию setup().


void setup() {
  Serial.begin(9600);
  Serial.print("А у кошки "); Serial.print(4); Serial.println(" ноги,");
  Serial.print("А сзади у ней длинный хвост.");
}

void loop() {

}

Можно заменить строки и числа на переменные. Перепишем пример.


String cat = "А у кошки ";
int leg = 4;

void setup() {
  Serial.begin(9600);
  Serial.print(cat); Serial.print(leg); Serial.println(" ноги,");
  Serial.print("А сзади у ней длинный хвост.");
}

void loop() {

}

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


void setup() {
  float number = 9.434346502;
  Serial.begin(9600);
  Serial.print(number, 2);
}

void loop() {

}

Работа с массивами и строками

Разберём пример отправки строк в случайном порядке. Любая строка уже является массивом символов. Поэтому вместо типа String, можно использовать массив char[]. Для примера создадим массив из четырёх имён и будем выводить их в случайном порядке через разные промежутки времени, используя функцию random().


char* catNames[] = {
  "Барсик",
  "Васька",
  "Мурзик",
  "Рыжик"
};

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

void loop() {
  int delayPeriod = random(2000, 5000);
  delay(delayPeriod);
  int index = random(4);
  Serial.println(catNames[index]);
}

Serial port

Приём данных

Выводить данные в порт просто. А вот принимать данные с компьютера и других источников сложнее. При отправлении данных, они складываются в буфер, ожидая, когда плата их прочитает. Объём буфера составляет 64 байта. Чтобы постоянно не читать пустой буфер, есть специальная функция проверки буфера Serial.available(). Она возвращает число байт, которые лежат в буфере. Обычно в коде создают условие проверки - если в буфере больше 0 байт, то выполняем какие-то команды.

Для демонстрации создадим странный пример - создадим переменную, присвоим ей данные через Serial.read() и попросим её прислать полученные данные через Serial.print(). Получится круговорот данных или эхо.


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

void loop() {
  if (Serial.available() > 0) {
    int data = Serial.read();
    Serial.println(data);
  }
}

Проверяем на числах. Отправляем число 9, а получаем 57. Если вы получаете две строки с числами 57 и 10, то в нижней части окна выберите настройку No line ending вместо Newline.

Попробуем также отправить букву. Опять вместо t возвращается 116. Ерунда какая-то. Всё просто, функция read() работает с символьными значениями и мы видим код символа из стандартной таблицы символов ASCII.

Чтобы решить проблему, нужно изменить тип данных на char.


char data = Serial.read();

Вроде проблема решена. Мы можем принимать отдельные цифры и буквы. Но буквы только английские, а числа только однозначные.

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


int data = Serial.read() - '0';

Решение какое-то половинчатое. А как быть с большими числами или словами?

Если отправить двузначное число 23, то ответ разбивается на части - 2 и 3. Получается, что переменная получит последнее число 3 (промежуточные значения перезаписываются). Чтобы обработать всё число, нужно использовать метод parseInt().


int data = Serial.parseInt();

Теперь вы можете вводить любые числа. Но, наверное, вы заметите теперь небольшую задержку в ответах. Метод внутри себя перемалывает данные. Кстати, вы можете использовать и обычные символы. Если набор символов состоит только из букв, то вернётся 0. Если будут попадаться и цифры, то будут возвращаться цифры. Попробуйте комбинировать различные сочетания цифр и букв, чтобы понять, как будут обрабатываться данные.

Управление светодиодом с клавиатуры

Напишем пример управления встроенным светодиодом с клавиатуры. Если нажата клавиша 1, то светодиод должен загореться, при нажатии клавиши 0 выключим светодиод.


int ledPin = 13;
byte incomingByte;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read();

    if(incomingByte == '1'){
      digitalWrite(ledPin, HIGH);

    }
    else if (incomingByte == '0'){
      digitalWrite(ledPin, LOW); 
    }
    
      Serial.print("I received: ");
      Serial.println(incomingByte, DEC);
  }
  delay(10);
}

Часть кода нам уже знакома - мы используем встроенный светодиод под номером 13.

Сигнал от компьютера поступает в виде байта. Создаём новую переменную incomingByte для этих целей.

Последовательный порт включается командой begin() с указанием скорости.

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

После первой проверки мы проверяем введённый символ, который может быть представлен и как байт. Если символ равен единице, то включаем светодиод, как мы делали раньше. Если символ равен 0, то выключаем.

Как это выглядит на практике. Заливаем скетч и запускаем Serial Monitor (Ctrl+Shift+M). В окне Serial Monitor наверху есть текстовое поле. Вводим в него числа 1 или 0 и нажимаем кнопку Send. Можно также нажать клавишу Enter для быстрого ввода.

Для общего развития в скетч добавлены также две строчки кода, определяющие код нажатой клавиши. Таким образом вы можете узнать код для клавиш 0 и 1. Вы также можете нажимать и на другие клавиши, они не повлияют на светодиод, но вы увидите коды клавиш.

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


#define ARRAY_SIZE 12 
//global variable definition 
char hello[ARRAY_SIZE] = {
  'h','e','l','l','o',' ','k','i','t','t','y','!'}; 
  
void setup() { 
  Serial.begin(9600); 
} 
void loop() { 
  //print characters from array to serial monitor 
  for(int x = 0; x < ARRAY_SIZE; x++) { 
    Serial.print(hello[x]); 
    delay(250); 
  } 
  Serial.println(); 
  delay(250); 
}

Функция Serial.end() закрывает последовательное соединение, порты RX и TX освобождаются и могут быть использованы для ввода/вывода.

В различных уроках вы будете принимать сигналы от платы Arduino. Это полезно, например, для отладки приложения, когда вы выводите сообщения и по ним ориентируетесь, какая часть программа работает, а какая - нет. Способность общения между Arduino и компьютером очень важна. Вы можете принимать сигналы не только в Arduino IDE, но и в других приложениях на компьютере. Например, в связке с Arduino часто используют приложение Processing, в котором рисуют графики поступаемых сигналов.

Если вы больше не нуждаетесь в получении данных, то закрывайте окно Serial Monitor.

Также существует библиотека SoftwareSerial. Она позволяет осуществить последовательную передачу данных через другие цифровые контакты Arduino.

Другие варианты

Чтение данных из последовательного порта возможно другими способами. Например, через Google Chrome API. Ищите расширения, например, Arduino Chrome Serial Monitor. На видео можно посмотреть, как создать расширение самостоятельно.

На C# также можно написать приложение, которое будет уметь считывать данные.

Processing также умеет работать с последовательным портом.

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

ASCIITable - распечатываем таблицу символов ASCII в разных форматах

Реклама