Перейти к содержимому

Как сделать приложение для ардуино

  • автор:

Связываем Arduino и Android через Bluetooth

В данной статье будет подробно расписано создание небольшого приложения для мобильной операционной системы Android и скетча для Arduino. На Arduino Uno будет стоять Wireless Shield с Bluetooth-модулем. Приложение будет подключаться к Bluetooth-модулю и посылать некую команду. В свою очередь скетч по этой команде будет зажигать или гасить один из подключенных к Arduino светодиодов.

Нам понадобится

Телефон на базе ОС Android

Создание приложения для Android

Заготовка

Разработка для ОС Android ведется в среде разработки ADT, Android Development Tools. Которую можно скачать с портала Google для разработчиков. После скачивания и установке ADT, смело его запускаем. Однако, еще рано приступать к разработке приложения. Надо еще скачать Android SDK нужной версии. Для этого необходимо открыть Android SDK Manager «Window → Android SDK Manager». В списке необходимо выбрать нужный нам SDK, в нашем случае Android 2.3.3 ( API 10). Если телефона нет, то выбирайте 2.3.3 или выше; а если есть — версию, совпадающую с версией ОС телефона. Затем нажимаем на кнопку «Install Packages», чтобы запустить процесс установки.

После завершения скачивания и установки мы начинаем создавать приложение. Выбираем «File → New → Android Application Project». Заполним содержимое окна так, как показано на рисунке.

Application Name — то имя приложения, которое будет показываться в Google Play Store. Но выкладывать приложение мы не собираемся, поэтому имя нам не особо важно.

Project Name — имя проекта в ADT.

Package Name — идентификатор приложения. Он должен быть составлен следующим образом: название Вашего сайта задом наперед, плюс какое-либо название приложения.

В выпадающих списках «Minimum Required SDK», «Target SDK», «Compile With» выбираем ту версию, которую мы скачали ранее. Более новые версии SDK поддерживают графические темы для приложений, а старые нет. Поэтому в поле «Theme» выбираем «None». Нажимаем «Next».

Снимаем галочку с «Create custom launcher icon»: в рамках данной статьи не будем заострять внимание на создании иконки приложения. Нажимаем «Next».

В появившемся окне можно выбрать вид «Activity»: вид того, что будет на экране, когда будет запущено приложение. Выбираем «Blank activity», что означает, что мы хотим начать всё с чистого листа. Нажимаем «Next».

В нашем приложении будет всего одно Activity, поэтому в появившемся окне можно ничего не менять. Поэтому просто жмем на «Finish».

Все, наше приложение создано.

Настройка эмулятора

Отладка приложений для Android производится на реальном устройстве или, если такового нет, то на эмуляторе. Сконфигурируем свой.

Для этого запустим «Window → Android Virtual Device Manager». В появившемся окне нажмем «New». Заполняем поля появившейся формы. От них зависит сколько и каких ресурсов будет предоставлять эмулятор «телефону». Выберите разумные значения и нажимайте «ОК».

В окне Android Virtual Device Manager нажимаем кнопку «Start». Это запустит эмулятор. Запуск занимает несколько минут. Так что наберитесь терпения.

В результате вы увидите окно эмулятора подобное этому:

Заполнение Activity

Activity — это то, что отображается на экране телефона после запуска приложения. На нем у нас будет две кнопки «Зажечь красный светодиод» и «Зажечь синий светодиод». Добавим их. В панели «Package Explorer» открываем res/layout/activity_main.xml . Его вид будет примерно таким же, как на скриншоте.

Перетаскиваем 2 кнопки «ToggleButton» на экранную форму. Переключаемся во вкладку «activity_main.xml» и видим следующий код:

 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" >  android:id="@+id/toggleButton1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="ToggleButton" />  android:id="@+id/toggleButton2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/textView1" android:layout_alignParentRight="true" android:text="ToggleButton" /> >

Это ни что иное, как наша Activity, которая отображается не в виде графики, а описанная в формате XML.

Сделаем имена компонентов более понятными. Изменим поля android:id следующим образом.

 android:id="@+id/toggleRedLed" .  android:id="@+id/toggleGreenLed" . 

А еще добавим им подписи, изменим их цвет и размер текста. Результирующий код разметки будет выглядеть следующим образом.

 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"  android:weightSum="2" android:orientation="horizontal">  android:id="@+id/toggleRedLed" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_weight="1" android:background="#FF0000" android:textOff="OFF" android:textOn="ON" android:textSize="30dp" />  android:id="@+id/toggleGreenLed" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_weight="1" android:background="#00FF00" android:textOff="OFF" android:textSize="30dp" android:textOn="ON" /> >

Эти же изменения можно сделать и в графическом режиме, воспользовавшись вкладкой «Outline/Properties».

Пробный запуск

Мы можем запустить только что созданное приложение на эмуляторе. Идем в настройки запуска «Run» → Run Configurations», в левой части нажимаем на «Android Application». Появляется новая конфигурация «New_configuration». В правой части окна выбираем вкладку «Target» и выбираем опцию «Launch on all compatible devices/AVD».

Нажимаем «Apply», а затем «Run». Приложение запустится в эмуляторе.

Можно понажимать кнопки. Но ничего происходить не будет, поскольку обработчики нажатий еще нами не написаны.

Чтобы запустить приложение на реальном устройстве, необходимо включить в его настройках опцию «Отладка USB» и подключить его к компьютеру.

На реальном устройстве приложение выглядит абсолютно аналогично.

Написание кода для Android

Правка манифеста

Каждое Android-приложение должно сообщить системе о том, какие права необходимо ему предоставить. Перечисление прав идет в так называемом файле манифеста AndroidManifest.xml . В нем мы должны указать тот факт, что хотим использовать Bluetooth в своем приложении. Для этого достаточно добавить буквально пару строк:

 version="1.0" encoding="utf-8"?>  xmlns:android="http://schemas.android.com/apk/res/android" package="ru.amperka.arduinobtled" android:versionCode="1" android:versionName="1.0" >  android:minSdkVersion="10" android:targetSdkVersion="10" />  android:name="android.permission.BLUETOOTH"/>  android:name="android.permission.BLUETOOTH_ADMIN"/>  android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >  android:name="ru.amperka.arduinobtled.MainActivity" android:label="@string/app_name" > >  android:name="android.intent.action.MAIN" />  android:name="android.intent.category.LAUNCHER" /> > > > >
