Работа с COM-портом Arduino из Java-приложения

Сап, хабр. Возможно, людям, начинающим изучать arduino, будет интересно, как легко и быстро организовать передачу информации между микроконтроллером и Java приложением. Данная связка открывает кучу интересных возможностей по сбору и обработке данных с датчиков, управлению различными свистелками-перделками, а также созданию своих первых IoT проектов.

Недавно на просторах интернета наткнулся на весьма простую библиотеку Java-Arduino Communication Library. Не найдя публикаций на эту тему здесь, решил поделиться с вами опытом использования. Для работы нам понадобятся установленные Arduino IDE, IntelliJ IDEA, Java SE Development Kit и, собственно, сам микроконтроллер (я тестировал на китайской Arduino Nano и Strela на базе Leonardo от Амперки, на обоих все все работало отлично).

Задача проста — создадим консольное приложение, которое при запуске устанавливает Serial-соединение с микроконтроллером и в бесконечном цикле ожидает ввода строки от пользователя. В зависимости от введенной строки возможны следующие варианты:

  • «on» — микроконтроллер включает встроенный светодиод;
  • «off» — микроконтроллер выключает встроенный светодиод;
  • «exit» — микроконтроллер выключает встроенный светодиод, и приложение завершает работу.

Скетч для микроконтроллера


Построение системы начнем с написания и загрузки скетча в Arduino Nano. Ничего сверхсложного. В блоке «setup» конфигурируем пин со светодиодом и Serial-порт, а в блоке «loop» слушаем Serial-порт на предмет пришедших байтов. В зависимости от полученного значения выполняем ту или иную операцию.

Исходный код скетча
/*пин №13 связан со встроенным светодиодом на платах Uno,
 * Mega, Nano, Leonardo, Mini и др.
 */
#define LED_PIN = 13

void setup() {
  //открытие Serial-порта со скоростью 9600 бод/c
  Serial.begin(9600);

  //настройка пина со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
}

void loop() {

  //если в буфере Serial-порта пришли байты (символы) и ожидают считывания
  if (Serial.available() != 0) {  
    
    //то считываем один полученный байт (символ)
    byte b = Serial.read();
    
    //если получен символ '1', то светодиод включается
    if (b == 49) digitalWrite(LED_PIN, HIGH);
    
    //если получен символ '0', то светодиод выключается
    if (b == 48) digitalWrite(LED_PIN, LOW);
}


Небольшого пояснения и внимательности требует лишь проверка условий (b == 49) и (b == 48). Если не понимаете почему так, то добро пожаловать под спойлер:

Ответ на главный вопрос жизни, вселенной и всего такого
Все дело в том, что при отправке на микроконтроллер по Serial-соединению символа (Chr) '1' используется кодировка ASCII, в которой символ '1' кодируется целочисленным десятичным значение (Dec) 49. При считывании символа микроконтроллером, значение символа '1' присваивается целочисленной переменной byte b. То есть фактически значение переменной b равно 49.

Для проверки на этом этапе можно из встроенного в Arduino IDE монитора порта отправить 1 и 0. Если светодиод на плате не включается/выключается, то ищите ошибку у себя в скетче.

Java-приложение


Теперь запустим IntelliJ IDEA и создадимм новый Java-проект. Для работы потребуется подключить две дополнительные библиотеки: jSerialComm-1.3.11.jar и arduino.jar. Как добавить скаченные jar-архивы можно прочитать вот здесь.

Все приложение будет состоять из одного единственного класса:

Исходный Java-код
import arduino.Arduino;
import java.util.Scanner;

public class AppMain {

    public static void main(String[] args) throws InterruptedException {

        Scanner scanner = new Scanner(System.in);
        Arduino arduino = new Arduino("COM52", 9600);

        boolean connected = arduino.openConnection();
        System.out.println("Соединение установлено: " + connected);
        Thread.sleep(2000);

        label_1:
        while (scanner.hasNext()) {

            String s = scanner.nextLine();

            switch (s) {
                case "on":
                    arduino.serialWrite('1');
                    break;
                case "off":
                    arduino.serialWrite('0');
                    break;
                case "exit":
                    arduino.serialWrite('0');
                    arduino.closeConnection();
                    break label_1;             
                default:
                    System.out.println(s + " - не является командой");
                    break;
            }
        }
    }
}

Для работы с COM портом создается объект класcа Arduino. Конструктор принимает два параметра:

  1. String portDescrition — название COM-порта
  2. int baud_rate — скорость передачи

Лучше указать эти параметры сразу в конструкторе, но можно и установить отдельно с помощью сеттеров. Название COM-порта можно посмотреть в Arduino IDE, либо в диспетчере устройств. Скорость передачи должна совпадать с той, что указана в блоке «setup» скетча для микроконтроллера, в данном случае 9600 бод/c:

void setup() {
  //открытие Serial-порта со скоростью 9600 бод/c
  Serial.begin(9600);

  //настройка пина со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
}

}

