Робо-футболист от начинающих. Соревнования в МФТИ. Android & Arduino & Bluetooth +15



Данная статья является полу-сиквелом к работе Love, Death and Robots «Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл», состоящей из двух частей (раз, два). Вещи, описанные там, были немного доработаны-переделаны, а сам робот из ездящей машинки превратился в футболиста. В общем, есть интересный материал о том, как делать не надо.

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

Физическая часть


За основу взяты все те же принципы, описанные в первой статье:

  • бутерброд из Arduino Uno и Motor Shield.
  • два мотора, подключенных к Motor Shield.

А вот изменения:

  • появилась ударная часть, как ни странно, отвечающая за удар по мячу.
  • корпус теперь полностью свой, распечатанный на 3D-принтере.

Корпус


Форма — круг, в который вмещается и плата, и два колеса. Удлинение для части, где будет стоять ударная сила.



При конструировании подобного обратить внимание на:

  • Высокие бортики. Роботы во время игры сталкиваются, бортики защищают не только ваши провода, но и ваших соперников от ваших проводов.
  • Центр тяжести и устойчивость. Центр тяжести конечно там, где плата. Колеса расположены возле нее, поэтому проскальзывать не будут. Плюс сверху на плату кладется батарейка.
  • Чтобы робот не клевал носом или задом, ставим и туда и сюда шарики, идущие в наборе от амперки (если их нет, можно заменить на любую другую скользящую конструкцию).
  • Жесткость конструкции. Платформа не должна провисать под тяжестью плат и моторов. Не поскупитесь, либо используйте твердые материалы (фанеру), либо усильте пластмассовую конструкцию рейками



А теперь основные глупости


Шарики, добавленные для отсутствия «клевания», поднимали платформу так, что колеса не доставали до пола. Чтобы этого избежать, либо используем колеса большего диаметра, либо укорачиваем опорные конструкции. В общем, просчитываем это заранее!

Ударная часть. Она не бьет. Бьет, но недостаточно круто. В нашей первой модели стояла серво-машинка, к которой подсоединялась деталь, похожая на ковш снегоуборочной машины. Меняя положение сервы (от 0 до 30 градусов) можно сымитировать удар. Но сервы оказались медленными, поэтому удар выходит на двоечку.

Выхода два: добавлять рывок при ударе или заменять сервы на соленоиды. Первый вариант — увеличить импульс можно за счет подачи скорости на колеса во время удара. На практике так: пользователь нажимает кнопку удара, робот стартует с места (чуть-чуть) и одновременно делать удар.

Второй вариант — соленоиды толкают ударную часть и тут все зависит от мощности (скорости) толчка, которая в свою очередь зависит от характеристик соленоида.



Программная часть


По доброй традиции, которой вот уже одна статья, разделим этот раздел на две части. Сначала Android-приложение, потом скетч Arduino.

Android


Напомню, приложения написано мной с нуля. За прошедшие полгода немного понял в этом деле поболее, поэтому опишу до чего допер додумался.

Во-первых, пойдем к упрощению. Теперь протокол общения следующий: «открывающий символ» + «значение» + «закрывающий символ» (Чтобы понять, как я получаю эти значения и о чем вообще речь, смотри полный разбор приложения здесь). Это работает как для значения скорости, так и для угла. Поскольку тип удара только один, ему такие мудрости не нужны, поэтому команда состоит из одного символа "/" (об команде удара через абзац).

   private void sendCommand(String speed, String angle) {
            String speedCommand = "#" + speed + "#"; //добавляем начальный и конечный символ
            String angleCommand = "@" + angle + "@";

            try {
                outputStream.write(speedCommand.getBytes()); //отсылаем обе команды
                outputStream.write(angleCommand.getBytes());
            } catch(Exception e) {
                e.printStackTrace();
            }
        }

Типичная команда будет выглядеть так: #125#@180@, где 125 — скорость, а 180 — угол. Конечно, это можно еще упростить, но одной из задач было сохранить легкость и читабельность, чтобы потом это можно было легко объяснить, в том числе детям.

Появилась новая команда sendHit(), которая срабатывает во время нажатия на кнопку «Удар». Она отправляет один знак "/". Поскольку обычный bluetooth 2.0+ не страдает от данных, поступаемых одновременно, то есть умеет ставить их в очередь и не терять, нам это контролировать не надо. Если же вы собираетесь работать с Bluetooth Low Energy 4.0+ (ну вдруг), там уже очередь надо будет организовывать вручную, иначе данные будут теряться.

...
  bHit = findViewById(R.id.b_high_hit); //находим кнопку удара
  bHit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                threadCommand.sendHit(); //при нажатии вызываем отправку команды "удар"
            }
        });
...

private void sendHit() {
            try {
                outputStream.write("/".getBytes()); //отправляем один символ
            }catch (Exception e) {
                e.printStackTrace(); 
            }
        }
}

Arduino


Так поменялся протокол отправки команд, также поменялся алгоритм приема. Он упростился. Также добавился один if, отслеживающий удар. Полный разбор скетча здесь.

bt.read() считывает один символ. Если он равен "#", значит начинаются символы скорости. Считываем их до тех пор, пока не появится закрывающий символ "#". Здесь нельзя использоваться цикл for, потому что заранее неизвестна длина скорости (она может быть и однозначным, и двузначным, и трехзначным числом). Полученное значение записываем в переменную.

То же самое происходит с поворотом. После того как считаны и скорость, и угол, передаем все в функцию turn(int speed, int angle).

