Взаимодействие с Arduino через Android

Добавлено 28 ноября 2016 в 02:00

Хотите послать текстовое сообщение с вашего смартфона с ОС Android на свою плату Arduino? В этой статье написано, как это сделать!

Взаимодействие с Arduino через Android

Что потребуется

  • смартфон на Android с поддержкой режима USB хоста (т.е. поддержка OTG) – большинство устройств, работающих с Android 3.1 и выше, поддерживают этот режим. Проверьте свой телефон с помощью USB Host Diagnostics App из Play Store;
  • Arduino – любая версия. Я буду использовать Uno R3;
  • USB кабель для Arduino;
  • USB OTG кабель – он необходим вам, чтобы подключить USB кабель Arduino к порту micro-USB телефона;
  • Android Studio – вам необходимо установить его. Это довольно просто сделать. Android Studio делает разработку приложений проще, благодаря своим предположениям и генерации кода. Это одна из лучших IDE. Вы также можете использовать эту статью в качестве руководства по установке Android IDE.

Основные компоненты приложения для Android

В Android приложении есть три основных файла:

MainActivity.java
Здесь находится выполняемый код на Java, который управляет тем, как будет функционировать приложение.
activity_main.xml
Содержит макет приложения, то есть, компоненты: кнопки, компоненты отображения текста и т.д.
AndroidManifest.xml
Здесь вы определяете, когда приложение должно запускаться, в какие права ему нужны, и к какому аппаратному обеспечению ему необходимо получить доступ.

Еще есть множество других файлов, но все они связаны друг с другом с помощью этих трех.

Активность может быть охарактеризована, как экран, где пользователь взаимодействует с телефоном. Активности содержат такие виджеты, как кнопки, текстовые поля, изображения и т.д., которые помогают в передаче информации. Данное руководство будет использовать только одну активность, MainActivity, которая будет принимать введенный пользователем текст, чтобы отправить его на Arduino, а также отображать принятый текст.

Макет

Макет Android приложения для взаимодействия с Arduino
Макет Android приложения для взаимодействия с Arduino

Мы будем использовать тот же макет, что и в USB App и Bluetooth App. Он прост и содержит минимум виджетов, необходимых для проверки соединения между устройствами.

Как вы можете видеть, он содержит виджет EditText для получения данных от пользователя, кнопки для запуска соединения, передачи данных, завершения соединения и очистки TextView. Полученные данные отображаются в TextView (пустое пространство под кнопками).

Вот часть XML кода. Поскольку код для кнопок похож, здесь он не приводится. Полный код можно скачать по ссылке в конце статьи.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" 
    tools:context=".MainActivity">

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Begin"
        android:id="@+id/buttonStart"
        android:layout_below="@+id/editText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:onClick="onClickStart"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textView"
        android:layout_below="@+id/buttonSend"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignRight="@+id/editText"
        android:layout_alignEnd="@+id/editText"
        android:layout_alignParentBottom="true" />

</RelativeLayout>

Я использовал здесь RelativeLayout, а это означает, что каждый виджет расположен относительно виджетов вокруг него. Макет может быть легко воссоздан на вкладке Design Tab, где вы можете перетащить виджеты туда, куда хотите. Нам необходимо описать, что будет происходить при нажатии на кнопку. Для этого используется метод onClick. Укажите имя метода в XML коде для кнопки. Для этого добавьте строку:

android:onClick="onClickMethod"

Теперь наведите курсор мыши на эту строку, слева должно будет появиться предупреждение, похожее на это:

Предупреждение в Android Studio
Предупреждение в Android Studio

Нажмите на варианте «Создать onClick...». Это автоматически добавит код метода onClick в MainActivity.java. Вам необходимо выполнить это для каждой кнопки.

Библиотека USB Serial

Настройка последовательного соединения в Android довольно трудоемка, так как требует от вас ручной настройки множества вещей, поэтому я нашел несколько библиотек, которые делают всё это автоматически. Я протестировал несколько из них и, наконец, остановился на библиотеке UsbSerial от Github пользователя felHR85. Среди подобных библиотек, что я нашел, она единственная до сих пор обновляется. Ее довольно легко настроить и использовать. Чтобы добавить библиотеку в свой проект, скачайте последнюю версию JAR файла на Github. Поместите его в подкаталог libs в каталоге вашего проекта. Затем в файловом проводнике в Android Studio кликните правой кнопкой мыши на JAR файле и выберите «Добавить как библиотеку» (Add as Library). Вот и всё!

