Робот "Микроша", управляемый с помощью геймпада DualShock 2

Общие сведения:

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

Изменяя положение левого стика (джойстика), робот будет менять направление движения, а нажатие на кнопки "КРУГ" или "КВАДРАТ" в правой части геймпада будет включать или выключать светодиодные модули.

Видео:

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

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

    • PSX — для работы с геймпадом;

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

    Описание работы робота:

    После подачи питания робот готов к работе.

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

    Нажимая кнопки на правой части геймпада, вы включаете/выключаете светодиоды.

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

    Arduino / Piranha UNO:

    Batery Shield:

    Установите Battery Shield на Arduino / Piranha UNO:
    Во время установки Battery Shield должен быть в выключенном состоянии.

    Motor Shield:

    На Battery Shield установите Motor Shield

    Светодиоды:

    Подключите светодиоды к Motor Shield

    Мотор-редукторы:

    Подключите мотор-редукторы к Motor Shield

    Крепёж 1:

    Закрепите на ПВХ-пластине шаровую опору и мотор-редукторы

    Крепёж 2:

    Закрепите на ПВХ-конструкторе светодиоды и приёмник геймпада (с помощью стяжки)

    Геймпад:

    Подключите приёмник геймпада к Motor Shield


    Вывод приёмникаВывод Arduino
    GNDGND
    VCC
    DAT12
    CMD11
    CLK13
    CS10

    Код программы (скетч):

    #include <PS2X_lib.h>                                                                                             // Подключаем библиотеку для работы с геймпадом
    
    PS2X ps2x;                                                                                                        // Создаём объект "ps2x"
    
    uint8_t  error              = 0;                                                                                  // Переменная для кода ошибки при инициализации геймпада
    byte     type               = 0;                                                                                  // Переменная для кода подключенного устройства
    
    uint8_t  PS2_clock          = 13;                                                                                 // Вывод Arduino, к которому подключен вывод приёмника геймпада CLK
    uint8_t  PS2_data           = 12;                                                                                 // Вывод Arduino, к которому подключен вывод приёмника геймпада DAT
    uint8_t  PS2_command        = 11;                                                                                 // Вывод Arduino, к которому подключен вывод приёмника геймпада CMD
    uint8_t  PS2_attention      = 10;                                                                                 // Вывод Arduino, к которому подключен вывод приёмника геймпада ATT
    
    bool     PS2_Pressures      = true;                                                                               // установка флага на чтение аналоговых значений с геймпада
    bool     PS2_Rumble         = true;                                                                               // установка флага на поддержку виброрежима
    bool     motor_1            = false;                                                                              // сбрасываем флаг для мотора 1 (используется в проводных геймпадах)
    
    byte     motor_2            = 0;                                                                                  // устанавливаем значение вибрации вибромотора
    uint8_t  RAW_UP_OR_DOWN     = 0;                                                                                  // переменная "сырых" значений с джойстика по оси Оу
    uint8_t  RAW_LEFT_OR_RIGHT  = 0;                                                                                  // переменная "сырых" значений с джойстика по оси Ох
    uint8_t  GO_LEFT            = 0;                                                                                  // переменная для хранения отсортированных значений по оси Ох для движения влево
    uint8_t  GO_RIGHT           = 0;                                                                                  // переменная для хранения отсортированных значений по оси Ох для движения вправо
    uint8_t  GO_UP              = 0;                                                                                  // переменная для хранения отсортированных значений по оси Оу для движения вверх
    uint8_t  GO_DOWN            = 0;                                                                                  // переменная для хранения отсортированных значений по оси Оу для движения вниз
    
    uint8_t  pinShield_H2       = 4;                                                                                  // Вывод, подключенный к драйверу, для задания направления вращения левым мотором
    uint8_t  pinShield_E2       = 5;                                                                                  // Вывод ШИМ, подключенный к драйверу, для задания скорости левого мотора
    uint8_t  pinShield_E1       = 6;                                                                                  // Вывод ШИМ, подключенный к драйверу, для задания скорости правого мотора
    uint8_t  pinShield_H1       = 7;                                                                                  // Вывод, подключенный к драйверу, для задания направления вращения правым мотором
    uint8_t  valSpeed           = 0;                                                                                  // Максимальная скорость ШИМ (число от 0 до 255)
    uint8_t  led_right          = 2;
    uint8_t  led_left           = 3;
    bool     arrRoute[2]        = {0, 0};                                                                             // Направление движения для каждого мотора ([0]- правый мотор, [1] - левый мотор)
    uint16_t arrSpeed[2];                                                                                             // Скорость для каждого мотора ([0]- правый мотор, [1] - левый мотор)
    uint8_t  flg;                                                                                                     // Флаг направлений движения машинки
    bool     flg_led_right = false;                                                                                   // Флаг работы светодиода красного цвета
    bool     flg_led_left  = false;                                                                                   // Флаг работы светодиода синего цвета
    
    void setup() {
      // ГЕЙМПАД
      error = ps2x.config_gamepad(PS2_clock,PS2_command,PS2_attention,PS2_data, PS2_Pressures, PS2_Rumble);           // Инициируем работу геймпада
      // МОТОРЫ
      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(led_right,OUTPUT);
      pinMode(led_left, OUTPUT);
    }
    
    void loop() {
     ps2x.read_gamepad(motor_1, motor_2);                                                                             // Опрашиваем геймпад
     
     RAW_UP_OR_DOWN      = ps2x.Analog(PSS_LY);                                                                       // Считываем "сырое" аналоговое значение с джойстика по оси Оу
     RAW_LEFT_OR_RIGHT   = ps2x.Analog(PSS_LX);                                                                       // Считываем "сырое" аналоговое значение с джойстика по оси Ох
     if(ps2x.ButtonPressed(PSB_RED))  {flg_led_right = !flg_led_right; digitalWrite(led_right, flg_led_right);}       // Если была нажата кнопка "КРУГ", то включаем красный светодоид
     if(ps2x.ButtonPressed(PSB_PINK)) {flg_led_left  = !flg_led_left;  digitalWrite(led_left,  flg_led_left );}       // Если была нажата кнопка "КВАДРАТ", то включаем синий светодоид
    
     if(RAW_UP_OR_DOWN <=120)   {GO_UP     = map(RAW_UP_OR_DOWN,       0,   120, 255,   0); GO_DOWN  = 0;} else       // Если значение по оси Оу меньше 120, значит джойстик сдвинут вверх, переопределяем диапазон значений с джойстика (переворачиваем его) 
     if(RAW_UP_OR_DOWN >=135)   {GO_DOWN   = map(RAW_UP_OR_DOWN,     135,   255,   0, 255); GO_UP    = 0;} else       // Если значение по оси Оу больше 135, значит джойстик сдвинут вниз, переопределяем диапазон значений с джойстика
                                {GO_DOWN = 0; GO_UP = 0;}                                                             // Если значение находится в диапазоне от 120 до 135 - значит джойстик находится в центральном положении
     if(RAW_LEFT_OR_RIGHT <=120){GO_LEFT   = map(RAW_LEFT_OR_RIGHT,    0,   120, 255,   0); GO_RIGHT = 0;} else       // Если значение по оси Ох меньше 120, значит джойстик сдвинут влево, переопределяем диапазон значений с джойстика (переворачиваем его) 
     if(RAW_LEFT_OR_RIGHT >=135){GO_RIGHT  = map(RAW_LEFT_OR_RIGHT,  135,   255,   0, 255); GO_LEFT  = 0;} else       // Если значение по оси Оу больше 135, значит джойстик сдвинут вправо, переопределяем диапазон значений с джойстика
                                {GO_LEFT = 0; GO_RIGHT = 0;}                                                          // Если значение находится в диапазоне от 120 до 135 - значит джойстик находится в центральном положении
     if(GO_UP){                                                                                                       // Если джойстик был сдвинут вверх, то
       if(!GO_LEFT || !GO_RIGHT) {flg = 2; valSpeed = GO_UP;}    else                                                 // проверяем, сдвинут ли он вправо/влево, и если нет, то устанавливаем флаг движения на север
       if(GO_LEFT)               {flg = 1; valSpeed = GO_UP;}    else                                                 // если джойстик сдвинут влево, то устанавливаем флаг движения на северо-запад
       if(GO_RIGHT)              {flg = 3; valSpeed = GO_UP;}}   else                                                 // если джойстик сдвинут вправо, то устанавливаем флаг движения на северо-восток
     if(GO_DOWN){                                                                                                     // если джойстик сдвинут вниз, то
       if(!GO_LEFT || !GO_RIGHT) {flg = 8; valSpeed = GO_DOWN;}  else                                                 // проверяем, сдвинут ли он вправо/влево, и если нет, то устанавливаем флаг движения на юг
       if(GO_LEFT)               {flg = 7; valSpeed = GO_DOWN;}  else                                                 // если джойстик сдвинут влево, то устанавливаем флаг движения на юго-запад
       if(GO_RIGHT)              {flg = 9; valSpeed = GO_DOWN;}} else                                                 // если джойстик сдвинут вправо, то устанавливаем флаг движения на юго-восток
     if(GO_LEFT)                 {flg = 4; valSpeed = GO_LEFT;}  else                                                 // Проверяем, сдвинут ли джойстик только влево, и если да, то устанавливаем флаг движения на запад
     if(GO_RIGHT)                {flg = 6; valSpeed = GO_RIGHT;} else                                                 // Проверяем, сдвинут ли джойстик только вправо, и если да, то устанавливаем флаг движения на восток
                                 {flg = 5;}                                                                           // Если джойстик не был сдвинут ни в одну из указанных выше сторон, то устанавливаем флаг остановки
     switch (flg) {// Направ. лев. м.     Направ. прав. м.        Скорость лев. м.                 Скорость прав. м.
       case 1:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = (valSpeed / 2);    arrSpeed[0] = valSpeed;         break;      // С-З
       case 2:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = valSpeed;          arrSpeed[0] = valSpeed;         break;      // С
       case 3:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = valSpeed;          arrSpeed[0] = (valSpeed / 2);   break;      // С-В
       case 4:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = 0;                 arrSpeed[0] = valSpeed;         break;      // З
       case 5:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = 0;                 arrSpeed[0] = 0;                break;      // Стоп
       case 6:       arrRoute[1] = 0;     arrRoute[0] = 0;        arrSpeed[1] = valSpeed;          arrSpeed[0] = 0;                break;      // В
       case 7:       arrRoute[1] = 1;     arrRoute[0] = 1;        arrSpeed[1] = (valSpeed / 2);    arrSpeed[0] = valSpeed;         break;      // Ю-З
       case 8:       arrRoute[1] = 1;     arrRoute[0] = 1;        arrSpeed[1] = valSpeed;          arrSpeed[0] = valSpeed;         break;      // Ю
       case 9:       arrRoute[1] = 1;     arrRoute[0] = 1;        arrSpeed[1] = valSpeed;          arrSpeed[0] = (valSpeed / 2);   break;      // Ю-В
     }
     digitalWrite(pinShield_H2, arrRoute[1]);                                                                         // тогда задаем направление вращения правого мотора
     digitalWrite(pinShield_H1, arrRoute[0]);                                                                         // и левого мотора
     analogWrite(pinShield_E2, arrSpeed[1]);                                                                          // Задаём скорость вращения для правого мотора
     analogWrite(pinShield_E1, arrSpeed[0]);                                                                          // и для левого мотора
     delay(50);                                                                                                       // Задержка 50мс
    }

    Алгоритм работы скетча:

    До кода void setup() определяются переменные, подключаются необходимые библиотеки.

    В коде void setup() инициализируется геймпад, настраивается режим работы выводов для моторов и светодиодов.

    Код void loop() делится на несколько частей:

    • Опрашивается геймпад
      • Считывается аналоговое значение с джойстика по оси Оу
      • Считывается аналоговое значение с джойстика по оси Ох
      • Опрашиваются кнопки "Квадрат" и "Круг"
        • Если они были нажаты, то состояние на выходах меняется на противоположное
    • Если на джойстиках были изменения положения, то:
    • Проверяем ось Оу:
      • Проверяем, было ли изменение в пределах от 0 до 120 - если да, то тогда робот движется вперёд;
      • Проверяем, было ли изменение в пределах от 135 до 255 - если да, то тогда робот движется назад;
    • Проверяем ось Ох:
        • Проверяем, было ли изменение в пределах от 0 до 120 - если да, то тогда робот движется влево;
        • Проверяем, было ли изменение в пределах от 135 до 255 - если да, то тогда робот движется вправо;
    • Проверяем, какое направление движения было определено и согласно ему задаём значение для флага flg;
    • В зависимости от флага flg устанавливаем направление вращения и скорость вращения колёс;
    • Устанавливаем на выводах Piranha Uno аналоговое значение для скорости вращения и цифровое значение для направления вращения.
    • Ставим задержку 50мс для того, чтобы геймпад опрашивался с некоторой задержкой.

    Ссылки:

    Обсуждение