Далее необходимо установить соединение с помощью метода openConnection(). Метод возвращает true в случае успешного соединения. Выведем это значение в консоль, чтобы убедиться в правильности выполненных действий.

Важно: после открытия соединения необходимо сделать паузу с помощью метода Thread.sleep(), в данном случае 2000 миллисекунд. Arduino Nano оказался настоящим тугодумом по сравнению со Strela, отправлять данные которой можно было сразу же после установки соединения. Вполне возможно, что вашему контроллеру понадобится даже больше времени. Поэтому если соединение установлено, данные отправляются, но не приходят, то первым делом увеличьте величину паузы.

Теперь входим в бесконечный цикл и начинаем ожидать ввода от пользователя:

        label_1:
        while (scanner.hasNext()) {

            String s = scanner.nextLine();

            switch (s) {
                case "on":
                    arduino.serialWrite('1');
                    break;
                case "off":
                    arduino.serialWrite('0');
                    break;
                case "exit":
                    arduino.serialWrite('0');
                    arduino.closeConnection();
                    break label_1;             
                default:
                    System.out.println(s + " - не является командой");
                    break;
            }
        }

При введении очередной строки и нажатии «enter» выполняется ее чтение и сохранение в переменную String s. В зависимости от значения этой строки оператор switch отсылает на микроконтроллер символ '1' или '0' с помощью метода serialWrite(char c). Не забывайте, что когда микроконтроллер получит эти символы и сохранит их в целочисленную переменную, то вы получите 49, либо 48).

Вообще для пересылки данных можно использовать следующие перегруженные методы класса Arduino:

  1. public void serialWrite(String s);
  2. public void serialWrite(char c);
  3. public void serialWrite(String s,int noOfChars, int delay);
  4. public void serialWrite(char c, int delay);

Как видим, можно отправлять строки целиком, символ и часть символов строки (noOfChars). При этом в последних двух методах можно указывать паузу после отправки очередного символа. Однако, строка все равно будет отсылаться символ за символом (а если быть еще точнее, то бит за битом). Поэтому если вы не передаете какое-то конкретное значение (например, угол, на который необходимо установить подключенный сервопривод), то проще отправлять именно один символ.

При завершении программы желательно закрыть COM-port с помощью метода close.connection(), чтобы при повторном запуске программы не получить ошибку, связанную с тем, что COM-порт прежнему занят, а для выхода из бесконечного цикла, ожидающего ввод строки, используйте оператор break c указанием метки label_1, который позволяет выйти из цикла, перед которым стоит соответствующая метка:

case "exit":
     arduino.serialWrite('0');
     arduino.closeConnection();
     break label_1;                    

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