Добавляем основной код

Пришла пора вдохнуть жизнь в наше приложение. Открываем файл MainActivity.java (src → ru.amperka.arduinobtled). Изначально он содержит следующий код:

package ru.amperka.arduinobtled; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }

Дополним код в соответствии с тем, что нам нужно:

Будем включать Bluetooth, если он выключен.
Будем обрабатывать нажатия на кнопки
Будем посылать информацию о том, какая кнопка была нажата.

Передавать на Arduino мы будем один байт с двузначным числом. Первая цифра числа — номер пина, к которому подключен тот или иной светодиод, вторая — состояние светодиода: 1 — включен, 0 — выключен.

Число-команда, рассчитывается очень просто: Если нажата красная кнопка, то берется число 60 (для красного светодиода мы выбрали 6-й пин Arduino) и к нему прибавляется 1 или 0 в зависимости от того, должен ли сейчас гореть светодиод или нет. Для зеленой кнопки всё аналогично, только вместо 60 берется 70 (поскольку зеленый светодиод подключен к 7 пину). В итоге, в нашем случае, возможны 4 команды: 60, 61, 70, 71.

Напишем код, который реализует всё сказанное.

package ru.amperka.arduinobtled; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; import android.widget.ToggleButton; public class MainActivity extends Activity implements View.OnClickListener{ //Экземпляры классов наших кнопок ToggleButton redButton; ToggleButton greenButton; //Сокет, с помощью которого мы будем отправлять данные на Arduino BluetoothSocket clientSocket; //Эта функция запускается автоматически при запуске приложения @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //"Соединям" вид кнопки в окне приложения с реализацией redButton = (ToggleButton) findViewById(R.id.toggleRedLed); greenButton = (ToggleButton) findViewById(R.id.toggleGreenLed); //Добавлем "слушатель нажатий" к кнопке redButton.setOnClickListener(this); greenButton.setOnClickListener(this); //Включаем bluetooth. Если он уже включен, то ничего не произойдет String enableBT = BluetoothAdapter.ACTION_REQUEST_ENABLE; startActivityForResult(new Intent(enableBT), 0); //Мы хотим использовать тот bluetooth-адаптер, который задается по умолчанию BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); //Пытаемся проделать эти действия try{ //Устройство с данным адресом - наш Bluetooth Bee //Адрес опредеяется следующим образом: установите соединение //между ПК и модулем (пин: 1234), а затем посмотрите в настройках //соединения адрес модуля. Скорее всего он будет аналогичным. BluetoothDevice device = bluetooth.getRemoteDevice("00:13:02:01:00:09"); //Инициируем соединение с устройством Method m = device.getClass().getMethod( "createRfcommSocket", new Class[] {int.class}); clientSocket = (BluetoothSocket) m.invoke(device, 1); clientSocket.connect(); //В случае появления любых ошибок, выводим в лог сообщение } catch (IOException e) { Log.d("BLUETOOTH", e.getMessage()); } catch (SecurityException e) { Log.d("BLUETOOTH", e.getMessage()); } catch (NoSuchMethodException e) { Log.d("BLUETOOTH", e.getMessage()); } catch (IllegalArgumentException e) { Log.d("BLUETOOTH", e.getMessage()); } catch (IllegalAccessException e) { Log.d("BLUETOOTH", e.getMessage()); } catch (InvocationTargetException e) { Log.d("BLUETOOTH", e.getMessage()); } //Выводим сообщение об успешном подключении Toast.makeText(getApplicationContext(), "CONNECTED", Toast.LENGTH_LONG).show(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } //Как раз эта функция и будет вызываться @Override public void onClick(View v) { //Пытаемся послать данные try { //Получаем выходной поток для передачи данных OutputStream outStream = clientSocket.getOutputStream(); int value = 0; //В зависимости от того, какая кнопка была нажата, //изменяем данные для посылки if (v == redButton) { value = (redButton.isChecked() ? 1 : 0) + 60; } else if (v == greenButton) { value = (greenButton.isChecked() ? 1 : 0) + 70; } //Пишем данные в выходной поток outStream.write(value); } catch (IOException e) { //Если есть ошибки, выводим их в лог Log.d("BLUETOOTH", e.getMessage()); } } }

Написание скетча

Данные, которые принимает Bluetooth-модуль, приходят через UART (он же Serial-соединение) на скорости 9600 бит/с. Настраивать Bluetooth-модуль нет никакой необходимости: он сразу готов к работе. Поэтому скетч должен уметь следующее:

Принять данные по UART
Зажечь нужный светодиод в зависимости от принятого кода

void setup() { //Устанавливаем скорость UART Serial.begin(9600); //Настраиваем нужные пины на выход pinMode(6, OUTPUT); pinMode(7, OUTPUT); } void loop() { //Если данные пришли if (Serial.available() > 0) { //Считываем пришедший байт byte incomingByte = Serial.read(); //Получаем номер пина путем целочисленного деления значения принятого байта на 10 //и нужное нам действие за счет получения остатка от деления на 2: //(1 - зажечь, 0 - погасить) digitalWrite(incomingByte / 10, incomingByte % 2); } }

Особенности заливки скетча

Для связи Bluetooth-Bee с контроллером используются те же пины (0 и 1), что и для прошивки. Поэтому при программировании контроллера переключатель «SERIAL SELECT» на «Wireless Shield» должен быть установлен в положение «USB», а после прошивки его надо вернуть в положение «MICRO».

Результат

Заключение

В данной статье мы научились создавать приложения для операционной системы Android и передавать данные по Bluetooth. Теперь при нажатии на кнопку на экране телефона на базе операционной системы Android, произойдет изменение состояния светодиода на плате.

Вы можете развить мысль и сделать более дружественный интерфейс на Android, управлять с его помощью гораздо более сложными устройствами, публиковать классные приложения в Android Market и ещё много-много всего интересного!

Если не указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution-Noncommercial-Share Alike 4.0 International

Производные работы должны содержать ссылку на http://wiki.amperka.ru, как на первоисточник, непосредственно перед содержимым работы.
Вики работает на суперском движке DokuWiki.

беспроводная-связь/android-и-bluetooth.txt · Последние изменения: 2013/06/04 15:34 — nailxx

Инструменты страницы

  • Показать исходный текст
  • История страницы
  • Ссылки сюда
  • Наверх

Python & Arduino. Просто, быстро и красиво

Очень часто, у начинающих и не только разработчиков возникают вопросы. Как управлять с Arduino с компьютера? А если компьютер — не Raspberry Pi, а обычный домашний компьютер? А если не хочется мучится с ESP8266 и управлять через веб интерфейс? Неужели надо каждый раз открывать Arduino IDE и отправлять команды через Монитор порта? Именно о том, как создать своё собственное приложение с графическим интерфейсом для управления Arduino я сейчас и расскажу.

Оборудование


Недавно я заполучил очень интересную плату: Arduino SS Micro. Эта плата, внешне напоминающая Digispark Attiny 85, тем не менее является китайской версией Arduino Micro, с выведенным выходом USB.

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

Как мне кажется — это довольно крутое и удобное устройство для небольших домашних проектов, ведь у проводов есть супер-свойство: теряться в самый неподходящий момент.

В качестве управляющего компьютера был использован MacBook Pro с операционной системой macOS Mojave, но не надо закрывать статью, если вы используете Windows или Linux — всё описанное в статье будет работать без изменений на любой операционной системе.

Скетч для Arduino

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

Светодиод в Arduino SS Micro висит на порте SS, и поэтому он автоматически выключается. Не смотря на это, стандартный пример Blink — мигающий светодиод работает.

char inChar; #define LED_PIN SS void setup() < pinMode(LED_PIN, OUTPUT); // Инициализация светодиода Serial.begin(115200); // Инициализация Serial - порта >void loop() < if (Serial.available() >0) < inChar = Serial.read(); if (inChar=='e') // e - Enable - включить < digitalWrite(LED_PIN,HIGH); >> else if (inChar=='d') // d - Disable - выключить < digitalWrite(LED_PIN,LOW); >else if (inChar=='b') // b - Blink - выключить режим мигания < while (true)< digitalWrite(LED_PIN,HIGH); delay(1000); digitalWrite(LED_PIN,LOW); delay(1000); >> > 

Если вы будете использовать другую Arduino — не забудьте сменить пин светодиода.

Код для компьютера

Одним из достоинств Python, кроме его кроссплатформенности — наличие гигантского числа библиотек. Нам понадобятся:

  • PySerial — библиотека для работы с Serial-портом
  • PyQT5 — библиотека для создания графического интерфейса
Установка

Для установки, воспользуемся встроенным менеджером пакетов — pip.

pip install pyserial pyqt5

Для удобства создания GUI можно установить программу QTDesigner.

Интерфейс

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

Исходный код

Вся работа с устройством происходит благодаря библиотеке PySerial. Но есть несколько нюансов. Например, как узнать, в какой из портов подключено устройство?

На всем прекрасно известном сайте stackoverflow, пользователь с ником Thomas предложил уже готовое решение, которое я и использовал.

def serial_ports(): """ Lists serial port names :raises EnvironmentError: On unsupported or unknown platforms :returns: A list of the serial ports available on the system """ if sys.platform.startswith('win'): ports = ['COM%s' % (i + 1) for i in range(256)] elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): # this excludes your current terminal "/dev/tty" ports = glob.glob('/dev/tty[A-Za-z]*') elif sys.platform.startswith('darwin'): ports = glob.glob('/dev/tty.*') else: raise EnvironmentError('Unsupported platform') result = [] for port in ports: try: s = serial.Serial(port) s.close() result.append(port) except (OSError, serial.SerialException): pass return result 

Кроме этого необходимо хранить список доступных скоростей:

speeds = ['1200','2400', '4800', '9600', '19200', '38400', '57600', '115200']

А теперь соберём вместе дизайн(созданный в QtDesigner и сконвертированный с помощью утилиты pyuic5 в .py файл), функции для сканирования портов и основной код программы.

Основной класс, содержащий в себе всю логику программы

 class LedApp(QtWidgets.QMainWindow, design.Ui_Form): def __init__(self): super().__init__() self.setupUi(self) self.Port.addItems(serial_ports()) self.Speed.addItems(speeds) self.realport = None self.ConnectButton.clicked.connect(self.connect) self.EnableBtn.clicked.connect(self.send) def connect(self): try: self.realport = serial.Serial(self.Port.currentText(),int(self.Speed.currentText())) self.ConnectButton.setStyleSheet("background-color: green") self.ConnectButton.setText('Подключено') except Exception as e: print(e) def send(self): if self.realport: self.realport.write(b'b') 

Переменные self.Port и self.Speed — это выпадающие списки, содержащие в себе значения доступных портов и скоростей.

При нажатии на кнопку self.ConnectButton вызывается функция connect, в которой производится попытка подключения к заданному порту с заданной скоростью. Если подключение успешно, то кнопка окрашивается в зелёный цвет, и меняется надпись.

Функция send отправляет в наш порт байтовую строку — заставляющую включить режим мигания.

Таким образом можно управлять различными устройствами, подключёнными к USB.

Данная статья является вводной и обзорной, более полную информацию можно найти например тут:

  • PySerial
  • PyQT
  • Serial в Arduino

Создаем мобильное приложение для управления «Умной теплицей» на Arduino

На 5 уроке про Умную теплицу на Ардуино, мы создадим свое собственное мобильное приложение для устройств на Андроиде. Для этого мы используем редактор визуального программирования Android App Invertor 2

В следующей статье про перенос « функции мониторинга и управления теплицей на телефон или планшет с ОС Androi d» мы установили связь нашей системы с телефоном (или планшетом) с операционной системой Android по Bluetooth, что позволило нам отправлять данные мониторига данных нашей теплицы на телефон и получать команды управления с телефона. Но для связи теплицы с телефоном мы использовали на телефоне приложение Bluetooth Terminal, что совсем неудобно. Нам нужно полноценное приложение. В этом уроке мы и займемся его созданием.
Глубоко внимать в вопросы программирования для операционной системы Android не входит в наши планы, поэтому нам нужна простая и понятная система создания кода для Android, наподобие системы Sctratch для Arduino, которую мы рассматривали на этом уроке – Программируем с Arduino… К счастью подобный визуальный редактор есть. Это онлайн визуальный редактор для визуального программирования для Android App Invertor 2. Страница проекта – http://ai2.appinventor.mit.edu.

Страница онлайн-редактора App Invertor 2 01

Рисунок 1. Страница онлайн-редактора App Invertor 2.

Рисунок 2 Ваш профиль программы App Invertor 2 Создание проекта для ардуино 02

После авторизации (можно использовать профиль google) или регистрации попадаем в свой профиль программы, где можем создать новый проект.

Рисунок 2. Ваш профиль программы. Создание проекта.

создаем моб приложение Создание интерфейса в Design 03

Сначала в панети Design создаем интерфейс нашего приложения, перетаскивая на экран необходимые компоненнты. Кроме визуальных компонентов необходимо добавить 3 невизуальных:
Bluetooth client из раздела Connectivity;
Clock из раздела Sensors (для получения данных из Bluetooth c периодичностью, установленной в Clock);
Notifer из UserInterface.

Рисунок 3. Создание интерфейса в Design.

Код для инициализации Bluetooth соединения и создания Bluetooth клиента 04

Теперь создаем код. Переходим в раздел Block. Сначала создаем код для инициализации Bluetooth соединения и создания Bluetooth клиента (рисунок 4).

Рисунок 4. Код для инициализации Bluetooth соединения и создания Bluetooth клиента.

Затем код для отправки сообщений при изменениии состояний chexckbox-ов для насоса, вентилятора и лампы (рисунок 5).

Код для отправки сообщений при изменениии состояний chexckbox-ов 05

Рисунок 5. Код для отправки сообщений при изменениии состояний chexckbox-ов.

И код получения по таймеру сообщений, поступающих по Bluetooth из Arduino (рисунок 6).

Код получения по таймеру сообщений поступающих из Arduino 06

Рисунок 6. Код получения по таймеру сообщений, поступающих из Arduino

Создаем app приложение (рисунок 7) и загружаем его на телефон.

Генерация app приложения 07

Рисунок 7. Генерация app приложения

Нам надо внести самые маленькие изменения в наш предыдущий скетч, заменив разделитель с пробела на символ ‘*’ при отправке данных с Arduino на Android.
Создадим в Arduino IDE новый скетч, занесем в него код из листинга 1 и загрузим скетч на на плату Arduino. Напоминаем, что в настройках Arduino IDE необходимо выбрать тип платы (Arduino UNO) и порт подключения платы.

// подключение библиотеки SoftwareSerial #include // подключение библиотеки DHT #include "DHT.h" // тип датчика DHT #define DHTTYPE DHT11 // контакты подключения bluetooth-модуля HC-05 int pinBlRx=17; int pinBlTx=18; // контакт подключения входа данных модуля DHT11 int pinDHT11=9; // контакт подключения аналогового выхода модуля влажности почвы int pinSoilMoisture=A0; // контакт подключения аналогового выхода датчика температуры TMP36 int pinTMP36=A1; // контакт подключения аналогового выхода фоторезистора int pinPhotoresistor=A2; // пины светодиодов индикации #define LED_TEMP 5 #define LED_MOISTURE 6 #define LED_LIGHT 7 // значения для условий #define TEMP_DETECT 30 #define MOISTURE_DETECT 500 #define LIGHT_DETECT 250 // реле int pinRelays[]=; // статусы полива, освещения, вентилятора boolean statusRelays[]=; // создание экземпляра объекта SoftwareSerial SoftwareSerial HC05Serial(pinBlRx,pinBlTx); // создание экземпляра объекта DHT DHT dht(pinDHT11, DHTTYPE); unsigned long millisupdate=0; // для получения данных из SoftwareSerial String inputString0 = ""; // признак конца полученной строки boolean stringComplete0 = false; void setup() < // запуск последовательного порта Serial.begin(9600); // pinMode(LED_TEMP,OUTPUT);digitalWrite(LED_TEMP,LOW); pinMode(LED_MOISTURE,OUTPUT);digitalWrite(LED_MOISTURE,LOW); pinMode(LED_LIGHT,OUTPUT);digitalWrite(LED_LIGHT,LOW); // инициализация dht dht.begin(); // запуск SoftwareSerial HC05Serial.begin(9600); // резервирование 50 bytes для the inputString: inputString0.reserve(50); >void loop() < // ожидание конца строки для анализа поступившего запроса: serialEvent0(); if (stringComplete0) < Serial.println(inputString0); parse_string0(inputString0); // очистить : inputString0 = ""; stringComplete0 = false; >// каждые 5 сек - получение показаний датчиков // и вывод на дисплей if(millis()-millisupdate>5000) < millisupdate=millis(); // получение данных с DHT11 float h = dht.readHumidity(); if (isnan(h)) < Serial.println("Failed to read from DHT"); HC05Serial.println("H1=101"); delay(10); >else < Serial.print("HumidityDHT11= "); Serial.print(h);Serial.println(" %"); HC05Serial.print("aH=");HC05Serial.print(h);HC05Serial.print("*"); delay(10); >// получение значения с аналогового вывода модуля влажности почвы int val0=analogRead(pinSoilMoisture); Serial.print("SoilMoisture= "); Serial.println(val0); HC05Serial.print("SM=");HC05Serial.print(h);HC05Serial.print("*"); delay(10); // получение значения с аналогового вывода датчика температуры TMP36 int val1=analogRead(pinTMP36); // перевод в мВ int mV= val1*1000/1024; // перевод в градусы цельсия int t=(mV-500)/10+75;//t=23; Serial.print("TempTMP36= "); Serial.print(t);Serial.println(" C"); HC05Serial.print("aT=");HC05Serial.print(t);HC05Serial.print("*"); delay(10); // получение значения с аналогового вывода фоторезистора int val2=analogRead(pinPhotoresistor); Serial.print("Light= "); Serial.println(val2); HC05Serial.print("Ph=");HC05Serial.print(val2);HC05Serial.print("*"); delay(10); // обновить // вывод состояние полива, лампы, вентилятора Serial.print("pump - "); Serial.println(statusRelays[2]); Serial.print("fun - "); Serial.println(statusRelays[1]); Serial.print("lamp - "); Serial.println(statusRelays[0]); HC05Serial.print("PM=");HC05Serial.print(statusRelays[2]);HC05Serial.print("*"); delay(10); HC05Serial.print("FN=");HC05Serial.print(statusRelays[1]);HC05Serial.print("*"); delay(10); HC05Serial.print("LM=");HC05Serial.print(statusRelays[0]); delay(10); //// проверка условий // увлажненность почвы if(val0 > MOISTURE_DETECT) digitalWrite(LED_MOISTURE,HIGH); else digitalWrite(LED_MOISTURE,LOW); // температура воздуха if(t > TEMP_DETECT) digitalWrite(LED_TEMP,HIGH); else digitalWrite(LED_TEMP,LOW); // освещенность if(val2 < LIGHT_DETECT) digitalWrite(LED_LIGHT,HIGH); else digitalWrite(LED_LIGHT,LOW); // пауза 5 секунд Serial.println(); >> // SerialEvent для HC05 void serialEvent0() < while (HC05Serial.available()) < // получить очередной байт: char inChar = (char)HC05Serial.read(); // добавить в строку inputString0 += inChar; // /n - конец передачи if (inChar == '#') < stringComplete0 = true; >> > // парсинг строки из android void parse_string0(String inputString) < // длина строки int length1=inputString.length(); if(length1!=5) if(inputString.charAt(2)!='=') if(inputString.charAt(4)!='#') String param1=inputString.substring(0,2); int param2=inputString.substring(3,4).toInt(); Serial.print("param1=");Serial.println(param1); Serial.print("param2=");Serial.println(param2); if(param1=="PM") doCommand(2,min(param2,1)); else if(param1=="FN") doCommand(1,min(param2,1)); else if(param1=="LM") doCommand(0,min(param2,1)); > // исполнение команды от смартфона void doCommand(int relay, int status1) < // изменить статус statusRelays[relay]=status1; // изменить состояние реле digitalWrite(pinRelays[relay],statusRelays[relay]); >

Загружаем скетч на Arduino, на телефоне запускаем приложение.

08 Рисунок Приложение в работе 09 Рисунок Приложение в работе10 Рисунок Приложение в работе

Рисунок 8, 9, 10. Приложение в работе.
На следующем уроке рассмотрим вопрос превращения нашей теплицы в объект IoT (Интернет вещей).

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — код приложения и мк (часть 2)

В первой части я описал физическую часть конструкции и лишь небольшой кусок кода. Теперь рассмотрим программную составляющую — приложение для Android и скетч Arduino.

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

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

Программа для андроида разбита на две части: первая — подключение устройства по Bluetooth, вторая — джойстик управления.

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

Верстка

Стартовая активность держится на верстке, элементы: кнопки и layout для списка устройств. Кнопка запускает процесс нахождения устройств с активным Bluetooth. В ListView отображаются найденные устройства.

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

Файл button_control_circle.xml (стиль), его нужно поместить в папку drawable:

Также нужно создать файл item_device.xml, он нужен для каждого элемента списка:

Манифест

На всякий случай приведу полный код манифеста. Нужно получить полный доступ к блютузу через uses-permission и не забыть обозначить вторую активность через тег activity.

Основная активность, сопряжение Arduino и Android

Наследуем класс от AppCompatActivity и объявляем переменные:

public class MainActivity extends AppCompatActivity < private BluetoothAdapter bluetoothAdapter; private ListView listView; private ArrayListpairedDeviceArrayList; private ArrayAdapter pairedDeviceAdapter; public static BluetoothSocket clientSocket; private Button buttonStartControl; > 

Метод onCreate() опишу построчно:

@Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); //обязательная строчка //прикрепляем ранее созданную разметку setContentView(R.layout.activity_main); //цепляем кнопку из разметки Button buttonStartFind = (Button) findViewById(R.id.button_start_find); //цепляем layout, в котором будут отображаться найденные устройства listView = (ListView) findViewById(R.id.list_device); //устанавливаем действие на клик buttonStartFind.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View v) < //если разрешения получены (функция ниже) if(permissionGranted()) < //адаптер для управления блютузом bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if(bluetoothEnabled()) < //если блютуз включен (функция ниже) findArduino(); //начать поиск устройства (функция ниже) >> > >); //цепляем кнопку для перехода к управлению buttonStartControl = (Button) findViewById(R.id.button_start_control); buttonStartControl.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View v) < //объект для запуска новых активностей Intent intent = new Intent(); //связываем с активностью управления intent.setClass(getApplicationContext(), ActivityControl.class); //закрыть эту активность, открыть экран управления startActivity(intent); >>); > 

