Дистанционное управление роботом "Дройдик" с помощью радиомодуля HC-12

Введение:

В этом уроке мы дополним робота «Дройдика» радиомодулем HC-12, а так же построим пульт дистанционного управления на основе этого же модуля. При подаче питания на робота и включении пульта управления устройства будут соединяться самостоятельно на установленной частоте.

Видео:

Редактируется...

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

Робот "Дройдик":

Пульт:

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

  • Библиотеки SoftwareSerial и Servo входят в базовый набор Arduino IDE и не требуют установки.

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

Схема подключения модуля для предварительной настройки:

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

  • Установите на Arduino / Piranha UNO плату Trema Shield;
  • Подключите к Trema Shield модуль HC-12 как показано ниже:
  • Радиомодуль HC-12Trema Shield
    TXD8
    RXD9
    SD13
    V5V
    GGND
  • Загрузите в плату следующий скетч:
#include "SoftwareSerial.h"            // подключаем библиотеку для работы с программным UART
SoftwareSerial mySerial(8, 9)          // Указываем выводы Arduino, к которым подключены (TX, RX) выводы МОДУЛЯ

#define S  13                          // Указываем, к какому выводу подключен вывод S модуля HC-12

void setup() {
  Serial.begin(9600);                  // Инициируем аппаратный последовательный порт
  mySerial.begin(9600);                // Инициируем программный последовательный порт
  pinMode(S, OUTPUT);                  // Переводим вывод S модуля в режим выход   
  digitalWrite(S, LOW);                // Назначаем выводу уровень логического нуля
  delay(40);                           // Ждём пока модуль войдёт в режим AT команд
}

