
Картинка rawpixel
Любой энтузиаст, строящий свои проекты на базе Arduino, рано или поздно сталкивается с необходимостью тем или иным способом обеспечить взаимосвязь этой платы со своим компьютером.
Такой симбиоз даёт возможность как управлять платой с компьютера, так и наоборот — компьютером с платы. Об этом мы и поговорим в этой статье.
Постановка задачи и общие сведения о библиотеке
В рамках этой задачи я не имею в виду стандартное общение самой платы со средой разработки, я имею в виду, что условно необходимо реализовать «прямое» общение платы и компьютера. Конечно, это подразумевает собой наличие на компьютере некой утилиты, которая и будет осуществлять подобное общение.
Подобная программа может быть написана на любом известном вам языке программирования, но так уж исторически сложилось, что я более-менее понимаю java, поэтому разговор далее будет идти только об этом языке и библиотеках под него.
Некоторое время назад для решения подобного вопроса — использовалась библиотека RxTx, однако в её работе отмечаются некоторые проблемы, ввиду необходимости установки DLL под Windows, а также проблемы со стабильностью (судя по ряду мнений активных пользователей этой библиотеки. Возможно, у вас был другой опыт).
Сегодня есть гораздо более простая и удобная в использовании библиотека, которая позволяет организовать общение через COM-порты: jSerialComm.
Она удобна тем, что обеспечивает доступ комфортным, независимым от платформы способом, без использования каких-либо дополнительных инструментов и библиотек.
Подробное описание API этой библиотеки находится по этому адресу:

