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

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

Связываемся с Processing

При работе с платой Arduino мы иногда выводим результат на Serial Monitor. Но это не единственная возможность для получения данных на экране. Например, вы можете воспользоваться программой Processing (http://processing.org/).

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

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

Запустим Arduino IDE и выберем простейший пример вывода данных на Serial Port:


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

void loop() {
  Serial.println("Hello Kitty!");
  // ждем 500 миллисекунд перед следующей отправкой
  delay(500);
}

Запустим пример и убедимся, что код работает.

Получение данных

Теперь мы хотим получить этот же текст в Processing. Запускаем новый проект и напишем код.

Первый шаг - импортировать библиотеку. Идем в Sketch | Import Library | Serial. В скетче появится строка:


import processing.serial.*;

Далее объявляем переменные, создаём обязательные функции. Обратите внимание, что в отличии от скетчей Arduino, в скетчах Processing используется функция draw() вместо loop().


import processing.serial.*;

Serial serial; // создаем объект последовательного порта
String received; // данные, получаемые с последовательного порта

void setup()
{
  String port = Serial.list()[0];
  serial = new Serial(this, port, 9600);
}

void draw() {

  if ( serial.available() > 0) { // если есть данные,
    received = serial.readStringUntil('\n'); // считываем данные
  }
  println(received); //отображаем данные в консоли
}

Чтобы обеспечить прием данных с последовательного порта, нам нужен объект класса Serial. Так как с Arduino мы отправляем данные типа String, нам надо получить строку и в Processing.

В методе setup() нужно получить доступный последовательный порт. Как правило, это первый доступный порт из списка. После этого мы можем настроить объект Serial, указав порт и скорость передачи данных (желательно, чтобы скорости совпадали).

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

Processing

Processing позволяет работать не только с консолью, но и создавать стандартные окна. Перепишем код.


import processing.serial.*;

Serial serial; // создаем объект последовательного порта
String received; // данные, получаемые с последовательного порта

void setup()
{
  size(320, 120);
  String port = Serial.list()[0];
  serial = new Serial(this, port, 9600);
}

void draw() {

  if ( serial.available() > 0) { // если есть данные,
    // считываем их и записываем в переменную received
    received = serial.readStringUntil('\n');
  }

  // Настройки для текста
  textSize(24);
  clear();
  if (received != null) {
    text(received, 10, 30);
  }
}

Запустим пример ещё раз и увидим окно с надписью, которое перерисовывается в одном месте.

Processing

Таким образом мы научились получать данные от Arduino. Это позволит нам рисовать красивые графики или создавать программы контроля за показаниями датчиков.

Отправка данных

Мы можем не только получать данные с платы, но и отправлять данные на плату, заставляя выполнять команды с компьютера.

Допустим, мы будем посылать символ "1" из Processing. Когда плата обнаружит присланный символ, включим светодиод на порту 13 (встроенный).

Скетч будет похож на предыдущий. Для примера создадим небольшое окно. При щелчке в области окна будем отсылать "1" и дублировать в консоли для проверки. Если щелчков не будет, то посылается команда "0".


import processing.serial.*;

Serial serial; // создаем объект последовательного порта
String received; // данные, получаемые с последовательного порта

void setup()
{
  size(320, 120);
  String port = Serial.list()[0];
  serial = new Serial(this, port, 9600);
}

void draw() {

  if (mousePressed == true) { 
    //если мы кликнули мышкой в пределах окна
    serial.write('1'); //отсылаем 1
    println("1");
  } else { //если щелчка не было
    serial.write('0'); //отсылаем 0
  }
}

Теперь напишем скетч для Arduino.


char commandValue; // данные, поступаемые с последовательного порта
int ledPin = 13; // встроенный светодиод

void setup() {
  pinMode(ledPin, OUTPUT); // режим на вывод данных
  Serial.begin(9600);
}

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

  if (commandValue == '1') {
    digitalWrite(ledPin, HIGH); // включаем светодиод
  }
  else {
    digitalWrite(ledPin, LOW); // в противном случае выключаем
  }
  delay(10); // задержка перед следующим чтением данных
}

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

Обмен данными

Теперь попытаемся объединить оба подхода и обмениваться сообщениями между платой и приложением в двух направлениях.

Для максимальной эффективности добавим булеву переменную. В результате у нас отпадает необходимость постоянно отсылать 1 или 0 от Processing и последовательный порт разгружается и не передает лишнюю информацию.

Когда плата обнаружит присланную единицу, то меняем булевое значение на противоположное относительно текущего состояния (LOW на HIGH и наоборот). В else используем строку "Hello Kity", которую будем отправлять только в случае, когда не обнаружим '1'.

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


char commandValue; // данные, поступаемые с последовательного порта
int ledPin = 13;
boolean ledState = LOW; //управляем состоянием светодиода

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  establishContact(); // отсылаем байт для контакта, пока ресивер отвечает
}

void loop() {
  // если можно прочитать данные
  if (Serial.available() > 0) {
    // считываем данные
    commandValue = Serial.read();
    if (commandValue == '1') {
      ledState = !ledState;
      digitalWrite(ledPin, ledState);
    }
    delay(100);
  }
  else {
    // Отсылаем обратно
    Serial.println("Hello Kitty");
  }
  delay(50);
}

void establishContact() {
  while (Serial.available() <= 0) {
    Serial.println("A"); // отсылает заглавную A
    delay(300);
  }
}

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

Добавим новую булеву переменную firstContact, которая позволяет определить, есть ли соединение с Arduino.

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

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

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

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


import processing.serial.*;

Serial serial; // создаем объект последовательного порта
String received; // данные, получаемые с последовательного порта
// Проверка на поступление данных от Arduino
boolean firstContact = false;

void setup()
{
  size(320, 120);
  String port = Serial.list()[0];
  serial = new Serial(this, port, 9600);
  serial.bufferUntil('\n');
}

void draw() {
}

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

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

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

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

    received = trim(received);

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

    //если находим, то очищаем буфер и отсылаем запрос на данные
    if (firstContact == false) {
      if (received.equals("A")) {
        serial.clear();
        firstContact = true;
        myPort.write("A");
        println("contact");
      }
    } else { //если контакт установлен, получаем и парсим данные
      println(received);

      if (mousePressed == true) { //если мы кликнули мышкой по окну
        serial.write('1'); //отсылаем 1
        println("1");
      } // когда вы все данные, делаем запрос на новый пакет
      serial.write("A");
    }
  }
}

При подключении и запуске в консоли должна появится фраза 'Hello Kitty'. Когда вы будете щелкать мышкой в окне Processing, светодиод на пине 13 будет включаться и выключаться.

Написано по мотивам статьи.

Реклама