Дистанционное управление робота «Малыша» по Bluetooth с телефона

Введение:

В этом уроке мы дополним робота «Малыш» модулем Bluetooth. Управление направлением и скоростью движения робота будет осуществляться с помощью приложения, установленного на телефон.

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

Подробно об управлении роботом и сопряжении Bluetooth модулей рассказано ниже, в разделе «Управление».

Видео:

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

Для реализации проекта нам необходимо установить библиотеки:

  • iarduino_Bluetooth_HC05 - для работы с Trema Bluetooth модулем HC-05.
  • Библиотеки SoftwareSerial входит в базовый набор Arduino IDE и не требует установки.

О том, как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Схема подключения робота «Малыш»:

Соберём механическую и электрическую части, как это описано в проектах № 1-8 в книге "Набор "Малыш"". Далее, на верхнюю стенку установим Bluetooth HC-05; модуль подключается к шине UART (в примере используется программная шина UART).

BluetoothMotor Shield
Bluetooth HC-05вывод RXцифровой вывод D10
вывод TXцифровой вывод D9
вывод K (Key)цифровой вывод D13
вывод V (Vcc)любой вывод 5V на красной колодке
вывод G (GND)любой вывод GND на чёрной колодке

Воспользуемся схемой сборки из проекта №9 книги "Набор "Малыш"", но, вместо Trema-модуля ИК-приёмник, установим в корпус Trema-модуль Bluetooth (подключается к выводам D13, D9 и D10).

Код программы для «Малыша»:

#include <SoftwareSerial.h>                                                      //  Подключаем библиотеку SoftwareSerial для общения с модулем по программной шине UART
#include <iarduino_Bluetooth_HC05.h>                                             //  Подключаем библиотеку iarduino_Bluetooth_HC05 для работы с Trema Bluetooth модулем HC-05
SoftwareSerial          softSerial(9, 10);                                       //  Создаём объект softSerial указывая выводы RX, TX (можно указывать любые выводы Arduino UNO). Вывод 2 Arduino подключается к выводу TX модуля, вывод 3 Arduino подключается к выводу RX модуля
iarduino_Bluetooth_HC05 hc05(13);                                                //  Создаём объект hc05 указывая любой вывод Arduino, который подключается к выводу K модуля
                                                                                 //  
uint8_t  pinShield_H2 = 4;                                                       //  Вывод, подключенный к драйверу, для задания направления вращения левым мотором
uint8_t  pinShield_E2 = 5;                                                       //  Вывод ШИМ, подключенный к драйверу, для задания скорости левого мотора
uint8_t  pinShield_E1 = 6;                                                       //  Вывод ШИМ, подключенный к драйверу, для задания скорости правого мотора
uint8_t  pinShield_H1 = 7;                                                       //  Вывод, подключенный к драйверу, для задания направления вращения правым мотором
uint8_t  pinLED_RED   = 12;                                                      //  Вывод с красным светодиодом
uint8_t  pinLED_BLUE  = 11;                                                      //  Вывод с синим светодиодом
uint16_t time_period  = 200;                                                     //  Частота мигания светодиодов (в миллисекундах)
uint8_t  valSpeed     = 255;                                                     //  Максимальная скорость ШИМ (число от 0 до 255)
bool     arrRoute[2]  = {1, 1};                                                  //  Направление движения для каждого мотора ([0]- правый мотор, [1] - левый мотор)
uint16_t arrSpeed[2];                                                            //  Скорость для каждого мотора ([0]- правый мотор, [1] - левый мотор)
uint32_t tmrLED;                                                                 //  Время  последнего включения светодиодов
uint32_t flgTime;                                                                //  Флаг для задания времени принятия пакетов от Bluetooth телефона
uint8_t  flg;                                                                    //  Флаг кнопок
uint32_t tmrWait;                                                                //  Время до начала сопряжения с новыми устройствами
bool     flg_LED;                                                                //  Флаг включения светодиодов
                                                                                 //  </iarduino_bluetooth_hc05.h></softwareserial.h>
