Processing и Arduino – основы

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

Что дальше? Вероятно, у вас мог возникнуть вопрос: можно ли заставить Arduino и Processing общаться друг с другом? Да, это вполне возможно! И в этой статье мы расскажем как именно обмениваться данными между Arduino и Processing.

  • Итак, более детально о вопросах, которые рассмотрены в статье:
  • Как передавать данные от Arduino к Processing через серийный порт;
  • Как Processing получает данные от Arduino;
  • Как передавать данные из Processing на Arduino;
  • Как Arduino получает данные от Processing;
  • Как обеспечить 'рукопожатие' Arduino и Processing, чтобы обеспечить контроль данных, которые передаются;

От Arduino к Processing...

Давайте начнем со стороны Arduino. Нам надо ознакомиться с основами настройки Arduino скетча для передачи данных на персональный компьютер по серийному порту.

  • Естественно, для работы вам понадобится Arduino IDE. Скачать ее можно на на официальном сайте Arduino.
  • Кроме того, вам нужна плата Arduino, подключенная к персональному компьютеру через USB.

Ок. Контроллер у вас есть, Arduino IDE установлена. Начинаем кодить! Не волнуйтесь, поставленные перед нами задачи реализуются достаточно легко.

Откройте Arduino IDE. Вы должны увидеть что-то вроде:

От Arduino к Processing 1

Белое пространство - это место, в котором мы будем писать нашу программу. Кликните мышкой по белому пространству и пропишите следующее (или просто скопируйте и вставьте код, который приведен ниже):

void setup() {

Serial.begin(9600); //инициализируем обмен данными по серийному протоколу со скоростью 9600 baud

}

Это наш метод setup. Именно в пределах этого метода мы обеспечиваем обмен данными между компьютером и Arduino с указанной скоростью. Baund - это (по сути) скорость, с которой мы передаем данные на персональный компьютер. Если мы отправляем и получаем данные с различной скоростью, ничего хорошего в результате не получится. Два устройства, которые обмениваются данными, просто не поймут друг друга. Это, конечно же, плохо.

После метода setup() нам понадобится метод под названием loop()с помощью которого мы обеспечим бесконечный повтор работы нашей программы. В качестве первого примера мы просто отправим строку ‘Hello, world!’ по серийному порту. Причем отправляться она будет вновь и вновь и вновь... Пока мы не отключим Arduino. Ниже представлен код этой программы:

void loop() { //отправляем 'Hello, world!' С помощью серийного порта

Serial.println("Hello, world!"); //ждем 100 миллисекунд перед следующей отправкой

delay(100); }

Это все, что потребуется со стороны Arduino для первого примера. Мы настраиваем обмен данными по серийному протоколу и ставим задачу отправлять данные каждые 100 миллисекунд. Ваш скетч для Arduino будет выглядеть примерно так:

От Arduino к Processing 2

Все, что нам осталось - подключить Arduino, выбрать тип платы (в меню Tools -> Board Type) и ваш серийный порт (Tools -> Serial Port). После этого нажмите кнопку ‘upload’ для загрузки вашего кода на Arduino.

Теперь мы сможем магическим образом (или с помощью отдельного кода) обнаружить нашу строку ‘Hello, world!’ с помощью Processing.

...Processing принимает данные

Наша задача - обнаружить данные, которые нам отсылает скетч Arduino. К счастью, в Processing есть готовая библиотека Serial library, которая написана как раз для подобных задач! Если вы до сих пор не установили себе Processing, скачать последнюю версию для вашей операционной системы вы можете на официальном сайте Processing.org. После установки, откройте Processing. На экране появится нечто вроде:

Processing принимает данные 1

Напоминает Arduino IDE, правда? При разработке Arduino IDE очень активно использовался Processing (именно в подобном и проявляется прелесть и мощь open-source проектов!). После открытия скетча наш первый шаг - импортировать библиотеку Serial library. Идем в Sketch->Import Library->Serial, как это показано ниже:

Processing принимает данные 2

В верхней части вашего скетча вы должны увидеть что-то вроде import processing.serial.*;. Магия! Под нашим выражением импорта надо объявить некоторые глобальные переменные. Это значит, что эти переменные мы сможем использовать в любой части нашего скетча. Добавьте эти две строки под выражением импорта:

Serial myPort; // создаем объект с серийного порта

class String val; // данные полученные с серийного порта

Для того, чтобы обеспечить прием данных с серийного порта, нам нужен объект (Serial object), который мы назвали myPort. Имя переменной не имеет значения, можете назвать его иначе. Нам также нужна переменная для получения данных в режиме онлайн. Так как с Arduino мы отсылаем данные типа String - строка, , нам надо получить строку и в Processing. В Arduino IDE есть методы setup( ) и loop( ). Аналогом в Processing являются setup() и draw()(вместо loop).

