Сап, хабр. Возможно, людям, начинающим изучать 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;                    

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

Комментарии (14)


  1. iliasam
    21.10.2017 18:53

    Небольшого пояснения и внимательности требует лишь проверка условий (b == 49) и (b == 48)

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


    1. CodeRunner Автор
      24.10.2017 12:08

      Поленился проверить, спасибо за замечание)


  1. izzholtik
    21.10.2017 22:12

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


    1. andd3dfx
      21.10.2017 22:21

      Labeled break Вам не так сильно понравился?)


      1. izzholtik
        21.10.2017 23:22

        Не знаю)
        Уродливо, конечно, но всё-таки придираться к качеству кода непрофессионала — как-то не очень честно. А вот к непониманию основ, из-за которого и возникают все эти библиотеки, таскаемые руками в виде jarников и не делающие ничего, кроме оборачивания кода общего назначения в try-catch{} и обзывания его функций понятными ардуинщику именами, вполне можно.


    1. Nikelandjelo
      22.10.2017 09:13

      Напишите тогда полезный коммент: предложите некостыльный вариант.


  1. znsoft
    22.10.2017 08:21

    А разве последовательные порты com / serial нельзя просто открыть как файл в системах Windows или Linux? Зачем эти библиотеки?


    1. Nikelandjelo
      22.10.2017 09:31

      Затем, чтобы не разбираться во всех особенностях работы с серийными портами как с файлами, а сосредоточиться на решаемой проблеме: зажечь светодиод с клавиатуры. Вы же не будете писать свой http сервер для того чтобы создать сайт.


    1. da-nie
      22.10.2017 09:33

      Конечно, можно. :)

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


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


      1. DmitriyN
        22.10.2017 13:36

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

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

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


      1. redial
        23.10.2017 17:25

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


      1. CodeRunner Автор
        24.10.2017 12:19

        Скорее всего, прав DmitriyN, изначально было ощущение, что он перезагружается.


  1. PowerREDs
    23.10.2017 17:26

    При общении с Arduino через COM-порт столкнулся с тем, что этот самый COM-порт периодически отваливался и появлялся в /dev/ под другим номером. Подозреваю, что виной тому был китайский кабель, который шел в комплекте с ардуиной.

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

    P.S: Либа EtherCard для них очень сыра, но альтернативы лучше не нашел.


  1. medvedevia
    23.10.2017 23:21

    А кто-нибудь юзал родную либу www.wch.cn/download/CH341SER_ANDROID_ZIP.html для китайских ардуин на чипе CH340?