Нижеприведенные функции проверяют, получено ли разрешение на использование блютуза (без разрешение пользователя мы не сможем передавать данные) и включен ли блютуз:

private boolean permissionGranted() < //если оба разрешения получены, вернуть true if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH) == PermissionChecker.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_ADMIN) == PermissionChecker.PERMISSION_GRANTED) < return true; >else < ActivityCompat.requestPermissions(this, new String[] , 0); return false; > > private boolean bluetoothEnabled() < //если блютуз включен, вернуть true, если нет, вежливо попросить пользователя его включить if(bluetoothAdapter.isEnabled()) < return true; >else < Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 0); return false; >> 

Если все проверки пройдены, начинается поиск устройства. Если одно из условий не выполнено, то высветится уведомление, мол, «разрешите\включите?», и это будет повторяться, пока проверка не будет пройдена.

Поиск устройства делится на три части: подготовка списка, добавление в список найденных устройств, установка соединения с выбранным устройством.

private void findArduino() < //получить список доступных устройств SetpairedDevice = bluetoothAdapter.getBondedDevices(); if (pairedDevice.size() > 0) < //если есть хоть одно устройство pairedDeviceArrayList = new ArrayList<>(); //создать список for(BluetoothDevice device: pairedDevice) < //добавляем в список все найденные устройства //формат: "уникальный адрес/имя" pairedDeviceArrayList.add(device.getAddress() + "/" + device.getName()); >> //передаем список адаптеру, пригождается созданный ранее item_device.xml pairedDeviceAdapter = new ArrayAdapter(getApplicationContext(), R.layout.item_device, R.id.item_device_textView, pairedDeviceArrayList); listView.setAdapter(pairedDeviceAdapter); //на каждый элемент списка вешаем слушатель listView.setOnItemClickListener(new AdapterView.OnItemClickListener() < @Override public void onItemClick(AdapterViewadapterView, View view, int i, long l) < //через костыль получаем адрес String itemMAC = listView.getItemAtPosition(i).toString().split("/", 2)[0]; //получаем класс с информацией об устройстве BluetoothDevice connectDevice = bluetoothAdapter.getRemoteDevice(itemMAC); try < //генерируем socket - поток, через который будут посылаться данные Method m = connectDevice.getClass().getMethod( "createRfcommSocket", new Class[]); clientSocket = (BluetoothSocket) m.invoke(connectDevice, 1); clientSocket.connect(); if(clientSocket.isConnected()) < //если соединение установлено, завершаем поиск bluetoothAdapter.cancelDiscovery(); >> catch(Exception e) < e.getStackTrace(); >> >); > 