Так как сама библиотека не содержит достаточно большого количества примеров использования (только самые основные), то наверняка вам пригодится и вот эта подборка, в которой собрано 20 достаточно полезных коротких примеров использования возможностей библиотеки.
Такой, например, как инициализация порта:
public Serial(SerialPort port) throws IOException { port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, TIME_OUT, TIME_OUT); port.setComPortParameters(DATA_RATE, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED); if (port.openPort()) { this.port = port; output = port.getOutputStream(); input = port.getInputStream(); } else { throw new IOException("Cannot open serial port."); } }
Открытие порта:
@Override public boolean openPort() throws Exception { responseMessageHandler = new ResponseMessageHandler(); if (serialPort == null) { throw new ConnectionException("The connection wasn't initialized"); } return serialPort.openPort(); }
Закрытие порта:
@Override public void closePort() throws Exception { if (serialPort != null) { serialPort.removeDataListener(); serialPort.closePort(); } }
Но до начала всех манипуляций, нам необходимо вывести в консоль, и узнать, какие COM-порты у нас вообще доступны:
package main; import com.fazecast.jSerialComm.SerialPort; public class Starter { public static void main(String[] args) { System.out.println("Hello world"); SerialPort[] ports = SerialPort.getCommPorts(); for (SerialPort port: ports) { System.out.println(port.getSystemPortName()); } } }
Библиотека может работать в ряде режимов, которые отличаются степенью блокировки (т.е., ожидания поступления данных).
Например, режимом по умолчанию является неблокирующий: методы, читающие данные, возвратят любые данные, которые доступны. Без какого-либо ожидания:
SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); try { while (true) { while (comPort.bytesAvailable() == 0) Thread.sleep(20); byte[] readBuffer = new byte[comPort.bytesAvailable()]; int numRead = comPort.readBytes(readBuffer, readBuffer.length); System.out.println("Read " + numRead + " bytes."); } } catch (Exception e) { e.printStackTrace(); } comPort.closePort();
В противовес ему бывают ситуации, когда вы заранее знаете количество данных, которые должны получить. В такой ситуации вам нужно, чтобы метод, запрашивающий данные, дождался их получения и не возвращался «хоть с каким-нибудь» значением раньше времени.
Как раз для работы в таком режиме и предназначен полностью блокирующий способ. Например, в коде ниже, метод readBytes() ждёт запрошенные 1024 байта в течение 1 секунды:
SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 1000, 0); try { while (true) { byte[] readBuffer = new byte[1024]; int numRead = comPort.readBytes(readBuffer, readBuffer.length); System.out.println("Read " + numRead + " bytes."); } } catch (Exception e) { e.printStackTrace(); } comPort.closePort();
Если нужно, чтобы он бесконечно ждал поступления этих данных, то тогда внесите вот такое изменение в строке, где содержится setComPortTimeouts:
comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
Более подробно обо всех доступных режимах вы можете прочитать здесь.
Также существует полностью асинхронный режим работы, в котором возможно прослушивание ряда событий и реагирование на них. В этом режиме тайм-ауты (всевозможные блокировки) игнорируются:
SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.addDataListener(new SerialPortDataListener() { @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) return; byte[] newData = new byte[comPort.bytesAvailable()]; int numRead = comPort.readBytes(newData, newData.length); System.out.println("Read " + numRead + " bytes."); } });
Например, в коде выше, при наличии любых данных, доступных для чтения — будет запущен обратный вызов.
Также (при желании) вы можете использовать стандартные Java интерфейсы Inputstream, Outputstream, например, так:
SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); InputStream in = comPort.getInputStream(); try { for (int j = 0; j < 1000; ++j) System.out.print((char)in.read()); in.close(); } catch (Exception e) { e.printStackTrace(); } comPort.closePort();
Подключаем Arduino к компьютеру
Случай, когда компьютер шлёт данные на Arduino
Теперь вернёмся к нашей задаче общения компьютера с Arduino. Понятно, что задача может быть двоякой: скажем так, «нисходящей» и «восходящей».
То есть, когда инициатором является компьютер или Arduino.
Хороший пример первого варианта можно посмотреть здесь. В нём содержится код как со стороны компьютера, так и со стороны Arduino. Рассмотрим компьютерную сторону:
Java-код для компьютера
package de.mschoeffler.arduino.serialcomm.example01; import java.io.IOException; import com.fazecast.jSerialComm.SerialPort; /** * Simple application that is part of an tutorial. * The tutorial shows how to establish a serial connection between * a Java and Arduino program. * @author Michael Schoeffler (www.mschoeffler.de) * */ public class Startup { public static void main(String[] args) throws IOException, InterruptedException { // device name TODO: must be changed SerialPort sp = SerialPort.getCommPort("/dev/ttyACM1"); // default connection settings for Arduino sp.setComPortParameters(9600, 8, 1, 0); // block until bytes can be written sp.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 0); if (sp.openPort()) { System.out.println("Port is open :)"); } else { System.out.println("Failed to open port :("); return; } for (Integer i = 0; i < 5; ++i) { sp.getOutputStream().write(i.byteValue()); sp.getOutputStream().flush(); System.out.println("Sent number: " + i); Thread.sleep(1000); } if (sp.closePort()) { System.out.println("Port is closed :)"); } else { System.out.println("Failed to close port :("); return; } } }
Мы видим, что код является полностью блокирующим (т.к. мы заранее знаем объём передаваемых данных, когда в цикле с компьютера пересылаем одну цифру на Arduino). Кроме того, следующая строка даёт нам хороший пример того, как нужно конфигурировать порт для общения с Arduino:
sp.setComPortParameters(9600, 8, 1, 0);
Также можно легко заметить (так как инициатором является компьютер), что в этом случае мы используем исходящий с компьютера поток (OutputStream):
sp.getOutputStream().write(i.byteValue()); sp.getOutputStream().flush();
Перейдём к стороне Arduino. Здесь всё достаточно стандартно, и для чтения используется простая конструкция:
if (Serial.available() > 0) { byte incomingByte = 0; incomingByte = Serial.read(); }
Случай, когда Arduino шлёт данные на компьютер
А теперь попробуем рассмотреть обратный вариант, когда необходимо общаться, так сказать, в «восходящем» режиме — то есть Arduino шлёт сообщения компьютеру.
Для этого нам надо внести изменения в Java код, а также изменить скетч прошивки Arduino.
Начнём с кода Java. Во-первых, так как в этом случае нужно принимать поток данных, нам необходимо изменить getOutputStream на getInputStream:
byte b= (byte) sp.getInputStream().read();
Также нам необходимо изменить режим записи — на режим чтения, переключив его на полублокирующий вариант. Как можно увидеть в строке ниже, полублокирующий режим у нас включён таким образом, что мы ждём, пока хотя бы 1 байт данных не будет успешно прочитан (это нули, следующие за выражением TIMEOUT_READ_SEMI_BLOCKING):
sp.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);
И весь код будет выглядеть следующим образом:
import com.fazecast.jSerialComm.*; import java.io.*; import java.util.ArrayList; /** * Based on the tutorial example, * showing how to establish a serial connection between * a Java and Arduino program: * @author Michael Schoeffler (www.mschoeffler.de) * */ public class Main { public static void main(String[] args) throws IOException, InterruptedException { SerialPort sp = SerialPort.getCommPorts()[1]; sp.setComPortParameters(9600, 8, 1, 0); // настройки для Arduino sp.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); if (sp.openPort()) { System.out.println("Port is open :)"); } else { System.out.println("Failed to open port :("); return; } //в бесконечном цикле слушаем порт while(true) { byte b= (byte) sp.getInputStream().read(); System.out.println("Received data: " + b); } } }
Как можно заметить из кода, мы сделали так, что поступающие данные из COM-порта читаются в бесконечном цикле.
Скетч Arduino, в свою очередь, достаточно прост и выглядит следующим образом:
byte b = 23; void setup() { Serial.begin (9600); } void loop() { delay(1000); Serial.write(b); }
Как можно заметить, с периодичностью в одну секунду мы просто пишем байты в COM-порт.
Для чего это всё можно применить?
А теперь попробуем прикинуть, для чего, собственно, вся эта последовательность телодвижений может пригодиться?
Понятно, что способ общения Arduino с компьютером является весьма полезным для целой массы применений, среди которых могут быть и разнообразные погодные станции, и управление системами робототехники и т. д.
Лично мой кейс использования выглядит приблизительно так (для чего, собственно, я это всё и затеял. Уже жду детали из Китая):
Дело в том, что я уже достаточно давно (лет 10) для просмотра телевизора и фильмов, использую видеопроектор. Телевизионный сигнал идёт с подключаемой приставки, а фильмы я смотрю с компьютера. Чтобы мне не нужно было постоянно перетыкать HDMI-кабель в гнездо видеопроектора — я купил специальный ра��ветвитель, который после нажатия кнопки переключает один источник сигнала на другой (это всё можно было и не писать, но так ситуация будет более понятна).
Периодически, когда я смотрю телевизор, я натыкаюсь на ряд фильмов, которые идут по телевизору в SD-качестве. Несмотря на то, что я эти фильмы уже видел 100500 раз — мне всё равно хочется пересматривать их ещё.
В этот момент я вспоминаю, что доступ к этим фильмам есть у меня на компьютере и в отличном качестве. После чего — я иду к компьютеру, включаю фильм, возвращаюсь обратно, переключаю источник сигнала и смотрю фильм уже в хорошем качестве.
То есть, если бы у меня был некий способ, не вставая с дивана включать фильм на компьютере — я вполне мог бы начать смотреть его с минимальными усилиями и без лишних походов.
Тут многие, наверное, скажут: «да купи ты себе беспроводную мышь и не парься!»
На это у меня есть что ответить: раньше так и было, однако однажды у меня родилась мысль, как можно упростить это ещё сильнее!
В одной из прошлых статей мы уже рассматривали способ, как можно подключить к своей Arduino любой имеющийся под рукой инфракрасный пульт дистанционного управления.
Суть этого способа в двух словах заключается в следующем:
- мы подключаем приёмник инфракрасного излучения к Arduino,
- загружаем в Arduino специальный скетч, после чего нажимаем на любую нужную кнопку пульта дистанционного управления (например, на ту кнопку, которая у нас обычно ни в чём не задействована). При нажатии на эту кнопку пульт излучает определённый код, который передаётся на Arduino, и отображается в мониторе порта. Записываем этот код (например, на бумажку),
- далее мы загружаем в Arduino другой скетч, который является уже исполнителем определённых действий, при получении этого кода на инфракрасный приёмник.
Таким образом, совместив между собой эти два способа (то есть приём и обработку кодов нажатий определённых кнопок на пульте), на подключённой к компьютеру Arduino — с Java программой компьютере, мы можем управлять с инфракрасного пульта процессами, протекающими на компьютере!
Например, в моём случае, я хочу запускать воспроизведение фильмов на компьютере.
Тут следует сделать одну оговорку: дело в том, что пульт управления от видеопроектора у меня неродной (родной вышел из строя) и поэтому я использую программируемый универсальный пульт, в котором у меня используется, дай бог, если 35% от имеющихся на нём кнопок.
Получается, я вполне могу назначить на оставшиеся кнопки весьма широкий набор функций: добавить громкость, убавить громкость, включить воспроизведение, поставить на паузу, перемотка, переключить на другой видеофайл и т.д. и т.п.
Весь проект со стороны компьютера я вижу следующим образом: в компьютер (всегда в один и тот же USB-разъём) будет воткнута самая маленькая Arduino, которая у меня имеется, — Arduino Nano, с подключённым к ней инфракрасным приёмником. Всё это будет выполнено достаточно компактно — в форм-факторе флешки, а корпус будет изготовлен 3D печатью.
Сама java-программа будет добавлена в автозагрузку компьютера.
Скетч для Arduino будет максимально простым. Приблизительно похожим на тот, что вы уже видели выше.
В завершение хочу сказать, что я таким образом собираюсь несколько увеличить комфорт своего просмотра фильмов и телевизора на досуге, а вы же — можете использовать этот способ для каких-то своих, более глобальных целей.
Если подумать, применений этому способу может быть очень много. С ходу мне в голову приходит изготовление некоего «презентера», который позволяет запускать файлы на компьютере и выводить их на большой экран во время выступлений на сцене перед большой аудиторией. Это всего лишь одна из идей. А так, думаю, этот способ может быть многим полезен.
Удачи всем в сборке!
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