Алгоритм выполнения программы

Алгоритм выполнения Android программы для взаимодействия с Arduino
Алгоритм выполнения Android программы для взаимодействия с Arduino

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

Открытие соединения

Во-первых, давайте определим метод onClick для кнопки Begin. При нажатии необходимо выполнить поиск всех подключенных устройств, а затем проверить, совпадает ли VendorID подключенного устройства (ID поставщика) с VendorID Arduino. Если совпадение найдено, то у пользователя должно быть запрошено разрешение. Каждое ведомое USB устройство имеет ID поставщика (Vendor ID) и ID продукта (Product ID), которые могут быть использованы для определения того, какие драйвера должны использоваться для этого устройства. Vendor ID для любой платы Arduino равен 0x2341 или 9025.

public void onClickStart(View view) {

    HashMap usbDevices = usbManager.getDeviceList();
    if (!usbDevices.isEmpty()) {
        boolean keep = true;
        for (Map.Entry entry : usbDevices.entrySet()) {
            device = entry.getValue();
            int deviceVID = device.getVendorId();
            if (deviceVID == 0x2341)   //Arduino Vendor ID
            {
                PendingIntent pi = PendingIntent.getBroadcast(this, 0, 
                new Intent(ACTION_USB_PERMISSION), 0);
                usbManager.requestPermission(device, pi);
                keep = false;
            } else {
                connection = null;
                device = null;
            }

            if (!keep)
                break;
        }
    }
}

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

// Приемник широковещательных сообщений для автоматического запуска и закрытия последовательного соединения.
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(ACTION_USB_PERMISSION)) {
            boolean granted = 
                intent.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED);
            if (granted) {
                connection = usbManager.openDevice(device);
                serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection);
                if (serialPort != null) {
                    if (serialPort.open()) { //Установить параметры последовательного соедниения.
                        setUiEnabled(true);  //Включить кнопки в UI.
                        serialPort.setBaudRate(9600);
                        serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8);
                        serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1);
                        serialPort.setParity(UsbSerialInterface.PARITY_NONE);
                        serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF);
                        serialPort.read(mCallback); //
                        tvAppend(textView,"Serial Connection Opened!\n");

                    } else {
                        Log.d("SERIAL", "PORT NOT OPEN");
                    }
                } else {
                    Log.d("SERIAL", "PORT IS NULL");
                }
            } else {
                Log.d("SERIAL", "PERM NOT GRANTED");
            }
        } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
            onClickStart(startButton);
        } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
            onClickStop(stopButton);
        }
    };
};

Если первое условие IF выполняется, и если пользователь дал разрешение, то начать соединение с устройством, у которого Vendor ID совпадает с необходимым нам Vendor ID. Кроме того, если принято широковещательное сообщение о подключении или отключении устройства, вручную вызывать методы onClick для кнопок Start и Stop. SerialPort определяется с использованием устройства и соединения в качестве аргументов. В случае успеха открыть SerialPort и установить соответствующие параметры. Значения параметров для Arduino Uno равны: 8 бит данных, 1 стоповый бит, бита четности нет, управление потоком выключено. Скорость передачи данных может быть 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600 или 115200 бит/с, но мы будем использовать стандартные 9600 бит/с.

Прием данных от устройства

Во фрагменте кода выше обратите внимание на строку, содержащую serialPort.read(mCallback). Здесь функции read передается ссылка на объект Callback, который будет автоматически срабатывать при обнаружении входящих данных.

UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { 
    // Определение метода обратного вызова, который вызывается при приеме данных.
    @Override
    public void onReceivedData(byte[] arg0) {
        String data = null;
        try {
            data = new String(arg0, "UTF-8");
            data.concat("/n");
            tvAppend(textView, data);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
};

Полученные данные будут в форме необработанных байтов. Нам придется перекодировать их в читаемый формат, например, UTF-8. Затем они добавляются в TextView с помощью специального метода tvAppend(). Это делается так потому, что любые изменения в пользовательском интерфейсе могут выполняться только в потоке пользовательского интерфейса. Так как данный Callback будет запущен, как фоновый поток, то он не может напрямую повлиять на пользовательский интерфейс.

private void tvAppend(TextView tv, CharSequence text) { 
    final TextView ftv = tv; 
    final CharSequence ftext = text; 

    runOnUiThread(new Runnable() { 
        @Override public void run() { 
            ftv.append(ftext); 
        } 
    }); 

} 

Передача данных на устройство

Передача данных относительно проста по сравнению с чтением данных с устройства. Это простой вызов функции с байтами данных, которые необходимо передать, в качестве аргумента. Это будет реализовано в методе onClick кнопки Send.

serialPort.write(string.getBytes());

Закрытие соединения

Чтобы закрыть соединение, просто закройте последовательный порт.

serialPort.close();

Манифест приложения

Манифест объявляет, какие дополнительные разрешения могут потребоваться приложению. Единственное необходимое нам разрешение – это разрешение сделать телефон USB хостом. Добавьте следующую строку в манифест:

<uses-feature android:name="android.hardware.usb.host" />

Приложение можно заставить запускаться автоматически, добавив фильтр интентов в главную активность MainActivity. Этот фильтр интентов будет срабатывать при подключении любого нового устройства. Вид устройства может быть указан явно с помощью ID поставщика (Vendor ID) и/или ID продукта (Product ID) в XML файле.

<?xml version="1.0" encoding="utf-8"?> 
<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>

    <meta-data
        android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
        
</activity>

Обратите внимание на строку "android:resource="@xml/device_filter". Она говорит компилятору, что он может найти свойства устройства в файле с именем device_filter в каталоге src/main/res/xml, поэтому создайте подкаталог "xml" в каталоге src/main/res и поместите в него файл со следующим содержанием:

<resources>
    <usb-device vendor-id="9025" />
    <!-- Vendor ID для Arduino -->
</resources>

Тестирование приложения

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

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

void loop()  
{  
  char c;
  if(Serial.available())  
  {  
    c = Serial.read();  
    Serial.print(c);  
  }  
}  

Теперь подключите Arduino к microUSB порту телефона, используя OTG кабель. Приложение должно запуститься автоматически. Попробуйте послать какой-нибудь текст, и те же данные будут возвращены обратно!

Тестирование Android приложения для взаимодействия с Arduino
Тестирование Android приложения для взаимодействия с Arduino

Заключение

Данная статья показывает, как Arduino может общаться с вашим смартфоном. И возможности использования этого бесконечны! В случае, когда необходимы данные с любого датчика, которого нет среди встроенных в смартфон, можно воспользоваться любым микроконтроллером для считывания данных с этого датчика и передачи их на смартфон. В следующей статье будет показано, как подключить смартфон к Arduino, используя популярный bluetooth модуль HC05.

Теги

AndroidArduinoOTGUSBПоследовательная связь

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.


  • 2023-04-15rumvit rumvit

    И все же, как это безусловно замечательный и рабочий материал научить работать с несколькими Vendor ?

  • 2023-04-15rumvit

    Спасибо.Очень интересная публикация.У меня получилось решить задачу, которая меня интересовала.Благодаря вашему труду.
    И все таки есть один вопрос, у меня используются три вендора.

    Работают все. Но получается либо-либо-либо.Можно ли сделать в программе выбор этого безобразия?
    Я имею в виду наличие файла device_filter.xml и строчку в коде if (deviceVID == 0x1A86)

  • 2022-09-02КиберБагет

    Добрый день.
    Приложение компилируется без проблем, устанавливается на смартфон, но не видит плату. Плата Arduino Nano.
    постоянно переходит до
    else {
    connection = null;
    device = null;

    добавил второй Vendor ID (0x2A03), но все равно не работает.

    Если убрать конструкции if\else, и вместо них сделать Toast с VendorVID, то просто вываливает ошибку.

  • 2020-01-16Константин

    Вопрос не по теме. В конце данной статьи Вы пишете "В следующей статье будет показано, как подключить смартфон к Arduino, используя популярный bluetooth модуль HC05." К сожалению не смог такую статью найти у Вас на сайте.

  • 2019-10-24Дмитрий Гладышев

    "Тупой" не получится, так как USB это пакетный интерфейс. И там контроллер обязателен.

  • 2019-09-15Rustam Mizov

    Лучше сразу интерфейс какой нибудь ("тупой" без процессора) с подключением к USB, так чтобы можно было опрашивать показания на пинах этого интерфейса с самого планшета.
    У планшета много оперативы и высотактовые процессоры.
    Без проблем можно фиксировать мегагерцевые выборки.

  • 2019-08-09Elnur Mehdiyev

    когда пытаюсь создать подписанный apk:
    Lint found fatal errors while assembling a release target.

  • 2019-08-09radioprog

    Какие именно ошибки?

  • 2019-08-09Elnur Mehdiyev

    Ne rabotaet proga v android studio, mnogochislennie oshibki...