Когда Bluetooth-модуль, повешенный на Arduino (подробнее об этом далее), будет найден, он появится в списке. Нажав на него, вы начнете создание socket (возможно, после клика придется подождать 3-5 секунд или нажать еще раз). Вы поймете, что соединение установлено, по светодиодам на Bluetooth-модуле: без соединения они мигают быстро, при наличии соединения заметно частота уменьшается.

Управление и отправка команд

После того как соединение установлено, можно переходить ко второй активности — ActivityControl. На экране будет только синий кружок — джойстик. Сделан он из обычной Button, разметка приведена выше.

public class ActivityControl extends AppCompatActivity < //переменные, которые понадобятся private Button buttonDriveControl; private float BDCheight, BDCwidth; private float centerBDCheight, centerBDCwidth; private String angle = "90"; //0, 30, 60, 90, 120, 150, 180 private ConnectedThread threadCommand; private long lastTimeSendCommand = System.currentTimeMillis(); >

В методе onCreate() происходит все основное действо:

//без этой строки студия потребует вручную переопределить метод performClick() //нам оно не недо @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) < //обязательная строка super.onCreate(savedInstanceState); //устанавливаем разметку, ее код выше setContentView(R.layout.activity_control); //привязываем кнопку buttonDriveControl = (Button) findViewById(R.id.button_drive_control); //получаем информацию о кнопке final ViewTreeObserver vto = buttonDriveControl.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() < @Override public void onGlobalLayout() < //получаем высоту и ширину кнопки в пикселях(!) BDCheight = buttonDriveControl.getHeight(); BDCwidth = buttonDriveControl.getWidth(); //находим центр кнопки в пикселях(!) centerBDCheight = BDCheight/2; centerBDCwidth = BDCwidth/2; //отключаем GlobalListener, он больше не понадобится buttonDriveControl.getViewTreeObserver().removeOnGlobalLayoutListener(this); >>); //устанавливаем листенер, который будет отлавливать прикосновения //его код представлен ниже buttonDriveControl.setOnTouchListener(new ControlDriveInputListener()); //создаем новый поток, он будет занят отправкой данных //в качестве параметра передаем сокет, созданный в первой активности //код потока представлен ниже threadCommand = new ConnectedThread(MainActivity.clientSocket); threadCommand.run(); > 

