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

Введение:

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

Видео:

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

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

Робот "Quadruped"

Пульт

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

  • Библиотеки 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 и установив аналогичные скорость и канал.

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

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

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

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

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

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

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

    Кнопка "ВПРАВО"Trema Shield
    SD2
    V5V
    GGND
    Кнопка "ВЛЕВО"Trema Shield
    SD3
    V5V
    GGND
    Кнопка "НАЗАД"Trema Shield
    SD4
    V5V
    GGND
    Кнопка "ВПЕРЁД"Trema Shield
    SD5
    V5V
    GGND
    ПотенциометрTrema Shield
    SA0
    V5V
    GGND
    Радиомодуль HC-12Trema Shield
    SD13
    TXD8
    RXD9
    V5V
    GGND

    Схема сборки робота QUADRUPED:

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

    СервоприводыTrema Shield
    1 конечностьГоризонтальный сустав (№ 0)вывод 4 на белой колодке
    Вертикальный сустав (№ 1)вывод 5 на белой колодке
    2 конечностьГоризонтальный сустав (№ 2)вывод 6 на белой колодке
    Вертикальный сустав (№ 3)вывод 7 на белой колодке
    3 конечностьГоризонтальный сустав (№ 4)вывод A0 на белой колодке
    Вертикальный сустав (№ 5)вывод A1 на белой колодке
    4 конечностьГоризонтальный сустав (№ 6)вывод 10 на белой колодке
    Вертикальный сустав (№ 7)вывод 11 на белой колодке

    Вы можете изменить выводы 4-11 для подключения сервоприводов на любые другие, указав их в скетче при объявлении массива объектов pinServo[8].

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

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

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

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

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

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

    Код программы для робота QUADRUPED:

    #define FRAME_HEADER  0xAA                                                  //  Задаём заголовок пакета
    #define FRAME_FOOTER  '\n'                                                  //  Задаём конец пакета
    #define radio_pin     13                                                    //  Определяем номер вывода S радио модуля
    
    //        Подключаем библиотеки:
    #include "SoftwareSerial.h"                                                 //  Подключаем библиотеку для общения с модулем по шине UART
    SoftwareSerial mySerial(8, 9);                                              //  Создаём объект mySerial указывая выводы RX, TX
    #include  "Servo.h"                                                         //  Подключаем библиотеку Servo, для работы с сервоприводами.
    Servo objServo[8];         /*  0    1    2    3    4    5    6    7 */      //  Создаём массив, каждый элемент которого является объектом библиотеки Servo. Цифры в комментарии указывают на № сервопривода
    
    //           Определяем сервы: Г    В    Г    В    Г    В    Г    В         //  Буквы в комментарии указывают на плоскость вращения сервопривода (Горизонтальная / Вертикальная)
    const uint8_t pinServo[8] = {  4,   5,   6,   7,  A0,  A1,  10,  11 };      //  Определяем массив хранящий номера выводов к которым подключены сервоприводы (можно менять)
    //                        ЗАПОЛНЯЕМ МАССИВ ЗНАЧЕНИЯМИ ПОСЛЕ КАЛИБРОВКИ
    const int     cenAngle[8] = { 72, 103, 120,  84, 125, 111,  93,  76 };      //  Определяем массив хранящий углы в градусах, при которых сервоприводы находятся в центральном положении (КОРРЕКТИРУЕТСЯ В КАЛИБРОВОЧНОМ СКЕТЧЕ)
    const int     minAngle[8] = { 50,  30,  50,  30,  50,  30,  50,  30 };      //  Определяем массив хранящий углы в градусах, на которые отклоняются сервоприводы от центрального положения назад  или вниз
    const int     maxAngle[8] = { 50,  40,  50,  40,  50,  40,  50,  40 };      //  Определяем массив хранящий углы в градусах, на которые отклоняются сервоприводы от центрального положения вперёд или вверх
    //            Объявляем функции:
    void          funLimbMove   ( uint8_t, bool, int8_t );                      //  Объявляем Функцию установки  одного сустава     в значение от -100 до 100 (конечность 1-4, сустав 0/1, положение -100...+100)
    void          funLimbStep   ( uint8_t, uint8_t, int, int );                 //  Объявляем Функцию установки  одной  конечности  в положение от 0 до 255   (конечность 1-4, положение 0...255, ограничение по горизонтали -100...+100, ограничение по вертикали 0...100%)
    void          funLimbGait   ( uint8_t, uint8_t, int, int );                 //  Объявляем Функцию установки  всех   конечностей в положение от 0 до 255   (тип походки 0-4, положение 0...255, ограничение по горизонтали -100...+100, ограничение по вертикали 0...100%)
    void          funLimbFree   ( void );                                       //  Объявляем Функцию ослабления всех   суставов                              (без параметров)
    void          funLimbCent   ( void );                                       //  Объявляем Функцию установки  всех   суставов в центральное положение      (без параметров)
    //            Определяем дополнительные переменные:
    uint8_t       varPosition = 0;                                              //  Текущая позиция походки от 0 до 255 (чем быстрее меняется значение тем выше скорость, если значение увеличивается - значит вперёд, если значение уменьшается - значит назад)
    int8_t        tempData[5];                                                  //  Промежуточный массив, куда записываются полученные данные
    int8_t        arrData[4];                                                   //  Массив, куда записываются команды для робота
    int8_t        temp_sum;                                                     //  Переменная, в которую записывается сумма всех полученных от пульта значений
    uint32_t      FindTimer;                                                    //  Таймер для отслеживания наличия связи с пультом
    
    void setup(){
         mySerial.begin(9600);                                                  //  инициируем работу с монитором последовательного порта
         funLimbFree();                                                         //  Выполняем функцию освобождения всех конечностей
         funLimbCent();                                                         //  Устанавливаем все конечности в центральное положение
    }
    
    void loop(){
      if(FindTimer + 3000 < millis()){                                          //  Если с последнего обновления таймера прошло больше 3 секунд, то
        arrData[0] = 0;                                                         //  сбрасываем значения массива данных
        arrData[1] = 0;                                                         // 
        arrData[3] = 0;                                                         // 
      }
      if( mySerial.available() && mySerial.read() == 0xAA){                     //  Если получен пакет данных, начинающийся с 0xAA, то
        delay(10);                                                              //  Ждём 10 мс для получения всего пакета данных
        FindTimer = millis();                                                   //  Обновляем таймер
        for (int i = 0; i < 5; i++){                                            //  Выполняем цикл получения и записи 4 полученных битов в промежуточный массив
          tempData[i] = mySerial.read();                                        //  
        }
        if(tempData[4] == FRAME_FOOTER){                                        //  Проверяем, что последний полученный бит равен биту конца пакета и если да, то
          temp_sum   = 0;                                                       //  обнуляем переменную
          for (int j = 0; j < 3; j++){                                          //  в цикле выполняем сложение всех значений массива и 
            temp_sum += tempData[j];                                            //  полученную сумму записываем в переменную temp_sum
          }
          if(tempData[3] == temp_sum){                                          //  Проверяем, что полученная сумма равна значению суммы всех элементов массива, который нам отправил пульт и если всё верно, то
            for (int k = 0; k < 3; k++){                                        //  в цикле выполняем
              arrData[k] = tempData[k];                                         //  перезапись элементов промежуточного массива в постоянный массив
            }
          }
        }
      }
      if(arrData[0] != 0){                                                      //  Если нажата кнопка "ВПЕРЁД" или "НАЗАД", то
        if(arrData[0] > 0){varPosition+=2;}                                     //  для кнопки ВПЕРЁД увеличиваем позицию походки varPosition
        else              {varPosition-=2;}                                     //  для кнопки НАЗАД  уменьшаем   позицию походки varPosition
        if(arrData[1] > 0){funLimbGait(1, varPosition,  50, arrData[2]);} else  //  Проверяем, нажата ли кнопка ВПРАВО или ВЛЕВО, и если нажата кнопка ВПРАВО, то указываем значение поворота = 50
        if(arrData[1] < 0){funLimbGait(1, varPosition, -50, arrData[2]);} else  //  для кнопки ВЛЕВО указываем значение поворота -50 
                          {funLimbGait(1, varPosition,   0, arrData[2]);}       //  Иначе робот идёт прямо
      }
      else if(arrData[1] != 0){                                                 //  Если нажата кнопка "ВПРАВО" или "ВЛЕВО", то
        if(arrData[1] > 0){varPosition+=2;}                                     //  для кнопки ВПРАВО увеличиваем позицию походки varPosition.
        else              {varPosition-=2;}                                     //  для кнопки ВЛЕВО  уменьшаем   позицию походки varPosition.
        funLimbGait(0, varPosition, 0, arrData[2]);                             //  Вызываем функцию походки с заданными условиями (поворот на месте в сторону нажатой кнопки)
      }
      else{                                                                     //  Если ни одна кнопка не нажата, то
        funLimbMove(1, 1, map(arrData[2],0,100,+100,-100));                     //  устанавливаем для первой    конечности значения высоты в простое (меняется потенциометром)
        funLimbMove(2, 1, map(arrData[2],0,100,+100,-100));                     //  устанавливаем для второй    конечности значения высоты в простое (меняется потенциометром)
        funLimbMove(3, 1, map(arrData[2],0,100,+100,-100));                     //  устанавливаем для третьей   конечности значения высоты в простое (меняется потенциометром)
        funLimbMove(4, 1, map(arrData[2],0,100,+100,-100));                     //  устанавливаем для четвёртой конечности значения высоты в простое (меняется потенциометром)
      }
    }
    
    //   Функция установки одного сустава конечности в значение от -100% (самое нижнее положение) до +100% (самое верхнее положение):
    void funLimbMove(uint8_t num, bool joint, int8_t pos){                      //  Аргументы функции: «num» - номер конечности от 1 до 4 , «joint» - тип сустава 0 (горизонтальный) или 1 (вертикальный) , «pos» - положение сустава от -100 (внизу-сзади) до +100 (вверху-спереди)
         uint8_t   i = (num-1) * 2 + joint;                                     //  Определяем № сервопривода (i) по № конечности (num-1) и типу сустава (joint)
         int       k = 0;                                                       //  Определяем переменную (k) для хранения экстремума
         int       j = pos? cenAngle[i]:0;                                      //  Определяем переменную (j) для хранения экстремума
         if(pos>0){
          k = +100;                                                             //  Находим (k) - максимально допустимое значение для аргумента (pos)
          if( num%2==0||i%2==1 ){j-=maxAngle[i];} else                          //  Находим (j) - максимально допустимый угол в градусах для чётной   конечности или сустава
                                {j+=maxAngle[i];}                               //  Находим (j) - максимально допустимый угол в градусах для нечётной конечности или сустава
         }
         if(pos<0){
          k = -100;                                                             //  Находим (k) - минимально допустимое значение для аргумента (pos)
          if( num%2==1&&i%2==0 ){j-=minAngle[i];} else                          //  Находим (j) - минимально  допустимый угол в градусах для чётной   конечности
                                {j+=minAngle[i];}}                              //  Находим (j) - минимально  допустимый угол в градусах для нечётной конечности
    
         if(!objServo[i].attached()){objServo[i].attach(pinServo[i]);}          //  Подключаем объект (objServo) работающий с сервоприводом (i) к выводу (pinServo[i])
         objServo[i].write(map(pos, 0, k, cenAngle[i], j));                     //  Устанавливаем сервопривод (i) в угол находящийся между центром (cenAngle[I]) и экстремумом (j)
    }
    
    //   Функция установки одной конечности в положение от 0 до 255:
    void funLimbStep(uint8_t num, uint8_t pos, int hor, int ver){               //  Аргументы функции: «num» - номер конечности от 1 до 4 , «pos» - позиция от 0 до 255 , «hor» - ограничение поворота горизонтального сустава от -100 до +100 , «ver» - ограничение высоты вертикального сустава от 0 до 100%
         int i;                                                                 //  Объявляем переменные (i)
         int j;                                                                 //  Объявляем переменные (j)
         if(pos < 225){i = map(pos,   0, 212, +100, -100); }else                //  Сустав конечности поворачивается назад                            (+100 >>> -100)
                      {i = map(pos, 225, 255, -100, +100); }                    //  Сустав конечности поворачивается вперёд                           (-100 >>> +100)
         if(pos < 225){j =                    -100;        }else                //  Сустав конечности опущен                                          (-100)
         if(pos < 235){j = map(pos, 225, 235, -100, +100); }else                //  Сустав конечности поднимается вверх                               (-100 >>> +100)
         if(pos < 245){j =                    +100;        }else                //  Сустав конечности поднят                                          (+100)
                      {j = map(pos, 245, 255, +100, -100); }                    //  Сустав конечности опускается вниз                                 (+100 >>> -100)
         if(hor<0 && num%2==1){ i = map(i, -100, +100, -(100+hor)  , 100+hor);} //  Ограничиваем  угол (i) горизонтального сустава левых  конечностей (поворот влево)
         if(hor>0 && num%2==0){ i = map(i, -100, +100, -(100-hor)  , 100-hor);} //  Ограничиваем  угол (i) горизонтального сустава правых конечностей (поворот вправо)
                                j = map(j, -100, +100, ver*(-2)+100, 100    );  //  Ограничиваем  угол (j) вертикального   сустава любых  конечностей (высота хексапода)
         funLimbMove(num, 0, i);                                                //  Устанавливаем угол (i) для горизонтального (0) сустава конечности (num)
         funLimbMove(num, 1, j);                                                //  Устанавливаем угол (j) для вертикального   (1) сустава конечности (num)
    }
    //   Функция выполнения походки по одному из возможных вариантов i          //
    void funLimbGait(uint8_t num, uint8_t pos, int hor, int ver){               //  Аргументы функции: (num) - номер походки от 0 до 2 , (pos) - позиция от 0 до 255 , «hor» - ограничение поворота горизонтальных суставов от -100 до +100 , «ver» - ограничение высоты вертикального сустава от 0 до 100%
         switch(num){                                                           //  
    //       Походка номер 0: разворот на месте:                                //  pos = 0         63       127       191       255
             case 0:                                                            //        |         |         |         |         |
                 funLimbStep(1,  31+pos, hor, ver);                             //   L    | > > > > | > > > > | > > > > | <<< > > | конечность №1 выполняет полный цикл движений (от 255 до 0) быстро возвращаясь в последней тетраде
                 funLimbStep(2,   0-pos, hor, ver);                             //   R    | >>> < < | < < < < | < < < < | < < < < | конечность №2 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в первой тетраде
                 funLimbStep(3,  95+pos, hor, ver);                             //   L    | > > > > | > > > > | <<< > > | > > > > | конечность №3 выполняет полный цикл движений (от 255 до 0) быстро возвращаясь в предпоследней тетраде
                 funLimbStep(4,  63-pos, hor, ver);                             //   R    | < < < < | >>> < < | < < < < | < < < < | конечность №4 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь во второй тетраде
             break;                                                             //        |         |         |         |         |
    //       Походка номер 1: движение вперёд или назад:                        //  pos = 0         63       127       191       255
             case 1:                                                            //        |         |         |         |         |
                 funLimbStep(1,   0+pos, hor, ver);                             //   L    | > > > > | > > > > | > > > > | > > <<< | конечность №1 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в последней тетраде
                 funLimbStep(2, 127+pos, hor, ver);                             //   R    | > > > > | > > <<< | > > > > | > > > > | конечность №2 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь во второй тетраде
                 funLimbStep(3,  63+pos, hor, ver);                             //   L    | > > > > | > > > > | > > <<< | > > > > | конечность №3 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в предпоследней тетраде
                 funLimbStep(4, 191+pos, hor, ver);                             //   R    | > > <<< | > > > > | > > > > | > > > > | конечность №4 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в первой тетраде
             break;                                                             //        |         |         |         |         |
    //       Походка номер 2: плывёт вперёд или назад:                          //  pos = 0         63       127       191       255
             case 2:                                                            //        |         |         |         |         |
                 funLimbStep(1,   0+pos, hor, ver);                             //   L    | > > > > | > > > > | > > > > | > > <<< | конечность №1 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в последней тетраде
                 funLimbStep(2,   0+pos, hor, ver);                             //   R    | > > > > | > > > > | > > > > | > > <<< | конечность №2 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в последней тетраде
                 funLimbStep(3,   0+pos, hor, ver);                             //   L    | > > > > | > > > > | > > > > | > > <<< | конечность №3 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в последней тетраде
                 funLimbStep(4,   0+pos, hor, ver);                             //   R    | > > > > | > > > > | > > > > | > > <<< | конечность №4 выполняет полный цикл движений (от 0 до 255) быстро возвращаясь в последней тетраде
             break;                                                             //        |         |         |         |         |
    }     }
    //   Функция освобождения конечностей:
    void funLimbFree(void){
      for(uint8_t i=0;i<8;i++){                                                 //  выполняем в цикле
        objServo[i].detach();                                                   //  отвязку всех сервоприводов
        digitalWrite(pinServo[i],LOW);                                          //  и отключаем питание на них
      }
    }
    //   Функция установки всех суставов конечностей в центральное положение:
    void funLimbCent(void){
      for(uint8_t i=1;i<=4;i++){                                                //  выполняем в цикле
        funLimbMove(i,0,0);                                                     //  устанавливаем все суставы (горизонтальные) в центральное положение
        funLimbMove(i,1,0);                                                     //  устанавливаем все суставы (вертикальные)   в центральное положение
      }
    }

    Управление:

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

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

    Ссылки:

    Обсуждение