Введение:
В этом уроке мы дополним робота «Малыш» модулем Bluetooth. Управление направлением и скоростью движения робота будет осуществляться с помощью приложения, установленного на телефон.
Bluetooth модуль телефона будет выполнять роль мастера, а Bluetooth модуль робота - роль ведомого. Сопряжение мастера и ведомого достаточно выполнить только один раз. В дальнейшем, при подаче питания на робота и включении Bluetooth модуля телефона, устройства будут соединяться самостоятельно и всё, что нам будет необходимо сделать, это соединиться с роботом в приложении на телефоне.
Подробно об управлении роботом и сопряжении Bluetooth модулей рассказано ниже, в разделе «Управление».
Видео:
Нам понадобится:
- Робот «Малыш» x1 шт.
- Trema-модуль Bluetooth HC-05 x1 шт.
Для реализации проекта нам необходимо установить библиотеки:
- iarduino_Bluetooth_HC05 - для работы с Trema Bluetooth модулем HC-05.
- Библиотеки SoftwareSerial входит в базовый набор Arduino IDE и не требует установки.
О том, как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Схема подключения робота «Малыш»:
Соберём механическую и электрическую части, как это описано в проектах № 1-8 в книге "Набор "Малыш"". Далее, на верхнюю стенку установим Bluetooth HC-05; модуль подключается к шине UART (в примере используется программная шина UART).
| Bluetooth | Motor 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 части: получение сигнала с телефона и изменение флага 
- Изменение скорости вращения колёс:- Данный бок включает в себя 2 части: получение сигнала с телефона и изменение флага flg; изменение значения переменнойvalSpeed;
- Первая часть начинается с оператора if, в условии которого написаноstr == XX. В зависимости от значенияXX, которое принимает переменнаяstr(одно из 11 для дополнительных функций), устанавливается значение флагаflg(от 10 до 20);
- Вторая часть начинается с оператора if, в условии которого написаноflg == XX. В зависимости от значенияXX, которое принимает переменнаяflg(одно из 11 для дополнительных функций), устанавливается значение переменнойvalSpeed(от 5 до 255);
 
- Данный бок включает в себя 2 части: получение сигнала с телефона и изменение флага 
- Вход в режим сопряжения (выполняется при подаче питания на робота):- Данный блок находится в коде 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-модуля телефона:
Установка приложения на телефон
|  Войдите в меню магазина приложений Google Play Market; | 
|  | 
|  Нажмите на строку поиска и наберите Arduino Bluetooth RC Car и выберите первую появившуюся стоку с именем приложения; | 
|  В окне установки приложения нажмите кнопку Установить; | 
|  В появившемся окне нажмите кнопку Подтвердить установку; | 
|  После успешной установки иконка программы появится у вас на рабочем столе телефона; | 
|  Для запуска приложения достаточно один раз нажать кнопку Открыть или выбрать иконку приложения на рабочем столе; | 
Настройка приложения Bluetooth RC Controller на телефоне:
Интерфейс программы на телефоне:

| Номер | Функция | 
|---|---|
| 1 | Индикация сопряжения с роботом | 
| 2 | Блок клавиш "движение вперёд-назад" | 
| 3 | Блок клавиш "движение вправо-влево" | 
| 4 | Индикация нажатия клавиш движения | 
| 5 | Ползунок регулировки скорости робота | 
| 6 | Клавиша включения/выключения светодиодов | 
| 7 | Клавиша входа в меню настроек | 
Управление:
Сразу после сборки, загрузки скетча и подачи питания на «Малыша» он не будет реагировать на команды с телефона, так как Bluetooth модулям требуется сопряжение (создание пары). Сопряжение достаточно выполнить только один раз, bluetooth модули запомнят созданную пару в своей энергонезависимой памяти и будут пытаться соединится друг с другом при каждой последующей подаче питания.
- Подключите питание робота (если оно не было подано). После подачи питания Bluetooth модуль робота будет в течении минуты (60 сек) ждать сопряжения с последним удачно подключенным устройством, а если сопряжения не произойдёт, то модуль назначит себе роль ведомого с именем «BT_CAR» и PIN-кодом «1234» и будет ожидать подключение мастера.
- Если Вы отключите телефон, то робот сразу остановится.
Управление роботом с телефона выполняется следующим образом:
- Нажимая на клавиши "вперёд/назад" (2) и "вправо/влево"(3) вы можете выбрать одно из 9 направлений движения, указанных стрелками в центре(4).
- Нажимая на клавишу (6) вы можете включить или выключить светодиоды, имитирующие спецсигнал полицейской машины;
- Зажав кнопку (5) и двигая её пальцем вправо или влево, вы можете регулировать скорость движения робота;










Обсуждение