Обратите внимание (!) — мы узнаем, сколько пикселей занимает кнопка. Благодаря этому получаем адаптивность: размер кнопки будет зависеть от разрешения экрана, но весь остальной код легко под это подстроится, потому что мы не фиксируем размеры заранее. Позже научим приложение узнавать, в каком месте было касание, а после переводить это в понятные для ардуинки значения от 0 до 255 (ведь касание может быть в 456 пикселях от центра, а МК с таким числом работать не будет).

Далее приведен код ControlDriveInputListener(), данный класс располагается в классе самой активности, после метода onCreate(). Находясь в файле ActivityControl, класс ControlDriveInputListener становится дочерним, а значит имеет доступ ко всем переменным основного класса.

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

Обратите внимание, использую класс java.util.Timer: он позволяет создать новый поток, который может иметь задержку и повторятся бесконечное число раз через каждое энное число секунд. Его нужно использовать для следующей ситуации: человек поставил палец, сработал метод ACTION_DOWN, информация пошла на ардуинку, а после этого человек решил не сдвигать палец, потому что скорость его устраивает. Второй раз метод ACTION_DOWN не сработает, так как сначала нужно вызвать ACTION_UP (отодрать палец от экрана).

Чтож, мы запускаем цикл класса Timer() и начинаем каждые 10 миллисекунд отправлять те же самые данные. Когда же палец будет сдвинут (сработает ACTION_MOVE) или поднят (ACTION_UP), цикл Timer надо убить, чтобы данные от старого нажатия не начали отправляться снова.