void setup() {                                                                   //  
    // BLUETOOTH МОДУЛЬ                                                          //  
    Serial.begin  (9600);                                                        //  Инициируем передачу данных по аппаратной шине UART для вывода результата в монитор последовательного порта
    Serial.print  ("begin: ");                                                   //  Выводим текст "begin: " в монитор последовательного порта
    if (hc05.begin(softSerial))     {Serial.println("Ok");}                      //  Инициируем работу с Trema модулем hc05, указывая объект softSerial через который осуществляется связь по шине UART
    else                            {Serial.println("Error");}                   //  Если работа с модулем не инициирована, то выводим сообщение об ошибке
    tmrWait = millis();                                                          //  Устанавливаем таймер ожидания сопряжения
    while (!hc05.checkConnect() && millis()<tmrWait+60000) {;}                   //  Ждём в течении 60 секунд сопряжения с последним устройством из памяти
    if (millis()<tmrWait+60000)     {Serial.println("Connect with last ADR");}   //  Если сопряжение произошло, то выдаём в монитор порта сообщение об этом
    else {                                                                       //  Если сопряжение не произошло, то
    if (hc05.createSlave("BT_CAR", "1234"))                                      //  Создаем ведомую роль модулю, указывая его имя и pin-код (в примере имя = "BT_CAR", pin-код = "1234")
                                    {Serial.println("Slave create");}            //  Если ведомая роль была создана, выводим сообщение об успехе в монитор порта,
    else                            {Serial.println("Slave not create");}        //  а если не была создана - выводим сообщение об ошибке в монитор порта.
      }                                                                          //  
    // МОТОРЫ                                                                    //  
    pinMode(pinShield_H2, OUTPUT);                                               //  Конфигурируем вывод pinShield_H2 как выход (направление вращения левого мотора)
    pinMode(pinShield_E2, OUTPUT);                                               //  Конфигурируем вывод pinShield_E2 как выход (скорость вращения левого мотора, ШИМ)
    pinMode(pinShield_E1, OUTPUT);                                               //  Конфигурируем вывод pinShield_E1 как выход (скорость вращения правого мотора, ШИМ)
    pinMode(pinShield_H1, OUTPUT);                                               //  Конфигурируем вывод pinShield_H1 как выход (направление вращения правого мотора)
    // СВЕТОДИОДЫ                                                                //  
    pinMode(pinLED_RED,OUTPUT);                                                  //  Конфигурируем вывод pinLED_RED как выход 
    pinMode(pinLED_BLUE,OUTPUT);                                                 //  Конфигурируем вывод pinLED_BLUE как выход
    tmrLED = millis();                                                           //  Устанавливаем таймер светодиодов равным millis()
    flg_LED = 0;                                                                 //  Сбрасываем флаг светодиодов
}                                                                                //  
void loop() {                                                                    //  
  if (softSerial.available()) {                                                  //  Если есть принятые данные, то ...
    String str;                                                                  //  Создаём строку str
    while (softSerial.available()) {                                             //  Выполняем цикл пока есть что читать ...
      str += char(softSerial.read());                                            //  Читаем очередной принятый символ из UART в строку str
      delay(5);                                                                  //  Задержка на 5 мс на случай медленного приёма
    }                                                                            //  Цикл завершён, значит читать больше нечего
                                            // КНОПКИ ДВИЖЕНИЯ                   //  
                               // Флаг времени            Флаг кнопки            //  
    if (str == "II") {    flgTime = millis();     flg = 1;    }                  //  Кнопка "стрелка вверх-влево"
    if (str == "FF") {    flgTime = millis();     flg = 2;    }                  //  Кнопка "стрелка вверх"
    if (str == "GG") {    flgTime = millis();     flg = 3;    }                  //  Кнопка "стрелка вверх-вправо"
    if (str == "RR") {    flgTime = millis();     flg = 4;    }                  //  Кнопка "стрелка влево"
    if (str == "SS") {    flgTime = millis();     flg = 5;    }                  //  СТОП
    if (str == "LL") {    flgTime = millis();     flg = 6;    }                  //  Кнопка "стрелка вправо"
    if (str == "JJ") {    flgTime = millis();     flg = 7;    }                  //  Кнопка "стрелка вниз-влево"
    if (str == "BB") {    flgTime = millis();     flg = 8;    }                  //  Кнопка "стрелка вниз"
    if (str == "HH") {    flgTime = millis();     flg = 9;    }                  //  Кнопка "стрелка вниз-вправо"
                                        // КНОПКИ ДОПОЛНИТЕЛЬНЫХ ФУНКЦИЙ         //  
                          // Если кнопка нажата        меняем флаг               //  
    if (str == "SWS" || str == "SwS") {flg_LED = !flg_LED;}                      //  Кнопка включения светодиодов
    if (str == "S0S") {flg     = 10;      }                                      //  Ползунок скорости в положении 0
    if (str == "S1S") {flg     = 11;      }                                      //  Ползунок скорости в положении 1
    if (str == "S2S") {flg     = 12;      }                                      //  Ползунок скорости в положении 2
    if (str == "S3S") {flg     = 13;      }                                      //  Ползунок скорости в положении 3
    if (str == "S4S") {flg     = 14;      }                                      //  Ползунок скорости в положении 4
    if (str == "S5S") {flg     = 15;      }                                      //  Ползунок скорости в положении 5
    if (str == "S6S") {flg     = 16;      }                                      //  Ползунок скорости в положении 6
    if (str == "S7S") {flg     = 17;      }                                      //  Ползунок скорости в положении 7
    if (str == "S8S") {flg     = 18;      }                                      //  Ползунок скорости в положении 8
    if (str == "S9S") {flg     = 19;      }                                      //  Ползунок скорости в положении 9
    if (str == "SqS") {flg     = 20;      }                                      //  Ползунок скорости в положении 10
    //  ======================================================================================================================================================
    switch (flg) {//Направление левого мотора    Направление правого мотора      Скорость левого мотора             Скорость правого мотора
      case 1:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = (valSpeed / 2);       arrSpeed[0] = valSpeed;             break;       //  С-З
      case 2:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = valSpeed;             arrSpeed[0] = valSpeed;             break;       //  С
      case 3:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = valSpeed;             arrSpeed[0] = (valSpeed / 2);       break;       //  С-В
      case 4:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = 0;                    arrSpeed[0] = valSpeed;             break;       //  З
      case 5:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = 0;                    arrSpeed[0] = 0;                    break;       //  Стоп
      case 6:          arrRoute[1] = 1;             arrRoute[0] = 1;            arrSpeed[1] = valSpeed;             arrSpeed[0] = 0;                    break;       //  В
      case 7:          arrRoute[1] = 0;             arrRoute[0] = 0;            arrSpeed[1] = (valSpeed / 2);       arrSpeed[0] = valSpeed;             break;       //  Ю-З
      case 8:          arrRoute[1] = 0;             arrRoute[0] = 0;            arrSpeed[1] = valSpeed;             arrSpeed[0] = valSpeed;             break;       //  Ю
      case 9:          arrRoute[1] = 0;             arrRoute[0] = 0;            arrSpeed[1] = valSpeed;             arrSpeed[0] = (valSpeed / 2);       break;       //  Ю-В
      }                                                                          //  
  } // =======================================================================================================================================================  
  if     (flg == 10){valSpeed = 5;}                                              //  0 режим скорости
  else if(flg == 11){valSpeed = 30;}                                             //  1 режим скорости
  else if(flg == 12){valSpeed = 55;}                                             //  2 режим скорости
  else if(flg == 13){valSpeed = 80;}                                             //  3 режим скорости
  else if(flg == 14){valSpeed = 105;}                                            //  4 режим скорости
  else if(flg == 15){valSpeed = 130;}                                            //  5 режим скорости
  else if(flg == 16){valSpeed = 155;}                                            //  6 режим скорости
  else if(flg == 17){valSpeed = 180;}                                            //  7 режим скорости
  else if(flg == 18){valSpeed = 205;}                                            //  8 режим скорости
  else if(flg == 19){valSpeed = 230;}                                            //  9 режим скорости
  else if(flg == 20){valSpeed = 255;}                                            //  10 режим скорости
                                                                                 //  
  if (flg_LED) {                                                                 //  Если флаг установлен (была нажата кнопка включения фары)
    if (millis() - tmrLED > time_period) {                                       //  мигаем светодиодами с заданной частотой
      tmrLED = millis();                                                         //  сохраняем время
      digitalWrite(pinLED_RED, digitalRead(pinLED_BLUE));                        //  управляем питанием красного светодиода
      digitalWrite(pinLED_BLUE, !digitalRead(pinLED_BLUE));                      //  управляем питанием синего светодиода
      }                                                                          //  
    } else {                                                                     //  если флаг сброшен, то
      digitalWrite(pinLED_RED, LOW);                                             //  гасим светодиоды
      digitalWrite(pinLED_BLUE, LOW);                                            //  
      }                                                                          //  
  if (flgTime > millis()) {                                                      //  Если millis() переполнен, то 
    flgTime = 0;                                                                 //  сбрасываем флаг в ноль
    }                                                                            //  
// ПОДАЧА ЗНАЧЕНИЙ СКОРОСТИ И НАПРАВЛЕНИЯ ВРАЩЕНИЯ НА ВЫВОДЫ                     //  
  if (flgTime > (millis() - 500)) {                                              //  Если сигналы с телефона приходят (в течении 50 мс)
    digitalWrite(pinShield_H2, arrRoute[1]);                                     //  тогда задаем направление вращения правого мотора
    digitalWrite(pinShield_H1, arrRoute[0]);                                     //  и левого мотора
    analogWrite(pinShield_E2, arrSpeed[1]);                                      //  Задаём скорость вращения для правого мотора
    analogWrite(pinShield_E1, arrSpeed[0]);                                      //  и для левого мотора
    } else {                                                                     //  Если пакеты не приходят
    analogWrite(pinShield_E2, 0);                                                //  Останавливаем работу моторов
    analogWrite(pinShield_E1, 0);                                                //  
    }                                                                            //  
}                                                                                //  