Подробнее
Реклама
Комментарии 17
  • 0
    Небольшого пояснения и внимательности требует лишь проверка условий (b == 49) и (b == 48)

    Вы видели вот эту статью: www.arduino.cc/en/Reference/Char?
    Язык Си позволяет записать упомянутые вами проверки так:
    (b == '1') и (b == '0')
    • 0
      Поленился проверить, спасибо за замечание)
    • 0

      В этой статье прекрасно всё. Особенно понравился костыль с задержкой.

      • 0
        Labeled break Вам не так сильно понравился?)
        • 0
          Не знаю)
          Уродливо, конечно, но всё-таки придираться к качеству кода непрофессионала — как-то не очень честно. А вот к непониманию основ, из-за которого и возникают все эти библиотеки, таскаемые руками в виде jarников и не делающие ничего, кроме оборачивания кода общего назначения в try-catch{} и обзывания его функций понятными ардуинщику именами, вполне можно.
        • 0
          Напишите тогда полезный коммент: предложите некостыльный вариант.
        • 0
          А разве последовательные порты com / serial нельзя просто открыть как файл в системах Windows или Linux? Зачем эти библиотеки?
          • 0
            Затем, чтобы не разбираться во всех особенностях работы с серийными портами как с файлами, а сосредоточиться на решаемой проблеме: зажечь светодиод с клавиатуры. Вы же не будете писать свой http сервер для того чтобы создать сайт.
            • 0
              Конечно, можно. :)

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


              Вот этот момент что-то я не понимаю. Вы подали питание на плату вашей Arduino. Прошла инициализация регистров контроллера. Всё, он готов работать по UART. Это занимает миллисекунды, не более. О какой задержке в таком случае идёт речь?
              • 0
                Насколько я помню, ардуино автоматически перезагружается по сигналу DTR, который поднимается, когда открывается порт. Это сделано, для того, чтобы можно было в ардуиновской IDE ткнуть на кнопку «загрузить скетч» и она сработала бы без дополнительных телодвижений.

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

                Вот для того, чтобы дождаться, когда запустится скетч, автор и вставил задержку, видимо.
                • 0
                  Как показывает практика, ардуино нужно время на инициализацию порта при получении первой посылки с компьютера, потом все в пределах нормы. Мое устройство на меге256 пребывает в ступоре примерно 5 секунд, прежде чем ответить на первый запрос «есть тут кто на этом порту». Видимо это особенность реализации моста com-usb в конкретном ардуино. Последовательный поиск устройства на 4-5 портах становится тяжким испытанием, поэтому выходом может быть распараллеливание — каждому порту свою нитку.
                  Почему порт не инициализируется сразу после включения, а ждет первой посылки — могу только догадываться. Может Serial.begin() такой медленный, и выполняется только при использовании порта, или контроллер тормозной. ХЗ.
                  • 0
                    Скорее всего, прав DmitriyN, изначально было ощущение, что он перезагружается.
                • 0
                  При общении с Arduino через COM-порт столкнулся с тем, что этот самый COM-порт периодически отваливался и появлялся в /dev/ под другим номером. Подозреваю, что виной тому был китайский кабель, который шел в комплекте с ардуиной.

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

                  P.S: Либа EtherCard для них очень сыра, но альтернативы лучше не нашел.
                  • 0
                    А кто-нибудь юзал родную либу www.wch.cn/download/CH341SER_ANDROID_ZIP.html для китайских ардуин на чипе CH340?
                    • 0
                      >>добавить скаченные jar-архивы
                      в Java обществе принято использовать что-то типо Maven/Gradle и выкладывать сразу весь проект на github, чтобы не делать подобного рода манипуляции.

                      О чем собственно статья? о том, что есть библиотека jar Arduino?
                      если открыть ее — там всего пара-тройка классов, которые даже wrapperом назвать сложно, несколько методов вызывающих jSerialComm
                      public boolean openConnection(){
                      		if(comPort.openPort()){
                      			try {Thread.sleep(100);} catch(Exception e){}
                      			return true;
                      		}
                      		else {
                      			AlertBox alert = new AlertBox(new Dimension(400,100),"Error Connecting", "Try Another port");
                      			alert.display();
                      			return false;
                      		}
                      	}

                      а это вообще ужас, зачем внутри библиотеки делать графический вывод ошибки.
                      • 0
                        Да, Maven приянят, Graddle принят, но мне теперь изъяснятся с помощью понятных для вас технологий, или как? Я сделал статью для начинающих, о чем упомянул в самом начале. Или мне надо было еще over 10500 скроллов вниз объяснять новичкам, что такое Maven, Graddle, и как добавить «дипэнденси» в свой проект? Данная статья- quick start, поэтом если ваш уровень upperintermidiate, то можете смело проходить мимо. Мне, в свое время, очень не хватило такой статьи, и если хоть одному она покажется полезной, и заставит копнуть немного глубже- то я буду только рад.
                        P.S. А графический вывод ошибки нужен для того, чтобы «особо внимательные начинающие» сразу поняли, в чем проблема, а не доставали таких как вы, просветленных гуру.
                        • 0
                          да нет же, если будет maven/gradle, то можно сделать git clone blablablq && mvn clean run например и все полетит и заструится.
                          это ведь проще, чем подкладывать библиотеки, тем более, что не все пользуются Idea.

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