public class ControlDriveInputListener implements View.OnTouchListener < private Timer timer; @Override public boolean onTouch(View view, MotionEvent motionEvent) < //получаем точки касания в пикселях //отсчет ведется от верхнего левого угла (!) final float x = motionEvent.getX(); final float y = motionEvent.getY(); //узнаем, какое действие было сделано switch(motionEvent.getAction()) < //если нажатие //оно сработает всегда, когда вы дотронетесь до кнопки case MotionEvent.ACTION_DOWN: //создаем таймер timer = new Timer(); //запускаем цикл //аргументы указывают: задержка между повторами 0, //повторять каждые 10 миллисекунд timer.schedule(new TimerTask() < @Override public void run() < //функцию рассмотрим ниже calculateAndSendCommand(x, y); >>, 0, 10); break; //если палец был сдвинут (сработает после ACTION_DOWN) case MotionEvent.ACTION_MOVE: //обязательно (!) //если ранее был запущен цикл Timer(), завершаем его if(timer != null) < timer.cancel(); timer = null; >//создаем новый цикл timer = new Timer(); //отправляем данные с той же частотой, пока не сработает ACTION_UP timer.schedule(new TimerTask() < @Override public void run() < calculateAndSendCommand(x, y); >>, 0, 10); break; //если палец убрали с экрана case MotionEvent.ACTION_UP: //убиваем цикл if(timer != null) < timer.cancel(); timer = null; >break; > return false; > > 