Ссылка для скачивания скетча.

В данном коде управление роботом осуществляется в три основных этапа: получение данных с телефона; изменение значений переменных arrSpeed, arrRoute; подача питания на моторы и задание направления их вращения. Так же в коде присутствуют дополнительные блоки: включение/выключение светодиодов; изменение скорости вращения колёс; вход в режим сопряжения.

  • Получение данных с пульта:
    • Данный блок начинается с оператора if , в условии которого написано softSerial.available(). Это условие будет верно, если в последовательный порт будут приходить данные с телефона, в противном случае условие будет ложно;
    • Далее следует еще один оператор while, условием которого опять является softSerial.available(). Если условие верно, то значение, поступившее в последовательный порт, будет записано в переменную str; задержка в 5 миллисекунд сделана для того, чтобы при низкой скорости передачи Arduino успел полностью прочитать значение из последовательного порта в переменную str;
  • Изменение значений переменных arrSpeed, arrRoute:
    • Данный блок начинается с оператора if, в условии которого написано str == XX. В зависимости от значения XX, которое принимает переменная str (одно из 9 для основных функций), сбрасывается flgTime - таймер начала выполнения функции, а так же устанавливается значение флага flg(от 1 до 9);
    • Далее следует конструкция switch...case, в которой оператор switch сравнивает значение флага flg с оператором case и, в зависимости от значения флага flg, выполняет код, где задаётся, на какой мотор будет подано питание (переменнаяarrSpeed ) и с каким направлением вращения ( переменная arrRoute);
  • Подача питания на моторы и задание направления их вращения:
    • Данный блок начинается с оператора if, в условии которого написано flgTime > (millis() - 50). Это условие будет верно в течении 50 миллисекунд после начала приёма сигнала от телефона и установит на выводах Arduino значения переменных arrSpeed и arrRoute. Если же сигнала в течении 50 миллисекунд не поступит, то оператор else сбросит значение скорости в 0 и робот остановится;
  • Включение/выключение светодиодов:
    • Данный блок включает в себя 2 части: получение сигнала с телефона и изменение флага flg_LED; включение/выключение светодиодов;
    • Первая часть начинается с оператора if, в условии которого написано ( str == "SWS" || str == "SwS"). Условие будет верно, если значение переменной str будет равно SWS ИЛИ SwS, что приведёт к установке флага flg_LED в противоположное от нынешнего значение;
    • Вторая часть начинается с оператора if, в условии которого написано (flg_LED). Условие будет верно, если значение флага flg_LED будет true, что приведёт к включению светодиодов. Если же значение флага flg_LED будет false, то светодиоды погаснут.
  • Изменение скорости вращения колёс:
    • Данный бок включает в себя 2 части: получение сигнала с телефона и изменение флага flg; изменение значения переменной valSpeed;
    • Первая часть начинается с оператора if, в условии которого написано str == XX. В зависимости от значения XX, которое принимает переменная str (одно из 11 для дополнительных функций), устанавливается значение флага flg(от 10 до 20);
    • Вторая часть начинается с оператора if, в условии которого написано flg == XX. В зависимости от значения XX, которое принимает переменная flg (одно из 11 для дополнительных функций), устанавливается значение переменнойvalSpeed(от 5 до 255);
  • Вход в режим сопряжения (выполняется при подаче питания на робота):
    • Данный блок находится в коде void setup() и начинается с оператора if , в условии которого написано hc05.begin(softSerial). Это условие будет верно, если произошла успешная инициализация с Bluetooth модулем по шине UART, о чём будет выведено сообщение в монитор последовательного порта;
    • Далее происходит сброс таймера tmrWait и идёт проверка условия !hc05.checkConnect() && millis()<tmrWait+60000 в операторе циклаwhile . До тех пор, пока не произойдёт сопряжения Bluetooth модуля робота и телефона И не истечёт минута(60 сек), модуль будет выполнять пустой цикл.
    • После цика while следует оператор if, в условии которого написано millis()<tmrWait+60000 . Условие будет верно, если одно из условие !hc05.checkConnect() цикла while изменится на противоположное и произойдёт это раньше, чем через 60 секунд от подачи питания на робота. Тогда в монитор последовательного порта будет выведено сообщение о том, что сопряжение произошло с ранее созданной парой из памяти устройства.
    • Если по истечении минуты не произошло сопряжение ранее созданной пары, то далее следует оператор else, который выполняет вызов функции createSlave("BT_CAR", "1234") объекта hc05, которая назначает Bluetooth модулю робота роль ведомого с именем "BT_CAR" и PIN-кодом "1234", разрывает ранее установленную связь с мастером (если она была) и стирает список ранее созданных пар. После этого модуль начинает ожидать подключения мастера, который правильно укажет имя и PIN-код модуля. Об успешном или, наоборот, неудачном выполнении функции будет выведено сообщение в монитор последовательного порта.