void loop() {
  if(mySerial.available()){            // Если в буфере программного последовательного порта есть данные 
    Serial.write(mySerial.read());     // Перенаправляем их в аппаратный последовательный порт 
    }
  if(Serial.available()){              // Если в буфере аппаратного последовательного порта есть данные
    mySerial.write(Serial.read());     // Перенаправляем их в программный последовательный порт 
  }
}
    • Откройте монитор последовательного порта и в открывшемся окне введите следующие команды:
      • AT+B**** - для задания скорости передачи данных;
      • AT+C*** - для задания канала (частоты) работы модуля;
      • Подробнее про AT-команды вы можете прочесть в статье, посвящённой модулю HC-12;

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

    После того, как все подготовительные работы выполнены, можно переходить непосредственно к сборке проекта.

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

    Соберите механику, подключите Trema Shieldсервоприводы и откалибруйте робота, как это описано в уроке 38 Сборка «Дройдика». Далее на боковую панель установите модуль  HC-12, который подключается к выводам D13, D8 и D9 (в примере используется программная шина UART).

    СервоприводыTrema Shield
    Верхние суставыЛевая нога «Дройдика»D4
    Правая нога «Дройдика»D5
    Нижние суставыЛевая нога «Дройдика»D6
    Правая нога «Дройдика»D7

    Вы можете изменить выводы 4-7 для подключения сервоприводов на любые другие, указав их в скетче при определении констант pinLeftTop, pinRightTop, pinLeftBot, pinRightBot. 

    Подключите радиомодуль к Trema Shield на роботе так, как показано на рисунке ниже:

    Радиомодуль HC-12Trema Shield
    SD13
    TXD8
    RXD9
    V5V
    GGND

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

    • Оранжевый провод подключается к выводу на белой колодке.
    • Красный провод подключается к выводу на красной колодке.
    • Коричневый провод подключается к выводу на чёрной колодке.

    Схема сборки пульта управления:

    Соберём блок пульта управления. Для этого вам понадобится Arduino / Piranha UNO:

    Теперь установим Battery Shield на Arduino / Piranha UNO:

    Во время установки Battery Shield должен быть в выключенном состоянии!

    Далее на Battery Shield установите Trema Shield для удобного подключения модулей:

    Теперь вновь воспользуемся модулем HC-12, который подключим к Trema Shield вместе с 4 Trema-кнопками:

    Вывод модуля Кнопка "ВПРАВО"Вывод Arduino
    SD2
    V5V
    GGND
    Вывод модуля Кнопка "ВЛЕВО"Вывод Arduino
    SD3
    V5V
    GGND
    Вывод модуля Кнопка "НАЗАД"Вывод Arduino
    SD4
    V5V
    GGND
    Вывод модуля Кнопка "ВПЕРЁД"Вывод Arduino
    SD5
    V5V
    GGND
    Вывод радиомодуля HC-12Вывод Arduino
    SD13
    TXD8
    RXD9
    V5V
    GGND

    Код программы для пульта дистанционного управления:

    #include "SoftwareSerial.h"                           // Подключаем библиотеку для общения с модулем по шине UART
    SoftwareSerial mySerial(8, 9);                        // Создаём объект mySerial указывая выводы RX, TX
    
    #define FRAME_HEADER        0xAA                      // Задаём заголовок пакета
    #define FRAME_FOOTER        '\n'                      // Задаём конец пакета
    #define button_up           5                         // Вывод для кнопки "Вперёд"
    #define button_down         4                         // Вывод для кнопки "Назад"
    #define button_left         3                         // Вывод для кнопки "Влево"
    #define button_right        2                         // Вывод для кнопки "Вправо"
    #define radio_pin           12                        // Определяем номер вывода S радио модуля
    int8_t  data[2];                                      // Массив для получения данных с пульта
    int     SIZE = sizeof(data)/sizeof(data[0]);          // Переменная, равная размерности массива с данными
    
    void setup() {
      mySerial.begin(9600);                               // инициируем работу с монитором последовательного порта
      pinMode(button_up,    INPUT );                      // настраиваем вывод на работу в режиме ВХОДА
      pinMode(button_down,  INPUT );                      // настраиваем вывод на работу в режиме ВХОДА
      pinMode(button_left,  INPUT );                      // настраиваем вывод на работу в режиме ВХОДА
      pinMode(button_right, INPUT );                      // настраиваем вывод на работу в режиме ВХОДА
    }
    
    void loop() {
      if (digitalRead(button_up))   {data[0] =  1;} else  // Если нажата кнопка ВПЕРЁД, то устанавливаем значение бита в 1
      if (digitalRead(button_down)) {data[0] = -1;} else  // Если нажата кнопка НАЗАД, то устанавливаем значение бита в -1
                                    {data[0] =  0;}       // иначе устанавливаем значение бита в 0
      if (digitalRead(button_right)){data[1] =  1;} else  // Если нажата кнопка ВПРАВО, то устанавливаем значение бита в 1
      if (digitalRead(button_left)) {data[1] = -1;} else  // Если нажата кнопка ВЛЕВО, то устанавливаем значение бита в -1
                                    {data[1] =  0;}       // иначе устанавливаем значение бита в 0
      mySerial.write(FRAME_HEADER);                       // Передаём заголовок пакета в последовательный порт
      for (int i = 0; i < SIZE; i++) {                    // Побайтово передаём данные
        mySerial.write(data[i]);                          // в последовательный порт
      }
      mySerial.write(FRAME_FOOTER);                       // Выводим байт конца пакета в последовательный порт
      delay(50);                                          // Ждём 50 миллисекунд
    }

    Код программы для «Дройдика»:

    //        Подключаем библиотеки:
    #include  "Servo.h"                                                 //  Подключаем библиотеку Servo для работы с сервоприводами
    #include "SoftwareSerial.h"                                         //  Подключаем библиотеку SoftwareSerial для общения с модулем по программной шине UART
    SoftwareSerial mySerial(8, 9);                                      //  Создаём объект mySerial указывая выводы RX, TX
    //      Определяем константы:
    #define FRAME_HEADER 0xAA                                           //  Определяем заголовок пакета
    #define FRAME_FOOTER '\n'                                           //  Определяем конец пакета
    #define radio_pin     13                                            //  Определяем номер вывода S радио модуля
    //            Определяем номера выводов:
    const uint8_t pinEcho       = 2;                                    //  Определяем константу с номером вывода подключённым к выводу Echo датчика расстояний    (можно указывать только те выводы Arduino, которые могут работать с внешними прерываниями)
    const uint8_t pinTrig       = 3;                                    //  Определяем константу с номером вывода подключённым к выводу Trig датчика расстояний    (может быть любым)
    const uint8_t pinLeftTop    = 4;                                    //  Определяем константу с номером вывода подключённым к верхнему сервоприводу левой  ноги (может быть любым)
    const uint8_t pinRightTop   = 5;                                    //  Определяем константу с номером вывода подключённым к верхнему сервоприводу правой ноги (может быть любым)
    const uint8_t pinLeftBot    = 6;                                    //  Определяем константу с номером вывода подключённым к нижнему  сервоприводу левой  ноги (может быть любым)
    const uint8_t pinRightBot   = 7;                                    //  Определяем константу с номером вывода подключённым к нижнему  сервоприводу правой ноги (может быть любым)
    //            Определяем константы (ВВОДЯТСЯ ПОСЛЕ КАЛИБРОВКИ РОБОТА):
    const uint8_t cenLeftTop    = 119;                                  //  Определяем константу центрального угла в градусах  для верхнего сервопривода левой  ноги (по умолчанию = 100)
    const uint8_t cenRightTop   = 65;                                   //  Определяем константу центрального угла в градусах  для верхнего сервопривода правой ноги (по умолчанию = 80 )
    const uint8_t cenLeftBot    = 79;                                   //  Определяем константу центрального угла в градусах  для нижнего  сервопривода левой  ноги (по умолчанию = 60 )
    const uint8_t cenRightBot   = 76;                                   //  Определяем константу центрального угла в градусах  для нижнего  сервопривода правой ноги (по умолчанию = 120)
    const uint8_t maxStepSize   = 25;                                   //  Определяем константу размера шага в градусах поворота верхних сервоприводов (чем больше угол, тем шире шаг)
    const uint8_t maxStepHeight = 20;                                   //  Определяем константу высоты  шага в градусах наклона в стороны при ходьбе (чем больше угол, тем выше шаг)
    //            Создаём объекты:
    Servo objServoLeftTop;                                              //  Создаём объект servoLeftTop  для работы с верхним левым  сервоприводом
    Servo objServoRightTop;                                             //  Создаём объект objServoRightTop для работы с верхним правым сервоприводом
    Servo objServoLeftBot;                                              //  Создаём объект objServoLeftBot  для работы с нижним  левым  сервоприводом
    Servo objServoRightBot;                                             //  Создаём объект objServoRightBot для работы с нижним  правым сервоприводом
    //                      Создаём переменные:
    uint8_t valPosition   = 224;                                        //  Определяем переменную (движение) для хранения текущей позиции шага (счёт от 0 до 255 или обратно), начальная позиция 224
     int8_t valTurning    = 0;                                          //  Определяем переменную (поворот ) для пересчета размера шага в градусах поворота верхних сервоприводов (-10 - влево ... 0 - прямо ... +10 вправо)
    uint8_t maxLeftSize   = maxStepSize;                                //  Определяем переменную максимального размера шага в градусах поворота верхнего левого  сервопривода (чем меньше угол, тем сильнее робот будет уходить вправо)
    uint8_t maxRightSize  = maxStepSize;                                //  Определяем переменную максимального размера шага в градусах поворота верхнего правого сервопривода (чем меньше угол, тем сильнее робот будет уходить влево )
    bool    flgPult       = false;                                      //  Определяем флаг запрещающий чтение данных с пульта более 1 раза за отведённое время
    bool    flgPosition   = false;                                      //  Определяем флаг запрещающий изменение позиции шага более 1 раза за отведённое время
    int8_t  b = 0;                                                      //  Создаём переменную для хранения текущего байта последовательного порта
    int8_t  data[2];                                                    //  Создаём массив для хранения данных пакета
    
    void setup(){
        mySerial.begin(9600);                                           //  инициируем работу с монитором последовательного порта
    //  Указываем объектам сервоприводов их выводы:
        objServoLeftTop. attach(pinLeftTop);                            //  Указываем объекту objServoLeftTop  работать с выводом pinLeftTop
        objServoRightTop.attach(pinRightTop);                           //  Указываем объекту objServoRightTop работать с выводом pinRightTop
        objServoLeftBot. attach(pinLeftBot);                            //  Указываем объекту objServoLeftBot  работать с выводом pinLeftBot
        objServoRightBot.attach(pinRightBot);                           //  Указываем объекту objServoRightBot работать с выводом pinRightBot
    //  Устанавливаем центральные углы сервоприводов:
        objServoLeftTop. write (cenLeftTop);                            //  Устанавливаем центральную позицию (угол cenLeftTop ) для сервопривода подключённого к выводу pinLeftTop
        objServoRightTop.write (cenRightTop);                           //  Устанавливаем центральную позицию (угол cenRightTop) для сервопривода подключённого к выводу pinRightTop
        objServoLeftBot. write (cenLeftBot);                            //  Устанавливаем центральную позицию (угол cenLeftBot ) для сервопривода подключённого к выводу pinLeftBot
        objServoRightBot.write (cenRightBot);                           //  Устанавливаем центральную позицию (угол cenRightBot) для сервопривода подключённого к выводу pinRightBot
    }
    
    void loop(){
        memset(data, 0, 2);                                             //  очищаем массив
        if( mySerial.available() && mySerial.read() == 0xAA){           //  Если получен пакет данных, начинающийся с 0xAA, то
          delay(10);                                                    //  Ждём 10 мс для получения всего пакета данных
          for (int i = 0; (b = mySerial.read()) != FRAME_FOOTER; i++){  //  выполняем цикл получения и записи полученных битов в массив пока не получим бит конца пакета
            data[i] = b;
          }
        }
        if(data[0]!=0){                                                 //  Если нажата кнопка ВПЕРЁД или НАЗАД, то
          if(data[1] != 0){                                             //  Проверяем, нажаты ли кнопки поворота:
            if(data[1] >0)  {valTurning =  5;}                          //  Если так же нажата кнопка ВПРАВО, то увеличиваем значение поворота valTurning
            else            {valTurning = -5;}                          //  Если так же нажата кнопка ВЛЕВО,  то уменьшаем   значение поворота valTurning
          }else {
             valTurning = 0;                                            //  Иначе двигаемся прямо
          }
          if(data[0]>0){valPosition += 10;}                             //  Если нажата кнопка ВПЕРЁД, то увеличиваем позицию походки varPosition.
          else         {valPosition -= 10;}                             //  Если нажата кнопка НАЗАД,  то уменьшаем   позицию походки varPosition.
        }else
        if(data[1]!=0){                                                 //  Иначе (если джойстик не отклонён вперёд или назад), но отклонён влево arrData[0]<0 или вправо arrData[0]>0, то ...
          valPosition += 10;                                            //  Увеличиваем позицию походки.
          if(data[1]>0){valTurning =  10;}                              //  Если джойстик отклонён вправо arrData[0]>0, то присваиваем переменной valTurning максимальное значение = 10.
          else         {valTurning = -10;}                              //  Если джойстик отклонён влево  arrData[0]<0, то присваиваем переменной valTurning минимальное значение = -10.
        }
    
        maxRightSize=maxStepSize; if(valTurning<0){maxRightSize=map(valTurning, 0,-10, maxStepSize, 0);}/*Прямо или влево */  //  Корректируем значение maxRightSize (размера шага правой ноги) в соответствии со значением valTurning.
        maxLeftSize =maxStepSize; if(valTurning>0){maxLeftSize =map(valTurning, 0, 10, maxStepSize, 0);}/*Прямо или вправо*/  //  Корректируем значение maxLeftSize  (размера шага левой  ноги) в соответствии со значением valTurning.
        
        if(valPosition<64 ){
          objServoLeftTop. write(map(valPosition,   0,  63, cenLeftTop  - maxLeftSize     , cenLeftTop  + maxLeftSize     )); //  Левая  нога поворачивается вправо => отходит    назад.
          objServoRightTop.write(map(valPosition,   0,  63, cenRightTop - maxRightSize    , cenRightTop + maxRightSize    )); //  Правая нога поворачивается вправо => выходит    вперёд.
        }else
        if(valPosition<128){
          objServoLeftBot. write(map(valPosition,  64, 127, cenLeftBot  - maxStepHeight   , cenLeftBot  +(maxStepHeight/2))); //  Левая  нога наклоняется    вправо => переносит  центр тяжести с себя на правую ногу, которая станет опорной.
          objServoRightBot.write(map(valPosition,  64, 127, cenRightBot -(maxStepHeight/2), cenRightBot + maxStepHeight   )); //  Правая нога наклоняется    вправо => опускается вниз (становится опорной) и поднимает левую ногу.
        }else
        if(valPosition<192){
          objServoLeftTop. write(map(valPosition, 128, 191, cenLeftTop  + maxLeftSize     , cenLeftTop  - maxLeftSize     )); //  Левая  нога поворачивается влево  => выходит    вперёд.
          objServoRightTop.write(map(valPosition, 128, 191, cenRightTop + maxRightSize    , cenRightTop - maxRightSize    )); //  Правая нога поворачивается влево  => отходит    назад.
        }else{ /*valPosition<255*/
          objServoLeftBot. write(map(valPosition, 192, 255, cenLeftBot  +(maxStepHeight/2), cenLeftBot  - maxStepHeight   )); //  Левая  нога наклоняется    влево  => опускается вниз (становится опорной) и поднимает правую ногу.
          objServoRightBot.write(map(valPosition, 192, 255, cenRightBot + maxStepHeight   , cenRightBot -(maxStepHeight/2))); //  Правая нога наклоняется    влево  => переносит  центр тяжести с себя на левую ногу, которая станет опорной.
        }
    }

    Управление:

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

    Если скорость движения или углы поворота кажутся вам слишком большими или, наоборот, слишком малыми, то измените величину, на которую изменяются переменные valTurning и ValPosition в скетче выше!

    Ссылки:

    Обсуждение