Обратите еще раз внимание: отсчет x и y метод onTouch() ведет от верхнего левого угла View. В нашем случае точка (0; 0) находится у Button тут:

Теперь, когда мы узнали, как получить актуальное расположение пальца на кнопки, разберемся, как преобразовать пиксели (ведь x и y — именно расстояние в пикселях) в рабочие значения. Для этого использую метод calculateAndSendCommand(x, y), который нужно разместить в классе ControlDriveInputListener. Также понадобятся некоторые вспомогательные методы, их пишем в этот же класс после calculateAndSendCommand(x, y).

private void calculateAndSendCommand(float x, float y) < //все методы описаны ниже //получаем нужные значения //четверть - 1, 2, 3, 4 //чтобы понять, о чем я, проведите через середину кнопки координаты //и да, дальше оно использоваться не будет, но для отладки пригождалось int quarter = identifyQuarter(x, y); //функция переводит отклонение от центра в скорость //вычитаем y, чтобы получить количество пикселей от центра кнопки int speed = speedCalculation(centerBDCheight - y); //определяет угол поворота //вспомните первую часть статьи, у нас есть 7 вариантов угла String angle = angleCalculation(x); //если хотите вывести информацию на экран, то используйте этот способ //но в финальной версии он не сработает, так как затрагивает отдельный поток /*String resultDown = "x: "+ Float.toString(x) + " y: " + Float.toString(y) + " qr: " + Integer.toString(quarter) + "\n" + "height: " + centerBDCheight + " width: " + centerBDCwidth + "\n" + "speed: " + Integer.toString(speed) + " angle: " + angle; */ //viewResultTouch.setText(resultDown); //все данные полученные, можно их отправлять //но делать это стоить не чаще (и не реже), чем в 100 миллисекунд if((System.currentTimeMillis() - lastTimeSendCommand) >100) < //функцию рассмотрим дальше threadCommand.sendCommand(Integer.toString(speed), angle); //перезаписываем время последней отправки данных lastTimeSendCommand = System.currentTimeMillis(); >> private int identifyQuarter(float x, float y) < //смотрим, как расположена точка относительно центра //возвращаем угол if(x >centerBDCwidth && y > centerBDCheight) < return 4; >else if (x < centerBDCwidth && y >centerBDCheight) < return 3; >else if (x < centerBDCwidth && y < centerBDCheight) < return 2; >else if (x > centerBDCwidth && y < centerBDCheight) < return 1; >return 0; > private int speedCalculation(float deviation) < //получаем коэффициент //он позволит превратить пиксели в скорость float coefficient = 255/(BDCheight/2); //высчитываем скорость по коэффициенту //округляем в целое int speed = Math.round(deviation * coefficient); //если скорость отклонение меньше 70, ставим скорость ноль //это понадобится, когда вы захотите повернуть, но не ехать if(speed >0 && speed < 70) speed = 0; if(speed < 0 && speed >- 70) speed = 0; //нет смысла отсылать скорость ниже 120 //слишком мало, колеса не начнут крутиться if(speed < 120 && speed >70) speed = 120; if(speed > -120 && speed < -70) speed = -120; //если вы унесете палец за кнопку, ACTION_MOVE продолжит считывание //вы сможете получить отклонение больше, чем пикселей в кнопке //на этот случай нужно ограничить скорость if(speed >255 ) speed = 255; if(speed < - 255) speed = -255; //пометка: скорость >0 - движемся вперед, < 0 - назад return speed; >private String angleCalculation(float x) < //разделяем ширину кнопки на 7 частей //0 - максимально влево, 180 - вправо //90 - это когда прямо if(x < BDCwidth/6) < angle = "0"; >else if (x > BDCwidth/6 && x < BDCwidth/3) < angle = "30"; >else if (x > BDCwidth/3 && x < BDCwidth/2) < angle = "60"; >else if (x > BDCwidth/2 && x < BDCwidth/3*2) < angle = "120"; >else if (x > BDCwidth/3*2 && x < BDCwidth/6*5) < angle = "150"; >else if (x > BDCwidth/6*5 && x < BDCwidth) < angle = "180"; >else < angle = "90"; >return angle; > 

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

Класс ConnectedThread также располагаем в классе ActivityControl.

private class ConnectedThread extends Thread < private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) < //получаем сокет this.socket = btSocket; //создаем стрим - нить для отправки данных на ардуино OutputStream os = null; try < os = socket.getOutputStream(); >catch(Exception e) <> outputStream = os; > public void run() < >public void sendCommand(String speed, String angle) < //блютуз умеет отправлять только байты, поэтому переводим byte[] speedArray = speed.getBytes(); byte[] angleArray = angle.getBytes(); //символы используются для разделения //как это работает, вы поймете, когда посмотрите принимающий код скетча ардуино String a = "#"; String b = "@"; String c = "*"; try < outputStream.write(b.getBytes()); outputStream.write(speedArray); outputStream.write(a.getBytes()); outputStream.write(c.getBytes()); outputStream.write(angleArray); outputStream.write(a.getBytes()); >catch(Exception e) <> > > 

Подводим итоги Андроид-приложения

Коротко обобщу все громоздкое вышеописанное.

  1. В ActivityMain настраиваем блютуз, устанавливаем соединение.
  2. В ActivityControl привязываем кнопку и получаем данные о ней.
  3. Вешаем на кнопку OnTouchListener, он отлавливает касание, передвижение и подъем пальца.
  4. Полученные данные (точку с координатами x и y) преобразуем в угол поворота и скорость
  5. Отправляем данные, разделяя их специальными знаками

Скетч Arduino

Андроид-приложение разобрано, написано, понято… а тут уже и попроще будет. Постараюсь поэтапно все рассмотреть, а потом дам ссылку на полный файл.

