Данная статья является полу-сиквелом к работе 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. Нам удалось выйти в 14 финала, но дальше не продвинулись.
Сам процесс интересен был нам, а здесь опишу выводы, которые удалось сделать, посмотрев на робота в поле:
- нужно мощнее. Желательно четыре колеса или более мощные двигатели и другие колеса. Хотя, конечно, именно четырехколесные модели смотрелись выигрышней
- управление не кайф. Нужно переводить робота на танковый разворот (разворот на одной точке за счет колес, крутящихся в противоположные стороны), иначе слишком большой радиус разворота. Да и в общем вариант с четырьмя стрелочками, а не кругом с пропорциональной скоростью, для футбола предпочтительнее. Описанный вариант лучше подходит для гонок, где едешь беспрерывно, а тут нужна четкость (повернулся на 10 градусов вокруг своей оси, нацелился на мяч и зажал кнопку вперед. а вот потом, когда уже захватил мяч, хотелось бы гибко маневрировать, а тут нужно пропорциональная скорость… нужно как-то это дело совмещать).
Замечаниям и предложениям буду очень рад. Под предыдущими статьями комментарии порой интереснее самой статьи. За работу спасибо мне, Саше и Дане.
Автор: DolgopolovDenis