Получение данных и работа с Trema-модулем Bluetooth HC-05 осуществляется через функции и методы объекта hc05 библиотеки iarduino_Bluetooth_HC05, с подробным описанием которых можно ознакомиться на странице Wiki - Trema-модуль bluetooth HC-05.

Подключение:

Настройка Bluetooth-модуля телефона:

Зайдите в настройки телефона и выберите настройки модуля Bluetooth;

Переведите модуль Bluetooth из состояния выключен в состояние включен.

В строке поиска оборудования выберите появившееся после включения "Малыша" новое устройство.

При первом сопряжении программа попросит Вас ввести код.

Данный код указывается в скетче при создании ведомой роли Bluetooth-модуля "Малыша". В нашем скетче это "1234";

После удачного подключения в списке сопряженных устройств должен появиться "Малыш";

Установка приложения на телефон

Войдите в меню магазина приложений Google Play Market;

Обратите внимание на строку поиска в верхней части экрана, где написано Google Play;

Нажмите на строку поиска и наберите Arduino Bluetooth RC Car и выберите первую появившуюся стоку с именем приложения;

В окне установки приложения нажмите кнопку Установить;

В появившемся окне нажмите кнопку Подтвердить установку;

После успешной установки иконка программы появится у вас на рабочем столе телефона;