Для нашего метода setup() в Processing, нам надо найти серийный порт по которому подключен наш Arduino и настроить наш Serial объект для работы с этим портом.

// С учетом того, что на этом ПК (MAC) первый серийный порт в списке

// is Serial.list()[0].

// На Windows это обычно COM1.

// откройте именно тот, порт, который вы используете

String portName = Serial.list()[0]; //Измените 0 на 1 или 2 и т.д. и т.п. Главное, чтобы порт соответствовал вашему

myPort = new Serial(this, portName, 9600);

Помните как мы настраивали Serial.begin(9600) на Arduino? Что ж, если нам не нужны неполадки, о которых говорилось выше, в качестве последнего аргумента в нашем Serial объекте надо указать 9600. Таким образом, мы настраиваем одинаковую скорость передачи/приема данных в Arduino/Processing.

В цикле draw() мы контролируем указанный серийный порт. Как только на нем появляются данные, мы присваиваем их переменной val и выводим в окне консоли (черное поле под окном вашего скетча в Processing).

void draw() {

if ( myPort.available() > 0) { // если есть данные,

val = myPort.readStringUntil('\n'); // считываем их и записываем в переменную val

}

println(val); //отображаем данные в консоли

}

После нажатия кнопки ‘run’ (и если ваш Arduino подключен к ПК и на плату загружен скетч, который мы рассмотрели в предыдущем разделе), в окне под вашим скетчем появится надпись `Hello, World!‘. Эта строка будет появляться вновь и вновь (смотрите на рисунке ниже).

Processing принимает данные 5

Отлично! Мы разобрались как передавать данные с Arduino в Processing. Наш следующий шаг - разобраться с противоположной операцией: передать данные из Processing в Arduino.

Processing передает данные...

Итак, мы передали данные от Arduino и обработали их с помощью Processing. Но как нам отправить данные обратно? Из Processing в Arduino? Запросто!

Начнем со стороны Processing. Во многом наш скетч будет похож на предыдущий. Мы импортируем библиотеку Serial library и объявляем глобальную переменную, в которой будем хранить Serial данные. В пределах метода setup() мы находим наш порт и устанавливаем через него обмен данными. На скорости 9600 baud. Мы также будем использовать команду size(), которая позволит создать небольшое окно для взаимодействия с пользователем. По сути это будет аналог кнопки, при взаимодействии с которой данные отправятся из Processing к Arduino.

import processing.serial.*; Serial myPort; // создаем объект класса Serial

void setup() {

size(200,200); //создаем окно размерами 200 x 200 пикселей

String portName = Serial.list()[0]; //меняем 0 на 1, 2 и т.д. Это значение должно совпадать с вашим портом

myPort = new Serial(this, portName, 9600); }

В пределах метода draw() мы мы будем отсылать необходимые данные по серийному порту. Для этого используем метод write из библиотеки Processing Serial library. В этом скетче мы передаем ‘1’ при клике мышкой в пределах окна Processing. Кроме того, мы отобразим ее в консоли, чтобы удостовериться, что мы что-то передали. Если мы не кликаем, будет передаваться ‘0’.

void draw() {

if (mousePressed == true) { //если мы кликнули мышкой в пределах окна

myPort.write('1'); //отсылаем 1

println("1"); }

else { //если клика не было

myPort.write('0'); //отсылаем 0

}

}

К этому моменту ваш скетч должен выглядеть примерно так:

Processing передает данные 1

После того как вы запустили программу, должен появится вектор-столбец, который содержит 1, если вы кликаете мышкой в пределах окна. Отлично! Но как наш Arduino будет искать эти 1-цы? И что полезного мы можем с ними сделать?

...Arduino принимает данные

Итак, теперь мы будем "отлавливать" 1-цы, которые идут от Processing. Когда мы обнаружим единицу, включим светодиод на 13 пине Arduino, идет (на многих платах Arduino есть встроенный светодиод на 13 пине, так что внешний нам даже не придется подключать)?

В верхней части скетча Arduino нам надо объявить две глобальные переменные: одна для получения и хранения данных, которые поступают от Processing, а вторая для того, чтобы объяснить Arduino, к какому пину подключен светодиод.

char val; // данные, полученные с серийного порта

int ledPin = 13; // наш светодиод будет использовать цифровой пин 13

Дальше. В пределах метода setup() мы устанавливаем режим работы пина, к которому подключен светодиод на output (вывод данных). Начинаем обмен данными по серийному порту на скорости 9600 baud.

void setup() {

pinMode(ledPin, OUTPUT); // устанавливаем режим работы пина - OUTPUT

Serial.begin(9600); // начинаем обмен данными со скоростью 9600 bps

}

В пределах метода loop() будем отслеживать данные, которые поступают на плату через серийный порт. Если мы обнаруживаем ‘1’, мы устанавливаем светодиод в HIGH (включен), если нет (то есть, если нам передается ‘0’), мы выключаем светодиод. В конце цикла делаем небольшую задержку, чтобы Arduino успевал справиться с потоком данных.

void loop() {

if (Serial.available()) { // если данные можно прочитать,

val = Serial.read(); // считываем их и передаем на хранение в val

}

if (val == '1') { // если мы получили 1,

digitalWrite(ledPin, HIGH); // включаем светодиод

}

else {

digitalWrite(ledPin, LOW); // в противоположном случае отключаем светодиод

}

delay(10); // ждем 10 миллисекунд до следующей итерации чтения данных

}

Вот как должен выглядеть ваш код:

Arduino принимает данные 1

Вуаля! После загрузки этого скетча на Arduino можно запускать программу в Processing, которую мы рассмотрели в предыдущей части статьи и включать светодиод на вашем Arduino кликом мыши по окну в Processing!

Рукопожатие (часть 1 - Arduino)

Итак, мы разобрались с тем, как Arduino и Processing могут обмениваться данными через серийный порт. При этом один передает данные, а второй принимает и наоборот. А можем ли мы сделать так, чтобы оба одновременно - и Arduino т Processing передавали и принимали данные? Можем! И назовем мы это "рукопожатием", так как оба согласовываются по времени для передачи и приема данных.

Дальше мы рассмотрим пример, в котором Processing примет ‘Hello, world!’ от Arduino и отправит обратно на наш контроллер 1. В результате на Arduino загорится светодиод. Это означает, что Arduino должен отсылать ‘Hello, world!’ и при этом отслеживать наличие 1-цы от Processing.

Начнем программирование с Arduino. Для реализации наших коварных замыслов надо чтобы обе стороны понимали, что именно они принимают и что надо отсылать. Одной из локальных задач, которая перед нами станет - минимизация траффика по серийному порту.

Как и в примере Serial read, нам нужна переменная для данных, которые поступают на контроллер и переменная для хранения информации о состоянии пина со светодиодом:

char val; // данные, полученные с серийного порта

int ledPin = 13; // пин для светодиода

boolean ledState = LOW; //управляем состоянием светодиода

Так как мы стремимся к максимальной эффективности нашей программы, организуем скетч таким образом, что он будет отслеживать только 1-цы. И каждый раз, когда мы получаем '1', будем включать светодиод. Для этого мы добавили булеву (boolean) переменную, которая имеет два состояния (правда или ложь). В результате у нас отпадает необходимость постоянно отсылать 1 или 0 от Processing. В результате серийный порт прилично разгружается и не передает лишнюю информацию.

Наш метод setup() выглядит практически так же. Единственно , добавлена функция establishContact() ,которую мы рассмотрим дальше. На данном этапе просто укажите ее в коде.

void setup() {

pinMode(ledPin, OUTPUT); // устанавливаем пин в режим OUTPUT

//устанавливаем скорость обмена данными 9600 baud

Serial.begin(9600);

establishContact(); // отсылаем байт для налаживания контакта, пока ресивер отвечает

}

В пределах функции loop мы просто объединяет код из наших предыдущих скетчей. Самое важное и принципиальное изменение - введение булевого значения. Восклицательный знак - '!' В коде означает, что когда мы обнаруживаем единицу, мы меняем булевое значение на противоположное относительно текущего состояния (то есть LOW становится HIGH и наоборот). В пределах условия else мы вставили наш ‘Hello, world!’. То есть мы будем отправлять эту строку только в случае, когда не обнаружим '1'.

void loop() {

if (Serial.available() > 0) { // если можно прочитать данные,

val = Serial.read(); // считываем их и передаем в переменную val

if(val == '1') //если мы получили 1

{ ledState = !ledState; //меняем значение ledState

digitalWrite(ledPin, ledState);

}

delay(100);

}

else

{ Serial.println("Hello, world!"); //отсылаем обратно hello world

delay(50);

}

}

Теперь мы добрались до функции establishContact(), которую использовали в методе setup(). Эта функция просто отсылает строку (именно ту, которую мы ожидаем получить в Processing). Если ответ приходит, значит Processing может получить данные.

void establishContact() {

while (Serial.available() <= 0) {

Serial.println("A"); // отсылает заглавную A

delay(300);

}

}

Ваш код Arduino должен выглядеть подобным образом:

Рукопожатие (часть 1 - Arduino)

Рукопожатие (часть 2 - Processing)

В коде Processing надо сделать более принципиальные изменения. Мы будем использовать метод serialEvent(), который будет вызываться каждый раз, когда обнаруживается определенный символ в буфере.

Начало нашего скетча будет такое эе, за исключением новой булевой переменной firstContact, которая позволяет нам определить, есть ли соединение с Arduino.

import processing.serial.*; //импортируем библиотеку Serial library

Serial myPort; //объект типа Serial

String val; // так как мы реализуем рукопожатие по серийному порту,

// надо проверить, поступали ли данные от Arduino

boolean firstContact = false;

Функция setup() такая же как и в программе для считывания данных с серийного порта. Единственное, мы добавили строку myPort.bufferUntil('\n');. Это позволяет нам хранить поступающие данные в буфере, пока мы не обнаружим определенный символ. В этом случае возвращаем (\n), так как мы отправляем Serial.println от Arduino. ‘\n’ в конце значит, что мы активируем новую строку, то есть это будут последние данные, которые мы увидим.

void setup() {

size(200, 200); //создаем окно с размерами 200 x 200 пикселей

// инициализируем серийный порт и устанавливаем скорость передачи данных 9600 baud

myPort = new Serial(this, Serial.list()[4], 9600);

myPort.bufferUntil('\n');

}

Так как мы постоянно отсылаем данные, метод serialEvent() выполняет задачи нашего нового цикла draw(), так что можно его оставить пустым:

void draw() { //можем оставить метод draw пустым,

//так как все необходимые операции производятся в пределах serialEvent (смотрите ниже)

}

Теперь рассмотрим основной и самый объемный метод: serialEvent(). Каждый раз, когда мы выходим на новую строку (\n), вызывается этот метод. И каждый раз проводится следующая последовательность действий:

  • Считываются поступающие данные;
  • Проверяется, содержат ли они какие-то значения (то есть, не передался ли нам пустой массив данных или "нуль");
  • Удаляем пробелы и другие ненужные вещи;
  • Если мы первый раз получили необходимые данные, изменяем значение булевой переменной firstContact и сообщаем Arduino, что мы готовы принимать новые данные;
  • Если это не первый прием необходимого типа данных , отображаем их в консоли и отсылаем микроконтроллеру данные о клике, который совершался;
  • В конце сообщаем Arduino, что мы готовы принимать новый пакет данных.

Это приличное количество шагов. К счастью Processing обеспечивает нас набором функций, которые значительно облегчают многие из перечисленных задач. Давайте взглянем, как все это реализуется в коде:

void serialEvent( Serial myPort) { //формируем строку из данных, которые поступают

// '\n' - разделитель, который конец пакета данных

val = myPort.readStringUntil('\n'); //убеждаемся, что наши данные не пустые перед тем, как продолжить

if (val != null) { //удаляем пробелы

val = trim(val);

println(val); //ищем нашу строку 'A' , чтобы начать рукопожатие

//если находим, то очищаем буфер и отсылаем запрос на данные

if (firstContact == false) {

if (val.equals("A")) {

myPort.clear();

firstContact = true;

myPort.write("A");

println("contact");

}

}

else { //если контакт установлен, получаем и парсим данные

println(val);

if (mousePressed == true) { //если мы кликнули мышкой по окну

myPort.write('1'); //отсылаем 1

println("1"); } // когда вы все данные, делаем запрос на новый пакет

myPort.write("A"); } } }

Выглядит немного устрашающе. Но если вы внимательно ознакомитесь с кодом (и особенно с комментариями), поймете, что все предельно просто. Если к этому моменту ваш Arduino прошит скетчем, который был рассмотрен в предыдущем разделе, можете подключать плату к персональному компьютеру и запускать программу в Processing. В консоли должна появится фраза 'Hello, world!'. Когда вы будете кликать мышкой в окне Processing, светодиод на 13 пине будет включаться и выключаться.

Подводные камни и наиболее частые ошибки

Порой при разработке проектов с Arduino и Processing (или без него) возникают проблемы. Если у вас что-то не получается, рекомендуем ознакомиться со списком ниже, в котором приведены самые распространенные ошибки:

  • Убедитесь, что скорость передачи данных (baud) на Arduino и в Processing установлены одинаковыми.
  • Убедитесь, что вы считываете данные с правильного порта в Processing. Есть команда Serial.list(), которая выведет вам перечень всех доступных портов.
  • Если вы используете метод serialEvent(), убедитесь, что вы включили функцию port.bufferUntil() в ваш метод setup().
  • Также проверьте, что символ, который вы ищете соответствует тому, который вы передаете с Arduino.

Оставляйте Ваши комментарии, вопросы и делитесь личным опытом ниже. В дискуссии часто рождаются новые идеи и проекты!