Переменные

Для начала рассмотрим константы и переменные, которые понадобятся.

#include //переназначаем пины входа\вывода блютуза //не придется вынимать его во время заливки скетча на плату SoftwareSerial BTSerial(8, 9); //пины поворота и скорости int speedRight = 6; int dirLeft = 3; int speedLeft = 11; int dirRight = 7; //пины двигателя, поворачивающего колеса int angleDirection = 4; int angleSpeed = 5; //пин, к которому подключен плюс штуки, определяющей поворот //подробная технология описана в первой части int pinAngleStop = 12; //сюда будем писать значения String val; //скорость поворота int speedTurn = 180; //пины, которые определяют поворот //таблица и описания системы в первой статье int pinRed = A0; int pinWhite = A1; int pinBlack = A2; //переменная для времени long lastTakeInformation; //переменные, показывающие, что сейчас будет считываться boolean readAngle = false; boolean readSpeed = false; 

Метод setup()

В методе setup() мы устанавливаем параметры пинов: будут работать они на вход или выход. Также установим скорость общения компьютера с ардуинкой, блютуза с ардуинкой.

void setup() < pinMode(dirLeft, OUTPUT); pinMode(speedLeft, OUTPUT); pinMode(dirRight, OUTPUT); pinMode(speedRight, OUTPUT); pinMode(pinRed, INPUT); pinMode(pinBlack, INPUT); pinMode(pinWhite, INPUT); pinMode(pinAngleStop, OUTPUT); pinMode(angleDirection, OUTPUT); pinMode(angleSpeed, OUTPUT); //данная скорость актуальна только для модели HC-05 //если у вас модуль другой версии, смотрите документацию BTSerial.begin(38400); //эта скорость постоянна Serial.begin(9600); >

Метод loop() и дополнительные функции

В постоянно повторяющемся методе loop() происходит считывание данных. Сначала рассмотрим основной алгоритм, а потом функции, задействованные в нем.

 void loop() < //если хоть несчитанные байты if(BTSerial.available() >0) < //считываем последний несчитанный байт char a = BTSerial.read(); if (a == '@') < //если он равен @ (случайно выбранный мною символ) //обнуляем переменную val val = ""; //указываем, что сейчас считаем скорость readSpeed = true; >else if (readSpeed) < //если пора считывать скорость и байт не равен решетке //добавляем байт к val if(a == '#') < //если байт равен решетке, данные о скорости кончились //выводим в монитор порта для отладки Serial.println(val); //указываем, что скорость больше не считываем readSpeed = false; //передаем полученную скорость в функцию езды go(val.toInt()); //обнуляем val val = ""; //выходим из цикла, чтобы считать следующий байт return; >val+=a; > else if (a == '*') < //начинаем считывать угол поворота readAngle = true; >else if (readAngle) < //если решетка, то заканчиваем считывать угол //пока не решетка, добавляем значение к val if(a == '#') < Serial.println(val); Serial.println("-----"); readAngle = false; //передаем значение в функцию поворота turn(val.toInt()); val= ""; return; >val+=a; > //получаем время последнего приема данных lastTakeInformation = millis(); > else < //если несчитанных байтов нет, и их не было больше 150 миллисекунд //глушим двигатели if(millis() - lastTakeInformation >150) < lastTakeInformation = 0; analogWrite(angleSpeed, 0); analogWrite(speedRight, 0); analogWrite(speedLeft, 0); >> > 

Получаем результат: с телефона отправляем байты в стиле «@скорость#угол#» (например, типичная команда «@200#60#». Данный цикл повторяется каждый 100 миллисекунд, так как на андроиде мы установили именно этот промежуток отправки команд. Короче делать нет смысла, так как они начнут становится в очередь, а если сделать длиннее, то колеса начнут двигаться рывками.

Все задержки через команду delay(), которые вы увидите далее, подобраны не через физико-математические вычисления, а опытным путем. Благодаря всем выставленным задрежам, машинка едет плавно, и у всех команд есть время на отработку (токи успевают пробежаться).

В цикле используются две побочные функции, они принимают полученные данные и заставляют машинку ехать и крутится.

void go(int mySpeed) < //если скорость больше 0 if(mySpeed >0) < //едем вперед digitalWrite(dirRight, HIGH); analogWrite(speedRight, mySpeed); digitalWrite(dirLeft, HIGH); analogWrite(speedLeft, mySpeed); >else < //а если меньше 0, то назад digitalWrite(dirRight, LOW); analogWrite(speedRight, abs(mySpeed) + 30); digitalWrite(dirLeft, LOW); analogWrite(speedLeft, abs(mySpeed) + 30); >delay(10); > void turn(int angle) < //подаем ток на плюс определителя угла digitalWrite(pinAngleStop, HIGH); //даем задержку, чтобы ток успел установиться delay(5); //если угол 150 и больше, поворачиваем вправо //если 30 и меньше, то влево //промежуток от 31 до 149 оставляем для движения прямо if(angle >149) < //если замкнут белый, но разомкнуты черный и красный //значит достигнуто крайнее положение, дальше крутить нельзя //выходим из функции через return if( digitalRead(pinWhite) == HIGH && digitalRead(pinBlack) == LOW && digitalRead(pinRed) == LOW) < return; >//если проверка на максимальный угол пройдена //крутим колеса digitalWrite(angleDirection, HIGH); analogWrite(angleSpeed, speedTurn); > else if (angle < 31) < if(digitalRead(pinRed) == HIGH && digitalRead(pinBlack) == HIGH && digitalRead(pinWhite) == HIGH) < return; >digitalWrite(angleDirection, LOW); analogWrite(angleSpeed, speedTurn); > //убираем питание digitalWrite(pinAngleStop, LOW); delay(5); > 

Поворачивать, когда андроид отправляет данные о том, что пользователь зажал угол 60, 90, 120, не стоит, иначе не сможете ехать прямо. Да, возможно сразу не стоило отправлять с андроида команду на поворот, если угол слишком мал, но это как-то коряво на мой взгляд.

Итоги скетча

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

В конце концов

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

https://kodirovanie.kapelnicza-ot-zapoya-sankt-peterburg.ru/