Для запуска приложения достаточно один раз нажать кнопку Открыть или выбрать иконку приложения на рабочем столе;

Настройка приложения Bluetooth RC Controller на телефоне:

При запуске приложения вы попадёте на главный экран.

Красный мигающий сигнал в верхнем левом углу означает отсутствие сопряжения приложения и "Малыша".

Для начала работы с "Малышом" нажмите на иконку шестерёнки  - крайнюю правую в верхнем ряду кнопок;

После нажатия на кнопку в появившемся меню Вам необходимо нажать на строку "Подключиться к машинке (Connect to car)";

В появившемся окне будет указано устройство, с которым телефон сопряжен в данный момент. Нажмите на строку с именем "BT_CAR";

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

Со стороны "Малыша" Вы увидите, что синий светодиод с надписью "Подключен" горит постоянным светом.

Интерфейс программы на телефоне:

НомерФункция
1Индикация сопряжения с роботом
2Блок клавиш "движение вперёд-назад"
3Блок клавиш "движение вправо-влево"
4Индикация нажатия клавиш движения
5Ползунок регулировки скорости робота
6Клавиша включения/выключения светодиодов
7Клавиша входа в меню настроек

Управление:

Сразу после сборки, загрузки скетча и подачи питания на «Малыша» он не будет реагировать на команды с телефона, так как Bluetooth модулям требуется сопряжение (создание пары). Сопряжение достаточно выполнить только один раз, bluetooth модули запомнят созданную пару в своей энергонезависимой памяти и будут пытаться соединится друг с другом при каждой последующей подаче питания.

  • Подключите питание робота (если оно не было подано). После подачи питания  Bluetooth модуль робота будет в течении минуты (60 сек) ждать сопряжения с последним удачно подключенным устройством, а если сопряжения не произойдёт, то модуль назначит себе роль ведомого с именем «BT_CAR» и PIN-кодом «1234» и будет ожидать подключение мастера.
  • Если Вы отключите телефон, то робот сразу остановится.

Управление роботом с телефона выполняется следующим образом:

  • Нажимая на клавиши "вперёд/назад" (2) и "вправо/влево"(3) вы можете выбрать одно из 9 направлений движения, указанных стрелками в центре(4).
  • Нажимая на клавишу (6) вы можете включить или выключить светодиоды, имитирующие спецсигнал полицейской машины;
  • Зажав кнопку (5) и двигая её пальцем вправо или влево, вы можете регулировать скорость движения робота;

Ссылки:

Обсуждение