void loop() {
 
  if(BTSerial.available() > 0) {//если есть присланные символы
     char a = BTSerial.read(); //считываем первый символ

    if(a == '#') { //начинается скорость
      sp="";
      char b = BTSerial.read();
      while( b != '#') { 
        //пока не закрывающий символ, плюсуем символы в переменную
        sp+=b;
        b = BTSerial.read();
      }
     
    } else if (a == '@') {//начинается угол
      val = "";
      char b = BTSerial.read();
      while(b != '@') { //пока не закрывающий символ
      val+=b; //прибавляем символ к переменной
      b = BTSerial.read();
    }
    turn(val.toInt(), sp.toInt()); //скорость и угол считаны, запускаем действие
     
    } else if (a == '/')  { //оп, нужно сделать удар
      Serial.println(a);
      servo.write(30); //делаем удар
      delay(150);
      servo.write(0); //возращаем в исходную позицию
    }
    
    lastTakeInformation = millis();
  } else {

     if(millis() - lastTakeInformation > 150) {
      //если команды не приходили больше 150мс
     //останавливаем робота
     lastTakeInformation = 0;
     analogWrite(speedRight, 0);
     analogWrite(speedLeft, 0);
     }
     
  }

  delay(5);
}


Функция turn() определяет, в какую сторону двигаться (вперед, назад) и куда поворачивать (вправо, влево, прямо). Ограничение if(speed > 0 && speed < 70) нужно для того, чтобы робот не тормозился, если байты потеряны. Столкнулся с этим, когда повысил скорость передачи (игрался с задержками в 100-300мс между командами) — иногда значение скорости не доходило и превращалось в 0, 40 (хотя, например, на самом деле отправлялось 240). Костыль, но работает.
Можно назвать «защитой от неконтролируемых факторов».

void turn(int angle, int speed) {
  
  if(speed >= 0 && speed < 70) return;
  
  if(speed > 0) {
     digitalWrite(dirLeft, HIGH);
     digitalWrite(dirRight, HIGH);
  } else if (sp < 0) {
      digitalWrite(dirLeft, LOW);
      digitalWrite(dirRight, LOW);
  }
  
  if(angle > 149) {
        analogWrite(speedLeft, speed);
        analogWrite(speedRight, speed - 65); //поворот вправо
  } else if (angle < 31) { 
        analogWrite(speedLeft, speed - 65); //поворот влево
        analogWrite(speedRight, speed);
  } else {
      analogWrite(speedLeft, speed);
      analogWrite(speedRight, speed);
  }
   
}

Соревнования в МФТИ вместо итога


С нашим роботом мы отправились на соревнования по робо-футболу, которые устраивало и проводилось в университете МФТИ, г. Долгопрудный, 14.04.2019. Нам удалось выйти в 1\4 финала, но дальше не продвинулись.



Сам процесс интересен был нам, а здесь опишу выводы, которые удалось сделать, посмотрев на робота в поле:

  • нужно мощнее. Желательно четыре колеса или более мощные двигатели и другие колеса. Хотя, конечно, именно четырехколесные модели смотрелись выигрышней
  • управление не кайф. Нужно переводить робота на танковый разворот (разворот на одной точке за счет колес, крутящихся в противоположные стороны), иначе слишком большой радиус разворота. Да и в общем вариант с четырьмя стрелочками, а не кругом с пропорциональной скоростью, для футбола предпочтительнее. Описанный вариант лучше подходит для гонок, где едешь беспрерывно, а тут нужна четкость (повернулся на 10 градусов вокруг своей оси, нацелился на мяч и зажал кнопку вперед. а вот потом, когда уже захватил мяч, хотелось бы гибко маневрировать, а тут нужно пропорциональная скорость… нужно как-то это дело совмещать).

Замечаниям и предложениям буду очень рад. Под предыдущими статьями комментарии порой интереснее самой статьи. За работу спасибо мне, Саше и Дане.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (7):

  1. Javian
    /#20030878

    >управление не кайф.
    Вот тут лучше показать на видео игровой процесс.

    Имхо трехколесный (два ведущих и одно подруливающее [ в виде шара]) может развернуться на месте. Поэтому думаю, что нынешний вариант ходовой части хороший.

    • fndrey357
      /#20031570

      Двухколесный конечно маневренный. И проще считать ходовую геометрию. И проще в управлении.
      Но 2 колеса скорее всего менее адекватно ведут себя при препятствии и ударе.
      Техническое противоречие: надо 4 точки опоры для удара/столкновения с другим роботом. Надо 2 колеса для маневренности.
      Изобретательская задача.
      Как решения: выдвигающиеся упоры. Динамики на видео не вижу, но может быть.
      3 колеса — сложности с танковым разворотом.
      4 колеса близко друг к другу (расстояние между колес меньше ширины колеи) -для танкового разворота.
      2 пары разновеликих колес. Нужны энкодеры для пересчета оборотов разновеликих колес или шаговики (чем черт не шутит?).
      Ну и банально поднять вес и увеличить мощность моторов…

      • Javian
        /#20036232

        Как вариант специальная функция поворота — подъем задней оси, чтобы корпус оперся на подруливающее колесо(шар).

        • fndrey357
          /#20037248

          … оперся на подруливающее колесо(шар)...

          Я так понимаю шары есть. Они не дают опрокинуться. Однако зафиксировать на плоскости тяжелЬше… Там что-то типа упоров для крана: и не дает опрокинуться и не дает сдвинуться по плоскости.

    • DolgopolovDenis
      /#20031578

      безусловно можно, и делается на двух колесах довольно легко (может и не идеально "на пяточке", но вполне допустимо). одно колесо назад, другое вперёд — готово.

  2. AntonSor
    /#20030906

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

    • shalm
      /#20031406

      Лучше если электромагнит освобождает сжатую пружину, а потом моторчик её опять сжимает и заряжает механизм для следующего использования.
      Жаль что соревнования на порядок примитивнее, чем это возможно в наше время, было б интереснее, если машинки были автономны